diff --git a/README.md b/README.md index d9a4b6e..3540c59 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,341 @@ -# FPKGi-node-server -A simple package server to work with the FPKGi PS4 client. This autogenerates the PS4 package meta and serves compatible JSON files dynamically. +# FPKGi Node Server -## How to use? -- Install NodeJS from https://nodejs.org/en/download -- Download the files in this repository to a folder and open command or terminal in the same directory. Run `npm install` to install the NodeJS dependency (express) -- Edit the `config.json` file and add the IP address of your PC/Server this is being ran on. (same network as the PS4) -- Run the `Start Server.bat` or to run from terminal: `node server` in the same directory. -- On first run it will generate the folder structure needed to serve packages. Place your fpkgs in the relevant folders to structure the data. +FPKGi Node Server is a set of tools for generating FPKGi-compatible JSON files, serving PKG and cover files over HTTP, and hosting a separate JSON library web UI. -You should now be able to visit the local webserver it generates, which should give you direct links to the auto-generated JSON files, please see FPKGi instructions on how to add these to your client. A basic preview library is available by clicking the 'eye' icon on the right hand side of the categories. +This repository contains the source code for: -If you need to refresh or rescan the packages (e.g. adding a new package whilst the server is running) you can visit `/refresh` to instruct the server to refresh the packages. +- a Windows GUI app +- a Windows CLI version +- a Linux CLI version -## Notes -- The background feature and cover-images are currently broken, however this seems like a client issue and it is correctly implemented in this server. This should be fixed in the next FPKGi client update. -- I have no experience in handling pkg files, this is a first-attempt at getting metadata extracted in NodeJS. If there is a package that doesn't process I would appreictate a message. -- This is a hobby project, may or may not be worked on in spare time. Updates may be sporadic, to be used for self-hosting package deployment on your local network and not to be used over the internet - there is no security or authentication. It's just a glorified file-server with JSON listings. +## Features + +- scan category folders containing PS4 `.pkg` files +- generate FPKGi JSON files +- serve PKG files and cover images over HTTP +- host a separate JSON library server on its own port +- support a background image for the JSON library +- extract cover icons automatically on Windows with `orbis-pub-cmd.exe` from [PS4 Fake PKG Tools by CyB1K](https://github.com/CyB1K/PS4-Fake-PKG-Tools-3.87) + +## Release Downloads + +Check ther [Releases](https://github.com/CyberMask367/FPKGi-node-server/releases) section for files: + +- `fpkgi-node-server-windows-gui_installer.exe` +- `fpkgi-node-server-windows-gui_portable.zip` +- `fpkgi-node-server-windows-cli.zip` +- `fpkgi-node-server-linux.zip` + +## Which Version To Use + +Use the Windows GUI if you want: + +- a desktop app +- settings page +- dashboard +- tray support +- live logs +- buttons for scan and stop scan + +Use the Windows CLI if you want: + +- a simple menu in the terminal +- Windows icon extraction support +- no GUI + +Use the Linux version if you want: + +- terminal-based operation +- JSON generation on Linux +- HTTP and JSON library servers without Windows-only extraction tools + +## Windows GUI Release + +You can use either the installer or the portable version. + +### Windows GUI Installer + +File: + +- `fpkgi-node-server-windows-gui_installer.exe` + +Installation: + +1. Run the installer. +2. Follow the setup wizard. +3. Launch `FPKGi Node Server` from the Start menu or installed folder. + +image + + +Notes: + +- Node.js is not required for the packaged GUI release. +- `orbis-pub-cmd.exe`, `orbis-pub-prx.dll`, and `ext` are bundled inside the app resources. + +### Windows GUI Portable + +File: + +- `fpkgi-node-server-windows-gui_portable.zip` + +Installation and usage: + +1. Extract the zip to a folder. +2. Keep all extracted files together. +3. Run `FPKGi Node Server.exe`. + +Important: + +- Do not move only the `.exe` out of the extracted folder. +- The portable build depends on the bundled `resources` folder next to the executable. +- Node.js is not required for the packaged GUI release. + +### First-Time Setup In The GUI + +Open the `Settings` page and set: + +- PKG scan path +- covers path +- JSON output path +- HTTP server enabled or disabled +- HTTP server IP and port +- JSON library server enabled or disabled +- JSON library server IP and port +- optional tray and startup behavior + +Then save the settings. + +### Running A Scan In The GUI + +From the dashboard: + +1. Click `Run Scan`. +2. The app scans PKGs, extracts icons on Windows, and generates JSON files. +3. Use `Stop Scan` if you want to cancel the current scan. + +The dashboard also shows: + +- current job status +- whether the HTTP server is running +- whether the JSON library server is running +- whether the Orbis tool is available +- live logs + +## Windows CLI Release + +File: + +- `fpkgi-node-server-windows-cli.zip` + +What is included: + +- `node-menu.js` +- `node-settings.json` +- `fpkgi-node-server-windows-cli.bat` +- `orbis-pub-cmd.exe` +- `orbis-pub-prx.dll` +- `ext` +- `app` +- empty `pkg`, `covers`, and `json` folders + +Requirements: + +- Windows +- [Node.js](https://nodejs.org/) + +Installation and usage: + +1. Extract the zip to a folder. +2. Make sure Node.js is installed. +3. Run `fpkgi-node-server-windows-cli.bat`, or open a terminal in the extracted folder and run: + +```bash +node node-menu.js +``` + +### Windows CLI Menu + +The menu includes: + +- `1. Run scan` +- `2. Edit settings` +- `3. Start servers` +- `4. Run scan and keep servers running` +- `5. Stop running servers` +- `6. Show current settings` +- `7. Exit` + +Enabled servers start automatically when the script launches, and the currently running servers are shown at the top of the menu. + +### Windows CLI Config + +The Windows CLI uses: + +- `node-settings.json` + +Main settings: + +- `paths.pkgScan` +- `paths.covers` +- `output.path` +- `httpServer.enabled` +- `httpServer.address` +- `httpServer.port` +- `jsonLibraryServer.enabled` +- `jsonLibraryServer.address` +- `jsonLibraryServer.port` +- `serverAddress` + +## Linux Release + +File: + +- `fpkgi-node-server-linux.zip` + +What is included: + +- `node-menu-linux.js` +- `node-settings-linux.json` +- `json-generator-linux.js` +- `app` +- empty `pkg`, `covers`, and `json` folders + +Requirements: + +- Linux +- [Node.js](https://nodejs.org/) + +Installation and usage: + +1. Extract the zip to a folder. +2. Make sure Node.js is installed. +3. Open a terminal in the extracted folder. +4. Run: + +```bash +node node-menu-linux.js +``` + +Note: + +- The archive includes `fpkgi-node-server-linux.sh`, but the safest way to launch the Linux version is running `node node-menu-linux.js` directly. + +### Linux Menu + +The Linux menu includes: + +- `1. Generate JSON` +- `2. Edit settings` +- `3. Start servers` +- `4. Generate JSON and keep servers running` +- `5. Stop running servers` +- `6. Show current settings` +- `7. Exit` + +Enabled servers start automatically when the script launches, and the currently running servers are shown at the top of the menu. + +### Linux Limitation + +The Linux version does not extract icons because `orbis-pub-cmd.exe` is Windows-only. + +You should provide cover images in the covers folder if you want custom artwork in generated output. + +## Covers And Background Image + +Cover images are read from the covers directory you set in the app or CLI config. + +The JSON library server also checks the root of the covers folder for a background image named: + +- `background.png` +- `background.bmp` +- `background.jpg` +- `background.jpeg` + +If one of those exists, it is served automatically through the JSON library server. + +The generated JSON library config uses: + +```json +"background_uri": "http://server-address/background" +``` + +## HTTP Server + +When enabled, the built-in HTTP server serves only: + +- `/pkg/...` +- `/covers/...` + +It does not expose the whole project folder. + +## JSON Library Server + +The JSON library server is separate from the PKG/covers HTTP server and has its own: + +- enable toggle +- IP address +- port + +It uses the JSON output path automatically and can be opened in a browser from the GUI. + +## Source Usage + +If you want to run from source instead of using the releases: + +- run the Electron project from [windows/gui](https://github.com/CyberMask367/FPKGi-node-server/tree/main/windows/gui) +- run the Windows Node CLI from [windows/cli](https://github.com/CyberMask367/FPKGi-node-server/tree/main/windows/cli) +- run the Linux Node tools from [linux](https://github.com/CyberMask367/FPKGi-node-server/tree/main/linux) + +After cloning the repo, install dependencies locally only where they are needed. + +### Windows GUI From Source + +In [windows/gui](https://github.com/CyberMask367/FPKGi-node-server/tree/main/windows/gui): + +```bash +npm install +npm start +``` + +To build: + +```bash +npm run build +``` + +If you clean the folder later or clone the repo on another machine, run `npm install` again in `windows/gui` to restore `node_modules`. + +The GUI source project expects these files in the same folder: + +- `orbis-pub-cmd.exe` +- `orbis-pub-prx.dll` +- `ext` + +### Windows CLI From Source + +In [windows/cli](https://github.com/CyberMask367/FPKGi-node-server/tree/main/windows/cli): + +```bash +node node-menu.js +``` + +The Windows CLI runs directly with Node.js and does not need the Electron GUI `node_modules` folder. + +### Linux From Source + +In [linux](https://github.com/CyberMask367/FPKGi-node-server/tree/main/linux): + +```bash +node node-menu-linux.js +``` + +The Linux scripts also run directly with Node.js and do not use the Windows GUI dependencies. + +## Safety Note + +This project is designed for local or private network use. + +It should not be exposed directly to the public internet because it does not include authentication or security hardening for public hosting. + +Use at your own risk if hosting publicly. diff --git a/Start Server.bat b/Start Server.bat deleted file mode 100644 index e581e73..0000000 --- a/Start Server.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -echo Package Server -node server.js -pause \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index 4a0ba1c..0000000 --- a/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "server": { - "ip": "0.0.0.0", - "port": 3000 - } -} \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..84b37b1 Binary files /dev/null and b/icon.png differ diff --git a/linux/app/json-library-server.js b/linux/app/json-library-server.js new file mode 100644 index 0000000..7b3d46b --- /dev/null +++ b/linux/app/json-library-server.js @@ -0,0 +1,464 @@ +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const zlib = require("zlib"); + +function send(res, status, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(status, { "Content-Type": contentType }); + res.end(body); +} + +function getBackgroundFilePath(coversDir) { + const extensions = [".png", ".bmp", ".jpg", ".jpeg"]; + for (const ext of extensions) { + const filePath = path.join(coversDir, `background${ext}`); + if (fs.existsSync(filePath)) { + return filePath; + } + } + return null; +} + +function makeCrcTable() { + const table = new Uint32Array(256); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) { + c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1); + } + table[n] = c >>> 0; + } + return table; +} + +const CRC_TABLE = makeCrcTable(); + +function crc32(buffer) { + let crc = 0xffffffff; + for (let i = 0; i < buffer.length; i++) { + crc = CRC_TABLE[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8); + } + return (crc ^ 0xffffffff) >>> 0; +} + +function pngChunk(type, data) { + const typeBuffer = Buffer.from(type, "ascii"); + const length = Buffer.alloc(4); + length.writeUInt32BE(data.length, 0); + + const crcInput = Buffer.concat([typeBuffer, data]); + const crc = Buffer.alloc(4); + crc.writeUInt32BE(crc32(crcInput), 0); + + return Buffer.concat([length, typeBuffer, data, crc]); +} + +function createBackgroundPng(width = 1280, height = 720) { + const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); + const raw = Buffer.alloc((width * 4 + 1) * height); + + for (let y = 0; y < height; y++) { + const rowOffset = y * (width * 4 + 1); + raw[rowOffset] = 0; + + for (let x = 0; x < width; x++) { + const t = x / Math.max(width - 1, 1); + const v = y / Math.max(height - 1, 1); + const offset = rowOffset + 1 + x * 4; + + const r = Math.round(18 + 125 * t + 20 * v); + const g = Math.round(6 + 10 * t + 4 * v); + const b = Math.round(10 + 22 * t + 18 * v); + + raw[offset] = Math.min(r, 255); + raw[offset + 1] = Math.min(g, 255); + raw[offset + 2] = Math.min(b, 255); + raw[offset + 3] = 255; + } + } + + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); + ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; + ihdr[9] = 6; + ihdr[10] = 0; + ihdr[11] = 0; + ihdr[12] = 0; + + const idat = zlib.deflateSync(raw, { level: 9 }); + const iend = Buffer.alloc(0); + + return Buffer.concat([ + signature, + pngChunk("IHDR", ihdr), + pngChunk("IDAT", idat), + pngChunk("IEND", iend) + ]); +} + +function sendBackgroundImage(res, coversDir) { + const backgroundFile = getBackgroundFilePath(coversDir); + if (backgroundFile) { + const stat = fs.statSync(backgroundFile); + const ext = path.extname(backgroundFile).toLowerCase(); + const contentTypes = { + ".png": "image/png", + ".bmp": "image/bmp", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg" + }; + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size, + "Cache-Control": "no-cache" + }); + + fs.createReadStream(backgroundFile).pipe(res); + return; + } + + const image = createBackgroundPng(); + res.writeHead(200, { + "Content-Type": "image/png", + "Content-Length": image.length, + "Cache-Control": "no-cache" + }); + res.end(image); +} + +function sendJsonFile(res, filePath) { + if (!fs.existsSync(filePath)) { + send(res, 404, JSON.stringify({ error: "File Not found" }), "application/json; charset=utf-8"); + return; + } + + const stat = fs.statSync(filePath); + res.writeHead(200, { + "Content-Type": "application/json; charset=utf-8", + "Content-Length": stat.size + }); + + fs.createReadStream(filePath).pipe(res); +} + +function renderHomePage(hostname, port, jsonDir) { + const jsonFiles = fs.readdirSync(jsonDir) + .filter(file => file.endsWith(".json")) + .map(file => { + const category = path.basename(file, ".json"); + return { + name: category, + url: `/${category}` + }; + }); + + return ` + + + + FPKGi Json Server + + + +

Available Categories

+
+

Click on the links below to view the JSON data and URL for your discovered packages. Either manually copy these URLs into your config, or use the generated config below. Click the Eye icon to see a parsed library view.

+ + +

Generated Config

+

Use the following configuration in your FPKGi config

+ +View Background + + + + +`; +} + +function renderLibraryPage(category) { + const title = category ? category.replace(/^./, c => c.toUpperCase()) : category; + + return "" + + "" + + "" + + " " + + " " + title + " Library" + + " " + + "" + + "" + + "

" + title + " Library

" + + "
" + + " Back" + + " " + + "" + + ""; +} + +function startJsonLibraryServer(settings, options = {}) { + if (!settings.jsonLibraryServer?.enabled) return Promise.resolve(null); + + const jsonDir = path.resolve(settings.output.path); + const coversDir = path.resolve(settings.paths.covers); + if (!fs.existsSync(jsonDir)) { + throw new Error(`JSON directory not found: ${jsonDir}`); + } + + const host = settings.jsonLibraryServer.address; + const port = settings.jsonLibraryServer.port; + const log = options.onLog || (() => {}); + + const server = http.createServer((req, res) => { + if (!req.url) { + send(res, 400, "Bad request"); + return; + } + + const requestUrl = new URL(req.url, `http://${host}:${port}`); + const pathname = decodeURIComponent(requestUrl.pathname); + + if (pathname === "/") { + send(res, 200, renderHomePage(host, port, jsonDir), "text/html; charset=utf-8"); + return; + } + + if (pathname === "/background") { + sendBackgroundImage(res, coversDir); + return; + } + + if (pathname === "/library" || pathname === "/library/") { + const cat = requestUrl.searchParams.get("cat"); + const jsonPath = path.join(jsonDir, `${cat}.json`); + + if (!cat || !fs.existsSync(jsonPath)) { + send(res, 404, "Category not found"); + return; + } + + send(res, 200, renderLibraryPage(cat), "text/html; charset=utf-8"); + return; + } + + const category = pathname.replace(/^\/+/, ""); + if (!category.includes("/") && category) { + sendJsonFile(res, path.join(jsonDir, `${category}.json`)); + return; + } + + send(res, 404, "Not found"); + }); + + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, host === "0.0.0.0" ? "0.0.0.0" : host, () => { + server.removeListener("error", reject); + log(`JSON library server running at http://${host}:${port}/`); + log(`Serving JSON files from: ${jsonDir}`); + resolve(server); + }); + }); +} + +function stopJsonLibraryServer(server, onLog) { + const log = onLog || (() => {}); + if (!server) return Promise.resolve(); + + return new Promise((resolve, reject) => { + server.close(error => { + if (error) { + reject(error); + return; + } + log("JSON library server stopped"); + resolve(); + }); + }); +} + +module.exports = { + startJsonLibraryServer, + stopJsonLibraryServer +}; diff --git a/linux/app/processor.js b/linux/app/processor.js new file mode 100644 index 0000000..6b4b864 --- /dev/null +++ b/linux/app/processor.js @@ -0,0 +1,478 @@ +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const { execSync } = require("child_process"); + +const DLC_CODES = { ac: true }; + +function createLogger(onLog) { + return message => { + if (onLog) onLog(message); + }; +} + +function getPkgInfo(pkgPath, orbisCmdPath) { + let output = ""; + try { + output = execSync(`"${orbisCmdPath}" img_info "${pkgPath}"`, { + stdio: "pipe", + timeout: 15000 + }).toString(); + } catch (err) { + output = ((err.stdout || "").toString() + (err.stderr || "").toString()); + } + + const get = key => { + const match = output.match(new RegExp(`${key}:\\s*(.+)`)); + return match ? match[1].trim() : null; + }; + + const category = get("Category"); + const titleId = get("Title ID"); + const contentId = get("Content ID"); + const isDlc = Boolean(category && DLC_CODES[category]); + const identifier = isDlc ? (contentId || titleId) : titleId; + + return { + titleId: titleId || path.basename(pkgPath, ".pkg"), + contentId: contentId || null, + identifier: identifier || path.basename(pkgPath, ".pkg"), + isDlc, + fallback: !titleId + }; +} + +function readBytes(fd, offset, length) { + const buf = Buffer.alloc(length); + fs.readSync(fd, buf, 0, length, offset); + return buf; +} + +function parseSFO(buffer) { + const keyTableOffset = buffer.readUInt32LE(8); + const dataOffset = buffer.readUInt32LE(12); + const numEntries = buffer.readUInt32LE(16); + const keyTable = buffer.slice(keyTableOffset); + const gameInfo = {}; + + for (let i = 0; i < numEntries; i++) { + const entryOffset = 20 + i * 16; + if (entryOffset + 16 > buffer.length) continue; + + const keyOffset = buffer.readUInt16LE(entryOffset); + const valueType = buffer.readUInt16LE(entryOffset + 2); + const valueSize = buffer.readUInt32LE(entryOffset + 4); + const relativeDataOffset = buffer.readUInt32LE(entryOffset + 12); + const absoluteDataOffset = dataOffset + relativeDataOffset; + if (keyOffset >= keyTable.length) continue; + + let keyEnd = keyTable.indexOf(0, keyOffset); + if (keyEnd === -1) keyEnd = keyTable.length; + const key = keyTable.slice(keyOffset, keyEnd).toString("utf8"); + if (absoluteDataOffset + valueSize > buffer.length) continue; + + if (valueType === 0x0204) { + gameInfo[key] = buffer.slice(absoluteDataOffset, absoluteDataOffset + valueSize).toString("utf8").replace(/\0/g, ""); + } else if (valueType === 0x0404) { + gameInfo[key] = buffer.readUInt32LE(absoluteDataOffset); + } + } + + return gameInfo; +} + +function extractGameDetails(pkgPath) { + let fd; + try { + fd = fs.openSync(pkgPath, "r"); + const fileSize = fs.statSync(pkgPath).size; + let offset = 0; + let foundOffset = null; + + while (offset < fileSize) { + const buf = readBytes(fd, offset, Math.min(1048576, fileSize - offset)); + const match = buf.indexOf(Buffer.from("\x00PSF\x01\x01\x00\x00", "binary")); + if (match !== -1) { + foundOffset = offset + match; + break; + } + offset += 1048576; + } + + if (foundOffset === null) return null; + return parseSFO(readBytes(fd, foundOffset, 2048)); + } catch (_) { + return null; + } finally { + if (typeof fd === "number") fs.closeSync(fd); + } +} + +function detectRegion(contentId) { + if (!contentId) return "UNK"; + if (contentId.startsWith("UP")) return "USA"; + if (contentId.startsWith("EP")) return "EUR"; + if (contentId.startsWith("JP")) return "JAP"; + return "UNK"; +} + +function getFormattedDate(filePath) { + const d = new Date(fs.statSync(filePath).birthtime); + return `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}-${d.getFullYear()}`; +} + +function findFileRecursive(dir, filename) { + try { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isFile() && entry.name.toLowerCase() === filename.toLowerCase()) return full; + if (entry.isDirectory()) { + const match = findFileRecursive(full, filename); + if (match) return match; + } + } + } catch (_) {} + return null; +} + +function extractIcon(pkgPath, identifier, tempDir, orbisCmdPath, log) { + const safeName = identifier.replace(/[<>:"/\\|?*]/g, "_"); + const outDir = path.join(tempDir, safeName); + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + + const cmd = `"${orbisCmdPath}" img_extract --no_passcode "${pkgPath}:Sc0/icon0.png" "${outDir}"`; + try { + execSync(cmd, { stdio: "pipe", timeout: 30000 }); + } catch (err) { + const out = ((err.stdout || "").toString() + (err.stderr || "").toString()).trim(); + if (out) log(` ${out.split("\n")[0]}`); + } + + return findFileRecursive(outDir, "icon0.png"); +} + +function collectPkgFiles(baseDir) { + const ignoredNames = ["node_modules", ".git", "covers", "_temp"]; + const pkgList = []; + const pkgsByCategory = {}; + + fs.readdirSync(baseDir, { withFileTypes: true }) + .filter(entry => entry.isDirectory() && !ignoredNames.includes(entry.name) && !entry.name.startsWith(".")) + .forEach(categoryEntry => { + const categoryPath = path.join(baseDir, categoryEntry.name); + const pkgFiles = []; + + fs.readdirSync(categoryPath, { withFileTypes: true }).forEach(entry => { + const fullPath = path.join(categoryPath, entry.name); + if (entry.isDirectory()) { + try { + fs.readdirSync(fullPath).forEach(fileName => { + if (fileName.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(path.join(fullPath, fileName)); + } + }); + } catch (_) {} + } else if (entry.name.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(fullPath); + } + }); + + if (pkgFiles.length > 0) { + pkgsByCategory[categoryEntry.name] = pkgFiles; + pkgFiles.forEach(pkgPath => pkgList.push({ pkgPath, category: categoryEntry.name })); + } + }); + + return { pkgList, pkgsByCategory }; +} + +function findCoverImage(coversDir, serverAddress, titleId, contentId, category) { + const extensions = [".png", ".jpg", ".jpeg"]; + const isDlcOrTheme = category === "DLC" || category === "themes"; + const primaryKey = isDlcOrTheme ? (contentId || titleId) : titleId; + const secondaryKey = titleId; + const allFolders = [category, "games", "apps", "updates", "DLC", "demos", "homebrew", "emulators", "themes", "PS1", "PS2", "PSP"]; + + if (primaryKey) { + for (const ext of extensions) { + const ownPath = path.join(coversDir, category, `${primaryKey}${ext}`); + if (fs.existsSync(ownPath)) { + return `http://${serverAddress}/covers/${category}/${primaryKey}${ext}`; + } + } + } + + if (isDlcOrTheme && secondaryKey && secondaryKey !== primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${secondaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${secondaryKey}${ext}`; + } + } + } + } + + if (primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${primaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${primaryKey}${ext}`; + } + } + } + } + + return `http://${serverAddress}/covers/default.png`; +} + +function generateJSON(jsonOutputDir, serverAddress, category, games) { + const jsonFilePath = path.join(jsonOutputDir, `${category}.json`); + const jsonData = { DATA: {} }; + + games.forEach(game => { + const pkgUrl = `http://${serverAddress}/pkg/${category}/${path.basename(game.pkgPath)}`; + jsonData.DATA[encodeURI(pkgUrl)] = { + region: game.region, + title_id: game.title_id, + name: game.title, + version: game.version, + release: game.release, + size: game.size, + min_fw: null, + cover_url: game.cover_url + }; + }); + + fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 4)); +} + +function validateSettings(settings) { + if (!settings.paths?.pkgScan) throw new Error("PKG scan path is required."); + if (!settings.paths?.covers) throw new Error("Covers path is required."); + if (!settings.output?.path) throw new Error("JSON output path is required."); + if (settings.httpServer?.enabled) { + if (!settings.httpServer.address) throw new Error("HTTP server IP is required."); + if (!settings.httpServer.port) throw new Error("HTTP server port is required."); + } else if (!settings.serverAddress) { + throw new Error("Server address is required when HTTP server is disabled."); + } + if (settings.jsonLibraryServer?.enabled) { + if (!settings.jsonLibraryServer.address) throw new Error("JSON library IP is required."); + if (!settings.jsonLibraryServer.port) throw new Error("JSON library port is required."); + } +} + +async function runProcessing(settings, options = {}) { + validateSettings(settings); + const log = createLogger(options.onLog); + const orbisCmdPath = options.orbisCmdPath; + if (!orbisCmdPath || !fs.existsSync(orbisCmdPath)) throw new Error("orbis-pub-cmd.exe is missing."); + + const baseDir = settings.paths.pkgScan; + const coversDir = settings.paths.covers; + const jsonOutputDir = settings.output.path; + const tempDir = path.join(coversDir, "_temp"); + const serverAddress = settings.httpServer?.enabled + ? `${settings.httpServer.address}:${settings.httpServer.port}` + : settings.serverAddress; + + if (!fs.existsSync(baseDir)) throw new Error(`PKG scan directory not found: ${baseDir}`); + + fs.mkdirSync(coversDir, { recursive: true }); + fs.mkdirSync(jsonOutputDir, { recursive: true }); + fs.mkdirSync(tempDir, { recursive: true }); + + const { pkgList, pkgsByCategory } = collectPkgFiles(baseDir); + if (pkgList.length === 0) { + log("No PKG files found in any category subfolder."); + return { totalPkgs: 0, categories: 0 }; + } + + log(`Found ${pkgList.length} PKG(s) across ${Object.keys(pkgsByCategory).length} categories`); + log("-".repeat(55)); + log("STEP 1: Extracting Icons"); + + let extracted = 0; + let skipped = 0; + let failed = 0; + + for (const { pkgPath, category } of pkgList) { + log(` Processing ${path.basename(pkgPath)}`); + const info = getPkgInfo(pkgPath, orbisCmdPath); + + if (info.fallback) log(` No Title ID found, using filename: ${info.identifier}`); + else if (info.isDlc) log(` DLC Content ID: ${info.identifier}`); + else log(` Title ID: ${info.titleId}`); + + const coverFolder = path.join(coversDir, category); + if (!fs.existsSync(coverFolder)) fs.mkdirSync(coverFolder, { recursive: true }); + + const outFile = path.join(coverFolder, `${info.identifier}.png`); + if (fs.existsSync(outFile)) { + log(" Icon already exists"); + skipped++; + continue; + } + + const iconPath = extractIcon(pkgPath, info.identifier, tempDir, orbisCmdPath, log); + if (iconPath) { + fs.copyFileSync(iconPath, outFile); + log(` Saved: ${outFile}`); + extracted++; + } else { + log(" icon0.png not found in PKG"); + failed++; + } + + try { + const tempFolder = path.join(tempDir, info.identifier.replace(/[<>:"/\\|?*]/g, "_")); + if (fs.existsSync(tempFolder)) fs.rmSync(tempFolder, { recursive: true, force: true }); + } catch (_) {} + } + + try { + if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true }); + } catch (_) {} + + log(`Icons done. Extracted: ${extracted} | Skipped: ${skipped} | Failed: ${failed}`); + log("-".repeat(55)); + log("STEP 2: Generating JSON Files"); + + let totalProcessed = 0; + + for (const [category, pkgFiles] of Object.entries(pkgsByCategory)) { + log(` Category: ${category}`); + const games = []; + + for (const pkgPath of pkgFiles) { + log(` Processing ${path.basename(pkgPath)}`); + const info = getPkgInfo(pkgPath, orbisCmdPath); + const details = extractGameDetails(pkgPath); + + if (!info.titleId && !details) { + log(" No metadata, skipping"); + continue; + } + + const version = details ? (() => { + const v = parseFloat(details.VERSION) || 0; + const a = parseFloat(details.APP_VER) || 0; + return (v >= a ? details.VERSION : details.APP_VER) || "0.00"; + })() : "0.00"; + + games.push({ + pkgPath, + region: detectRegion(info.contentId), + title_id: info.titleId, + content_id: info.contentId, + title: (details && details.TITLE) || "Unknown", + version, + release: getFormattedDate(pkgPath), + size: fs.statSync(pkgPath).size.toString(), + cover_url: findCoverImage(coversDir, serverAddress, info.titleId, info.contentId, category) + }); + totalProcessed++; + } + + if (games.length > 0) { + generateJSON(jsonOutputDir, serverAddress, category, games); + log(` ${games.length} PKG(s) -> ${category}.json`); + } else { + log(` No valid PKGs in ${category}`); + } + } + + log(`JSON done. ${totalProcessed} PKG(s) processed`); + return { totalPkgs: pkgList.length, categories: Object.keys(pkgsByCategory).length, extracted, skipped, failed, totalProcessed }; +} + +function sendResponse(res, statusCode, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(statusCode, { "Content-Type": contentType }); + res.end(body); +} + +function serveStaticFile(req, res, urlPrefix, baseDir) { + const relativePath = decodeURIComponent(req.url.slice(urlPrefix.length)).replace(/^\/+/, ""); + const resolvedBase = path.resolve(baseDir); + const absolutePath = path.resolve(baseDir, relativePath); + + if (!absolutePath.startsWith(resolvedBase + path.sep) && absolutePath !== resolvedBase) { + sendResponse(res, 403, "Forbidden"); + return; + } + + if (!fs.existsSync(absolutePath)) { + sendResponse(res, 404, "Not found"); + return; + } + + const stat = fs.statSync(absolutePath); + if (stat.isDirectory()) { + sendResponse(res, 403, "Directory listing is disabled"); + return; + } + + const contentTypes = { + ".json": "application/json; charset=utf-8", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".pkg": "application/octet-stream" + }; + const ext = path.extname(absolutePath).toLowerCase(); + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size + }); + + fs.createReadStream(absolutePath).pipe(res); +} + +function startHttpServer(settings, options = {}) { + validateSettings(settings); + if (!settings.httpServer?.enabled) return Promise.resolve(null); + + const log = createLogger(options.onLog); + const address = settings.httpServer.address; + const port = settings.httpServer.port; + const pkgPath = settings.paths.pkgScan; + const coversPath = settings.paths.covers; + + const server = http.createServer((req, res) => { + if (!req.url) return sendResponse(res, 400, "Bad request"); + if (req.url === "/" || req.url === "") return sendResponse(res, 200, "Available paths: /pkg and /covers"); + if (req.url === "/pkg" || req.url === "/pkg/" || req.url === "/covers" || req.url === "/covers/") { + return sendResponse(res, 403, "Directory listing is disabled"); + } + if (req.url.startsWith("/pkg/")) return serveStaticFile(req, res, "/pkg/", pkgPath); + if (req.url.startsWith("/covers/")) return serveStaticFile(req, res, "/covers/", coversPath); + return sendResponse(res, 404, "Not found"); + }); + + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, address, () => { + server.removeListener("error", reject); + log(`HTTP server running at http://${address}:${port}`); + resolve(server); + }); + }); +} + +function stopHttpServer(server, onLog) { + const log = createLogger(onLog); + if (!server) return Promise.resolve(); + + return new Promise((resolve, reject) => { + server.close(error => { + if (error) return reject(error); + log("HTTP server stopped"); + resolve(); + }); + }); +} + +module.exports = { runProcessing, startHttpServer, stopHttpServer, validateSettings }; diff --git a/linux/json-generator-linux.js b/linux/json-generator-linux.js new file mode 100644 index 0000000..284ac82 --- /dev/null +++ b/linux/json-generator-linux.js @@ -0,0 +1,483 @@ +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); +const http = require("http"); + +const configPath = path.join(__dirname, "json-generator-config.json"); + +let BASE_DIR = null; +let COVERS_DIR = null; +let JSON_OUTPUT_DIR = null; +let SERVER_ADDRESS = null; + +function readBytes(fd, offset, length) { + const buf = Buffer.alloc(length); + fs.readSync(fd, buf, 0, length, offset); + return buf; +} + +function parseSFO(buffer) { + const keyTableOffset = buffer.readUInt32LE(8); + const dataOffset = buffer.readUInt32LE(12); + const numEntries = buffer.readUInt32LE(16); + const keyTable = buffer.slice(keyTableOffset); + const gameInfo = {}; + + for (let i = 0; i < numEntries; i++) { + const entryOffset = 20 + i * 16; + if (entryOffset + 16 > buffer.length) continue; + + const keyOffset = buffer.readUInt16LE(entryOffset); + const valueType = buffer.readUInt16LE(entryOffset + 2); + const valueSize = buffer.readUInt32LE(entryOffset + 4); + const relativeDataOffset = buffer.readUInt32LE(entryOffset + 12); + const absoluteDataOffset = dataOffset + relativeDataOffset; + + if (keyOffset >= keyTable.length) continue; + + let keyEnd = keyTable.indexOf(0, keyOffset); + if (keyEnd === -1) keyEnd = keyTable.length; + const key = keyTable.slice(keyOffset, keyEnd).toString("utf8"); + + if (absoluteDataOffset + valueSize > buffer.length) continue; + + if (valueType === 0x0204) { + gameInfo[key] = buffer + .slice(absoluteDataOffset, absoluteDataOffset + valueSize) + .toString("utf8") + .replace(/\0/g, ""); + } else if (valueType === 0x0404) { + gameInfo[key] = buffer.readUInt32LE(absoluteDataOffset); + } + } + + return gameInfo; +} + +function extractGameDetails(pkgPath) { + let fd; + + try { + fd = fs.openSync(pkgPath, "r"); + const fileSize = fs.statSync(pkgPath).size; + let offset = 0; + let foundOffset = null; + + while (offset < fileSize) { + const buf = readBytes(fd, offset, Math.min(1048576, fileSize - offset)); + const match = buf.indexOf(Buffer.from("\x00PSF\x01\x01\x00\x00", "binary")); + if (match !== -1) { + foundOffset = offset + match; + break; + } + offset += 1048576; + } + + if (foundOffset === null) { + return null; + } + + const sfoBuffer = readBytes(fd, foundOffset, 2048); + return parseSFO(sfoBuffer); + } catch (_) { + return null; + } finally { + if (typeof fd === "number") { + fs.closeSync(fd); + } + } +} + +function readPkgMetadata(pkgPath) { + let fd; + + try { + fd = fs.openSync(pkgPath, "r"); + const buffer = Buffer.alloc(0x800); + fs.readSync(fd, buffer, 0, 0x800, 0); + const contentId = buffer.slice(0x30, 0x50).toString("utf8").replace(/\0/g, ""); + const titleIdMatch = contentId.match(/-(CUSA\d+)/); + + return { + contentId: contentId || null, + titleId: titleIdMatch ? titleIdMatch[1] : null + }; + } catch (_) { + return { + contentId: null, + titleId: null + }; + } finally { + if (typeof fd === "number") { + fs.closeSync(fd); + } + } +} + +function detectRegion(contentId) { + if (!contentId) return "UNK"; + if (contentId.startsWith("UP")) return "USA"; + if (contentId.startsWith("EP")) return "EUR"; + if (contentId.startsWith("JP")) return "JAP"; + return "UNK"; +} + +function getFormattedDate(filePath) { + const d = new Date(fs.statSync(filePath).birthtime); + return `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}-${d.getFullYear()}`; +} + +function findCoverImage(titleId, contentId, category) { + const extensions = [".png", ".jpg", ".jpeg"]; + const isDlcOrTheme = category === "DLC" || category === "themes"; + const primaryKey = isDlcOrTheme ? (contentId || titleId) : titleId; + const secondaryKey = titleId; + const allFolders = [category, "games", "apps", "updates", "DLC", "demos", "homebrew", "emulators", "themes", "PS1", "PS2", "PSP"]; + + for (const ext of extensions) { + const ownPath = path.join(COVERS_DIR, category, `${primaryKey}${ext}`); + if (primaryKey && fs.existsSync(ownPath)) { + return `http://${SERVER_ADDRESS}/covers/${category}/${primaryKey}${ext}`; + } + } + + if (isDlcOrTheme && secondaryKey && secondaryKey !== primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(COVERS_DIR, folder, `${secondaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${SERVER_ADDRESS}/covers/${folder}/${secondaryKey}${ext}`; + } + } + } + } + + if (primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(COVERS_DIR, folder, `${primaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${SERVER_ADDRESS}/covers/${folder}/${primaryKey}${ext}`; + } + } + } + } + + return `http://${SERVER_ADDRESS}/covers/default.png`; +} + +function generateJSON(category, games) { + const jsonFilePath = path.join(JSON_OUTPUT_DIR, `${category}.json`); + const jsonData = { DATA: {} }; + + for (const game of games) { + const pkgUrl = `http://${SERVER_ADDRESS}/pkg/${category}/${path.basename(game.pkgPath)}`; + jsonData.DATA[encodeURI(pkgUrl)] = { + region: game.region, + title_id: game.title_id, + name: game.title, + version: game.version, + release: game.release, + size: game.size, + min_fw: null, + cover_url: game.cover_url + }; + } + + fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 4)); + console.log(` Generated: ${jsonFilePath}`); +} + +function runJsonGeneration(pkgsByCategory) { + console.log("-".repeat(55)); + console.log("STEP 1: Generating JSON Files\n"); + + let totalProcessed = 0; + + for (const [category, pkgFiles] of Object.entries(pkgsByCategory)) { + console.log(` Category: ${category}`); + const games = []; + + pkgFiles.forEach(pkgPath => { + console.log(` Processing ${path.basename(pkgPath)}`); + + const details = extractGameDetails(pkgPath) || {}; + const pkgMeta = readPkgMetadata(pkgPath); + const titleId = details.TITLE_ID || pkgMeta.titleId || path.basename(pkgPath, ".pkg"); + const contentId = details.CONTENT_ID || pkgMeta.contentId || null; + const version = (() => { + const v = parseFloat(details.VERSION) || 0; + const a = parseFloat(details.APP_VER) || 0; + return (v >= a ? details.VERSION : details.APP_VER) || "0.00"; + })(); + + games.push({ + pkgPath, + region: detectRegion(contentId), + title_id: titleId, + content_id: contentId, + title: details.TITLE || "Unknown", + version, + release: getFormattedDate(pkgPath), + size: fs.statSync(pkgPath).size.toString(), + cover_url: findCoverImage(titleId, contentId, category) + }); + totalProcessed++; + }); + + if (games.length > 0) { + generateJSON(category, games); + console.log(` ${games.length} PKG(s) -> ${category}.json\n`); + } else { + console.log(` No valid PKGs in ${category}\n`); + } + } + + console.log(`JSON done. ${totalProcessed} PKG(s) processed\n`); +} + +function collectPkgFiles() { + const ignoredNames = ["node_modules", ".git", "covers", "_temp"]; + const pkgList = []; + const pkgsByCategory = {}; + + fs.readdirSync(BASE_DIR, { withFileTypes: true }) + .filter(entry => entry.isDirectory() && !ignoredNames.includes(entry.name) && !entry.name.startsWith(".")) + .forEach(categoryEntry => { + const categoryPath = path.join(BASE_DIR, categoryEntry.name); + const pkgFiles = []; + + fs.readdirSync(categoryPath, { withFileTypes: true }).forEach(entry => { + const fullPath = path.join(categoryPath, entry.name); + if (entry.isDirectory()) { + try { + fs.readdirSync(fullPath).forEach(fileName => { + if (fileName.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(path.join(fullPath, fileName)); + } + }); + } catch (_) {} + } else if (entry.name.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(fullPath); + } + }); + + if (pkgFiles.length > 0) { + pkgsByCategory[categoryEntry.name] = pkgFiles; + pkgFiles.forEach(pkgPath => pkgList.push({ pkgPath, category: categoryEntry.name })); + } + }); + + return { pkgList, pkgsByCategory }; +} + +function sendResponse(res, statusCode, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(statusCode, { "Content-Type": contentType }); + res.end(body); +} + +function serveStaticFile(req, res, urlPrefix, baseDir) { + const relativePath = decodeURIComponent(req.url.slice(urlPrefix.length)).replace(/^\/+/, ""); + const resolvedBase = path.resolve(baseDir); + const absolutePath = path.resolve(baseDir, relativePath); + + if (!absolutePath.startsWith(resolvedBase + path.sep) && absolutePath !== resolvedBase) { + sendResponse(res, 403, "Forbidden"); + return; + } + + if (!fs.existsSync(absolutePath)) { + sendResponse(res, 404, "Not found"); + return; + } + + const stat = fs.statSync(absolutePath); + if (stat.isDirectory()) { + sendResponse(res, 403, "Directory listing is disabled"); + return; + } + + const contentTypes = { + ".json": "application/json; charset=utf-8", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".pkg": "application/octet-stream" + }; + const ext = path.extname(absolutePath).toLowerCase(); + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size + }); + + fs.createReadStream(absolutePath).pipe(res); +} + +function startHttpServer(config) { + const address = config.httpServer.address; + const port = config.httpServer.port; + const pkgPath = config.paths.pkgScan; + const coversPath = config.paths.covers; + + console.log("Starting built-in HTTP server"); + console.log(` Address: http://${address}:${port}`); + console.log(" Structure:"); + console.log(` /pkg/ -> ${pkgPath}`); + console.log(` /covers/ -> ${coversPath}\n`); + + const server = http.createServer((req, res) => { + if (!req.url) { + sendResponse(res, 400, "Bad request"); + return; + } + + if (req.url === "/" || req.url === "") { + sendResponse(res, 200, "Available paths: /pkg and /covers"); + return; + } + + if (req.url === "/pkg" || req.url === "/pkg/" || req.url === "/covers" || req.url === "/covers/") { + sendResponse(res, 403, "Directory listing is disabled"); + return; + } + + if (req.url.startsWith("/pkg/")) { + serveStaticFile(req, res, "/pkg/", pkgPath); + return; + } + + if (req.url.startsWith("/covers/")) { + serveStaticFile(req, res, "/covers/", coversPath); + return; + } + + sendResponse(res, 404, "Not found"); + }); + + server.on("error", err => { + console.error(`Failed to start HTTP server: ${err.message}`); + process.exit(1); + }); + + server.listen(port, address); + return server; +} + +async function loadOrCreateConfig() { + if (fs.existsSync(configPath)) { + console.log("Loading existing config...\n"); + return JSON.parse(fs.readFileSync(configPath, "utf8")); + } + + console.log("No config found. Let's set it up.\n"); + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + const question = q => new Promise(resolve => rl.question(q, resolve)); + + const useHttpRaw = await question("Enable built-in HTTP server to serve files? (y/n): "); + const useHttpServer = useHttpRaw.trim().toLowerCase() === "y"; + const pkgPath = (await question("Path to PKG files folder (e.g., /mnt/data/pkg): ")).trim(); + const coversPath = (await question("Path to Covers folder (e.g., /mnt/data/covers): ")).trim(); + + let httpServerConfig = { enabled: false }; + let serverAddress = ""; + + if (useHttpServer) { + const httpAddress = await question("HTTP server bind address (e.g., 0.0.0.0): "); + const httpPort = await question("HTTP server port (e.g., 8080): "); + + serverAddress = `${httpAddress.trim()}:${httpPort.trim()}`; + httpServerConfig = { + enabled: true, + address: httpAddress.trim(), + port: parseInt(httpPort.trim(), 10) + }; + + console.log(`\nServer URLs will use: http://${serverAddress}`); + console.log(` PKGs path: ${pkgPath}`); + console.log(` Covers path: ${coversPath}\n`); + } else { + serverAddress = (await question("Server address for generated URLs (e.g., 192.168.1.100:8080 or myhome.server): ")).trim(); + } + + const outputPath = await question("Path where JSON files should be saved (e.g., ./json): "); + rl.close(); + + const config = { + serverAddress, + httpServer: httpServerConfig, + paths: { + pkgScan: pkgPath, + covers: coversPath + }, + output: { + path: outputPath.trim() + } + }; + + fs.writeFileSync(configPath, JSON.stringify(config, null, 4)); + console.log("\nConfig saved to json-generator-config.json\n"); + return config; +} + +function findFileRecursive(dir, filename) { + try { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isFile() && entry.name.toLowerCase() === filename.toLowerCase()) return full; + if (entry.isDirectory()) { + const match = findFileRecursive(full, filename); + if (match) return match; + } + } + } catch (_) {} + return null; +} + +(async () => { + console.log("JSON Generator for Linux - JSON Builder Only\n"); + + const config = await loadOrCreateConfig(); + + SERVER_ADDRESS = (config.httpServer && config.httpServer.enabled) + ? `${config.httpServer.address}:${config.httpServer.port}` + : config.serverAddress; + + BASE_DIR = config.paths.pkgScan; + COVERS_DIR = config.paths.covers; + JSON_OUTPUT_DIR = config.output.path; + + if (!fs.existsSync(BASE_DIR)) { + console.error(`PKG scan directory not found: ${BASE_DIR}`); + process.exit(1); + } + + fs.mkdirSync(COVERS_DIR, { recursive: true }); + fs.mkdirSync(JSON_OUTPUT_DIR, { recursive: true }); + + console.log(`PKG folder: ${BASE_DIR}`); + console.log(`Covers: ${COVERS_DIR}`); + console.log(`JSON Output: ${JSON_OUTPUT_DIR}`); + console.log(`Server URL: http://${SERVER_ADDRESS}\n`); + + let httpProc = null; + if (config.httpServer && config.httpServer.enabled) { + httpProc = startHttpServer(config); + } + + const { pkgList, pkgsByCategory } = collectPkgFiles(); + + if (pkgList.length === 0) { + console.log("No PKG files found in any category subfolder."); + if (httpProc) console.log("\nHTTP server is still running. Press Ctrl+C to stop."); + return; + } + + console.log(`Found ${pkgList.length} PKG(s) across ${Object.keys(pkgsByCategory).length} category/categories\n`); + + runJsonGeneration(pkgsByCategory); + + console.log("-".repeat(55)); + console.log("All done!"); + if (httpProc) console.log("\nHTTP server is still running. Press Ctrl+C to stop."); +})(); diff --git a/linux/node-menu-linux.js b/linux/node-menu-linux.js new file mode 100644 index 0000000..64953b3 --- /dev/null +++ b/linux/node-menu-linux.js @@ -0,0 +1,529 @@ +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); +const { startHttpServer, stopHttpServer, validateSettings } = require("./app/processor"); +const { startJsonLibraryServer, stopJsonLibraryServer } = require("./app/json-library-server"); + +let httpServerInstance = null; +let jsonLibraryServerInstance = null; + +function log(message = "") { + process.stdout.write(`${message}\n`); +} + +function error(message) { + process.stderr.write(`${message}\n`); +} + +function defaultSettings() { + return { + serverAddress: "localhost:8080", + httpServer: { + enabled: true, + address: "localhost", + port: 8080 + }, + jsonLibraryServer: { + enabled: false, + address: "localhost", + port: 3000 + }, + paths: { + pkgScan: "", + covers: "" + }, + output: { + path: "" + } + }; +} + +function getConfigPath() { + const argIndex = process.argv.indexOf("--config"); + if (argIndex !== -1 && process.argv[argIndex + 1]) { + return path.resolve(process.argv[argIndex + 1]); + } + return path.join(process.cwd(), "node-settings-linux.json"); +} + +function saveSettings(settings) { + fs.writeFileSync(getConfigPath(), JSON.stringify(settings, null, 4)); +} + +function loadOrCreateSettings() { + const configPath = getConfigPath(); + const defaults = defaultSettings(); + + if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, JSON.stringify(defaults, null, 4)); + return defaults; + } + + const loaded = JSON.parse(fs.readFileSync(configPath, "utf8")); + return { + ...defaults, + ...loaded, + httpServer: { ...defaults.httpServer, ...(loaded.httpServer || {}) }, + jsonLibraryServer: { ...defaults.jsonLibraryServer, ...(loaded.jsonLibraryServer || {}) }, + paths: { ...defaults.paths, ...(loaded.paths || {}) }, + output: { ...defaults.output, ...(loaded.output || {}) } + }; +} + +function readBytes(fd, offset, length) { + const buf = Buffer.alloc(length); + fs.readSync(fd, buf, 0, length, offset); + return buf; +} + +function parseSFO(buffer) { + const keyTableOffset = buffer.readUInt32LE(8); + const dataOffset = buffer.readUInt32LE(12); + const numEntries = buffer.readUInt32LE(16); + const keyTable = buffer.slice(keyTableOffset); + const gameInfo = {}; + + for (let i = 0; i < numEntries; i++) { + const entryOffset = 20 + i * 16; + if (entryOffset + 16 > buffer.length) continue; + + const keyOffset = buffer.readUInt16LE(entryOffset); + const valueType = buffer.readUInt16LE(entryOffset + 2); + const valueSize = buffer.readUInt32LE(entryOffset + 4); + const relativeDataOffset = buffer.readUInt32LE(entryOffset + 12); + const absoluteDataOffset = dataOffset + relativeDataOffset; + + if (keyOffset >= keyTable.length) continue; + + let keyEnd = keyTable.indexOf(0, keyOffset); + if (keyEnd === -1) keyEnd = keyTable.length; + const key = keyTable.slice(keyOffset, keyEnd).toString("utf8"); + + if (absoluteDataOffset + valueSize > buffer.length) continue; + + if (valueType === 0x0204) { + gameInfo[key] = buffer.slice(absoluteDataOffset, absoluteDataOffset + valueSize).toString("utf8").replace(/\0/g, ""); + } else if (valueType === 0x0404) { + gameInfo[key] = buffer.readUInt32LE(absoluteDataOffset); + } + } + + return gameInfo; +} + +function extractGameDetails(pkgPath) { + let fd; + + try { + fd = fs.openSync(pkgPath, "r"); + const fileSize = fs.statSync(pkgPath).size; + let offset = 0; + let foundOffset = null; + + while (offset < fileSize) { + const buf = readBytes(fd, offset, Math.min(1048576, fileSize - offset)); + const match = buf.indexOf(Buffer.from("\x00PSF\x01\x01\x00\x00", "binary")); + if (match !== -1) { + foundOffset = offset + match; + break; + } + offset += 1048576; + } + + if (foundOffset === null) return null; + return parseSFO(readBytes(fd, foundOffset, 2048)); + } catch (_) { + return null; + } finally { + if (typeof fd === "number") fs.closeSync(fd); + } +} + +function readPkgMetadata(pkgPath) { + let fd; + + try { + fd = fs.openSync(pkgPath, "r"); + const buffer = Buffer.alloc(0x800); + fs.readSync(fd, buffer, 0, 0x800, 0); + const contentId = buffer.slice(0x30, 0x50).toString("utf8").replace(/\0/g, ""); + const titleIdMatch = contentId.match(/-(CUSA\d+)/); + return { + contentId: contentId || null, + titleId: titleIdMatch ? titleIdMatch[1] : null + }; + } catch (_) { + return { contentId: null, titleId: null }; + } finally { + if (typeof fd === "number") fs.closeSync(fd); + } +} + +function detectRegion(contentId) { + if (!contentId) return "UNK"; + if (contentId.startsWith("UP")) return "USA"; + if (contentId.startsWith("EP")) return "EUR"; + if (contentId.startsWith("JP")) return "JAP"; + return "UNK"; +} + +function getFormattedDate(filePath) { + const d = new Date(fs.statSync(filePath).birthtime); + return `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}-${d.getFullYear()}`; +} + +function collectPkgFiles(baseDir) { + const ignoredNames = ["node_modules", ".git", "covers", "_temp"]; + const pkgList = []; + const pkgsByCategory = {}; + + fs.readdirSync(baseDir, { withFileTypes: true }) + .filter(entry => entry.isDirectory() && !ignoredNames.includes(entry.name) && !entry.name.startsWith(".")) + .forEach(categoryEntry => { + const categoryPath = path.join(baseDir, categoryEntry.name); + const pkgFiles = []; + + fs.readdirSync(categoryPath, { withFileTypes: true }).forEach(entry => { + const fullPath = path.join(categoryPath, entry.name); + if (entry.isDirectory()) { + try { + fs.readdirSync(fullPath).forEach(fileName => { + if (fileName.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(path.join(fullPath, fileName)); + } + }); + } catch (_) {} + } else if (entry.name.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(fullPath); + } + }); + + if (pkgFiles.length > 0) { + pkgsByCategory[categoryEntry.name] = pkgFiles; + pkgFiles.forEach(pkgPath => pkgList.push({ pkgPath, category: categoryEntry.name })); + } + }); + + return { pkgList, pkgsByCategory }; +} + +function findCoverImage(coversDir, serverAddress, titleId, contentId, category) { + const extensions = [".png", ".jpg", ".jpeg"]; + const isDlcOrTheme = category === "DLC" || category === "themes"; + const primaryKey = isDlcOrTheme ? (contentId || titleId) : titleId; + const secondaryKey = titleId; + const allFolders = [category, "games", "apps", "updates", "DLC", "demos", "homebrew", "emulators", "themes", "PS1", "PS2", "PSP"]; + + if (primaryKey) { + for (const ext of extensions) { + const ownPath = path.join(coversDir, category, `${primaryKey}${ext}`); + if (fs.existsSync(ownPath)) { + return `http://${serverAddress}/covers/${category}/${primaryKey}${ext}`; + } + } + } + + if (isDlcOrTheme && secondaryKey && secondaryKey !== primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${secondaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${secondaryKey}${ext}`; + } + } + } + } + + if (primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${primaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${primaryKey}${ext}`; + } + } + } + } + + return `http://${serverAddress}/covers/default.png`; +} + +function generateJSON(jsonOutputDir, serverAddress, category, games) { + const jsonFilePath = path.join(jsonOutputDir, `${category}.json`); + const jsonData = { DATA: {} }; + + games.forEach(game => { + const pkgUrl = `http://${serverAddress}/pkg/${category}/${path.basename(game.pkgPath)}`; + jsonData.DATA[encodeURI(pkgUrl)] = { + region: game.region, + title_id: game.title_id, + name: game.title, + version: game.version, + release: game.release, + size: game.size, + min_fw: null, + cover_url: game.cover_url + }; + }); + + fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 4)); +} + +function createPrompt() { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const question = prompt => new Promise(resolve => rl.question(prompt, answer => resolve(answer))); + return { rl, question }; +} + +async function promptForValue(question, label, currentValue, options = {}) { + const suffix = currentValue !== undefined && currentValue !== null && currentValue !== "" + ? ` [${currentValue}]` + : ""; + const answer = (await question(`${label}${suffix}: `)).trim(); + if (!answer) return currentValue; + if (options.type === "number") return Number(answer); + return answer; +} + +function printSettings(settings) { + log(""); + log("Current Settings"); + log("----------------"); + log(`PKG scan path: ${settings.paths.pkgScan || "(not set)"}`); + log(`Covers path: ${settings.paths.covers || "(not set)"}`); + log(`JSON output path: ${settings.output.path || "(not set)"}`); + log(`HTTP server enabled: ${settings.httpServer.enabled}`); + log(`HTTP server address: ${settings.httpServer.address}:${settings.httpServer.port}`); + log(`Fallback server address: ${settings.serverAddress || "(not set)"}`); + log(`JSON library enabled: ${settings.jsonLibraryServer.enabled}`); + log(`JSON library address: ${settings.jsonLibraryServer.address}:${settings.jsonLibraryServer.port}`); + log(""); +} + +async function editSettings(settings, question) { + while (true) { + printSettings(settings); + log("Settings Menu"); + log("1. Change PKG scan path"); + log("2. Change covers path"); + log("3. Change JSON output path"); + log("4. Toggle HTTP server"); + log("5. Change HTTP server IP"); + log("6. Change HTTP server port"); + log("7. Change fallback server address"); + log("8. Toggle JSON library server"); + log("9. Change JSON library IP"); + log("10. Change JSON library port"); + log("11. Save and return"); + log("12. Return without saving"); + const choice = (await question("Choose an option: ")).trim(); + + if (choice === "1") settings.paths.pkgScan = await promptForValue(question, "PKG scan path", settings.paths.pkgScan); + else if (choice === "2") settings.paths.covers = await promptForValue(question, "Covers path", settings.paths.covers); + else if (choice === "3") settings.output.path = await promptForValue(question, "JSON output path", settings.output.path); + else if (choice === "4") settings.httpServer.enabled = !settings.httpServer.enabled; + else if (choice === "5") settings.httpServer.address = await promptForValue(question, "HTTP server IP", settings.httpServer.address); + else if (choice === "6") settings.httpServer.port = await promptForValue(question, "HTTP server port", settings.httpServer.port, { type: "number" }); + else if (choice === "7") settings.serverAddress = await promptForValue(question, "Fallback server address", settings.serverAddress); + else if (choice === "8") settings.jsonLibraryServer.enabled = !settings.jsonLibraryServer.enabled; + else if (choice === "9") settings.jsonLibraryServer.address = await promptForValue(question, "JSON library IP", settings.jsonLibraryServer.address); + else if (choice === "10") settings.jsonLibraryServer.port = await promptForValue(question, "JSON library port", settings.jsonLibraryServer.port, { type: "number" }); + else if (choice === "11") { + validateSettings(settings); + saveSettings(settings); + log(`Settings saved to ${getConfigPath()}`); + return settings; + } else if (choice === "12") { + return null; + } else { + log("Invalid option."); + } + } +} + +async function startServers(settings) { + if (settings.httpServer?.enabled && !httpServerInstance) { + httpServerInstance = await startHttpServer(settings, { onLog: log }); + } + if (settings.jsonLibraryServer?.enabled && !jsonLibraryServerInstance) { + jsonLibraryServerInstance = await startJsonLibraryServer(settings, { onLog: log }); + } +} + +function getRunningServersSummary(settings) { + const active = []; + + if (httpServerInstance && settings.httpServer?.enabled) { + active.push(`HTTP Server: http://${settings.httpServer.address}:${settings.httpServer.port}`); + } + + if (jsonLibraryServerInstance && settings.jsonLibraryServer?.enabled) { + active.push(`JSON Library: http://${settings.jsonLibraryServer.address}:${settings.jsonLibraryServer.port}/`); + } + + return active.length > 0 ? active : ["No servers currently running"]; +} + +async function stopServers() { + if (jsonLibraryServerInstance) { + await stopJsonLibraryServer(jsonLibraryServerInstance, log); + jsonLibraryServerInstance = null; + } + if (httpServerInstance) { + await stopHttpServer(httpServerInstance, log); + httpServerInstance = null; + } +} + +async function runJsonOnly(settings) { + validateSettings(settings); + + const baseDir = settings.paths.pkgScan; + const coversDir = settings.paths.covers; + const jsonOutputDir = settings.output.path; + const serverAddress = settings.httpServer?.enabled + ? `${settings.httpServer.address}:${settings.httpServer.port}` + : settings.serverAddress; + + if (!fs.existsSync(baseDir)) throw new Error(`PKG scan directory not found: ${baseDir}`); + + fs.mkdirSync(coversDir, { recursive: true }); + fs.mkdirSync(jsonOutputDir, { recursive: true }); + + const { pkgList, pkgsByCategory } = collectPkgFiles(baseDir); + if (pkgList.length === 0) { + log("No PKG files found in any category subfolder."); + return; + } + + log(`Found ${pkgList.length} PKG(s) across ${Object.keys(pkgsByCategory).length} categories`); + log("-".repeat(55)); + log("STEP 1: Generating JSON Files"); + + let totalProcessed = 0; + + for (const [category, pkgFiles] of Object.entries(pkgsByCategory)) { + log(` Category: ${category}`); + const games = []; + + for (const pkgPath of pkgFiles) { + log(` Processing ${path.basename(pkgPath)}`); + const details = extractGameDetails(pkgPath) || {}; + const pkgMeta = readPkgMetadata(pkgPath); + const titleId = details.TITLE_ID || pkgMeta.titleId || path.basename(pkgPath, ".pkg"); + const contentId = details.CONTENT_ID || pkgMeta.contentId || null; + const version = (() => { + const v = parseFloat(details.VERSION) || 0; + const a = parseFloat(details.APP_VER) || 0; + return (v >= a ? details.VERSION : details.APP_VER) || "0.00"; + })(); + + games.push({ + pkgPath, + region: detectRegion(contentId), + title_id: titleId, + content_id: contentId, + title: details.TITLE || "Unknown", + version, + release: getFormattedDate(pkgPath), + size: fs.statSync(pkgPath).size.toString(), + cover_url: findCoverImage(coversDir, serverAddress, titleId, contentId, category) + }); + totalProcessed++; + } + + if (games.length > 0) { + generateJSON(jsonOutputDir, serverAddress, category, games); + log(` ${games.length} PKG(s) -> ${category}.json`); + } else { + log(` No valid PKGs in ${category}`); + } + } + + log(`JSON done. ${totalProcessed} PKG(s) processed`); +} + +async function main() { + const prompt = createPrompt(); + let settings = loadOrCreateSettings(); + saveSettings(settings); + await startServers(settings); + + try { + while (true) { + log(""); + log("FPKGi Node Server Linux"); + log("----------------------"); + log(`Config: ${getConfigPath()}`); + log("Running servers:"); + getRunningServersSummary(settings).forEach(line => log(`- ${line}`)); + log("1. Generate JSON"); + log("2. Edit settings"); + log("3. Start servers"); + log("4. Generate JSON and keep servers running"); + log("5. Stop running servers"); + log("6. Show current settings"); + log("7. Exit"); + + const choice = (await prompt.question("Choose an option: ")).trim(); + + try { + if (choice === "1") { + await runJsonOnly(settings); + } else if (choice === "2") { + const updated = await editSettings(JSON.parse(JSON.stringify(settings)), prompt.question); + if (updated) { + settings = loadOrCreateSettings(); + await stopServers(); + await startServers(settings); + } + } else if (choice === "3") { + validateSettings(settings); + await startServers(settings); + log("Servers are running in this session."); + } else if (choice === "4") { + validateSettings(settings); + await startServers(settings); + await runJsonOnly(settings); + log("JSON generation complete. Servers are still running."); + } else if (choice === "5") { + await stopServers(); + log("Servers stopped."); + } else if (choice === "6") { + printSettings(settings); + } else if (choice === "7") { + await stopServers(); + break; + } else { + log("Invalid option."); + } + } catch (err) { + error(`Error: ${err.message}`); + } + } + } finally { + prompt.rl.close(); + } +} + +async function shutdown() { + try { + await stopServers(); + } catch (err) { + error(`Shutdown error: ${err.message}`); + } finally { + process.exit(0); + } +} + +process.on("SIGINT", shutdown); +process.on("SIGTERM", shutdown); + +main().catch(err => { + error(err.message); + process.exit(1); +}); diff --git a/linux/node-settings-linux.json b/linux/node-settings-linux.json new file mode 100644 index 0000000..0474ab1 --- /dev/null +++ b/linux/node-settings-linux.json @@ -0,0 +1,20 @@ +{ + "serverAddress": "localhost:8080", + "httpServer": { + "enabled": false, + "address": "localhost", + "port": 8080 + }, + "jsonLibraryServer": { + "enabled": false, + "address": "localhost", + "port": 3000 + }, + "paths": { + "pkgScan": "", + "covers": "" + }, + "output": { + "path": "" + } +} \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index 0f7ff3b..0000000 --- a/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "server", - "version": "1.0.0", - "main": "server.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" - }, - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "express": "^4.21.2" - } -} diff --git a/server.js b/server.js deleted file mode 100644 index fc98847..0000000 --- a/server.js +++ /dev/null @@ -1,563 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const express = require("express"); - -//Our configuration file. -const configPath = path.join(__dirname, "config.json"); - -const config = loadConfig(); -const HOSTNAME = config.server.ip; -const PORT = config.server.port; - -const BASE_DIR = path.join(__dirname, "data"); -const JSON_DIR = path.join(__dirname, "json"); -const IMAGE_DIR = path.join(BASE_DIR, "covers"); - -const GAMES_DIR = path.join(BASE_DIR, "games"); -const APPS_DIR = path.join(BASE_DIR, "apps"); -const UPDATES_DIR = path.join(BASE_DIR, "updates"); -const DLC_DIR = path.join(BASE_DIR, "DLC"); -const DEMOS_DIR = path.join(BASE_DIR, "demos"); -const HOMEBREW_DIR = path.join(BASE_DIR, "homebrew"); - -// Create our Directory Structure -if (!fs.existsSync(JSON_DIR)) fs.mkdirSync(JSON_DIR, { recursive: true }); -if (!fs.existsSync(IMAGE_DIR)) fs.mkdirSync(IMAGE_DIR, { recursive: true }); -if (!fs.existsSync(GAMES_DIR)) fs.mkdirSync(GAMES_DIR, { recursive: true }); -if (!fs.existsSync(APPS_DIR)) fs.mkdirSync(APPS_DIR, { recursive: true }); -if (!fs.existsSync(UPDATES_DIR)) fs.mkdirSync(UPDATES_DIR, { recursive: true }); -if (!fs.existsSync(DEMOS_DIR)) fs.mkdirSync(DEMOS_DIR, { recursive: true }); -if (!fs.existsSync(DLC_DIR)) fs.mkdirSync(DLC_DIR, { recursive: true }); -if (!fs.existsSync(HOMEBREW_DIR)) fs.mkdirSync(HOMEBREW_DIR, { recursive: true }); - -// Ignore -const IGNORED_DIRS = ["covers"]; - -function loadConfig() { - try { - const rawData = fs.readFileSync(configPath, "utf8"); - return JSON.parse(rawData); - } catch (error) { - console.error("⌠Error loading config.json:", error.message); - process.exit(1); // Exit if config cannot be loaded - } -} - -// Read bytes at a given offset -function readBytes(fd, offset, length) { - const buffer = Buffer.alloc(length); - fs.readSync(fd, buffer, 0, length, offset); - return buffer; -} - -// Extract data from the Package SFO -function parseSFO(buffer) { - const keyTableOffset = buffer.readUInt32LE(8); - const dataOffset = buffer.readUInt32LE(12); - const numEntries = buffer.readUInt32LE(16); - - let keyTable = buffer.slice(keyTableOffset); - let gameInfo = {}; - - for (let i = 0; i < numEntries; i++) { - let entryOffset = 20 + i * 16; - if (entryOffset + 16 > buffer.length) continue; - - let keyOffset = buffer.readUInt16LE(entryOffset); - let valueType = buffer.readUInt16LE(entryOffset + 2); - let valueSize = buffer.readUInt32LE(entryOffset + 4); - let relativeDataOffset = buffer.readUInt32LE(entryOffset + 12); - let absoluteDataOffset = dataOffset + relativeDataOffset; - - if (keyOffset >= keyTable.length) continue; - - let keyEnd = keyTable.indexOf(0, keyOffset); - if (keyEnd === -1) keyEnd = keyTable.length; - let key = keyTable.slice(keyOffset, keyEnd).toString("utf8"); - - if (absoluteDataOffset + valueSize > buffer.length) continue; - - let value; - if (valueType === 0x0204) { - let rawValue = buffer.slice(absoluteDataOffset, absoluteDataOffset + valueSize); - value = rawValue.toString("utf8").replace(/\0/g, ''); - } else if (valueType === 0x0404) { - value = buffer.readUInt32LE(absoluteDataOffset); - } else { - continue; - } - - gameInfo[key] = value; - } - - return gameInfo; -} - -// Extract a game image from a package. -function extractGameImage(pkgPath, titleID) { - try { - const fd = fs.openSync(pkgPath, "r"); - const fileSize = fs.statSync(pkgPath).size; - - let offset = 0; - const blockSize = 1048576; - let foundOffset = null; - - while (offset < fileSize) { - const buffer = readBytes(fd, offset, blockSize); - const match = buffer.indexOf(Buffer.from("PNG", "binary")); - - if (match !== -1) { - foundOffset = offset + match - 1; - break; - } - offset += blockSize; - } - - if (foundOffset === null) { - fs.closeSync(fd); - return false; - } - - const imageBuffer = readBytes(fd, foundOffset, 512 * 1024); - const imagePath = path.join(IMAGE_DIR, `${titleID}.png`); - - fs.writeFileSync(imagePath, imageBuffer); - fs.closeSync(fd); - - return true; - } catch (error) { - console.error(`⌠Image extraction failed for: ${pkgPath} - ${error.message}`); - return false; - } -} - -// Detect region based on the content ID. -function detectRegion(contentID) { - if (!contentID) return null; - if (contentID.startsWith("UP")) return "USA"; - if (contentID.startsWith("EP")) return "EUR"; - if (contentID.startsWith("JP")) return "JAP"; - return "UNK"; -} - -// Get a date to fill in for the JSON. We'll default to the file creation date. -function getFormattedDate(filePath) { - const creationDate = new Date( fs.statSync(filePath).birthtime); - return `${("0" + (creationDate.getMonth() + 1)).slice(-2)}-${("0" + creationDate.getDate()).slice(-2)}-${creationDate.getFullYear()}`; -} - -// Extract game details directly from the package metadata. -function extractGameDetails(pkgPath) { - try { - const fd = fs.openSync(pkgPath, "r"); - const fileSize = fs.statSync(pkgPath).size; - - let offset = 0; - const blockSize = 1048576; - let foundOffset = null; - - while (offset < fileSize) { - const buffer = readBytes(fd, offset, blockSize); - const match = buffer.indexOf(Buffer.from("\x00PSF\x01\x01\x00\x00", "binary")); - - if (match !== -1) { - foundOffset = offset + match; - break; - } - offset += blockSize; - } - - if (foundOffset === null) { - console.error(`⌠PARAM.SFO not found in ${pkgPath}`); - return null; - } - - const sfoBuffer = readBytes(fd, foundOffset, 2048); - fs.closeSync(fd); - - return parseSFO(sfoBuffer); - } catch (error) { - console.error(`⌠Failed to process PKG: ${pkgPath} - ${error.message}`); - return null; - } -} - -// Generate JSON data specifically for the FPKGi client. -function generateJSON(folder, details) { - const jsonFilePath = path.join(JSON_DIR, `${folder}.json`); - - let jsonData = { DATA: {} }; - if (fs.existsSync(jsonFilePath)) { - jsonData = JSON.parse(fs.readFileSync(jsonFilePath, "utf8")); - } - // console.log(details); - jsonData.DATA[encodeURI(details.pkgPath)] = { - region: details.region, - title_id: details.title_id, - name: details.title, - version: details.version, - release: details.release, - size: details.size, - min_fw: null, - cover_url: details.cover_url - }; - - fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 4)); - console.log(`✅ Updated JSON: ${jsonFilePath}`); -} - -// Start our local webserver implementation. -const app = express(); -app.use("/pkg", express.static(BASE_DIR)); -app.use("/images", express.static(IMAGE_DIR)); -app.get("/:category.json", (req, res) => { - const jsonPath = path.join(JSON_DIR, `${req.params.category}.json`); - if (fs.existsSync(jsonPath)) { - res.sendFile(jsonPath); - } else { - res.status(404).json({ error: "File Not found" }); - } -}); - -app.get("/background", (req, res) => { - if (fs.existsSync("background.png")) { - res.sendFile(path.join(__dirname, "background.png")); - } else { - res.status(404).json({ error: "Background not found. Create background.png in the root folder." }); - } -}); - -app.get("/background.png", (req, res) => { - if (fs.existsSync("background.png")) { - res.sendFile(path.join(__dirname, "background.png")); - } else { - res.status(404).json({ error: "Background not found. Create background.png in the root folder." }); - } -}); - -app.get("/refresh", (req, res) => { - scanPackages(); - res.send("Library refresh started. Check the JSON files once the files have finished processing..."); -}); - -app.get("/", (req, res) => { - const jsonFiles = fs.readdirSync(JSON_DIR) - .filter(file => file.endsWith('.json')) - .map(file => { - const category = path.basename(file, '.json'); - return { - name: category, - url: `/${category}.json` - }; - }); - - const html = ` - - - - Available Categories - - - -

Package Categories

-
-

Click on the links below to view the JSON data and URL for your discovered packages. Either manually copy these URLs into your config, or use the generated config below. Click the Eye icon to see a parsed library view.

- - -

Generated Config

-

Use the following configuration to setup your FPKGi config

- - - -

Notes:

-

Create background.png in the root folder and use the URL /background inside of the config file to specify a custom background.

- - -`; - - res.send(html); -}); - -app.get("/library", (req, res) => { - const cat = req.query.cat; - const allowedCategories = ["apps", "demos", "DLC", "games", "homebrew", "updates"]; - if (!allowedCategories.includes(cat)) { - res.status(404).send("Not Found"); - return; - } - const capitalize = s => s ? s.replace(/^./, c => c.toUpperCase()) : s; - const title = capitalize(cat); - const html = '' + - '' + - '' + - ' ' + - ' ' + title + ' Library' + - ' ' + - '' + - '' + - '

' + title + ' Library

' + - '
' + - ' Back' + - ' ' + - '' + - ''; - res.send(html); -}); - -app.get("/:category", (req, res) => { - const jsonPath = path.join(JSON_DIR, `${req.params.category}.json`); - // console.log(req); - if (fs.existsSync(jsonPath)) { - res.sendFile(jsonPath); - } else { - res.status(404).json({ error: "File Not found" }); - } -}); - -// Look for package files in the correct folders -function scanPackages() { - console.log("🔄 Scanning for PKG files..."); - let processedCount = 0; - - const folders = fs.readdirSync(BASE_DIR).filter(folder => - fs.statSync(path.join(BASE_DIR, folder)).isDirectory() && !IGNORED_DIRS.includes(folder) - ); - - folders.forEach(folder => { - const folderPath = path.join(BASE_DIR, folder); - const jsonPath = path.join(JSON_DIR, `${folder}.json`); - fs.writeFileSync(jsonPath, JSON.stringify({ DATA: {} }, null, 4)); - - fs.readdirSync(folderPath, { withFileTypes: true }).forEach(entry => { - const fullPath = path.join(folderPath, entry.name); - - if (entry.isDirectory()) { - fs.readdirSync(fullPath).forEach(subFile => { - processPKG(folder, path.join(fullPath, subFile)); - }); - } else if (entry.name.endsWith(".pkg")) { - processPKG(folder, fullPath); - processedCount++; - } - }); - }); - - console.log(`✅ Processed ${processedCount} PKG files.`); -} - -// Get metadata from the package so we can grab the CUSA ID -function readPkgMetadata(pkgPath) { - const fd = fs.openSync(pkgPath, "r"); - const buffer = Buffer.alloc(0x800); // Read the first 2KB - fs.readSync(fd, buffer, 0, 0x800, 0); - fs.closeSync(fd); - - // Extracting content ID (offset 0x30 to 0x50) - const contentId = buffer.slice(0x30, 0x50).toString("utf-8").replace(/\0/g, ""); - - // Extract only the Title ID (the part after the '-') - const titleIdMatch = contentId.match(/-(CUSA\d+)/); - return titleIdMatch ? titleIdMatch[1] : null; // Returns only 'CUSA01126' -} - -// Process an individual package -function processPKG(folder, pkgPath) { - console.log(`🔄 Processing: ${pkgPath}`); - const details = extractGameDetails(pkgPath); - //If the PKG doesn't have a TITLE_ID extracted, use the content ID? - if (!details.TITLE_ID) { - if (details.CONTENT_ID) { - details.TITLE_ID = readPkgMetadata(pkgPath); - } - } - - if (details && details.TITLE_ID) { - const url = `http://${HOSTNAME}:${PORT}/pkg${pkgPath.replace(BASE_DIR, "").replace(/\\/g, "/")}`; - const coverUrl = `http://${HOSTNAME}:${PORT}/images/${details.TITLE_ID}.png`; - - extractGameImage(pkgPath, details.TITLE_ID); - generateJSON(folder, { pkgPath: url, region: detectRegion(details.CONTENT_ID), title_id: details.TITLE_ID, title: details.TITLE || "Unknown", version: details.VERSION || "0.00", release: getFormattedDate(pkgPath), size: fs.statSync(pkgPath).size.toString(), cover_url: coverUrl }); - } -} - -// Start the webserver. -app.listen(PORT, "0.0.0.0", () => { - console.log(`🚀 Package Server running at: http://${HOSTNAME}:${PORT}/`); - scanPackages(); -}); \ No newline at end of file diff --git a/windows/cli/app/json-library-server.js b/windows/cli/app/json-library-server.js new file mode 100644 index 0000000..7b3d46b --- /dev/null +++ b/windows/cli/app/json-library-server.js @@ -0,0 +1,464 @@ +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const zlib = require("zlib"); + +function send(res, status, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(status, { "Content-Type": contentType }); + res.end(body); +} + +function getBackgroundFilePath(coversDir) { + const extensions = [".png", ".bmp", ".jpg", ".jpeg"]; + for (const ext of extensions) { + const filePath = path.join(coversDir, `background${ext}`); + if (fs.existsSync(filePath)) { + return filePath; + } + } + return null; +} + +function makeCrcTable() { + const table = new Uint32Array(256); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) { + c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1); + } + table[n] = c >>> 0; + } + return table; +} + +const CRC_TABLE = makeCrcTable(); + +function crc32(buffer) { + let crc = 0xffffffff; + for (let i = 0; i < buffer.length; i++) { + crc = CRC_TABLE[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8); + } + return (crc ^ 0xffffffff) >>> 0; +} + +function pngChunk(type, data) { + const typeBuffer = Buffer.from(type, "ascii"); + const length = Buffer.alloc(4); + length.writeUInt32BE(data.length, 0); + + const crcInput = Buffer.concat([typeBuffer, data]); + const crc = Buffer.alloc(4); + crc.writeUInt32BE(crc32(crcInput), 0); + + return Buffer.concat([length, typeBuffer, data, crc]); +} + +function createBackgroundPng(width = 1280, height = 720) { + const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); + const raw = Buffer.alloc((width * 4 + 1) * height); + + for (let y = 0; y < height; y++) { + const rowOffset = y * (width * 4 + 1); + raw[rowOffset] = 0; + + for (let x = 0; x < width; x++) { + const t = x / Math.max(width - 1, 1); + const v = y / Math.max(height - 1, 1); + const offset = rowOffset + 1 + x * 4; + + const r = Math.round(18 + 125 * t + 20 * v); + const g = Math.round(6 + 10 * t + 4 * v); + const b = Math.round(10 + 22 * t + 18 * v); + + raw[offset] = Math.min(r, 255); + raw[offset + 1] = Math.min(g, 255); + raw[offset + 2] = Math.min(b, 255); + raw[offset + 3] = 255; + } + } + + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); + ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; + ihdr[9] = 6; + ihdr[10] = 0; + ihdr[11] = 0; + ihdr[12] = 0; + + const idat = zlib.deflateSync(raw, { level: 9 }); + const iend = Buffer.alloc(0); + + return Buffer.concat([ + signature, + pngChunk("IHDR", ihdr), + pngChunk("IDAT", idat), + pngChunk("IEND", iend) + ]); +} + +function sendBackgroundImage(res, coversDir) { + const backgroundFile = getBackgroundFilePath(coversDir); + if (backgroundFile) { + const stat = fs.statSync(backgroundFile); + const ext = path.extname(backgroundFile).toLowerCase(); + const contentTypes = { + ".png": "image/png", + ".bmp": "image/bmp", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg" + }; + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size, + "Cache-Control": "no-cache" + }); + + fs.createReadStream(backgroundFile).pipe(res); + return; + } + + const image = createBackgroundPng(); + res.writeHead(200, { + "Content-Type": "image/png", + "Content-Length": image.length, + "Cache-Control": "no-cache" + }); + res.end(image); +} + +function sendJsonFile(res, filePath) { + if (!fs.existsSync(filePath)) { + send(res, 404, JSON.stringify({ error: "File Not found" }), "application/json; charset=utf-8"); + return; + } + + const stat = fs.statSync(filePath); + res.writeHead(200, { + "Content-Type": "application/json; charset=utf-8", + "Content-Length": stat.size + }); + + fs.createReadStream(filePath).pipe(res); +} + +function renderHomePage(hostname, port, jsonDir) { + const jsonFiles = fs.readdirSync(jsonDir) + .filter(file => file.endsWith(".json")) + .map(file => { + const category = path.basename(file, ".json"); + return { + name: category, + url: `/${category}` + }; + }); + + return ` + + + + FPKGi Json Server + + + +

Available Categories

+
+

Click on the links below to view the JSON data and URL for your discovered packages. Either manually copy these URLs into your config, or use the generated config below. Click the Eye icon to see a parsed library view.

+ + +

Generated Config

+

Use the following configuration in your FPKGi config

+ +View Background + + + + +`; +} + +function renderLibraryPage(category) { + const title = category ? category.replace(/^./, c => c.toUpperCase()) : category; + + return "" + + "" + + "" + + " " + + " " + title + " Library" + + " " + + "" + + "" + + "

" + title + " Library

" + + "
" + + " Back" + + " " + + "" + + ""; +} + +function startJsonLibraryServer(settings, options = {}) { + if (!settings.jsonLibraryServer?.enabled) return Promise.resolve(null); + + const jsonDir = path.resolve(settings.output.path); + const coversDir = path.resolve(settings.paths.covers); + if (!fs.existsSync(jsonDir)) { + throw new Error(`JSON directory not found: ${jsonDir}`); + } + + const host = settings.jsonLibraryServer.address; + const port = settings.jsonLibraryServer.port; + const log = options.onLog || (() => {}); + + const server = http.createServer((req, res) => { + if (!req.url) { + send(res, 400, "Bad request"); + return; + } + + const requestUrl = new URL(req.url, `http://${host}:${port}`); + const pathname = decodeURIComponent(requestUrl.pathname); + + if (pathname === "/") { + send(res, 200, renderHomePage(host, port, jsonDir), "text/html; charset=utf-8"); + return; + } + + if (pathname === "/background") { + sendBackgroundImage(res, coversDir); + return; + } + + if (pathname === "/library" || pathname === "/library/") { + const cat = requestUrl.searchParams.get("cat"); + const jsonPath = path.join(jsonDir, `${cat}.json`); + + if (!cat || !fs.existsSync(jsonPath)) { + send(res, 404, "Category not found"); + return; + } + + send(res, 200, renderLibraryPage(cat), "text/html; charset=utf-8"); + return; + } + + const category = pathname.replace(/^\/+/, ""); + if (!category.includes("/") && category) { + sendJsonFile(res, path.join(jsonDir, `${category}.json`)); + return; + } + + send(res, 404, "Not found"); + }); + + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, host === "0.0.0.0" ? "0.0.0.0" : host, () => { + server.removeListener("error", reject); + log(`JSON library server running at http://${host}:${port}/`); + log(`Serving JSON files from: ${jsonDir}`); + resolve(server); + }); + }); +} + +function stopJsonLibraryServer(server, onLog) { + const log = onLog || (() => {}); + if (!server) return Promise.resolve(); + + return new Promise((resolve, reject) => { + server.close(error => { + if (error) { + reject(error); + return; + } + log("JSON library server stopped"); + resolve(); + }); + }); +} + +module.exports = { + startJsonLibraryServer, + stopJsonLibraryServer +}; diff --git a/windows/cli/app/processor.js b/windows/cli/app/processor.js new file mode 100644 index 0000000..6b4b864 --- /dev/null +++ b/windows/cli/app/processor.js @@ -0,0 +1,478 @@ +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const { execSync } = require("child_process"); + +const DLC_CODES = { ac: true }; + +function createLogger(onLog) { + return message => { + if (onLog) onLog(message); + }; +} + +function getPkgInfo(pkgPath, orbisCmdPath) { + let output = ""; + try { + output = execSync(`"${orbisCmdPath}" img_info "${pkgPath}"`, { + stdio: "pipe", + timeout: 15000 + }).toString(); + } catch (err) { + output = ((err.stdout || "").toString() + (err.stderr || "").toString()); + } + + const get = key => { + const match = output.match(new RegExp(`${key}:\\s*(.+)`)); + return match ? match[1].trim() : null; + }; + + const category = get("Category"); + const titleId = get("Title ID"); + const contentId = get("Content ID"); + const isDlc = Boolean(category && DLC_CODES[category]); + const identifier = isDlc ? (contentId || titleId) : titleId; + + return { + titleId: titleId || path.basename(pkgPath, ".pkg"), + contentId: contentId || null, + identifier: identifier || path.basename(pkgPath, ".pkg"), + isDlc, + fallback: !titleId + }; +} + +function readBytes(fd, offset, length) { + const buf = Buffer.alloc(length); + fs.readSync(fd, buf, 0, length, offset); + return buf; +} + +function parseSFO(buffer) { + const keyTableOffset = buffer.readUInt32LE(8); + const dataOffset = buffer.readUInt32LE(12); + const numEntries = buffer.readUInt32LE(16); + const keyTable = buffer.slice(keyTableOffset); + const gameInfo = {}; + + for (let i = 0; i < numEntries; i++) { + const entryOffset = 20 + i * 16; + if (entryOffset + 16 > buffer.length) continue; + + const keyOffset = buffer.readUInt16LE(entryOffset); + const valueType = buffer.readUInt16LE(entryOffset + 2); + const valueSize = buffer.readUInt32LE(entryOffset + 4); + const relativeDataOffset = buffer.readUInt32LE(entryOffset + 12); + const absoluteDataOffset = dataOffset + relativeDataOffset; + if (keyOffset >= keyTable.length) continue; + + let keyEnd = keyTable.indexOf(0, keyOffset); + if (keyEnd === -1) keyEnd = keyTable.length; + const key = keyTable.slice(keyOffset, keyEnd).toString("utf8"); + if (absoluteDataOffset + valueSize > buffer.length) continue; + + if (valueType === 0x0204) { + gameInfo[key] = buffer.slice(absoluteDataOffset, absoluteDataOffset + valueSize).toString("utf8").replace(/\0/g, ""); + } else if (valueType === 0x0404) { + gameInfo[key] = buffer.readUInt32LE(absoluteDataOffset); + } + } + + return gameInfo; +} + +function extractGameDetails(pkgPath) { + let fd; + try { + fd = fs.openSync(pkgPath, "r"); + const fileSize = fs.statSync(pkgPath).size; + let offset = 0; + let foundOffset = null; + + while (offset < fileSize) { + const buf = readBytes(fd, offset, Math.min(1048576, fileSize - offset)); + const match = buf.indexOf(Buffer.from("\x00PSF\x01\x01\x00\x00", "binary")); + if (match !== -1) { + foundOffset = offset + match; + break; + } + offset += 1048576; + } + + if (foundOffset === null) return null; + return parseSFO(readBytes(fd, foundOffset, 2048)); + } catch (_) { + return null; + } finally { + if (typeof fd === "number") fs.closeSync(fd); + } +} + +function detectRegion(contentId) { + if (!contentId) return "UNK"; + if (contentId.startsWith("UP")) return "USA"; + if (contentId.startsWith("EP")) return "EUR"; + if (contentId.startsWith("JP")) return "JAP"; + return "UNK"; +} + +function getFormattedDate(filePath) { + const d = new Date(fs.statSync(filePath).birthtime); + return `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}-${d.getFullYear()}`; +} + +function findFileRecursive(dir, filename) { + try { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isFile() && entry.name.toLowerCase() === filename.toLowerCase()) return full; + if (entry.isDirectory()) { + const match = findFileRecursive(full, filename); + if (match) return match; + } + } + } catch (_) {} + return null; +} + +function extractIcon(pkgPath, identifier, tempDir, orbisCmdPath, log) { + const safeName = identifier.replace(/[<>:"/\\|?*]/g, "_"); + const outDir = path.join(tempDir, safeName); + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + + const cmd = `"${orbisCmdPath}" img_extract --no_passcode "${pkgPath}:Sc0/icon0.png" "${outDir}"`; + try { + execSync(cmd, { stdio: "pipe", timeout: 30000 }); + } catch (err) { + const out = ((err.stdout || "").toString() + (err.stderr || "").toString()).trim(); + if (out) log(` ${out.split("\n")[0]}`); + } + + return findFileRecursive(outDir, "icon0.png"); +} + +function collectPkgFiles(baseDir) { + const ignoredNames = ["node_modules", ".git", "covers", "_temp"]; + const pkgList = []; + const pkgsByCategory = {}; + + fs.readdirSync(baseDir, { withFileTypes: true }) + .filter(entry => entry.isDirectory() && !ignoredNames.includes(entry.name) && !entry.name.startsWith(".")) + .forEach(categoryEntry => { + const categoryPath = path.join(baseDir, categoryEntry.name); + const pkgFiles = []; + + fs.readdirSync(categoryPath, { withFileTypes: true }).forEach(entry => { + const fullPath = path.join(categoryPath, entry.name); + if (entry.isDirectory()) { + try { + fs.readdirSync(fullPath).forEach(fileName => { + if (fileName.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(path.join(fullPath, fileName)); + } + }); + } catch (_) {} + } else if (entry.name.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(fullPath); + } + }); + + if (pkgFiles.length > 0) { + pkgsByCategory[categoryEntry.name] = pkgFiles; + pkgFiles.forEach(pkgPath => pkgList.push({ pkgPath, category: categoryEntry.name })); + } + }); + + return { pkgList, pkgsByCategory }; +} + +function findCoverImage(coversDir, serverAddress, titleId, contentId, category) { + const extensions = [".png", ".jpg", ".jpeg"]; + const isDlcOrTheme = category === "DLC" || category === "themes"; + const primaryKey = isDlcOrTheme ? (contentId || titleId) : titleId; + const secondaryKey = titleId; + const allFolders = [category, "games", "apps", "updates", "DLC", "demos", "homebrew", "emulators", "themes", "PS1", "PS2", "PSP"]; + + if (primaryKey) { + for (const ext of extensions) { + const ownPath = path.join(coversDir, category, `${primaryKey}${ext}`); + if (fs.existsSync(ownPath)) { + return `http://${serverAddress}/covers/${category}/${primaryKey}${ext}`; + } + } + } + + if (isDlcOrTheme && secondaryKey && secondaryKey !== primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${secondaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${secondaryKey}${ext}`; + } + } + } + } + + if (primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${primaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${primaryKey}${ext}`; + } + } + } + } + + return `http://${serverAddress}/covers/default.png`; +} + +function generateJSON(jsonOutputDir, serverAddress, category, games) { + const jsonFilePath = path.join(jsonOutputDir, `${category}.json`); + const jsonData = { DATA: {} }; + + games.forEach(game => { + const pkgUrl = `http://${serverAddress}/pkg/${category}/${path.basename(game.pkgPath)}`; + jsonData.DATA[encodeURI(pkgUrl)] = { + region: game.region, + title_id: game.title_id, + name: game.title, + version: game.version, + release: game.release, + size: game.size, + min_fw: null, + cover_url: game.cover_url + }; + }); + + fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 4)); +} + +function validateSettings(settings) { + if (!settings.paths?.pkgScan) throw new Error("PKG scan path is required."); + if (!settings.paths?.covers) throw new Error("Covers path is required."); + if (!settings.output?.path) throw new Error("JSON output path is required."); + if (settings.httpServer?.enabled) { + if (!settings.httpServer.address) throw new Error("HTTP server IP is required."); + if (!settings.httpServer.port) throw new Error("HTTP server port is required."); + } else if (!settings.serverAddress) { + throw new Error("Server address is required when HTTP server is disabled."); + } + if (settings.jsonLibraryServer?.enabled) { + if (!settings.jsonLibraryServer.address) throw new Error("JSON library IP is required."); + if (!settings.jsonLibraryServer.port) throw new Error("JSON library port is required."); + } +} + +async function runProcessing(settings, options = {}) { + validateSettings(settings); + const log = createLogger(options.onLog); + const orbisCmdPath = options.orbisCmdPath; + if (!orbisCmdPath || !fs.existsSync(orbisCmdPath)) throw new Error("orbis-pub-cmd.exe is missing."); + + const baseDir = settings.paths.pkgScan; + const coversDir = settings.paths.covers; + const jsonOutputDir = settings.output.path; + const tempDir = path.join(coversDir, "_temp"); + const serverAddress = settings.httpServer?.enabled + ? `${settings.httpServer.address}:${settings.httpServer.port}` + : settings.serverAddress; + + if (!fs.existsSync(baseDir)) throw new Error(`PKG scan directory not found: ${baseDir}`); + + fs.mkdirSync(coversDir, { recursive: true }); + fs.mkdirSync(jsonOutputDir, { recursive: true }); + fs.mkdirSync(tempDir, { recursive: true }); + + const { pkgList, pkgsByCategory } = collectPkgFiles(baseDir); + if (pkgList.length === 0) { + log("No PKG files found in any category subfolder."); + return { totalPkgs: 0, categories: 0 }; + } + + log(`Found ${pkgList.length} PKG(s) across ${Object.keys(pkgsByCategory).length} categories`); + log("-".repeat(55)); + log("STEP 1: Extracting Icons"); + + let extracted = 0; + let skipped = 0; + let failed = 0; + + for (const { pkgPath, category } of pkgList) { + log(` Processing ${path.basename(pkgPath)}`); + const info = getPkgInfo(pkgPath, orbisCmdPath); + + if (info.fallback) log(` No Title ID found, using filename: ${info.identifier}`); + else if (info.isDlc) log(` DLC Content ID: ${info.identifier}`); + else log(` Title ID: ${info.titleId}`); + + const coverFolder = path.join(coversDir, category); + if (!fs.existsSync(coverFolder)) fs.mkdirSync(coverFolder, { recursive: true }); + + const outFile = path.join(coverFolder, `${info.identifier}.png`); + if (fs.existsSync(outFile)) { + log(" Icon already exists"); + skipped++; + continue; + } + + const iconPath = extractIcon(pkgPath, info.identifier, tempDir, orbisCmdPath, log); + if (iconPath) { + fs.copyFileSync(iconPath, outFile); + log(` Saved: ${outFile}`); + extracted++; + } else { + log(" icon0.png not found in PKG"); + failed++; + } + + try { + const tempFolder = path.join(tempDir, info.identifier.replace(/[<>:"/\\|?*]/g, "_")); + if (fs.existsSync(tempFolder)) fs.rmSync(tempFolder, { recursive: true, force: true }); + } catch (_) {} + } + + try { + if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true }); + } catch (_) {} + + log(`Icons done. Extracted: ${extracted} | Skipped: ${skipped} | Failed: ${failed}`); + log("-".repeat(55)); + log("STEP 2: Generating JSON Files"); + + let totalProcessed = 0; + + for (const [category, pkgFiles] of Object.entries(pkgsByCategory)) { + log(` Category: ${category}`); + const games = []; + + for (const pkgPath of pkgFiles) { + log(` Processing ${path.basename(pkgPath)}`); + const info = getPkgInfo(pkgPath, orbisCmdPath); + const details = extractGameDetails(pkgPath); + + if (!info.titleId && !details) { + log(" No metadata, skipping"); + continue; + } + + const version = details ? (() => { + const v = parseFloat(details.VERSION) || 0; + const a = parseFloat(details.APP_VER) || 0; + return (v >= a ? details.VERSION : details.APP_VER) || "0.00"; + })() : "0.00"; + + games.push({ + pkgPath, + region: detectRegion(info.contentId), + title_id: info.titleId, + content_id: info.contentId, + title: (details && details.TITLE) || "Unknown", + version, + release: getFormattedDate(pkgPath), + size: fs.statSync(pkgPath).size.toString(), + cover_url: findCoverImage(coversDir, serverAddress, info.titleId, info.contentId, category) + }); + totalProcessed++; + } + + if (games.length > 0) { + generateJSON(jsonOutputDir, serverAddress, category, games); + log(` ${games.length} PKG(s) -> ${category}.json`); + } else { + log(` No valid PKGs in ${category}`); + } + } + + log(`JSON done. ${totalProcessed} PKG(s) processed`); + return { totalPkgs: pkgList.length, categories: Object.keys(pkgsByCategory).length, extracted, skipped, failed, totalProcessed }; +} + +function sendResponse(res, statusCode, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(statusCode, { "Content-Type": contentType }); + res.end(body); +} + +function serveStaticFile(req, res, urlPrefix, baseDir) { + const relativePath = decodeURIComponent(req.url.slice(urlPrefix.length)).replace(/^\/+/, ""); + const resolvedBase = path.resolve(baseDir); + const absolutePath = path.resolve(baseDir, relativePath); + + if (!absolutePath.startsWith(resolvedBase + path.sep) && absolutePath !== resolvedBase) { + sendResponse(res, 403, "Forbidden"); + return; + } + + if (!fs.existsSync(absolutePath)) { + sendResponse(res, 404, "Not found"); + return; + } + + const stat = fs.statSync(absolutePath); + if (stat.isDirectory()) { + sendResponse(res, 403, "Directory listing is disabled"); + return; + } + + const contentTypes = { + ".json": "application/json; charset=utf-8", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".pkg": "application/octet-stream" + }; + const ext = path.extname(absolutePath).toLowerCase(); + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size + }); + + fs.createReadStream(absolutePath).pipe(res); +} + +function startHttpServer(settings, options = {}) { + validateSettings(settings); + if (!settings.httpServer?.enabled) return Promise.resolve(null); + + const log = createLogger(options.onLog); + const address = settings.httpServer.address; + const port = settings.httpServer.port; + const pkgPath = settings.paths.pkgScan; + const coversPath = settings.paths.covers; + + const server = http.createServer((req, res) => { + if (!req.url) return sendResponse(res, 400, "Bad request"); + if (req.url === "/" || req.url === "") return sendResponse(res, 200, "Available paths: /pkg and /covers"); + if (req.url === "/pkg" || req.url === "/pkg/" || req.url === "/covers" || req.url === "/covers/") { + return sendResponse(res, 403, "Directory listing is disabled"); + } + if (req.url.startsWith("/pkg/")) return serveStaticFile(req, res, "/pkg/", pkgPath); + if (req.url.startsWith("/covers/")) return serveStaticFile(req, res, "/covers/", coversPath); + return sendResponse(res, 404, "Not found"); + }); + + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, address, () => { + server.removeListener("error", reject); + log(`HTTP server running at http://${address}:${port}`); + resolve(server); + }); + }); +} + +function stopHttpServer(server, onLog) { + const log = createLogger(onLog); + if (!server) return Promise.resolve(); + + return new Promise((resolve, reject) => { + server.close(error => { + if (error) return reject(error); + log("HTTP server stopped"); + resolve(); + }); + }); +} + +module.exports = { runProcessing, startHttpServer, stopHttpServer, validateSettings }; diff --git a/windows/cli/ext/di.exe b/windows/cli/ext/di.exe new file mode 100644 index 0000000..4fd8035 Binary files /dev/null and b/windows/cli/ext/di.exe differ diff --git a/windows/cli/ext/libatrac9.dll b/windows/cli/ext/libatrac9.dll new file mode 100644 index 0000000..cca0446 Binary files /dev/null and b/windows/cli/ext/libatrac9.dll differ diff --git a/windows/cli/ext/sc.exe b/windows/cli/ext/sc.exe new file mode 100644 index 0000000..45b953a Binary files /dev/null and b/windows/cli/ext/sc.exe differ diff --git a/windows/cli/ext/trp_compare_default.css b/windows/cli/ext/trp_compare_default.css new file mode 100644 index 0000000..61b9252 --- /dev/null +++ b/windows/cli/ext/trp_compare_default.css @@ -0,0 +1,259 @@ +/* body ƒ^ƒOÝ’è */ +body { + background-color: rgb(0, 77, 149); /* ”wŒiF */ + color: rgb(15, 15, 15); /* •¶ŽšF */ + font-family: verdana,arial,helvetica,sans-serif; /* ƒtƒHƒ“ƒg */ + font-size: 12px; /* •¶ŽšƒTƒCƒY */ + line-height: 18px; /* s‚Ì‚‚³ */ + -ms-scrollbar-base-color: transparent; /* ƒXƒNƒ[ƒ‹ƒo[‚ÌŠî–{F(IEŒü‚¯) */ + -ms-scrollbar-face-color: rgb(214, 214, 214); /* ƒXƒNƒ[ƒ‹ƒo[‚̃o[‚ÌF(IEŒü‚¯) */ + -ms-scrollbar-arrow-color: rgb(214, 214, 214); /* ƒXƒNƒ[ƒ‹ƒo[‚Ì–îˆó‚ÌF(IEŒü‚¯) */ + -ms-scrollbar-track-color: transparent; /* ƒXƒNƒ[ƒ‹ƒo[‚̃Œ[ƒ‹‚ÌF(IEŒü‚¯) */ +} +/* h1 ƒ^ƒO•¶ŽšÝ’è */ +h1 { + font-size: 1.5em; /* •¶ŽšƒTƒCƒY */ + font-weight: 600; /* •¶Žš‘¾‚³ */ + line-height: 24px; /* s‚Ì‚‚³ */ +} +/* h2 ƒ^ƒO•¶ŽšÝ’è */ +h2 { + font-size: 1.25em; /* •¶ŽšƒTƒCƒY */ + font-weight: 600; /* •¶Žš‘¾‚³ */ + line-height: 24px; /* s‚Ì‚‚³ */ +} +/* p ƒ^ƒO•¶ŽšÝ’è */ +p { + /* non setting */ +} + +/* header ƒ^ƒOÝ’è */ +header { + background-color: rgb(214, 249, 159); /* ”wŒiF */ + border-radius: 2px 2px 0px 0px; /* Šp‚ÌŠÛ‚Ý */ + box-shadow: inset 0px -1px 0px rgb(195, 234, 133); /* ‰eÝ’è */ + color: rgb(61, 96, 6); /* •¶ŽšF */ +} +/* id="content" ƒ^ƒOÝ’è */ +div#content { + background-color: rgb(255, 255, 255); /* ”wŒiF */ + border-radius: 0px 0px 2px 2px; /* Šp‚ÌŠÛ‚Ý */ +} +/* footer ƒ^ƒOÝ’è */ +footer { + color: rgb(201, 229, 255); /* •¶ŽšF */ +} +/* header ƒ^ƒO, id="content" ƒ^ƒO, footer ƒ^ƒOÝ’è */ +header, div#content, footer { + margin: 0 auto; /* —̈æŠÔ‚̃Xƒy[ƒX• */ + padding: 8px; /* —̈æ“à‚̃Xƒy[ƒX• */ +} + +/* class="toggle_icon" Ý’è (img ƒ^ƒO“à) */ +img.toggle_icon { + padding-right: 0.5em; /* ‰E‘¤ƒXƒy[ƒX• (0,5•¶Žš) */ +} + +/* class="hide_onload" Ý’è */ +.hide_onload { + display: none; /* —v‘f‚Ì•\ަ–³‚µ*/ +} + +/* class="toggle" Ý’è (ƒŠƒ“ƒN•¶ŽšÝ’è) */ +.toggle a { + text-decoration: none; /* ‰ºü–³‚µ */ +} +.toggle a:link { + color:rgb(0, 90, 175); /* ƒŠƒ“ƒN•¶ŽšF */ +} +.toggle a:visited { + color:rgb(0, 90, 175); /* Šù‚ÉŒ©‚½ƒŠƒ“ƒN•¶ŽšF */ +} +.toggle a:active { + color:rgb(0, 90, 175); /* ƒNƒŠƒbƒNŽž‚ÌƒŠƒ“ƒN•¶ŽšF */ +} +.toggle a:hover { + color:rgb(0, 90, 175); /* ƒJ[ƒ\ƒ‹‚ªã‚É‚ ‚鎞‚ÌƒŠƒ“ƒN•¶ŽšF */ +} + +/* class="active" Ý’è (id="Summary" “àƒŠƒ“ƒN•¶ŽšÝ’è) */ +#summary .active a:link { + color: rgb(0, 90, 175); /* ƒŠƒ“ƒN•¶ŽšF */ +} + +/* class="grayout" Ý’è (id="Summary" “àƒŠƒ“ƒN•¶ŽšÝ’è) */ +#summary .grayout a:link { + color: rgb(169, 169, 169); /* ƒŠƒ“ƒN•¶ŽšF */ +} +#summary .grayout a:visited { + color: rgb(169, 169, 169); /* Šù‚ÉŒ©‚½ƒŠƒ“ƒN•¶ŽšF */ +} +#summary .grayout a:active { + color: rgb(169, 169, 169); /* ƒNƒŠƒbƒNŽž‚ÌƒŠƒ“ƒN•¶ŽšF */ +} +#summary .grayout a:hover { + color: rgb(169, 169, 169); /* ƒJ[ƒ\ƒ‹‚ªã‚É‚ ‚鎞‚ÌƒŠƒ“ƒN•¶ŽšF */ +} + + +/* class="rbox" Ý’è */ +.rbox { + border-collapse: collapse; /* •\‚Ì—×Ú‚·‚é˜gü‚Ìd‚È‚è */ + border-color: rgb(214, 214, 214); /* •\˜gü‚ÌF */ + border-style: solid; /* •\˜gü‚̃Xƒ^ƒCƒ‹ */ + border-width: 1px; /* •\˜gü‚Ì• */ + margin-bottom: 0.5em; /* •\‚̉º‘¤ƒXƒy[ƒX• */ + margin-top: 0.5em; /* •\‚Ì㑤ƒXƒy[ƒX• */ +} + +/* class="rbox" th ƒ^ƒOÝ’è */ +.rbox th { + background-color: rgb(238, 238, 238); /* •\˜g“à‚Ì”wŒiF */ + border-color: rgb(214, 214, 214); /* •\˜gü‚ÌF */ + border-style: solid; /* •\˜gü‚̃Xƒ^ƒCƒ‹ */ + border-width: 1px; /* •\˜gü‚Ì• */ + padding: 0px 5px; /* —̈æ“àƒXƒy[ƒX• */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + vertical-align: middle; /* c•ûŒü‚Ì•\ަˆÊ’u */ + white-space: pre-wrap; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="vertical_item" Ý’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th.vertical_item { + font-weight: bold; /* •¶Žš‘¾‚³ */ +} +/* class="horizontal_item" Ý’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th.horizontal_item { + font-weight: bold; /* •¶Žš‘¾‚³ */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="banner" Ý’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th.banner { + background-color: rgb(110, 110, 110); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(238, 238, 238); /* •¶ŽšF */ + font-weight: bold; /* •¶Žš‘¾‚³ */ + padding: 0px 15px; /* —̈æ“àƒXƒy[ƒX• */ + text-align: left; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ +} +/* id="return_top" ƒ^ƒOÝ’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th div#return_top { + text-align: right; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + margin-top: -18px; /* •\‚ÌãƒXƒy[ƒX• (body ‚©‚çŒp³‚µ‚Ä‚¢‚é1s•ªã‚É) */ +} +/* ƒŠƒ“ƒN•¶ŽšFÝ’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th a:link { + color:rgb(214, 249, 159); /* ƒŠƒ“ƒN•¶ŽšF */ +} +.rbox th a:visited { + color:rgb(214, 249, 159); /* Šù‚ÉŒ©‚½ƒŠƒ“ƒN•¶ŽšF */ +} +.rbox th a:active { + color:rgb(214, 249, 159); /* ƒNƒŠƒbƒNŽž‚ÌƒŠƒ“ƒN•¶ŽšF */ +} +.rbox th a:hover { + color:rgb(214, 249, 159); /* ƒJ[ƒ\ƒ‹‚ªã‚É‚ ‚鎞‚ÌƒŠƒ“ƒN•¶ŽšF */ +} + +/* class="rbox" td ƒ^ƒOÝ’è */ +.rbox td { + border-color: rgb(214, 214, 214); /* •\˜gü‚ÌF */ + border-style: dotted; /* •\˜gü‚̃Xƒ^ƒCƒ‹ */ + border-width: 1px; /* •\˜gü‚Ì• */ + text-align: left; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + vertical-align: middle; /* c•ûŒü‚Ì•\ަˆÊ’u */ + table-layout: fixed; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è */ + width: 500px; /* ƒZƒ‹• */ +} +/* class="input" td ƒ^ƒOÝ’è (class="rbox" td ƒ^ƒO“à)(Input table) */ +.rbox td.input { + width: auto; /* ƒZƒ‹• (Input table ‚ÍŽ©“®’²®) */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ + white-space: pre-wrap; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="result_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.result_equal { + width: 70px; /* Result ƒZƒ‹‚̕ݒè (ŒÅ’è) */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ +} +/* class="result_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.result_diff { + width: 70px; /* Result ƒZƒ‹‚̕ݒè (ŒÅ’è) */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(160, 0, 0); /* •¶ŽšF */ + font-weight: bold; /* •¶Žš‘¾‚³ */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ +} +/* class="info_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_equal { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="info_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_diff { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(160, 0, 0); /* •¶ŽšF */ + font-weight: bold; /* •¶Žš‘¾‚³ */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="info_sicon_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_sicon_equal { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ +} +/* class="info_sicon_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_sicon_diff { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ +} +/* class="info_ticon_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_ticon_equal { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ +} +/* class="info_ticon_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_ticon_diff { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(160, 0, 0); /* •¶ŽšF */ +} + +/* class="rtl" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.rtl { + direction: rtl; /* •¶Žš•\‹L•ûŒü (right to left) */ + text-align: right; /* ‰EŠñ‚¹ */ +} + +/* class="sicon_ratio" Ý’è (img ƒ^ƒO“à) */ +.rbox td img.sicon_ratio { + width: 30%; /* image ƒTƒCƒY‚ð % ‚ÅŽw’è */ + height: auto; /* ŒÅ’è */ +} +/* class="ticon_ratio" Ý’è (img ƒ^ƒO“à) */ +.rbox td img.ticon_ratio { + width: 30%; /* image ƒTƒCƒY‚ð % ‚ÅŽw’è */ + height: auto; /* ŒÅ’è */ +} + +/* window •‚ª 1165px ˆÈ‰º‚ÌꇂÌÝ’è */ +@media all and (max-width:1165px) { + header, div#content, footer { + width: 852px; /* • */ + } +} +/* window •‚ª 1166px ˆÈã(1566px –¢–ž)‚ÌꇂÌÝ’è */ +@media all and (min-width:1166px) { + header, div#content, footer { + width: 1120px; /* • */ + } +} +/* window •‚ª 1566px ˆÈã‚ÌꇂÌÝ’è */ +@media all and (min-width:1566px) { + header, div#content, footer { + width: 1520px; /* • */ + } +} diff --git a/windows/cli/node-menu.js b/windows/cli/node-menu.js new file mode 100644 index 0000000..8ad940a --- /dev/null +++ b/windows/cli/node-menu.js @@ -0,0 +1,274 @@ +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); +const { runProcessing, startHttpServer, stopHttpServer, validateSettings } = require("./app/processor"); +const { startJsonLibraryServer, stopJsonLibraryServer } = require("./app/json-library-server"); + +let httpServerInstance = null; +let jsonLibraryServerInstance = null; + +function log(message = "") { + process.stdout.write(`${message}\n`); +} + +function error(message) { + process.stderr.write(`${message}\n`); +} + +function getOrbisPath() { + return path.join(__dirname, "orbis-pub-cmd.exe"); +} + +function defaultSettings() { + return { + serverAddress: "localhost:8080", + httpServer: { + enabled: true, + address: "localhost", + port: 8080 + }, + jsonLibraryServer: { + enabled: false, + address: "localhost", + port: 3000 + }, + paths: { + pkgScan: "", + covers: "" + }, + output: { + path: "" + } + }; +} + +function getConfigPath() { + const argIndex = process.argv.indexOf("--config"); + if (argIndex !== -1 && process.argv[argIndex + 1]) { + return path.resolve(process.argv[argIndex + 1]); + } + return path.join(process.cwd(), "node-settings.json"); +} + +function saveSettings(settings) { + fs.writeFileSync(getConfigPath(), JSON.stringify(settings, null, 4)); +} + +function loadOrCreateSettings() { + const configPath = getConfigPath(); + const defaults = defaultSettings(); + + if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, JSON.stringify(defaults, null, 4)); + return defaults; + } + + const loaded = JSON.parse(fs.readFileSync(configPath, "utf8")); + return { + ...defaults, + ...loaded, + httpServer: { ...defaults.httpServer, ...(loaded.httpServer || {}) }, + jsonLibraryServer: { ...defaults.jsonLibraryServer, ...(loaded.jsonLibraryServer || {}) }, + paths: { ...defaults.paths, ...(loaded.paths || {}) }, + output: { ...defaults.output, ...(loaded.output || {}) } + }; +} + +function createPrompt() { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const question = prompt => new Promise(resolve => rl.question(prompt, answer => resolve(answer))); + return { rl, question }; +} + +async function promptForValue(question, label, currentValue, options = {}) { + const suffix = currentValue !== undefined && currentValue !== null && currentValue !== "" + ? ` [${currentValue}]` + : ""; + const answer = (await question(`${label}${suffix}: `)).trim(); + if (!answer) return currentValue; + if (options.type === "number") return Number(answer); + return answer; +} + +function printSettings(settings) { + log(""); + log("Current Settings"); + log("----------------"); + log(`PKG scan path: ${settings.paths.pkgScan || "(not set)"}`); + log(`Covers path: ${settings.paths.covers || "(not set)"}`); + log(`JSON output path: ${settings.output.path || "(not set)"}`); + log(`HTTP server enabled: ${settings.httpServer.enabled}`); + log(`HTTP server address: ${settings.httpServer.address}:${settings.httpServer.port}`); + log(`Fallback server address: ${settings.serverAddress || "(not set)"}`); + log(`JSON library enabled: ${settings.jsonLibraryServer.enabled}`); + log(`JSON library address: ${settings.jsonLibraryServer.address}:${settings.jsonLibraryServer.port}`); + log(""); +} + +async function editSettings(settings, question) { + while (true) { + printSettings(settings); + log("Settings Menu"); + log("1. Change PKG scan path"); + log("2. Change covers path"); + log("3. Change JSON output path"); + log("4. Toggle HTTP server"); + log("5. Change HTTP server IP"); + log("6. Change HTTP server port"); + log("7. Change fallback server address"); + log("8. Toggle JSON library server"); + log("9. Change JSON library IP"); + log("10. Change JSON library port"); + log("11. Save and return"); + log("12. Return without saving"); + const choice = (await question("Choose an option: ")).trim(); + + if (choice === "1") settings.paths.pkgScan = await promptForValue(question, "PKG scan path", settings.paths.pkgScan); + else if (choice === "2") settings.paths.covers = await promptForValue(question, "Covers path", settings.paths.covers); + else if (choice === "3") settings.output.path = await promptForValue(question, "JSON output path", settings.output.path); + else if (choice === "4") settings.httpServer.enabled = !settings.httpServer.enabled; + else if (choice === "5") settings.httpServer.address = await promptForValue(question, "HTTP server IP", settings.httpServer.address); + else if (choice === "6") settings.httpServer.port = await promptForValue(question, "HTTP server port", settings.httpServer.port, { type: "number" }); + else if (choice === "7") settings.serverAddress = await promptForValue(question, "Fallback server address", settings.serverAddress); + else if (choice === "8") settings.jsonLibraryServer.enabled = !settings.jsonLibraryServer.enabled; + else if (choice === "9") settings.jsonLibraryServer.address = await promptForValue(question, "JSON library IP", settings.jsonLibraryServer.address); + else if (choice === "10") settings.jsonLibraryServer.port = await promptForValue(question, "JSON library port", settings.jsonLibraryServer.port, { type: "number" }); + else if (choice === "11") { + validateSettings(settings); + saveSettings(settings); + log(`Settings saved to ${getConfigPath()}`); + return settings; + } else if (choice === "12") { + return null; + } else { + log("Invalid option."); + } + } +} + +async function startServers(settings) { + if (settings.httpServer?.enabled && !httpServerInstance) { + httpServerInstance = await startHttpServer(settings, { onLog: log }); + } + if (settings.jsonLibraryServer?.enabled && !jsonLibraryServerInstance) { + jsonLibraryServerInstance = await startJsonLibraryServer(settings, { onLog: log }); + } +} + +function getRunningServersSummary(settings) { + const active = []; + + if (httpServerInstance && settings.httpServer?.enabled) { + active.push(`HTTP Server: http://${settings.httpServer.address}:${settings.httpServer.port}`); + } + + if (jsonLibraryServerInstance && settings.jsonLibraryServer?.enabled) { + active.push(`JSON Library: http://${settings.jsonLibraryServer.address}:${settings.jsonLibraryServer.port}/`); + } + + return active.length > 0 ? active : ["No servers currently running"]; +} + +async function stopServers() { + if (jsonLibraryServerInstance) { + await stopJsonLibraryServer(jsonLibraryServerInstance, log); + jsonLibraryServerInstance = null; + } + if (httpServerInstance) { + await stopHttpServer(httpServerInstance, log); + httpServerInstance = null; + } +} + +async function runScan(settings) { + validateSettings(settings); + await runProcessing(settings, { + orbisCmdPath: getOrbisPath(), + onLog: log + }); +} + +async function main() { + const prompt = createPrompt(); + let settings = loadOrCreateSettings(); + saveSettings(settings); + await startServers(settings); + + try { + while (true) { + log(""); + log("FPKGi Node Server CLI"); + log("----------------"); + log(`Config: ${getConfigPath()}`); + log("Running servers:"); + getRunningServersSummary(settings).forEach(line => log(`- ${line}`)); + log("1. Run scan"); + log("2. Edit settings"); + log("3. Start servers"); + log("4. Run scan and keep servers running"); + log("5. Stop running servers"); + log("6. Show current settings"); + log("7. Exit"); + + const choice = (await prompt.question("Choose an option: ")).trim(); + + try { + if (choice === "1") { + await runScan(settings); + } else if (choice === "2") { + const updated = await editSettings(JSON.parse(JSON.stringify(settings)), prompt.question); + if (updated) { + settings = loadOrCreateSettings(); + await stopServers(); + await startServers(settings); + } + } else if (choice === "3") { + validateSettings(settings); + await startServers(settings); + log("Servers are running in this session."); + } else if (choice === "4") { + validateSettings(settings); + await startServers(settings); + await runScan(settings); + log("Scan complete. Servers are still running."); + } else if (choice === "5") { + await stopServers(); + log("Servers stopped."); + } else if (choice === "6") { + printSettings(settings); + } else if (choice === "7") { + await stopServers(); + break; + } else { + log("Invalid option."); + } + } catch (err) { + error(`Error: ${err.message}`); + } + } + } finally { + prompt.rl.close(); + } +} + +async function shutdown() { + try { + await stopServers(); + } catch (err) { + error(`Shutdown error: ${err.message}`); + } finally { + process.exit(0); + } +} + +process.on("SIGINT", shutdown); +process.on("SIGTERM", shutdown); + +main().catch(err => { + error(err.message); + process.exit(1); +}); diff --git a/windows/cli/node-settings.json b/windows/cli/node-settings.json new file mode 100644 index 0000000..0474ab1 --- /dev/null +++ b/windows/cli/node-settings.json @@ -0,0 +1,20 @@ +{ + "serverAddress": "localhost:8080", + "httpServer": { + "enabled": false, + "address": "localhost", + "port": 8080 + }, + "jsonLibraryServer": { + "enabled": false, + "address": "localhost", + "port": 3000 + }, + "paths": { + "pkgScan": "", + "covers": "" + }, + "output": { + "path": "" + } +} \ No newline at end of file diff --git a/windows/cli/orbis-pub-cmd.exe b/windows/cli/orbis-pub-cmd.exe new file mode 100644 index 0000000..53b06d2 Binary files /dev/null and b/windows/cli/orbis-pub-cmd.exe differ diff --git a/windows/cli/orbis-pub-prx.dll b/windows/cli/orbis-pub-prx.dll new file mode 100644 index 0000000..1009fc1 Binary files /dev/null and b/windows/cli/orbis-pub-prx.dll differ diff --git a/windows/gui/app/json-library-server.js b/windows/gui/app/json-library-server.js new file mode 100644 index 0000000..7b3d46b --- /dev/null +++ b/windows/gui/app/json-library-server.js @@ -0,0 +1,464 @@ +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const zlib = require("zlib"); + +function send(res, status, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(status, { "Content-Type": contentType }); + res.end(body); +} + +function getBackgroundFilePath(coversDir) { + const extensions = [".png", ".bmp", ".jpg", ".jpeg"]; + for (const ext of extensions) { + const filePath = path.join(coversDir, `background${ext}`); + if (fs.existsSync(filePath)) { + return filePath; + } + } + return null; +} + +function makeCrcTable() { + const table = new Uint32Array(256); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) { + c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1); + } + table[n] = c >>> 0; + } + return table; +} + +const CRC_TABLE = makeCrcTable(); + +function crc32(buffer) { + let crc = 0xffffffff; + for (let i = 0; i < buffer.length; i++) { + crc = CRC_TABLE[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8); + } + return (crc ^ 0xffffffff) >>> 0; +} + +function pngChunk(type, data) { + const typeBuffer = Buffer.from(type, "ascii"); + const length = Buffer.alloc(4); + length.writeUInt32BE(data.length, 0); + + const crcInput = Buffer.concat([typeBuffer, data]); + const crc = Buffer.alloc(4); + crc.writeUInt32BE(crc32(crcInput), 0); + + return Buffer.concat([length, typeBuffer, data, crc]); +} + +function createBackgroundPng(width = 1280, height = 720) { + const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); + const raw = Buffer.alloc((width * 4 + 1) * height); + + for (let y = 0; y < height; y++) { + const rowOffset = y * (width * 4 + 1); + raw[rowOffset] = 0; + + for (let x = 0; x < width; x++) { + const t = x / Math.max(width - 1, 1); + const v = y / Math.max(height - 1, 1); + const offset = rowOffset + 1 + x * 4; + + const r = Math.round(18 + 125 * t + 20 * v); + const g = Math.round(6 + 10 * t + 4 * v); + const b = Math.round(10 + 22 * t + 18 * v); + + raw[offset] = Math.min(r, 255); + raw[offset + 1] = Math.min(g, 255); + raw[offset + 2] = Math.min(b, 255); + raw[offset + 3] = 255; + } + } + + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); + ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; + ihdr[9] = 6; + ihdr[10] = 0; + ihdr[11] = 0; + ihdr[12] = 0; + + const idat = zlib.deflateSync(raw, { level: 9 }); + const iend = Buffer.alloc(0); + + return Buffer.concat([ + signature, + pngChunk("IHDR", ihdr), + pngChunk("IDAT", idat), + pngChunk("IEND", iend) + ]); +} + +function sendBackgroundImage(res, coversDir) { + const backgroundFile = getBackgroundFilePath(coversDir); + if (backgroundFile) { + const stat = fs.statSync(backgroundFile); + const ext = path.extname(backgroundFile).toLowerCase(); + const contentTypes = { + ".png": "image/png", + ".bmp": "image/bmp", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg" + }; + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size, + "Cache-Control": "no-cache" + }); + + fs.createReadStream(backgroundFile).pipe(res); + return; + } + + const image = createBackgroundPng(); + res.writeHead(200, { + "Content-Type": "image/png", + "Content-Length": image.length, + "Cache-Control": "no-cache" + }); + res.end(image); +} + +function sendJsonFile(res, filePath) { + if (!fs.existsSync(filePath)) { + send(res, 404, JSON.stringify({ error: "File Not found" }), "application/json; charset=utf-8"); + return; + } + + const stat = fs.statSync(filePath); + res.writeHead(200, { + "Content-Type": "application/json; charset=utf-8", + "Content-Length": stat.size + }); + + fs.createReadStream(filePath).pipe(res); +} + +function renderHomePage(hostname, port, jsonDir) { + const jsonFiles = fs.readdirSync(jsonDir) + .filter(file => file.endsWith(".json")) + .map(file => { + const category = path.basename(file, ".json"); + return { + name: category, + url: `/${category}` + }; + }); + + return ` + + + + FPKGi Json Server + + + +

Available Categories

+
+

Click on the links below to view the JSON data and URL for your discovered packages. Either manually copy these URLs into your config, or use the generated config below. Click the Eye icon to see a parsed library view.

+ + +

Generated Config

+

Use the following configuration in your FPKGi config

+ +View Background + + + + +`; +} + +function renderLibraryPage(category) { + const title = category ? category.replace(/^./, c => c.toUpperCase()) : category; + + return "" + + "" + + "" + + " " + + " " + title + " Library" + + " " + + "" + + "" + + "

" + title + " Library

" + + "
" + + " Back" + + " " + + "" + + ""; +} + +function startJsonLibraryServer(settings, options = {}) { + if (!settings.jsonLibraryServer?.enabled) return Promise.resolve(null); + + const jsonDir = path.resolve(settings.output.path); + const coversDir = path.resolve(settings.paths.covers); + if (!fs.existsSync(jsonDir)) { + throw new Error(`JSON directory not found: ${jsonDir}`); + } + + const host = settings.jsonLibraryServer.address; + const port = settings.jsonLibraryServer.port; + const log = options.onLog || (() => {}); + + const server = http.createServer((req, res) => { + if (!req.url) { + send(res, 400, "Bad request"); + return; + } + + const requestUrl = new URL(req.url, `http://${host}:${port}`); + const pathname = decodeURIComponent(requestUrl.pathname); + + if (pathname === "/") { + send(res, 200, renderHomePage(host, port, jsonDir), "text/html; charset=utf-8"); + return; + } + + if (pathname === "/background") { + sendBackgroundImage(res, coversDir); + return; + } + + if (pathname === "/library" || pathname === "/library/") { + const cat = requestUrl.searchParams.get("cat"); + const jsonPath = path.join(jsonDir, `${cat}.json`); + + if (!cat || !fs.existsSync(jsonPath)) { + send(res, 404, "Category not found"); + return; + } + + send(res, 200, renderLibraryPage(cat), "text/html; charset=utf-8"); + return; + } + + const category = pathname.replace(/^\/+/, ""); + if (!category.includes("/") && category) { + sendJsonFile(res, path.join(jsonDir, `${category}.json`)); + return; + } + + send(res, 404, "Not found"); + }); + + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, host === "0.0.0.0" ? "0.0.0.0" : host, () => { + server.removeListener("error", reject); + log(`JSON library server running at http://${host}:${port}/`); + log(`Serving JSON files from: ${jsonDir}`); + resolve(server); + }); + }); +} + +function stopJsonLibraryServer(server, onLog) { + const log = onLog || (() => {}); + if (!server) return Promise.resolve(); + + return new Promise((resolve, reject) => { + server.close(error => { + if (error) { + reject(error); + return; + } + log("JSON library server stopped"); + resolve(); + }); + }); +} + +module.exports = { + startJsonLibraryServer, + stopJsonLibraryServer +}; diff --git a/windows/gui/app/processor.js b/windows/gui/app/processor.js new file mode 100644 index 0000000..6b4b864 --- /dev/null +++ b/windows/gui/app/processor.js @@ -0,0 +1,478 @@ +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +const { execSync } = require("child_process"); + +const DLC_CODES = { ac: true }; + +function createLogger(onLog) { + return message => { + if (onLog) onLog(message); + }; +} + +function getPkgInfo(pkgPath, orbisCmdPath) { + let output = ""; + try { + output = execSync(`"${orbisCmdPath}" img_info "${pkgPath}"`, { + stdio: "pipe", + timeout: 15000 + }).toString(); + } catch (err) { + output = ((err.stdout || "").toString() + (err.stderr || "").toString()); + } + + const get = key => { + const match = output.match(new RegExp(`${key}:\\s*(.+)`)); + return match ? match[1].trim() : null; + }; + + const category = get("Category"); + const titleId = get("Title ID"); + const contentId = get("Content ID"); + const isDlc = Boolean(category && DLC_CODES[category]); + const identifier = isDlc ? (contentId || titleId) : titleId; + + return { + titleId: titleId || path.basename(pkgPath, ".pkg"), + contentId: contentId || null, + identifier: identifier || path.basename(pkgPath, ".pkg"), + isDlc, + fallback: !titleId + }; +} + +function readBytes(fd, offset, length) { + const buf = Buffer.alloc(length); + fs.readSync(fd, buf, 0, length, offset); + return buf; +} + +function parseSFO(buffer) { + const keyTableOffset = buffer.readUInt32LE(8); + const dataOffset = buffer.readUInt32LE(12); + const numEntries = buffer.readUInt32LE(16); + const keyTable = buffer.slice(keyTableOffset); + const gameInfo = {}; + + for (let i = 0; i < numEntries; i++) { + const entryOffset = 20 + i * 16; + if (entryOffset + 16 > buffer.length) continue; + + const keyOffset = buffer.readUInt16LE(entryOffset); + const valueType = buffer.readUInt16LE(entryOffset + 2); + const valueSize = buffer.readUInt32LE(entryOffset + 4); + const relativeDataOffset = buffer.readUInt32LE(entryOffset + 12); + const absoluteDataOffset = dataOffset + relativeDataOffset; + if (keyOffset >= keyTable.length) continue; + + let keyEnd = keyTable.indexOf(0, keyOffset); + if (keyEnd === -1) keyEnd = keyTable.length; + const key = keyTable.slice(keyOffset, keyEnd).toString("utf8"); + if (absoluteDataOffset + valueSize > buffer.length) continue; + + if (valueType === 0x0204) { + gameInfo[key] = buffer.slice(absoluteDataOffset, absoluteDataOffset + valueSize).toString("utf8").replace(/\0/g, ""); + } else if (valueType === 0x0404) { + gameInfo[key] = buffer.readUInt32LE(absoluteDataOffset); + } + } + + return gameInfo; +} + +function extractGameDetails(pkgPath) { + let fd; + try { + fd = fs.openSync(pkgPath, "r"); + const fileSize = fs.statSync(pkgPath).size; + let offset = 0; + let foundOffset = null; + + while (offset < fileSize) { + const buf = readBytes(fd, offset, Math.min(1048576, fileSize - offset)); + const match = buf.indexOf(Buffer.from("\x00PSF\x01\x01\x00\x00", "binary")); + if (match !== -1) { + foundOffset = offset + match; + break; + } + offset += 1048576; + } + + if (foundOffset === null) return null; + return parseSFO(readBytes(fd, foundOffset, 2048)); + } catch (_) { + return null; + } finally { + if (typeof fd === "number") fs.closeSync(fd); + } +} + +function detectRegion(contentId) { + if (!contentId) return "UNK"; + if (contentId.startsWith("UP")) return "USA"; + if (contentId.startsWith("EP")) return "EUR"; + if (contentId.startsWith("JP")) return "JAP"; + return "UNK"; +} + +function getFormattedDate(filePath) { + const d = new Date(fs.statSync(filePath).birthtime); + return `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}-${d.getFullYear()}`; +} + +function findFileRecursive(dir, filename) { + try { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isFile() && entry.name.toLowerCase() === filename.toLowerCase()) return full; + if (entry.isDirectory()) { + const match = findFileRecursive(full, filename); + if (match) return match; + } + } + } catch (_) {} + return null; +} + +function extractIcon(pkgPath, identifier, tempDir, orbisCmdPath, log) { + const safeName = identifier.replace(/[<>:"/\\|?*]/g, "_"); + const outDir = path.join(tempDir, safeName); + if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true }); + + const cmd = `"${orbisCmdPath}" img_extract --no_passcode "${pkgPath}:Sc0/icon0.png" "${outDir}"`; + try { + execSync(cmd, { stdio: "pipe", timeout: 30000 }); + } catch (err) { + const out = ((err.stdout || "").toString() + (err.stderr || "").toString()).trim(); + if (out) log(` ${out.split("\n")[0]}`); + } + + return findFileRecursive(outDir, "icon0.png"); +} + +function collectPkgFiles(baseDir) { + const ignoredNames = ["node_modules", ".git", "covers", "_temp"]; + const pkgList = []; + const pkgsByCategory = {}; + + fs.readdirSync(baseDir, { withFileTypes: true }) + .filter(entry => entry.isDirectory() && !ignoredNames.includes(entry.name) && !entry.name.startsWith(".")) + .forEach(categoryEntry => { + const categoryPath = path.join(baseDir, categoryEntry.name); + const pkgFiles = []; + + fs.readdirSync(categoryPath, { withFileTypes: true }).forEach(entry => { + const fullPath = path.join(categoryPath, entry.name); + if (entry.isDirectory()) { + try { + fs.readdirSync(fullPath).forEach(fileName => { + if (fileName.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(path.join(fullPath, fileName)); + } + }); + } catch (_) {} + } else if (entry.name.toLowerCase().endsWith(".pkg")) { + pkgFiles.push(fullPath); + } + }); + + if (pkgFiles.length > 0) { + pkgsByCategory[categoryEntry.name] = pkgFiles; + pkgFiles.forEach(pkgPath => pkgList.push({ pkgPath, category: categoryEntry.name })); + } + }); + + return { pkgList, pkgsByCategory }; +} + +function findCoverImage(coversDir, serverAddress, titleId, contentId, category) { + const extensions = [".png", ".jpg", ".jpeg"]; + const isDlcOrTheme = category === "DLC" || category === "themes"; + const primaryKey = isDlcOrTheme ? (contentId || titleId) : titleId; + const secondaryKey = titleId; + const allFolders = [category, "games", "apps", "updates", "DLC", "demos", "homebrew", "emulators", "themes", "PS1", "PS2", "PSP"]; + + if (primaryKey) { + for (const ext of extensions) { + const ownPath = path.join(coversDir, category, `${primaryKey}${ext}`); + if (fs.existsSync(ownPath)) { + return `http://${serverAddress}/covers/${category}/${primaryKey}${ext}`; + } + } + } + + if (isDlcOrTheme && secondaryKey && secondaryKey !== primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${secondaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${secondaryKey}${ext}`; + } + } + } + } + + if (primaryKey) { + for (const folder of allFolders.filter(f => f !== category)) { + for (const ext of extensions) { + const fallbackPath = path.join(coversDir, folder, `${primaryKey}${ext}`); + if (fs.existsSync(fallbackPath)) { + return `http://${serverAddress}/covers/${folder}/${primaryKey}${ext}`; + } + } + } + } + + return `http://${serverAddress}/covers/default.png`; +} + +function generateJSON(jsonOutputDir, serverAddress, category, games) { + const jsonFilePath = path.join(jsonOutputDir, `${category}.json`); + const jsonData = { DATA: {} }; + + games.forEach(game => { + const pkgUrl = `http://${serverAddress}/pkg/${category}/${path.basename(game.pkgPath)}`; + jsonData.DATA[encodeURI(pkgUrl)] = { + region: game.region, + title_id: game.title_id, + name: game.title, + version: game.version, + release: game.release, + size: game.size, + min_fw: null, + cover_url: game.cover_url + }; + }); + + fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 4)); +} + +function validateSettings(settings) { + if (!settings.paths?.pkgScan) throw new Error("PKG scan path is required."); + if (!settings.paths?.covers) throw new Error("Covers path is required."); + if (!settings.output?.path) throw new Error("JSON output path is required."); + if (settings.httpServer?.enabled) { + if (!settings.httpServer.address) throw new Error("HTTP server IP is required."); + if (!settings.httpServer.port) throw new Error("HTTP server port is required."); + } else if (!settings.serverAddress) { + throw new Error("Server address is required when HTTP server is disabled."); + } + if (settings.jsonLibraryServer?.enabled) { + if (!settings.jsonLibraryServer.address) throw new Error("JSON library IP is required."); + if (!settings.jsonLibraryServer.port) throw new Error("JSON library port is required."); + } +} + +async function runProcessing(settings, options = {}) { + validateSettings(settings); + const log = createLogger(options.onLog); + const orbisCmdPath = options.orbisCmdPath; + if (!orbisCmdPath || !fs.existsSync(orbisCmdPath)) throw new Error("orbis-pub-cmd.exe is missing."); + + const baseDir = settings.paths.pkgScan; + const coversDir = settings.paths.covers; + const jsonOutputDir = settings.output.path; + const tempDir = path.join(coversDir, "_temp"); + const serverAddress = settings.httpServer?.enabled + ? `${settings.httpServer.address}:${settings.httpServer.port}` + : settings.serverAddress; + + if (!fs.existsSync(baseDir)) throw new Error(`PKG scan directory not found: ${baseDir}`); + + fs.mkdirSync(coversDir, { recursive: true }); + fs.mkdirSync(jsonOutputDir, { recursive: true }); + fs.mkdirSync(tempDir, { recursive: true }); + + const { pkgList, pkgsByCategory } = collectPkgFiles(baseDir); + if (pkgList.length === 0) { + log("No PKG files found in any category subfolder."); + return { totalPkgs: 0, categories: 0 }; + } + + log(`Found ${pkgList.length} PKG(s) across ${Object.keys(pkgsByCategory).length} categories`); + log("-".repeat(55)); + log("STEP 1: Extracting Icons"); + + let extracted = 0; + let skipped = 0; + let failed = 0; + + for (const { pkgPath, category } of pkgList) { + log(` Processing ${path.basename(pkgPath)}`); + const info = getPkgInfo(pkgPath, orbisCmdPath); + + if (info.fallback) log(` No Title ID found, using filename: ${info.identifier}`); + else if (info.isDlc) log(` DLC Content ID: ${info.identifier}`); + else log(` Title ID: ${info.titleId}`); + + const coverFolder = path.join(coversDir, category); + if (!fs.existsSync(coverFolder)) fs.mkdirSync(coverFolder, { recursive: true }); + + const outFile = path.join(coverFolder, `${info.identifier}.png`); + if (fs.existsSync(outFile)) { + log(" Icon already exists"); + skipped++; + continue; + } + + const iconPath = extractIcon(pkgPath, info.identifier, tempDir, orbisCmdPath, log); + if (iconPath) { + fs.copyFileSync(iconPath, outFile); + log(` Saved: ${outFile}`); + extracted++; + } else { + log(" icon0.png not found in PKG"); + failed++; + } + + try { + const tempFolder = path.join(tempDir, info.identifier.replace(/[<>:"/\\|?*]/g, "_")); + if (fs.existsSync(tempFolder)) fs.rmSync(tempFolder, { recursive: true, force: true }); + } catch (_) {} + } + + try { + if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true }); + } catch (_) {} + + log(`Icons done. Extracted: ${extracted} | Skipped: ${skipped} | Failed: ${failed}`); + log("-".repeat(55)); + log("STEP 2: Generating JSON Files"); + + let totalProcessed = 0; + + for (const [category, pkgFiles] of Object.entries(pkgsByCategory)) { + log(` Category: ${category}`); + const games = []; + + for (const pkgPath of pkgFiles) { + log(` Processing ${path.basename(pkgPath)}`); + const info = getPkgInfo(pkgPath, orbisCmdPath); + const details = extractGameDetails(pkgPath); + + if (!info.titleId && !details) { + log(" No metadata, skipping"); + continue; + } + + const version = details ? (() => { + const v = parseFloat(details.VERSION) || 0; + const a = parseFloat(details.APP_VER) || 0; + return (v >= a ? details.VERSION : details.APP_VER) || "0.00"; + })() : "0.00"; + + games.push({ + pkgPath, + region: detectRegion(info.contentId), + title_id: info.titleId, + content_id: info.contentId, + title: (details && details.TITLE) || "Unknown", + version, + release: getFormattedDate(pkgPath), + size: fs.statSync(pkgPath).size.toString(), + cover_url: findCoverImage(coversDir, serverAddress, info.titleId, info.contentId, category) + }); + totalProcessed++; + } + + if (games.length > 0) { + generateJSON(jsonOutputDir, serverAddress, category, games); + log(` ${games.length} PKG(s) -> ${category}.json`); + } else { + log(` No valid PKGs in ${category}`); + } + } + + log(`JSON done. ${totalProcessed} PKG(s) processed`); + return { totalPkgs: pkgList.length, categories: Object.keys(pkgsByCategory).length, extracted, skipped, failed, totalProcessed }; +} + +function sendResponse(res, statusCode, body, contentType = "text/plain; charset=utf-8") { + res.writeHead(statusCode, { "Content-Type": contentType }); + res.end(body); +} + +function serveStaticFile(req, res, urlPrefix, baseDir) { + const relativePath = decodeURIComponent(req.url.slice(urlPrefix.length)).replace(/^\/+/, ""); + const resolvedBase = path.resolve(baseDir); + const absolutePath = path.resolve(baseDir, relativePath); + + if (!absolutePath.startsWith(resolvedBase + path.sep) && absolutePath !== resolvedBase) { + sendResponse(res, 403, "Forbidden"); + return; + } + + if (!fs.existsSync(absolutePath)) { + sendResponse(res, 404, "Not found"); + return; + } + + const stat = fs.statSync(absolutePath); + if (stat.isDirectory()) { + sendResponse(res, 403, "Directory listing is disabled"); + return; + } + + const contentTypes = { + ".json": "application/json; charset=utf-8", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".pkg": "application/octet-stream" + }; + const ext = path.extname(absolutePath).toLowerCase(); + + res.writeHead(200, { + "Content-Type": contentTypes[ext] || "application/octet-stream", + "Content-Length": stat.size + }); + + fs.createReadStream(absolutePath).pipe(res); +} + +function startHttpServer(settings, options = {}) { + validateSettings(settings); + if (!settings.httpServer?.enabled) return Promise.resolve(null); + + const log = createLogger(options.onLog); + const address = settings.httpServer.address; + const port = settings.httpServer.port; + const pkgPath = settings.paths.pkgScan; + const coversPath = settings.paths.covers; + + const server = http.createServer((req, res) => { + if (!req.url) return sendResponse(res, 400, "Bad request"); + if (req.url === "/" || req.url === "") return sendResponse(res, 200, "Available paths: /pkg and /covers"); + if (req.url === "/pkg" || req.url === "/pkg/" || req.url === "/covers" || req.url === "/covers/") { + return sendResponse(res, 403, "Directory listing is disabled"); + } + if (req.url.startsWith("/pkg/")) return serveStaticFile(req, res, "/pkg/", pkgPath); + if (req.url.startsWith("/covers/")) return serveStaticFile(req, res, "/covers/", coversPath); + return sendResponse(res, 404, "Not found"); + }); + + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, address, () => { + server.removeListener("error", reject); + log(`HTTP server running at http://${address}:${port}`); + resolve(server); + }); + }); +} + +function stopHttpServer(server, onLog) { + const log = createLogger(onLog); + if (!server) return Promise.resolve(); + + return new Promise((resolve, reject) => { + server.close(error => { + if (error) return reject(error); + log("HTTP server stopped"); + resolve(); + }); + }); +} + +module.exports = { runProcessing, startHttpServer, stopHttpServer, validateSettings }; diff --git a/windows/gui/electron/assets/.gitkeep b/windows/gui/electron/assets/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/windows/gui/electron/assets/.gitkeep @@ -0,0 +1 @@ + diff --git a/windows/gui/electron/assets/icon.ico b/windows/gui/electron/assets/icon.ico new file mode 100644 index 0000000..37c1296 Binary files /dev/null and b/windows/gui/electron/assets/icon.ico differ diff --git a/windows/gui/electron/main.js b/windows/gui/electron/main.js new file mode 100644 index 0000000..8f37947 --- /dev/null +++ b/windows/gui/electron/main.js @@ -0,0 +1,385 @@ +const { app, BrowserWindow, ipcMain, dialog, Tray, Menu, Notification, nativeImage, shell } = require("electron"); +const fs = require("fs"); +const path = require("path"); +const { Worker } = require("worker_threads"); +const { startHttpServer, stopHttpServer, validateSettings } = require("../app/processor"); +const { startJsonLibraryServer, stopJsonLibraryServer } = require("../app/json-library-server"); + +let mainWindow = null; +let tray = null; +let httpServerInstance = null; +let jsonLibraryServerInstance = null; +let isScanning = false; +let logBuffer = []; +let scanWorker = null; +let isQuitting = false; + +function getUserConfigPath() { + return path.join(app.getPath("userData"), "settings.json"); +} + +function getBundledOrbisPath() { + if (app.isPackaged) return path.join(process.resourcesPath, "bin", "orbis-pub-cmd.exe"); + return path.join(app.getAppPath(), "orbis-pub-cmd.exe"); +} + +function getAppIconPath() { + if (app.isPackaged) { + const packagedIcon = path.join(process.resourcesPath, "icon.ico"); + return fs.existsSync(packagedIcon) ? packagedIcon : null; + } + const devIcon = path.join(app.getAppPath(), "electron", "assets", "icon.ico"); + return fs.existsSync(devIcon) ? devIcon : null; +} + +function getTrayIconImage() { + const iconPath = getAppIconPath(); + if (!iconPath) return nativeImage.createEmpty(); + const image = nativeImage.createFromPath(iconPath); + if (image.isEmpty()) return nativeImage.createEmpty(); + return process.platform === "win32" ? image.resize({ width: 16, height: 16 }) : image; +} + +function defaultSettings() { + return { + serverAddress: "localhost:8080", + httpServer: { + enabled: true, + address: "localhost", + port: 8080 + }, + jsonLibraryServer: { + enabled: false, + address: "localhost", + port: 3000 + }, + paths: { + pkgScan: "", + covers: "" + }, + output: { + path: "" + }, + appBehavior: { + runInBackground: false, + runOnStartup: false, + startInBackgroundOnStartup: false + } + }; +} + +function loadSettings() { + const configPath = getUserConfigPath(); + const defaults = defaultSettings(); + if (!fs.existsSync(configPath)) { + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify(defaults, null, 4)); + return defaults; + } + + try { + const loaded = JSON.parse(fs.readFileSync(configPath, "utf8")); + return { + ...defaults, + ...loaded, + httpServer: { ...defaults.httpServer, ...(loaded.httpServer || {}) }, + jsonLibraryServer: { ...defaults.jsonLibraryServer, ...(loaded.jsonLibraryServer || {}) }, + paths: { ...defaults.paths, ...(loaded.paths || {}) }, + output: { ...defaults.output, ...(loaded.output || {}) }, + appBehavior: { ...defaults.appBehavior, ...(loaded.appBehavior || {}) } + }; + } catch (_) { + return defaults; + } +} + +function saveSettings(settings) { + const configPath = getUserConfigPath(); + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify(settings, null, 4)); +} + +function addLog(message) { + const line = `[${new Date().toLocaleTimeString()}] ${message}`; + logBuffer.push(line); + if (logBuffer.length > 500) logBuffer = logBuffer.slice(-500); + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("log:append", line); + } +} + +function getJsonLibraryUrl(settings = loadSettings()) { + return `http://${settings.jsonLibraryServer.address}:${settings.jsonLibraryServer.port}/`; +} + +function getStatusPayload() { + const settings = loadSettings(); + const orbisPath = getBundledOrbisPath(); + return { + scanning: isScanning, + httpServerRunning: Boolean(httpServerInstance), + httpServerUrl: settings.httpServer?.enabled ? `http://${settings.httpServer.address}:${settings.httpServer.port}` : "", + jsonLibraryRunning: Boolean(jsonLibraryServerInstance), + jsonLibraryUrl: settings.jsonLibraryServer?.enabled ? getJsonLibraryUrl(settings) : "", + orbisExists: fs.existsSync(orbisPath), + orbisPath, + settings, + logs: logBuffer + }; +} + +function emitStatusUpdate() { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("status:update", getStatusPayload()); + } +} + +function applyStartupSetting(settings) { + app.setLoginItemSettings({ + openAtLogin: Boolean(settings.appBehavior?.runOnStartup) + }); +} + +function serverConfigChanged(prevConfig = {}, nextConfig = {}) { + return JSON.stringify(prevConfig) !== JSON.stringify(nextConfig); +} + +async function syncHttpServer(settings) { + if (httpServerInstance) { + await stopHttpServer(httpServerInstance, addLog); + httpServerInstance = null; + } + if (settings.httpServer?.enabled) { + httpServerInstance = await startHttpServer(settings, { onLog: addLog }); + } +} + +async function syncJsonLibraryServer(settings) { + if (jsonLibraryServerInstance) { + await stopJsonLibraryServer(jsonLibraryServerInstance, addLog); + jsonLibraryServerInstance = null; + } + if (settings.jsonLibraryServer?.enabled) { + jsonLibraryServerInstance = await startJsonLibraryServer(settings, { onLog: addLog }); + } +} + +function showWindow() { + if (!mainWindow) return; + mainWindow.show(); + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); +} + +function hideToTray() { + if (!mainWindow) return; + mainWindow.hide(); + addLog("App minimized to tray"); +} + +function createTray() { + if (tray) return; + tray = new Tray(getTrayIconImage()); + tray.setToolTip("FPKGi Node Server"); + tray.setContextMenu(Menu.buildFromTemplate([ + { label: "Open", click: showWindow }, + { + label: "Quit", + click: () => { + isQuitting = true; + app.quit(); + } + } + ])); + tray.on("click", showWindow); +} + +function sendDesktopNotification(title, body) { + if (!Notification.isSupported()) return; + const iconPath = getAppIconPath(); + new Notification({ title, body, icon: iconPath || undefined }).show(); +} + +function createWindow() { + const settings = loadSettings(); + const showOnLaunch = !settings.appBehavior?.startInBackgroundOnStartup; + + mainWindow = new BrowserWindow({ + width: 1280, + height: 860, + minWidth: 1080, + minHeight: 760, + backgroundColor: "#150609", + show: showOnLaunch, + icon: getAppIconPath() || undefined, + webPreferences: { + preload: path.join(__dirname, "preload.js"), + contextIsolation: true, + nodeIntegration: false + } + }); + + mainWindow.loadFile(path.join(__dirname, "renderer", "index.html")); + + if (!showOnLaunch) { + mainWindow.once("ready-to-show", () => { + mainWindow.hide(); + addLog("App started in tray mode"); + }); + } + + mainWindow.on("close", event => { + const settingsNow = loadSettings(); + if (!isQuitting && settingsNow.appBehavior?.runInBackground) { + event.preventDefault(); + hideToTray(); + } + }); +} + +app.whenReady().then(async () => { + createTray(); + createWindow(); + try { + const settings = loadSettings(); + applyStartupSetting(settings); + if (settings.httpServer?.enabled) await syncHttpServer(settings); + if (settings.jsonLibraryServer?.enabled) await syncJsonLibraryServer(settings); + emitStatusUpdate(); + } catch (error) { + addLog(`Failed to start server: ${error.message}`); + } +}); + +app.on("window-all-closed", async () => { + const settings = loadSettings(); + if (settings.appBehavior?.runInBackground && !isQuitting) return; + + if (httpServerInstance) { + try { await stopHttpServer(httpServerInstance, addLog); } catch (_) {} + } + if (jsonLibraryServerInstance) { + try { await stopJsonLibraryServer(jsonLibraryServerInstance, addLog); } catch (_) {} + } + + if (process.platform !== "darwin") app.quit(); +}); + +app.on("before-quit", () => { + isQuitting = true; +}); + +ipcMain.handle("app:get-status", async () => getStatusPayload()); + +ipcMain.handle("settings:save", async (_event, nextSettings) => { + const previousSettings = loadSettings(); + validateSettings(nextSettings); + saveSettings(nextSettings); + applyStartupSetting(nextSettings); + + if (serverConfigChanged(previousSettings.httpServer, nextSettings.httpServer) || + previousSettings.serverAddress !== nextSettings.serverAddress || + previousSettings.paths?.pkgScan !== nextSettings.paths?.pkgScan || + previousSettings.paths?.covers !== nextSettings.paths?.covers) { + await syncHttpServer(nextSettings); + } + + if (serverConfigChanged(previousSettings.jsonLibraryServer, nextSettings.jsonLibraryServer) || + previousSettings.output?.path !== nextSettings.output?.path) { + await syncJsonLibraryServer(nextSettings); + } + + emitStatusUpdate(); + return getStatusPayload(); +}); + +ipcMain.handle("settings:browse-directory", async () => { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ["openDirectory", "createDirectory"] + }); + if (result.canceled || result.filePaths.length === 0) return null; + return result.filePaths[0]; +}); + +ipcMain.handle("json-library:open", async () => { + const settings = loadSettings(); + if (!settings.jsonLibraryServer?.enabled || !jsonLibraryServerInstance) { + throw new Error("JSON library server is not running."); + } + await shell.openExternal(getJsonLibraryUrl(settings)); + return true; +}); + +ipcMain.handle("scan:run", async () => { + if (isScanning) throw new Error("A scan is already running."); + + const settings = loadSettings(); + const orbisPath = getBundledOrbisPath(); + + isScanning = true; + emitStatusUpdate(); + try { + addLog("Starting scan"); + const summary = await new Promise((resolve, reject) => { + let settled = false; + scanWorker = new Worker(path.join(__dirname, "scan-worker.js"), { + workerData: { + settings, + orbisCmdPath: orbisPath + } + }); + + scanWorker.on("message", message => { + if (message.type === "log") { + addLog(message.line); + return; + } + if (message.type === "done") { + settled = true; + resolve(message.summary); + } + }); + + scanWorker.on("error", error => { + if (!settled) reject(error); + }); + + scanWorker.on("exit", code => { + if (!settled && code !== 0) { + reject(new Error(code === 1 ? "Scan stopped." : `Scan worker stopped with exit code ${code}`)); + } + }); + }); + + isScanning = false; + scanWorker = null; + emitStatusUpdate(); + addLog("Scan finished"); + sendDesktopNotification("FPKGi Node Server", "Scan completed successfully."); + return { ok: true, summary, status: getStatusPayload() }; + } catch (error) { + isScanning = false; + scanWorker = null; + emitStatusUpdate(); + addLog(`Scan failed: ${error.message}`); + if (error.message === "Scan stopped.") { + sendDesktopNotification("FPKGi Node Server", "Scan stopped."); + } + return { ok: false, error: error.message, status: getStatusPayload() }; + } +}); + +ipcMain.handle("scan:stop", async () => { + if (!scanWorker || !isScanning) { + return getStatusPayload(); + } + + addLog("Stopping scan..."); + const workerToStop = scanWorker; + scanWorker = null; + await workerToStop.terminate(); + isScanning = false; + emitStatusUpdate(); + sendDesktopNotification("FPKGi Node Server", "Scan stopped."); + return getStatusPayload(); +}); diff --git a/windows/gui/electron/preload.js b/windows/gui/electron/preload.js new file mode 100644 index 0000000..7358567 --- /dev/null +++ b/windows/gui/electron/preload.js @@ -0,0 +1,12 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("fpkgiApi", { + getStatus: () => ipcRenderer.invoke("app:get-status"), + saveSettings: settings => ipcRenderer.invoke("settings:save", settings), + browseDirectory: () => ipcRenderer.invoke("settings:browse-directory"), + runScan: () => ipcRenderer.invoke("scan:run"), + stopScan: () => ipcRenderer.invoke("scan:stop"), + openJsonLibrary: () => ipcRenderer.invoke("json-library:open"), + onLog: callback => ipcRenderer.on("log:append", (_event, line) => callback(line)), + onStatus: callback => ipcRenderer.on("status:update", (_event, status) => callback(status)) +}); diff --git a/windows/gui/electron/renderer/index.html b/windows/gui/electron/renderer/index.html new file mode 100644 index 0000000..99dcf1d --- /dev/null +++ b/windows/gui/electron/renderer/index.html @@ -0,0 +1,169 @@ + + + + + + FPKGi Node Server + + + +
+ + +
+
+
+
+

Dashboard

+

Use the button to extract icons, rebuild JSON files, and keep an eye on app logs while it runs.

+
+
+ + +
+
+ +
+
+ HTTP Server + Checking... + +
+
+ Orbis Tool + Checking... + +
+
+ Current Job + Idle + The dashboard updates live while the scan is running. +
+
+ JSON Library Server + Checking... + + +
+
+ +
+
+
+

Live Logs

+

Processing Output

+
+
+

+                
+
+ +
+
+
+

Settings

+

Choose where PKGs are scanned, where covers live, and whether the built-in HTTP or JSON server should stay on.

+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+ PKG scan path +
+ + +
+
+ +
+ Covers path +
+ + +
+
+ +
+ JSON output path +
+ + +
+
+
+
+
+
+
+ +
+ + + + diff --git a/windows/gui/electron/renderer/renderer.js b/windows/gui/electron/renderer/renderer.js new file mode 100644 index 0000000..46eadbd --- /dev/null +++ b/windows/gui/electron/renderer/renderer.js @@ -0,0 +1,194 @@ +const state = { settings: null, logs: [], toastTimer: null }; + +const elements = { + navLinks: Array.from(document.querySelectorAll(".nav-link")), + panels: Array.from(document.querySelectorAll(".view")), + runScanButton: document.getElementById("runScanButton"), + stopScanButton: document.getElementById("stopScanButton"), + saveSettingsButton: document.getElementById("saveSettingsButton"), + httpServerStatus: document.getElementById("httpServerStatus"), + httpServerUrl: document.getElementById("httpServerUrl"), + jsonLibraryStatus: document.getElementById("jsonLibraryStatus"), + jsonLibraryUrl: document.getElementById("jsonLibraryUrl"), + openJsonLibraryButton: document.getElementById("openJsonLibraryButton"), + orbisStatus: document.getElementById("orbisStatus"), + orbisPath: document.getElementById("orbisPath"), + scanStatus: document.getElementById("scanStatus"), + logOutput: document.getElementById("logOutput"), + toast: document.getElementById("toast"), + httpEnabled: document.getElementById("httpEnabled"), + jsonLibraryEnabled: document.getElementById("jsonLibraryEnabled"), + runInBackground: document.getElementById("runInBackground"), + runOnStartup: document.getElementById("runOnStartup"), + startInBackgroundOnStartup: document.getElementById("startInBackgroundOnStartup"), + httpAddress: document.getElementById("httpAddress"), + httpPort: document.getElementById("httpPort"), + jsonLibraryAddress: document.getElementById("jsonLibraryAddress"), + jsonLibraryPort: document.getElementById("jsonLibraryPort"), + serverAddress: document.getElementById("serverAddress"), + pkgScanPath: document.getElementById("pkgScanPath"), + coversPath: document.getElementById("coversPath"), + jsonOutputPath: document.getElementById("jsonOutputPath"), + browseButtons: Array.from(document.querySelectorAll("[data-browse-target]")) +}; + +function setActiveView(view) { + elements.navLinks.forEach(link => link.classList.toggle("is-active", link.dataset.view === view)); + elements.panels.forEach(panel => panel.classList.toggle("is-active", panel.dataset.viewPanel === view)); +} + +function showToast(message, type = "success") { + elements.toast.textContent = message; + elements.toast.dataset.type = type; + elements.toast.classList.add("is-visible"); + + if (state.toastTimer) clearTimeout(state.toastTimer); + state.toastTimer = setTimeout(() => { + elements.toast.classList.remove("is-visible"); + }, 2400); +} + +function appendLog(line) { + state.logs.push(line); + if (state.logs.length > 500) state.logs = state.logs.slice(-500); + elements.logOutput.textContent = state.logs.join("\n"); + elements.logOutput.scrollTop = elements.logOutput.scrollHeight; +} + +function renderSettings(settings) { + state.settings = settings; + elements.httpEnabled.checked = Boolean(settings.httpServer?.enabled); + elements.jsonLibraryEnabled.checked = Boolean(settings.jsonLibraryServer?.enabled); + elements.runInBackground.checked = Boolean(settings.appBehavior?.runInBackground); + elements.runOnStartup.checked = Boolean(settings.appBehavior?.runOnStartup); + elements.startInBackgroundOnStartup.checked = Boolean(settings.appBehavior?.startInBackgroundOnStartup); + elements.httpAddress.value = settings.httpServer?.address || ""; + elements.httpPort.value = settings.httpServer?.port || ""; + elements.jsonLibraryAddress.value = settings.jsonLibraryServer?.address || ""; + elements.jsonLibraryPort.value = settings.jsonLibraryServer?.port || ""; + elements.serverAddress.value = settings.serverAddress || ""; + elements.pkgScanPath.value = settings.paths?.pkgScan || ""; + elements.coversPath.value = settings.paths?.covers || ""; + elements.jsonOutputPath.value = settings.output?.path || ""; +} + +function renderStatus(payload) { + renderSettings(payload.settings); + state.logs = payload.logs || []; + elements.logOutput.textContent = state.logs.join("\n"); + elements.logOutput.scrollTop = elements.logOutput.scrollHeight; + + elements.httpServerStatus.textContent = payload.httpServerRunning ? "Running" : "Stopped"; + elements.httpServerStatus.className = payload.httpServerRunning ? "status-good" : "status-bad"; + elements.httpServerUrl.textContent = payload.httpServerRunning ? payload.httpServerUrl : "HTTP server is turned off."; + + elements.jsonLibraryStatus.textContent = payload.jsonLibraryRunning ? "Running" : "Stopped"; + elements.jsonLibraryStatus.className = payload.jsonLibraryRunning ? "status-good" : "status-bad"; + elements.jsonLibraryUrl.textContent = payload.jsonLibraryRunning ? payload.jsonLibraryUrl : "JSON library server is turned off."; + elements.openJsonLibraryButton.disabled = !payload.jsonLibraryRunning; + + elements.orbisStatus.textContent = payload.orbisExists ? "Detected" : "Missing"; + elements.orbisStatus.className = payload.orbisExists ? "status-good" : "status-bad"; + elements.orbisPath.textContent = payload.orbisPath; + + elements.scanStatus.textContent = payload.scanning ? "Scanning" : "Idle"; + elements.runScanButton.disabled = payload.scanning; + elements.stopScanButton.disabled = !payload.scanning; +} + +function collectSettingsFromForm() { + return { + serverAddress: elements.serverAddress.value.trim(), + httpServer: { + enabled: elements.httpEnabled.checked, + address: elements.httpAddress.value.trim(), + port: Number(elements.httpPort.value) + }, + jsonLibraryServer: { + enabled: elements.jsonLibraryEnabled.checked, + address: elements.jsonLibraryAddress.value.trim(), + port: Number(elements.jsonLibraryPort.value) + }, + paths: { + pkgScan: elements.pkgScanPath.value.trim(), + covers: elements.coversPath.value.trim() + }, + output: { + path: elements.jsonOutputPath.value.trim() + }, + appBehavior: { + runInBackground: elements.runInBackground.checked, + runOnStartup: elements.runOnStartup.checked, + startInBackgroundOnStartup: elements.startInBackgroundOnStartup.checked + } + }; +} + +async function bootstrap() { + renderStatus(await window.fpkgiApi.getStatus()); +} + +elements.navLinks.forEach(link => link.addEventListener("click", () => setActiveView(link.dataset.view))); + +elements.browseButtons.forEach(button => { + button.addEventListener("click", async () => { + const selected = await window.fpkgiApi.browseDirectory(); + if (!selected) return; + const input = document.getElementById(button.dataset.browseTarget); + if (input) input.value = selected; + }); +}); + +elements.saveSettingsButton.addEventListener("click", async () => { + try { + renderStatus(await window.fpkgiApi.saveSettings(collectSettingsFromForm())); + appendLog("Settings saved"); + showToast("Settings saved"); + } catch (error) { + appendLog(`Settings save failed: ${error.message}`); + showToast("Failed to save settings", "error"); + } +}); + +elements.runScanButton.addEventListener("click", async () => { + appendLog("Launching scan from dashboard"); + elements.scanStatus.textContent = "Scanning"; + elements.runScanButton.disabled = true; + elements.stopScanButton.disabled = false; + const result = await window.fpkgiApi.runScan(); + renderStatus(result.status); + if (!result.ok) { + if (result.error === "Scan stopped.") { + showToast("Scan stopped"); + } else { + appendLog(`Scan error: ${result.error}`); + showToast("Scan failed", "error"); + } + return; + } + showToast("Scan completed"); +}); + +elements.stopScanButton.addEventListener("click", async () => { + try { + const status = await window.fpkgiApi.stopScan(); + renderStatus(status); + showToast("Stopping scan..."); + } catch (error) { + appendLog(`Stop scan failed: ${error.message}`); + showToast("Failed to stop scan", "error"); + } +}); + +elements.openJsonLibraryButton.addEventListener("click", async () => { + try { + await window.fpkgiApi.openJsonLibrary(); + } catch (error) { + appendLog(`Open JSON library failed: ${error.message}`); + showToast("Failed to open JSON library", "error"); + } +}); + +window.fpkgiApi.onLog(appendLog); +window.fpkgiApi.onStatus(renderStatus); +bootstrap(); diff --git a/windows/gui/electron/renderer/styles.css b/windows/gui/electron/renderer/styles.css new file mode 100644 index 0000000..036f346 --- /dev/null +++ b/windows/gui/electron/renderer/styles.css @@ -0,0 +1,316 @@ +:root { + --bg: #0c0507; + --bg-alt: #16080b; + --panel: #1a0c10; + --panel-2: #231016; + --ink: #f8e9ec; + --muted: #d4aeb7; + --line: rgba(255, 110, 132, 0.16); + --accent: #d42045; + --accent-2: #ff6b6b; + --warn: #ff8a5b; + --ok: #5bdb93; + --shadow: 0 24px 50px rgba(0, 0, 0, 0.38); + --radius: 22px; + --mono: "Cascadia Code", "SFMono-Regular", Consolas, monospace; + --sans: "Segoe UI", "Trebuchet MS", sans-serif; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + font-family: var(--sans); + color: var(--ink); + overflow: hidden; + background: + radial-gradient(circle at top left, rgba(212, 32, 69, 0.22), transparent 32%), + radial-gradient(circle at bottom right, rgba(255, 107, 107, 0.16), transparent 28%), + linear-gradient(180deg, var(--bg) 0%, #12070a 100%); +} + +button, input { font: inherit; } + +.app-shell { + min-height: 100vh; + display: grid; + grid-template-columns: 280px 1fr; +} + +.sidebar { + padding: 28px; + background: linear-gradient(180deg, #26070f 0%, #3c0d19 100%); + color: #fdeff2; + display: flex; + flex-direction: column; + justify-content: space-between; + border-right: 1px solid rgba(255, 110, 132, 0.16); +} + +.eyebrow { + margin: 0 0 10px; + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 12px; + color: rgba(253, 239, 242, 0.68); +} + +.sidebar h1, .hero h2 { + margin: 0; + font-weight: 700; + line-height: 1.05; +} + +.sidebar h1 { font-size: 40px; } + +.intro { + color: rgba(253, 239, 242, 0.82); + line-height: 1.55; + margin-top: 14px; +} + +.nav { display: grid; gap: 12px; } + +.nav-link { + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(255, 255, 255, 0.04); + color: inherit; + padding: 15px 18px; + border-radius: 16px; + text-align: left; + cursor: pointer; + transition: background 140ms ease, border-color 140ms ease, transform 140ms ease; +} + +.nav-link:hover, +.nav-link.is-active { + background: rgba(212, 32, 69, 0.24); + border-color: rgba(255, 110, 132, 0.24); + transform: translateX(2px); +} + +.sidebar-note { + display: grid; + gap: 6px; + padding: 18px; + border-radius: 18px; + background: rgba(255, 255, 255, 0.05); + color: rgba(253, 239, 242, 0.84); +} + +.content { padding: 30px; } +.content { + height: 100vh; + overflow-y: auto; +} +.view { display: none; gap: 22px; } +.view.is-active { display: grid; } + +.hero { + display: flex; + justify-content: space-between; + align-items: end; + gap: 20px; + padding: 24px 28px; + background: linear-gradient(140deg, rgba(212, 32, 69, 0.16), rgba(50, 14, 24, 0.96)); + border: 1px solid var(--line); + border-radius: var(--radius); + box-shadow: var(--shadow); +} + +.hero.compact { align-items: center; } +.hero-actions { + display: flex; + gap: 12px; + align-items: center; +} + +.hero p { + margin: 12px 0 0; + max-width: 720px; + color: var(--muted); +} + +.primary-button, .secondary-button, .ghost-button { + border: 0; + border-radius: 999px; + cursor: pointer; +} + +.primary-button, +.secondary-button { + padding: 14px 24px; + font-weight: 700; +} + +.primary-button { + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%); + color: white; + box-shadow: 0 12px 30px rgba(212, 32, 69, 0.28); +} + +.primary-button:disabled { + opacity: 0.55; + cursor: wait; +} + +.secondary-button { + background: #3a161f; + color: #ffe4e8; + border: 1px solid rgba(255, 110, 132, 0.18); +} + +.ghost-button { + padding: 11px 14px; + background: #2b1319; + color: #ffe4e8; + border: 1px solid rgba(255, 110, 132, 0.18); +} + +.status-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.status-card, +.panel { + background: linear-gradient(180deg, var(--panel) 0%, var(--panel-2) 100%); + border-radius: var(--radius); + border: 1px solid var(--line); + box-shadow: var(--shadow); +} + +.status-card { + padding: 22px; + display: grid; + gap: 8px; +} + +.card-button { + justify-self: start; + margin-top: 6px; +} + +.status-card strong { font-size: 28px; } +.status-label { color: var(--muted); text-transform: uppercase; letter-spacing: 0.14em; font-size: 12px; } +.status-meta { color: var(--muted); overflow-wrap: anywhere; } +.panel { padding: 22px; } +.panel-head h3 { margin: 6px 0 0; } + +.log-box { + margin: 16px 0 0; + min-height: 380px; + max-height: 480px; + overflow: auto; + padding: 18px; + border-radius: 18px; + background: #090304; + color: #ffd5dd; + border: 1px solid rgba(255, 110, 132, 0.12); + font-family: var(--mono); + font-size: 13px; + line-height: 1.55; + white-space: pre-wrap; +} + +.settings-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; +} + +.field { display: grid; gap: 10px; } +.field span { font-size: 14px; color: var(--muted); } + +.field input[type="text"], +.field input[type="number"] { + width: 100%; + padding: 13px 14px; + border-radius: 14px; + border: 1px solid var(--line); + background: #12080b; + color: var(--ink); +} + +.field-toggle { + grid-column: 1 / -1; + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 18px; + border: 1px solid var(--line); + border-radius: 18px; + background: #12080b; +} + +.field-toggle input { + width: 22px; + height: 22px; + accent-color: var(--accent); +} + +.field-path { grid-column: 1 / -1; } + +.path-row { + display: grid; + grid-template-columns: 1fr auto; + gap: 10px; +} + +.status-good { color: var(--ok); } +.status-bad { color: var(--warn); } + +.toast { + position: fixed; + right: 26px; + bottom: 24px; + padding: 14px 18px; + border-radius: 16px; + background: rgba(20, 8, 11, 0.96); + color: #ffe8ed; + border: 1px solid rgba(255, 110, 132, 0.24); + box-shadow: var(--shadow); + opacity: 0; + transform: translateY(12px); + pointer-events: none; + transition: opacity 160ms ease, transform 160ms ease; +} + +.toast[data-type="error"] { + border-color: rgba(255, 138, 91, 0.4); +} + +.toast.is-visible { + opacity: 1; + transform: translateY(0); +} + +* { + scrollbar-width: thin; + scrollbar-color: rgba(212, 32, 69, 0.75) rgba(34, 10, 15, 0.95); +} + +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +::-webkit-scrollbar-track { + background: rgba(34, 10, 15, 0.95); +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, rgba(212, 32, 69, 0.95), rgba(255, 107, 107, 0.82)); + border-radius: 999px; + border: 2px solid rgba(34, 10, 15, 0.95); +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(180deg, rgba(232, 52, 89, 0.98), rgba(255, 127, 127, 0.9)); +} + +@media (max-width: 980px) { + .app-shell { grid-template-columns: 1fr; } + .status-grid, .settings-grid { grid-template-columns: 1fr; } +} diff --git a/windows/gui/electron/scan-worker.js b/windows/gui/electron/scan-worker.js new file mode 100644 index 0000000..df2bf85 --- /dev/null +++ b/windows/gui/electron/scan-worker.js @@ -0,0 +1,13 @@ +const { parentPort, workerData } = require("worker_threads"); +const { runProcessing } = require("../app/processor"); + +runProcessing(workerData.settings, { + orbisCmdPath: workerData.orbisCmdPath, + onLog: line => parentPort.postMessage({ type: "log", line }) +}) + .then(summary => { + parentPort.postMessage({ type: "done", summary }); + }) + .catch(error => { + throw error; + }); diff --git a/windows/gui/ext/di.exe b/windows/gui/ext/di.exe new file mode 100644 index 0000000..4fd8035 Binary files /dev/null and b/windows/gui/ext/di.exe differ diff --git a/windows/gui/ext/libatrac9.dll b/windows/gui/ext/libatrac9.dll new file mode 100644 index 0000000..cca0446 Binary files /dev/null and b/windows/gui/ext/libatrac9.dll differ diff --git a/windows/gui/ext/sc.exe b/windows/gui/ext/sc.exe new file mode 100644 index 0000000..45b953a Binary files /dev/null and b/windows/gui/ext/sc.exe differ diff --git a/windows/gui/ext/trp_compare_default.css b/windows/gui/ext/trp_compare_default.css new file mode 100644 index 0000000..61b9252 --- /dev/null +++ b/windows/gui/ext/trp_compare_default.css @@ -0,0 +1,259 @@ +/* body ƒ^ƒOÝ’è */ +body { + background-color: rgb(0, 77, 149); /* ”wŒiF */ + color: rgb(15, 15, 15); /* •¶ŽšF */ + font-family: verdana,arial,helvetica,sans-serif; /* ƒtƒHƒ“ƒg */ + font-size: 12px; /* •¶ŽšƒTƒCƒY */ + line-height: 18px; /* s‚Ì‚‚³ */ + -ms-scrollbar-base-color: transparent; /* ƒXƒNƒ[ƒ‹ƒo[‚ÌŠî–{F(IEŒü‚¯) */ + -ms-scrollbar-face-color: rgb(214, 214, 214); /* ƒXƒNƒ[ƒ‹ƒo[‚̃o[‚ÌF(IEŒü‚¯) */ + -ms-scrollbar-arrow-color: rgb(214, 214, 214); /* ƒXƒNƒ[ƒ‹ƒo[‚Ì–îˆó‚ÌF(IEŒü‚¯) */ + -ms-scrollbar-track-color: transparent; /* ƒXƒNƒ[ƒ‹ƒo[‚̃Œ[ƒ‹‚ÌF(IEŒü‚¯) */ +} +/* h1 ƒ^ƒO•¶ŽšÝ’è */ +h1 { + font-size: 1.5em; /* •¶ŽšƒTƒCƒY */ + font-weight: 600; /* •¶Žš‘¾‚³ */ + line-height: 24px; /* s‚Ì‚‚³ */ +} +/* h2 ƒ^ƒO•¶ŽšÝ’è */ +h2 { + font-size: 1.25em; /* •¶ŽšƒTƒCƒY */ + font-weight: 600; /* •¶Žš‘¾‚³ */ + line-height: 24px; /* s‚Ì‚‚³ */ +} +/* p ƒ^ƒO•¶ŽšÝ’è */ +p { + /* non setting */ +} + +/* header ƒ^ƒOÝ’è */ +header { + background-color: rgb(214, 249, 159); /* ”wŒiF */ + border-radius: 2px 2px 0px 0px; /* Šp‚ÌŠÛ‚Ý */ + box-shadow: inset 0px -1px 0px rgb(195, 234, 133); /* ‰eÝ’è */ + color: rgb(61, 96, 6); /* •¶ŽšF */ +} +/* id="content" ƒ^ƒOÝ’è */ +div#content { + background-color: rgb(255, 255, 255); /* ”wŒiF */ + border-radius: 0px 0px 2px 2px; /* Šp‚ÌŠÛ‚Ý */ +} +/* footer ƒ^ƒOÝ’è */ +footer { + color: rgb(201, 229, 255); /* •¶ŽšF */ +} +/* header ƒ^ƒO, id="content" ƒ^ƒO, footer ƒ^ƒOÝ’è */ +header, div#content, footer { + margin: 0 auto; /* —̈æŠÔ‚̃Xƒy[ƒX• */ + padding: 8px; /* —̈æ“à‚̃Xƒy[ƒX• */ +} + +/* class="toggle_icon" Ý’è (img ƒ^ƒO“à) */ +img.toggle_icon { + padding-right: 0.5em; /* ‰E‘¤ƒXƒy[ƒX• (0,5•¶Žš) */ +} + +/* class="hide_onload" Ý’è */ +.hide_onload { + display: none; /* —v‘f‚Ì•\ަ–³‚µ*/ +} + +/* class="toggle" Ý’è (ƒŠƒ“ƒN•¶ŽšÝ’è) */ +.toggle a { + text-decoration: none; /* ‰ºü–³‚µ */ +} +.toggle a:link { + color:rgb(0, 90, 175); /* ƒŠƒ“ƒN•¶ŽšF */ +} +.toggle a:visited { + color:rgb(0, 90, 175); /* Šù‚ÉŒ©‚½ƒŠƒ“ƒN•¶ŽšF */ +} +.toggle a:active { + color:rgb(0, 90, 175); /* ƒNƒŠƒbƒNŽž‚ÌƒŠƒ“ƒN•¶ŽšF */ +} +.toggle a:hover { + color:rgb(0, 90, 175); /* ƒJ[ƒ\ƒ‹‚ªã‚É‚ ‚鎞‚ÌƒŠƒ“ƒN•¶ŽšF */ +} + +/* class="active" Ý’è (id="Summary" “àƒŠƒ“ƒN•¶ŽšÝ’è) */ +#summary .active a:link { + color: rgb(0, 90, 175); /* ƒŠƒ“ƒN•¶ŽšF */ +} + +/* class="grayout" Ý’è (id="Summary" “àƒŠƒ“ƒN•¶ŽšÝ’è) */ +#summary .grayout a:link { + color: rgb(169, 169, 169); /* ƒŠƒ“ƒN•¶ŽšF */ +} +#summary .grayout a:visited { + color: rgb(169, 169, 169); /* Šù‚ÉŒ©‚½ƒŠƒ“ƒN•¶ŽšF */ +} +#summary .grayout a:active { + color: rgb(169, 169, 169); /* ƒNƒŠƒbƒNŽž‚ÌƒŠƒ“ƒN•¶ŽšF */ +} +#summary .grayout a:hover { + color: rgb(169, 169, 169); /* ƒJ[ƒ\ƒ‹‚ªã‚É‚ ‚鎞‚ÌƒŠƒ“ƒN•¶ŽšF */ +} + + +/* class="rbox" Ý’è */ +.rbox { + border-collapse: collapse; /* •\‚Ì—×Ú‚·‚é˜gü‚Ìd‚È‚è */ + border-color: rgb(214, 214, 214); /* •\˜gü‚ÌF */ + border-style: solid; /* •\˜gü‚̃Xƒ^ƒCƒ‹ */ + border-width: 1px; /* •\˜gü‚Ì• */ + margin-bottom: 0.5em; /* •\‚̉º‘¤ƒXƒy[ƒX• */ + margin-top: 0.5em; /* •\‚Ì㑤ƒXƒy[ƒX• */ +} + +/* class="rbox" th ƒ^ƒOÝ’è */ +.rbox th { + background-color: rgb(238, 238, 238); /* •\˜g“à‚Ì”wŒiF */ + border-color: rgb(214, 214, 214); /* •\˜gü‚ÌF */ + border-style: solid; /* •\˜gü‚̃Xƒ^ƒCƒ‹ */ + border-width: 1px; /* •\˜gü‚Ì• */ + padding: 0px 5px; /* —̈æ“àƒXƒy[ƒX• */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + vertical-align: middle; /* c•ûŒü‚Ì•\ަˆÊ’u */ + white-space: pre-wrap; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="vertical_item" Ý’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th.vertical_item { + font-weight: bold; /* •¶Žš‘¾‚³ */ +} +/* class="horizontal_item" Ý’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th.horizontal_item { + font-weight: bold; /* •¶Žš‘¾‚³ */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="banner" Ý’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th.banner { + background-color: rgb(110, 110, 110); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(238, 238, 238); /* •¶ŽšF */ + font-weight: bold; /* •¶Žš‘¾‚³ */ + padding: 0px 15px; /* —̈æ“àƒXƒy[ƒX• */ + text-align: left; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ +} +/* id="return_top" ƒ^ƒOÝ’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th div#return_top { + text-align: right; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + margin-top: -18px; /* •\‚ÌãƒXƒy[ƒX• (body ‚©‚çŒp³‚µ‚Ä‚¢‚é1s•ªã‚É) */ +} +/* ƒŠƒ“ƒN•¶ŽšFÝ’è (class="rbox" th ƒ^ƒO“à) */ +.rbox th a:link { + color:rgb(214, 249, 159); /* ƒŠƒ“ƒN•¶ŽšF */ +} +.rbox th a:visited { + color:rgb(214, 249, 159); /* Šù‚ÉŒ©‚½ƒŠƒ“ƒN•¶ŽšF */ +} +.rbox th a:active { + color:rgb(214, 249, 159); /* ƒNƒŠƒbƒNŽž‚ÌƒŠƒ“ƒN•¶ŽšF */ +} +.rbox th a:hover { + color:rgb(214, 249, 159); /* ƒJ[ƒ\ƒ‹‚ªã‚É‚ ‚鎞‚ÌƒŠƒ“ƒN•¶ŽšF */ +} + +/* class="rbox" td ƒ^ƒOÝ’è */ +.rbox td { + border-color: rgb(214, 214, 214); /* •\˜gü‚ÌF */ + border-style: dotted; /* •\˜gü‚̃Xƒ^ƒCƒ‹ */ + border-width: 1px; /* •\˜gü‚Ì• */ + text-align: left; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + vertical-align: middle; /* c•ûŒü‚Ì•\ަˆÊ’u */ + table-layout: fixed; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è */ + width: 500px; /* ƒZƒ‹• */ +} +/* class="input" td ƒ^ƒOÝ’è (class="rbox" td ƒ^ƒO“à)(Input table) */ +.rbox td.input { + width: auto; /* ƒZƒ‹• (Input table ‚ÍŽ©“®’²®) */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ + white-space: pre-wrap; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="result_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.result_equal { + width: 70px; /* Result ƒZƒ‹‚̕ݒè (ŒÅ’è) */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ +} +/* class="result_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.result_diff { + width: 70px; /* Result ƒZƒ‹‚̕ݒè (ŒÅ’è) */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(160, 0, 0); /* •¶ŽšF */ + font-weight: bold; /* •¶Žš‘¾‚³ */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ +} +/* class="info_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_equal { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="info_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_diff { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(160, 0, 0); /* •¶ŽšF */ + font-weight: bold; /* •¶Žš‘¾‚³ */ + padding: 2px 6px; /* —̈æ“àƒXƒy[ƒX• */ + word-break: break-all; /* •¶ŽšÜ‚è•Ô‚µÝ’è */ +} +/* class="info_sicon_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_sicon_equal { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ +} +/* class="info_sicon_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_sicon_diff { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ +} +/* class="info_ticon_equal" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_ticon_equal { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ +} +/* class="info_ticon_diff" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.info_ticon_diff { + table-layout: inherit; /* ƒZƒ‹‚Ì•‹ÏˆêÝ’è (.rbox td ‚©‚çŒp³) */ + text-align: center; /* …•½•ûŒü‚Ì•\ަˆÊ’u */ + background-color: rgb(255, 224, 224); /* •\˜g“à‚Ì”wŒiF */ + color: rgb(160, 0, 0); /* •¶ŽšF */ +} + +/* class="rtl" Ý’è (class="rbox" td ƒ^ƒO“à) */ +.rbox td.rtl { + direction: rtl; /* •¶Žš•\‹L•ûŒü (right to left) */ + text-align: right; /* ‰EŠñ‚¹ */ +} + +/* class="sicon_ratio" Ý’è (img ƒ^ƒO“à) */ +.rbox td img.sicon_ratio { + width: 30%; /* image ƒTƒCƒY‚ð % ‚ÅŽw’è */ + height: auto; /* ŒÅ’è */ +} +/* class="ticon_ratio" Ý’è (img ƒ^ƒO“à) */ +.rbox td img.ticon_ratio { + width: 30%; /* image ƒTƒCƒY‚ð % ‚ÅŽw’è */ + height: auto; /* ŒÅ’è */ +} + +/* window •‚ª 1165px ˆÈ‰º‚ÌꇂÌÝ’è */ +@media all and (max-width:1165px) { + header, div#content, footer { + width: 852px; /* • */ + } +} +/* window •‚ª 1166px ˆÈã(1566px –¢–ž)‚ÌꇂÌÝ’è */ +@media all and (min-width:1166px) { + header, div#content, footer { + width: 1120px; /* • */ + } +} +/* window •‚ª 1566px ˆÈã‚ÌꇂÌÝ’è */ +@media all and (min-width:1566px) { + header, div#content, footer { + width: 1520px; /* • */ + } +} diff --git a/windows/gui/orbis-pub-cmd.exe b/windows/gui/orbis-pub-cmd.exe new file mode 100644 index 0000000..53b06d2 Binary files /dev/null and b/windows/gui/orbis-pub-cmd.exe differ diff --git a/windows/gui/orbis-pub-prx.dll b/windows/gui/orbis-pub-prx.dll new file mode 100644 index 0000000..1009fc1 Binary files /dev/null and b/windows/gui/orbis-pub-prx.dll differ diff --git a/windows/gui/package-lock.json b/windows/gui/package-lock.json new file mode 100644 index 0000000..067ade6 --- /dev/null +++ b/windows/gui/package-lock.json @@ -0,0 +1,4895 @@ +{ + "name": "fpkgi-node-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fpkgi-node-server", + "version": "1.0.0", + "devDependencies": { + "electron": "^37.2.0", + "electron-builder": "^26.0.12" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/fuses/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/fuses/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", + "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "got": "^11.7.0", + "graceful-fs": "^4.2.11", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^11.2.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^7.5.6", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/rebuild/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/windows-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", + "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", + "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.3", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.8.1", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.3", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.8.1", + "electron-builder-squirrel-windows": "26.8.1" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", + "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.12", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", + "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", + "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "37.10.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-37.10.3.tgz", + "integrity": "sha512-3IjCGSjQmH50IbW2PFveaTzK+KwcFX9PEhE7KXb9v5IT8cLAiryAN7qezm/XzODhDRlLu0xKG1j8xWBtZ/bx/g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz", + "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.8.1", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz", + "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", + "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", + "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/windows/gui/package.json b/windows/gui/package.json new file mode 100644 index 0000000..f1dfbd5 --- /dev/null +++ b/windows/gui/package.json @@ -0,0 +1,60 @@ +{ + "name": "fpkgi-node-server", + "version": "1.0.0", + "author": "CyberMask367", + "description": "FPKGi JSON generator desktop app", + "main": "electron/main.js", + "scripts": { + "start": "electron .", + "start:node-menu": "node node-menu.js", + "start:node-menu-linux": "node node-menu-linux.js", + "start:cli": "electron cli/main.js", + "build": "electron-builder --win nsis", + "build:cli": "electron-builder --config electron-builder.cli.json --win nsis" + }, + "build": { + "appId": "com.codex.fpkgi.node.server", + "productName": "FPKGi Node Server", + "directories": { + "buildResources": "electron/assets", + "output": "dist" + }, + "files": [ + "electron/**/*", + "app/**/*", + "package.json" + ], + "extraResources": [ + { + "from": "electron/assets/icon.ico", + "to": "icon.ico" + }, + { + "from": "orbis-pub-cmd.exe", + "to": "bin/orbis-pub-cmd.exe" + }, + { + "from": "orbis-pub-prx.dll", + "to": "bin/orbis-pub-prx.dll" + }, + { + "from": "ext", + "to": "bin/ext" + } + ], + "win": { + "icon": "electron/assets/icon.ico", + "target": [ + "nsis" + ] + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true + } + }, + "devDependencies": { + "electron": "^37.2.0", + "electron-builder": "^26.0.12" + } +}