From de537bf246f38af0491630cbbef7cc143a8c01ed Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:36:42 +0700 Subject: [PATCH 01/12] fix(windows): use platform-aware ping and shutdown commands The shutdown command used Linux-only flags (-h now) which silently did nothing on Windows. Replaced with the Windows equivalent (/s /t 0). The ping command also used Linux-only flags (-c/-W) and passed the IP directly into a shell string, opening a command injection vector. Fixed by using platform-specific flags and validating the IP against a strict IPv4 regex before building the command. Co-Authored-By: Claude Sonnet 4.6 --- src/api/android/adb/DeviceFinder.ts | 7 ++++++- src/api/core/Controller.ts | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/api/android/adb/DeviceFinder.ts b/src/api/android/adb/DeviceFinder.ts index ad174f7..9f6fbb6 100644 --- a/src/api/android/adb/DeviceFinder.ts +++ b/src/api/android/adb/DeviceFinder.ts @@ -91,7 +91,12 @@ class DeviceFinder { private isDeviceReachable(ipAddress: string): Promise { return new Promise((resolve) => { - exec(`ping -c 1 -W 1 ${ipAddress}`, (error) => resolve(!error)); + // Sanitize: only allow valid IPv4 to prevent command injection + if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(ipAddress)) { resolve(false); return; } + const cmd = process.platform === 'win32' + ? `ping -n 1 -w 1000 ${ipAddress}` + : `ping -c 1 -W 1 ${ipAddress}`; + exec(cmd, (error) => resolve(!error)); }); } diff --git a/src/api/core/Controller.ts b/src/api/core/Controller.ts index e574fb6..135c54d 100644 --- a/src/api/core/Controller.ts +++ b/src/api/core/Controller.ts @@ -83,7 +83,11 @@ export class Controller { // (3) Shutdown host computer after 30s to allow headsets and UPS time to process setTimeout(() => { logger.warn('Shutting down host computer now'); - spawnSync('shutdown', ['-h', 'now']); + if (process.platform === 'win32') { + spawnSync('shutdown', ['/s', '/t', '0']); + } else { + spawnSync('shutdown', ['-h', 'now']); + } }, 30_000); } From 338b240c286ed5ae5681c3a9604ae64e32044c40 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:36:52 +0700 Subject: [PATCH 02/12] feat(sea): replace uWS companion plugin with SEA-aware loader The previous uwsCompanionPlugin copied the uWebSockets.js .node binary alongside the pkg output. This does not work with SEA (no node_modules). The new uwsSeaPlugin does a string replacement at bundle time on the compiled CJS output, replacing require("uWebSockets.js") with an IIFE that in SEA mode extracts the embedded .node asset from the SEA blob to a temp directory and loads it via process.dlopen. In dev/pkg mode it falls through to the regular require() call unchanged. process.dlopen is used instead of require() for the extracted path because embedderRequire (the SEA require) cannot load .node files from arbitrary absolute paths. Co-Authored-By: Claude Sonnet 4.6 --- vite.backend.config.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/vite.backend.config.ts b/vite.backend.config.ts index a66881d..cac56f1 100644 --- a/vite.backend.config.ts +++ b/vite.backend.config.ts @@ -1,8 +1,34 @@ -import { defineConfig } from 'vite'; +import { defineConfig, Plugin } from 'vite'; import { builtinModules } from 'module'; import path from 'path'; +// In SEA mode, uWebSockets.js cannot be loaded from node_modules (there are none). +// This plugin replaces require("uWebSockets.js") with an IIFE that: +// - In SEA mode: extracts the .node binary from the SEA asset store to a temp +// directory and loads it directly via require(absolutePath). +// - Otherwise: falls through to require('uWebSockets.js') for dev/pkg use. +// process.dlopen is NOT patched in SEA (unlike pkg), so loading from disk works. +function uwsSeaPlugin(): Plugin { + return { + name: 'uws-sea-loader', + generateBundle(_, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type === 'chunk' && /require\(["']uWebSockets\.js["']\)/.test(chunk.code)) { + chunk.code = chunk.code.replace( + /\brequire\(["']uWebSockets\.js["']\)/g, + // Use process.dlopen (not require) to load the extracted .node file. + // In SEA, process.dlopen is unpatched; require() for arbitrary .node + // paths does not work through embedderRequire. + `(()=>{const _os=require('os'),_path=require('path'),_fs=require('fs');const _nn='uws_'+process.platform+'_'+process.arch+'_'+process.versions.modules+'.node';try{const _sea=require('node:sea');if(_sea.isSea()){const _td=_path.join(_os.tmpdir(),'swp-uws-'+process.versions.modules);_fs.mkdirSync(_td,{recursive:true});const _np=_path.join(_td,_nn);_fs.writeFileSync(_np,Buffer.from(_sea.getAsset(_nn)));const _m={exports:{}};process.dlopen(_m,_np);return _m.exports;}}catch(_e){}return require('uWebSockets.js');})()`, + ); + } + } + }, + }; +} + export default defineConfig({ + plugins: [uwsSeaPlugin()], build: { target: 'node24', lib: { @@ -17,7 +43,6 @@ export default defineConfig({ ...builtinModules.map((m) => `node:${m}`), 'uWebSockets.js', 'fsevents', - // Add other native modules if any ], }, ssr: true, From d64161b4cb9e266f4f8f1efb663c37f1adb6c0d1 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:37:01 +0700 Subject: [PATCH 03/12] feat(sea): detect SEA mode in IS_PLATFORM_PACKAGED Added _isSea() helper that checks node:sea.isSea() to determine whether the process is running inside a Node.js SEA binary. This is included in the IS_PLATFORM_PACKAGED condition so that the static server and other packaged-mode behaviour activate correctly when running as a SEA binary. createRequire(process.execPath) is used instead of import.meta.url because import.meta.url is undefined in Vite's CJS bundle output. process.execPath is always a valid absolute path on all platforms. Co-Authored-By: Claude Sonnet 4.6 --- src/api/index.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/api/index.ts b/src/api/index.ts index b71e78c..13add38 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -14,6 +14,7 @@ import { getPrettyFormatter } from "@logtape/pretty"; import Controller from './core/Controller.ts'; import { StaticServer } from './infra/StaticServer.ts'; import path from 'path'; +import { createRequire } from 'module'; /* TOOLBOX ================================ @@ -42,10 +43,21 @@ function isCommandAvailable(commandName: string): boolean { PROCESS .env FILE ================================ */ +function _isSea(): boolean { + try { + // process.execPath is always an absolute path and works as a createRequire + // base on any platform. We avoid import.meta.url because it is not a valid + // absolute path in Vite's CJS bundle output. + const _req = createRequire(process.execPath); + return (_req('node:sea') as any).isSea(); + } catch (_) { return false; } +} + // Load options export const IS_PLATFORM_PACKAGED = (process as any).pkg || process.env.PKG_EXECPATH + || _isSea() // The runner isn't called `node`, and not starting file from root `/snapshot` || (!path.basename(process.argv[0]).includes('node') && !process.argv[1].startsWith("/snapshot")) ; From 53ec966a010e80d34488c3521085026522855122 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:37:10 +0700 Subject: [PATCH 04/12] feat(sea): serve frontend from embedded SEA assets in StaticServer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In SEA mode there is no dist/ directory on disk — frontend files are embedded as assets in the SEA blob. StaticServer now detects SEA mode via getSea() (using createRequire(process.execPath) for the same reason as index.ts) and serves all responses directly from sea.getAsset(). Asset keys follow the convention "dist/" matching the keys written into sea-config.json by the build script. A SPA catch-all fallback serves dist/index.html for unknown routes. The original filesystem-based serving path is kept as a fallback for dev mode and pkg (Linux/macOS) builds. Co-Authored-By: Claude Sonnet 4.6 --- src/api/infra/StaticServer.ts | 125 +++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 40 deletions(-) diff --git a/src/api/infra/StaticServer.ts b/src/api/infra/StaticServer.ts index dc6ab18..7b7e26c 100644 --- a/src/api/infra/StaticServer.ts +++ b/src/api/infra/StaticServer.ts @@ -3,51 +3,96 @@ import path from 'path'; import fs from 'fs'; import { getLogger } from "@logtape/logtape"; import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; const logger = getLogger(["infra", "StaticServer"]); -export class StaticServer { - constructor() { - logger.debug(`Starting express server for static files...`); - const app = express(); - const port = process.env.WEB_APPLICATION_PORT || '5173'; - - // In bundled environment, dist might be relative to the executable or the script - // When using pkg, assets are often at /snapshot/project/dist - // We will try to find the dist folder - - const __dirname = path.dirname(fileURLToPath(import.meta.url)); - - // Try multiple possible paths for 'dist' - const possiblePaths = [ - path.resolve(__dirname, 'dist'), // If index.cjs is next to dist - path.resolve(__dirname, '../dist'), // If index.cjs is in a subfolder (like dist-api) - path.resolve(__dirname, '../../../dist'), // If running from src/api/infra - ]; - - let distPath = ''; - for (const p of possiblePaths) { - if (fs.existsSync(p)) { - distPath = p; - break; - } - } +const MIME: Record = { + html: 'text/html; charset=utf-8', + js: 'application/javascript', + mjs: 'application/javascript', + css: 'text/css', + png: 'image/png', + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + gif: 'image/gif', + svg: 'image/svg+xml', + ico: 'image/x-icon', + json: 'application/json', + woff: 'font/woff', + woff2: 'font/woff2', + ttf: 'font/ttf', + otf: 'font/otf', + webp: 'image/webp', +}; - if (!distPath) { - logger.error("Could not find 'dist' directory for static files"); - return; - } +function getSea(): any { + try { + // process.execPath is always an absolute path and works as a createRequire + // base on any platform; import.meta.url is not valid in Vite's CJS bundle. + const req = createRequire(process.execPath); + const sea = req('node:sea'); + return sea.isSea() ? sea : null; + } catch (_) { + return null; + } +} - logger.debug(`Serving static files from: ${distPath}`); - app.use(express.static(distPath)); - - // Handle SPA routing - app.get('*', (_req: any, res: any) => { - res.sendFile(path.join(distPath, 'index.html')); - }); +export class StaticServer { + constructor() { + logger.debug(`Starting express server for static files...`); + const app = express(); + const port = process.env.WEB_APPLICATION_PORT || '5173'; - app.listen(port, () => { - logger.info(`=========================================\n\n\tWebplatform started and is accessible\n\t\t http://localhost:${port}\n\n=========================================`); - }); + const sea = getSea(); + + if (sea) { + // SEA mode: serve frontend files from embedded SEA assets. + // Assets are keyed as "dist/" (e.g. "dist/index.html"). + app.use((req: any, res: any, next: any) => { + const urlPath = req.path === '/' ? '/index.html' : req.path; + const assetKey = 'dist' + urlPath; + try { + const buf = sea.getAsset(assetKey); + const ext = path.extname(urlPath).slice(1).toLowerCase(); + res.setHeader('Content-Type', MIME[ext] || 'application/octet-stream'); + res.send(Buffer.from(buf)); + } catch (_) { + next(); + } + }); + // SPA fallback + app.get('*', (_req: any, res: any) => { + try { + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(Buffer.from(sea.getAsset('dist/index.html'))); + } catch (_) { + res.status(404).send('Not found'); + } + }); + } else { + // Dev / pkg mode: serve from the filesystem. + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + const candidates = [ + path.resolve(__dirname, 'dist'), + path.resolve(__dirname, '../dist'), + path.resolve(__dirname, '../../../dist'), + ]; + let distPath = ''; + for (const p of candidates) { + if (fs.existsSync(p)) { distPath = p; break; } + } + if (!distPath) { + logger.error("Could not find 'dist' directory for static files"); + return; + } + logger.debug(`Serving static files from: ${distPath}`); + app.use(express.static(distPath)); + app.get('*', (_req: any, res: any) => res.sendFile(path.join(distPath, 'index.html'))); } + + app.listen(port, () => { + logger.info(`=========================================\n\n\tWebplatform started and is accessible\n\t\t http://localhost:${port}\n\n=========================================`); + }); + } } From a5f1ee995ee6a7e042a83d04cf74d0b41f18c36f Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:37:24 +0700 Subject: [PATCH 05/12] feat(sea): add Windows SEA build pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/build-sea-win.mjs which produces a single Windows binary using Node.js SEA (Single Executable Application) instead of pkg. The pipeline: 1. Builds frontend (Vite) and backend (Vite CJS) 2. Embeds all dist/ files and uws_win32_x64_.node as SEA assets 3. Generates the SEA blob via node --experimental-sea-config 4. Copies the node binary from pkg cache or process.execPath 5. Strips the Authenticode signature and clears CFG via patch-pe-no-cfg.mjs so postject does not leave the binary in a corrupted-signature state 6. Injects the blob with postject 7. Compiles and bundles the Go launcher (see next commit) The output binary MUST be named node.exe — Windows applies an AppCompat shim exclusively to executables with that name, which is required for process.dlopen to load the uWebSockets.js NAPI module without crashing with 0xC0000005. The launcher (simple-win.exe) provides the user-facing entry point. Removes node24-windows-x64 from pkg targets; Windows now uses SEA. Adds postject devDependency and build:sea:win npm script. Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 251 ++++++++++++++++++++---------------- package.json | 7 +- scripts/build-sea-win.mjs | 183 ++++++++++++++++++++++++++ scripts/patch-pe-no-cfg.mjs | 74 +++++++++++ 4 files changed, 402 insertions(+), 113 deletions(-) create mode 100644 scripts/build-sea-win.mjs create mode 100644 scripts/patch-pe-no-cfg.mjs diff --git a/package-lock.json b/package-lock.json index ca7d6cc..c7a41f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "globals": "^17.3.0", "jest": "^29.7.0", "jiti": "^2.6.1", + "postject": "^1.0.0-alpha.6", "typescript-eslint": "^8.56.0" } }, @@ -2828,6 +2829,49 @@ "pkg-fetch": "lib-es5/bin.js" } }, + "node_modules/@yao-pkg/pkg-fetch/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/@yao-pkg/pkg-fetch/node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@yao-pkg/pkg-fetch/node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/@yume-chan/adb": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@yume-chan/adb/-/adb-2.5.1.tgz", @@ -3514,9 +3558,9 @@ "license": "MIT" }, "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -3529,9 +3573,9 @@ } }, "node_modules/bare-fs": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", - "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3554,9 +3598,9 @@ } }, "node_modules/bare-os": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.7.1.tgz", - "integrity": "sha512-ebvMaS5BgZKmJlvuWh14dg9rbUI84QeV3WlWn6Ph6lFI8jJoh7ADtVTyD2c93euwbe+zgi0DVrl4YmqXeM9aIA==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3574,20 +3618,24 @@ } }, "node_modules/bare-stream": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", "dev": true, "license": "Apache-2.0", "dependencies": { - "streamx": "^2.21.0", + "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { + "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, "bare-buffer": { "optional": true }, @@ -3597,9 +3645,9 @@ } }, "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -10243,6 +10291,32 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "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", + "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", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -10271,68 +10345,6 @@ "node": ">=10" } }, - "node_modules/prebuild-install/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC" - }, - "node_modules/prebuild-install/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/prebuild-install/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/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11708,9 +11720,9 @@ } }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", + "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", "dev": true, "license": "MIT", "dependencies": { @@ -12139,46 +12151,65 @@ } }, "node_modules/tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, "license": "MIT", "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" + "tar-stream": "^2.1.4" } }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/tar-stream/node_modules/b4a": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "node_modules/tar-stream/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": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-stream/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/tar/node_modules/yallist": { @@ -12251,9 +12282,9 @@ } }, "node_modules/text-decoder/node_modules/b4a": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "dev": true, "license": "Apache-2.0", "peerDependencies": { diff --git a/package.json b/package.json index 303cd4b..0fd0da1 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,17 @@ "build:frontend": "vite build", "build:backend": "vite build -c vite.backend.config.ts", "build:executable": "npm run build:frontend && npm run build:backend && pkg .", - "build:executable-compressed": "npm run build:frontend && npm run build:backend && pkg . --compress Brotli" + "build:executable-compressed": "npm run build:frontend && npm run build:backend && pkg . --compress Brotli", + "build:sea:win": "node scripts/build-sea-win.mjs" }, "bin": "dist-api/index.cjs", "pkg": { "assets": [ "dist/**/*", - "node_modules/uWebSockets.js/**.node" + "node_modules/uWebSockets.js/*.node" ], "targets": [ "node24-linux-x64", - "node24-windows-x64", "node24-macos-x64" ], "outputPath": "bin" @@ -83,6 +83,7 @@ "globals": "^17.3.0", "jest": "^29.7.0", "jiti": "^2.6.1", + "postject": "^1.0.0-alpha.6", "typescript-eslint": "^8.56.0" } } diff --git a/scripts/build-sea-win.mjs b/scripts/build-sea-win.mjs new file mode 100644 index 0000000..f752a54 --- /dev/null +++ b/scripts/build-sea-win.mjs @@ -0,0 +1,183 @@ +/** + * Build a Node.js SEA (Single Executable Application) binary for Windows x64. + * + * Produces: bin/simple-win-sea.exe + * + * This replaces @yao-pkg/pkg for the Windows target because pkg's patched + * process.dlopen crashes on Windows when loading uWebSockets.js native modules. + * Node.js SEA does not patch process.dlopen, so the crash is avoided. + * + * The SEA binary embeds: + * - dist/** (compiled frontend, served from memory via express in SEA mode) + * - uws_win32_x64_.node (uWebSockets.js native module for Windows x64) + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { patchPe } from './patch-pe-no-cfg.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); + +// ── 1. Build frontend and backend ──────────────────────────────────────────── + +console.log('\n[SEA] Building frontend...'); +execSync('npm run build:frontend', { cwd: root, stdio: 'inherit' }); + +console.log('\n[SEA] Building backend...'); +execSync('npm run build:backend', { cwd: root, stdio: 'inherit' }); + +// ── 2. Collect all dist/ files as SEA assets ───────────────────────────────── + +const distDir = path.join(root, 'dist'); + +function collectAssets(dir, base = distDir) { + const out = {}; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + Object.assign(out, collectAssets(full, base)); + } else { + const key = 'dist/' + path.relative(base, full).replace(/\\/g, '/'); + const value = path.relative(root, full).replace(/\\/g, '/'); + out[key] = value; + } + } + return out; +} + +const distAssets = collectAssets(distDir); +console.log(`\n[SEA] ${Object.keys(distAssets).length} frontend files collected.`); + +// ── 3. Add uWebSockets.js native module for Windows x64 ────────────────────── + +const modulesVer = process.versions.modules; // e.g. '137' +const uwsFile = `uws_win32_x64_${modulesVer}.node`; +const uwsPath = path.join(root, 'node_modules', 'uWebSockets.js', uwsFile); + +if (!fs.existsSync(uwsPath)) { + console.error(`[SEA] ERROR: ${uwsFile} not found. Is uWebSockets.js installed?`); + process.exit(1); +} + +const assets = { + ...distAssets, + [uwsFile]: path.relative(root, uwsPath).replace(/\\/g, '/'), +}; + +console.log(`[SEA] Including native module: ${uwsFile}`); + +// ── 4. Write sea-config.json ────────────────────────────────────────────────── + +const seaConfig = { + main: 'dist-api/index.cjs', + output: 'sea-prep.blob', + disableExperimentalSEAWarning: true, + assets, +}; + +fs.writeFileSync(path.join(root, 'sea-config.json'), JSON.stringify(seaConfig, null, 2)); +console.log(`[SEA] sea-config.json written (${Object.keys(assets).length} assets).`); + +// ── 5. Generate SEA blob ────────────────────────────────────────────────────── + +console.log('\n[SEA] Generating blob...'); +execSync('node --experimental-sea-config sea-config.json', { cwd: root, stdio: 'inherit' }); +console.log('[SEA] Blob generated: sea-prep.blob'); + +// ── 6. Locate the Windows node.exe to use as the base binary ───────────────── + +const nodeVersion = process.version; // e.g. 'v24.16.0' +const pkgCachedExe = path.join( + process.env.USERPROFILE || process.env.HOME || '', + '.pkg-cache', 'sea', `node-${nodeVersion}-win-x64.exe`, +); + +let sourceExe; +if (fs.existsSync(pkgCachedExe)) { + sourceExe = pkgCachedExe; + console.log(`\n[SEA] Using pkg-cached node binary: ${sourceExe}`); +} else { + sourceExe = process.execPath; + console.log(`\n[SEA] Using current node.exe: ${sourceExe}`); +} + +// ── 7. Copy node.exe and inject SEA blob ───────────────────────────────────── + +// The output binary MUST be named "node.exe". +// Windows applies an AppCompat shim to any executable named "node.exe" that +// is required for process.dlopen to load the uWebSockets.js native module. +// Without this shim the DLL initialization crashes with 0xC0000005 regardless +// of path, signature, or CFG status. We place it in its own subdirectory so +// it does not conflict with a system node installation. +const outDir = path.join(root, 'bin', 'win'); +fs.mkdirSync(outDir, { recursive: true }); +const outExe = path.join(outDir, 'node.exe'); + +fs.copyFileSync(sourceExe, outExe); +console.log(`[SEA] Copied to: ${outExe}`); + +// Strip the Authenticode signature and clear the CFG flag BEFORE postject +// injects the SEA blob. postject adds a raw PE section which invalidates the +// Authenticode signature, leaving it "corrupted". A corrupted-signature binary +// behaves differently from an unsigned one under Windows loader security policies, +// causing 0xC0000005 when process.dlopen tries to load the uWebSockets.js NAPI +// module. Starting from a clean unsigned binary avoids that regression. +patchPe(outExe); + +console.log('[SEA] PE patched (signature stripped, CFG cleared).'); + +console.log('[SEA] Injecting blob with postject...'); +execSync( + `npx postject "${outExe}" NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite`, + { cwd: root, stdio: 'inherit' }, +); + +const sizeMB = (fs.statSync(outExe).size / 1024 / 1024).toFixed(1); +console.log(`\n[SEA] Done!`); +console.log(` Output: ${outExe}`); +console.log(` Size: ${sizeMB} MB`); + +// ── 10. Build the self-extracting launcher and bundle node.exe into it ─────── +// +// The SEA binary must be named "node.exe" (Windows AppCompat shim requirement). +// Rather than shipping two files, we embed node.exe inside a thin .NET launcher +// named whatever the user wants (default: simple-win.exe). +// +// Bundle layout: +// [launcher exe bytes] [node.exe bytes] [8-byte size LE] [4-byte magic "SWPN"] +// +// On first launch the embedded node.exe is extracted to a per-size temp folder +// and reused on subsequent launches. + +const launcherName = process.env.LAUNCHER_NAME || 'simple-win.exe'; +const launcherExe = path.join(outDir, launcherName); +const goExe = 'C:\\Program Files\\Go\\bin\\go.exe'; +const launcherSrc = path.join(__dirname, 'launcher-win.go'); + +console.log(`\n[Launcher] Compiling ${launcherName} (Go)...`); +execSync( + `"${goExe}" build -ldflags="-s -w" -trimpath -o "${launcherExe}" "${launcherSrc}"`, + { cwd: root, stdio: 'inherit' }, +); + +console.log('[Launcher] Bundling node.exe into launcher...'); +const launcherBytes = fs.readFileSync(launcherExe); +const nodeBytes = fs.readFileSync(outExe); + +// 12-byte footer: [8 bytes: embedded size as int64 LE] [4 bytes: magic "SWPN"] +const footer = Buffer.alloc(12); +footer.writeBigInt64LE(BigInt(nodeBytes.length), 0); +footer.write('SWPN', 8, 'ascii'); + +fs.writeFileSync(launcherExe, Buffer.concat([launcherBytes, nodeBytes, footer])); + +// The separate node.exe is no longer needed — everything is inside the launcher. +fs.unlinkSync(outExe); + +const bundleMB = (fs.statSync(launcherExe).size / 1024 / 1024).toFixed(1); +console.log(`\n[Build] Done!`); +console.log(` Output: ${launcherExe}`); +console.log(` Size: ${bundleMB} MB (launcher + embedded node.exe)`); diff --git a/scripts/patch-pe-no-cfg.mjs b/scripts/patch-pe-no-cfg.mjs new file mode 100644 index 0000000..1662982 --- /dev/null +++ b/scripts/patch-pe-no-cfg.mjs @@ -0,0 +1,74 @@ +/** + * Strips the Authenticode digital signature from a Windows PE binary and + * clears the IMAGE_DLLCHARACTERISTICS_GUARD_CF (0x4000) CFG flag. + * + * Both steps are necessary before postject injects the SEA blob into a signed + * Windows node.exe binary: + * + * 1. Signature strip: postject adds a raw PE section which invalidates the + * Authenticode signature, leaving it "corrupted" (present but invalid). + * Windows may apply different security policies (process mitigation, + * loader restrictions) to a binary with a corrupted vs absent signature. + * Stripping it first produces a cleanly UNSIGNED binary — postject then + * keeps it unsigned rather than corrupting it. + * + * 2. CFG clear: postject adds a section without updating the CFG guard tables. + * Clearing the GUARD_CF DllCharacteristic opts the process out of CFG + * enforcement, which prevents 0xC0000005 crashes when NAPI functions make + * indirect calls back into node.exe. + */ + +import fs from 'fs'; + +const IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000; + +export function patchPe(filePath) { + const orig = fs.readFileSync(filePath); + const buf = Buffer.from(orig); // mutable copy + + // ── 0. Verify PE signature ────────────────────────────────────────────── + const e_lfanew = buf.readUInt32LE(0x3c); + if (buf.readUInt32LE(e_lfanew) !== 0x00004550) { + throw new Error(`${filePath}: not a PE file (no PE signature)`); + } + + const optStart = e_lfanew + 4 + 20; // after PE sig + COFF header + + // ── 1. Clear CFG flag ─────────────────────────────────────────────────── + const dllCharOffset = optStart + 70; + const dllCharBefore = buf.readUInt16LE(dllCharOffset); + const dllCharAfter = dllCharBefore & ~IMAGE_DLLCHARACTERISTICS_GUARD_CF; + if (dllCharBefore !== dllCharAfter) { + buf.writeUInt16LE(dllCharAfter, dllCharOffset); + console.log(`[patch-pe] CFG cleared: 0x${dllCharBefore.toString(16)} → 0x${dllCharAfter.toString(16)}`); + } else { + console.log(`[patch-pe] CFG already clear (DllCharacteristics=0x${dllCharBefore.toString(16)})`); + } + + // ── 2. Strip Authenticode signature ──────────────────────────────────── + // DataDirectory[4] = CertificateTable, at OptionalHeader+144. + // Each DataDirectory entry is 8 bytes: VirtualAddress (4) + Size (4). + const certDirOffset = optStart + 144; + const certVA = buf.readUInt32LE(certDirOffset); + const certSize = buf.readUInt32LE(certDirOffset + 4); + + if (certVA === 0 && certSize === 0) { + console.log('[patch-pe] No Authenticode signature found (already unsigned).'); + fs.writeFileSync(filePath, buf); + return; + } + + console.log(`[patch-pe] Stripping Authenticode signature at file offset 0x${certVA.toString(16)}, size ${certSize} bytes`); + + // Zero the Certificate Table directory entry + buf.writeUInt32LE(0, certDirOffset); + buf.writeUInt32LE(0, certDirOffset + 4); + + // Truncate the file at the start of the certificate data. + // Authenticode certificates are typically appended at the end of the file; + // certVA is a raw file offset (not RVA) per the PE spec for this directory. + const truncated = buf.subarray(0, certVA); + fs.writeFileSync(filePath, truncated); + + console.log(`[patch-pe] Done. File size: ${orig.length} → ${truncated.length} bytes`); +} From 3748d32e91150e0511a37c8c74881ad5a7ff47b9 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:37:36 +0700 Subject: [PATCH 06/12] feat(sea): add self-extracting Go launcher for Windows The SEA worker binary must be named node.exe, but users need a recognisable entry point. launcher-win.go is a small Go program that: - Reads the footer appended to itself at build time ([8-byte embedded size LE][4-byte magic "SWPN"]) to locate the embedded node.exe bytes - On first launch, extracts node.exe to a temp directory keyed by the embedded size (%TEMP%\swp-node-\node.exe) and reuses the cache on subsequent launches (validated by file size) - Spawns the extracted node.exe with inherited stdin/stdout/stderr, ignores SIGINT so node.exe handles its own graceful shutdown, and forwards the exit code The build script appends the SEA node.exe bytes and footer to the compiled launcher, producing a single distributable file (simple-win.exe) that contains everything. Go was chosen because it produces a statically linked native Windows PE with no runtime dependencies (~2 MB overhead). Co-Authored-By: Claude Sonnet 4.6 --- scripts/launcher-win.go | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 scripts/launcher-win.go diff --git a/scripts/launcher-win.go b/scripts/launcher-win.go new file mode 100644 index 0000000..7ef87c7 --- /dev/null +++ b/scripts/launcher-win.go @@ -0,0 +1,125 @@ +package main + +import ( + "encoding/binary" + "fmt" + "io" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strconv" +) + +// Self-extracting launcher for the Simple WebPlatform Windows binary. +// +// Bundle layout (written by build-sea-win.mjs): +// [this launcher exe] [node.exe bytes] [8-byte size LE] [4-byte magic "SWPN"] +// +// On first launch the embedded node.exe is extracted to a temp folder keyed by +// size and reused on subsequent launches. The worker MUST be named "node.exe" — +// Windows applies an AppCompat shim to that name required for uWebSockets.js. + +const magic = "SWPN" +const footerSize = 12 + +func main() { + nodePath, err := extractNode() + if err != nil { + fmt.Fprintf(os.Stderr, "[simple] Fatal: %v\n", err) + os.Exit(1) + } + + // Ignore Ctrl+C in the launcher — node.exe is in the same console group and + // will receive it directly, handling its own graceful shutdown. + signal.Ignore(os.Interrupt) + + cmd := exec.Command(nodePath, os.Args[1:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + os.Exit(exitErr.ExitCode()) + } + os.Exit(1) + } +} + +func extractNode() (string, error) { + selfPath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("cannot find own executable: %w", err) + } + + f, err := os.Open(selfPath) + if err != nil { + return "", fmt.Errorf("cannot open self: %w", err) + } + defer f.Close() + + stat, err := f.Stat() + if err != nil { + return "", err + } + totalLen := stat.Size() + if totalLen < int64(footerSize) { + return "", fmt.Errorf("no embedded payload") + } + + // Read footer without moving the read cursor (ReadAt is pread). + footer := make([]byte, footerSize) + if _, err := f.ReadAt(footer, totalLen-int64(footerSize)); err != nil { + return "", err + } + if string(footer[8:12]) != magic { + return "", fmt.Errorf("magic not found — binary may not be bundled") + } + + embeddedSize := int64(binary.LittleEndian.Uint64(footer[0:8])) + embeddedStart := totalLen - int64(footerSize) - embeddedSize + if embeddedStart < 0 || embeddedSize <= 0 { + return "", fmt.Errorf("invalid embedded size %d", embeddedSize) + } + + // Cache directory keyed by embedded size — a new build always has a different size. + tempDir := filepath.Join(os.TempDir(), "swp-node-"+strconv.FormatInt(embeddedSize, 16)) + nodePath := filepath.Join(tempDir, "node.exe") + + if info, err := os.Stat(nodePath); err == nil && info.Size() == embeddedSize { + return nodePath, nil // cache hit + } + + fmt.Fprintln(os.Stderr, "[simple] First launch: extracting runtime, please wait...") + if err := os.MkdirAll(tempDir, 0755); err != nil { + return "", fmt.Errorf("cannot create temp dir: %w", err) + } + + tmpOut := nodePath + ".tmp" + outF, err := os.Create(tmpOut) + if err != nil { + return "", fmt.Errorf("cannot create output file: %w", err) + } + + if _, err := f.Seek(embeddedStart, io.SeekStart); err != nil { + outF.Close() + os.Remove(tmpOut) + return "", err + } + if _, err := io.CopyN(outF, f, embeddedSize); err != nil { + outF.Close() + os.Remove(tmpOut) + return "", fmt.Errorf("extraction failed: %w", err) + } + outF.Close() + + // Atomic rename so a partial extraction is never used on the next launch. + os.Remove(nodePath) + if err := os.Rename(tmpOut, nodePath); err != nil { + os.Remove(tmpOut) + return "", fmt.Errorf("cannot finalize extraction: %w", err) + } + + return nodePath, nil +} From 8672938332d1f280bac3126eef8c6c697f1882ed Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:37:48 +0700 Subject: [PATCH 07/12] ci: add dedicated Windows build job using Go and Node.js SEA build-ci.yaml: - Renamed the ubuntu job to compilation-unix (builds Linux + macOS via pkg with cross-compilation tools: qemu, ldid) - Removed the dead Windows artifact upload (pkg no longer targets Windows) - Added a parallel compilation-windows job on windows-latest that sets up Node 24 and Go, runs build:sea:win, and uploads bin/win/simple-win.exe as the windows-compiled-archive artifact release.yaml: - Split the single job into three: build-unix (ubuntu): builds Linux + macOS, uploads each as a separate named artifact build-windows (windows-latest): builds the SEA binary, uploads bin/win/simple-win.exe release: depends on both build jobs, downloads all three artifacts into release-files/ and publishes them to GitHub in a single softprops/action-gh-release step Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build-ci.yaml | 35 ++++++++++---- .github/workflows/release.yaml | 83 ++++++++++++++++++++++++++++----- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-ci.yaml b/.github/workflows/build-ci.yaml index 1ae3362..cd5ec4b 100644 --- a/.github/workflows/build-ci.yaml +++ b/.github/workflows/build-ci.yaml @@ -4,7 +4,7 @@ on: push: jobs: - compilation: + compilation-unix: runs-on: ubuntu-latest steps: @@ -28,8 +28,6 @@ jobs: - name: Package application run: npm run build:executable - # Publish - - name: Export release for Linux uses: actions/upload-artifact@v4 with: @@ -40,22 +38,39 @@ jobs: path: | ${{ github.workspace }}/bin/simple-linux* - - name: Export release for Windows + - name: Export release for MacOS uses: actions/upload-artifact@v4 with: - name: windows-compiled-archive + name: macos-compiled-archive if-no-files-found: error compression-level: 0 overwrite: true path: | - ${{ github.workspace }}/bin/simple-win* + ${{ github.workspace }}/bin/simple-macos* - - name: Export release for MacOS + compilation-windows: + runs-on: windows-latest + steps: + + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install npm modules + run: npm i + + - name: Build Windows binary + run: npm run build:sea:win + + - name: Export release for Windows uses: actions/upload-artifact@v4 with: - name: macos-compiled-archive + name: windows-compiled-archive if-no-files-found: error compression-level: 0 overwrite: true - path: | - ${{ github.workspace }}/bin/simple-macos* + path: ${{ github.workspace }}/bin/win/simple-win.exe diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ca2056e..4a26482 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,7 +4,7 @@ on: workflow_call: jobs: - compilation: + build-unix: runs-on: ubuntu-latest steps: @@ -25,16 +25,77 @@ jobs: - name: Install npm modules run: npm i - - name: Get release metadata - id: setup_new_release_data - run: | - date=$(date +'%d/%m/%y %R') - echo "date=$date" >> "$GITHUB_OUTPUT" - - name: Package application run: npm run build:executable-compressed - # Publish + - name: Upload Linux artifact + uses: actions/upload-artifact@v4 + with: + name: linux-release-binary + if-no-files-found: error + compression-level: 0 + path: ${{ github.workspace }}/bin/simple-linux* + + - name: Upload macOS artifact + uses: actions/upload-artifact@v4 + with: + name: macos-release-binary + if-no-files-found: error + compression-level: 0 + path: ${{ github.workspace }}/bin/simple-macos* + + build-windows: + runs-on: windows-latest + steps: + + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install npm modules + run: npm i + + - name: Build Windows binary + run: npm run build:sea:win + + - name: Upload Windows artifact + uses: actions/upload-artifact@v4 + with: + name: windows-release-binary + if-no-files-found: error + compression-level: 0 + path: ${{ github.workspace }}/bin/win/simple-win.exe + + release: + needs: [build-unix, build-windows] + runs-on: ubuntu-latest + steps: + + - name: Download Linux binary + uses: actions/download-artifact@v4 + with: + name: linux-release-binary + path: release-files/ + + - name: Download macOS binary + uses: actions/download-artifact@v4 + with: + name: macos-release-binary + path: release-files/ + + - name: Download Windows binary + uses: actions/download-artifact@v4 + with: + name: windows-release-binary + path: release-files/ + + - name: Get release metadata + id: meta + run: echo "date=$(date +'%d/%m/%y %R')" >> "$GITHUB_OUTPUT" - name: Publish alpha to Github uses: softprops/action-gh-release@v2 @@ -45,7 +106,7 @@ jobs: name: Alpha Version body: | Alpha release of the SIMPLE webplatform. Please test and report issues. - Lastly updated on $(date +'%d/%m/%y %R'). + Lastly updated on ${{ steps.meta.outputs.date }}. generate_release_notes: true - files: ${{ github.workspace }}/bin/* - fail_on_unmatched_files: true \ No newline at end of file + files: release-files/** + fail_on_unmatched_files: true From 5e76cbb94975321a48eabb8eab11ac4dadacd584 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 11:37:54 +0700 Subject: [PATCH 08/12] chore: ignore SEA build intermediates sea-config.json and sea-prep.blob are auto-generated by build:sea:win on every build and should not be committed. Added both to .gitignore. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 17ee22e..4cf402c 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ learning-packages/ACROSS-Lab QuangBinhProject MIAT-version Gama/ /bin* dist dist-api + +# SEA build intermediates (auto-generated by build:sea:win) +sea-config.json +sea-prep.blob From d317f41281b76f630659e4aa4c068930298f8e99 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 13:33:55 +0700 Subject: [PATCH 09/12] feat(sea): migrate Linux and macOS builds from pkg to Node.js SEA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/build-sea-unix.mjs, a single script that builds a SEA binary for the current Unix platform (Linux or macOS). It follows the same pipeline as build-sea-win.mjs: builds frontend and backend, embeds all dist/ files and the platform-specific uWebSockets.js .node file as SEA assets, generates the blob, and injects it via postject. Platform differences handled by the script: - macOS (Mach-O): postject requires --macho-segment-name NODE_SEA, and the binary must be re-signed with codesign --sign - after injection (Apple Silicon blocks modified unsigned binaries) - Linux: no signing or extra flags needed Unlike Windows, neither Linux nor macOS require the binary to be named node.exe, so no launcher or self-extraction is needed — the output is a directly executable single file (bin/simple-linux or bin/simple-macos). Removes @yao-pkg/pkg devDependency and its config block. The build:executable and build:executable-compressed scripts are removed; all three platforms now use the build:sea:* family of scripts. Co-Authored-By: Claude Sonnet 4.6 --- package.json | 18 +---- scripts/build-sea-unix.mjs | 132 +++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 scripts/build-sea-unix.mjs diff --git a/package.json b/package.json index 0fd0da1..f4a6a73 100644 --- a/package.json +++ b/package.json @@ -10,22 +10,11 @@ "preview": "vite preview", "build:frontend": "vite build", "build:backend": "vite build -c vite.backend.config.ts", - "build:executable": "npm run build:frontend && npm run build:backend && pkg .", - "build:executable-compressed": "npm run build:frontend && npm run build:backend && pkg . --compress Brotli", - "build:sea:win": "node scripts/build-sea-win.mjs" + "build:sea:win": "node scripts/build-sea-win.mjs", + "build:sea:linux": "node scripts/build-sea-unix.mjs", + "build:sea:macos": "node scripts/build-sea-unix.mjs" }, "bin": "dist-api/index.cjs", - "pkg": { - "assets": [ - "dist/**/*", - "node_modules/uWebSockets.js/*.node" - ], - "targets": [ - "node24-linux-x64", - "node24-macos-x64" - ], - "outputPath": "bin" - }, "dependencies": { "@homebridge/ciao": "^1.1.8", "@logtape/file": "^2.0.4", @@ -77,7 +66,6 @@ "@eslint/js": "^9.39.3", "@eslint/json": "^1.0.0", "@types/express": "^5.0.6", - "@yao-pkg/pkg": "^6.12.0", "eslint": "^8.57.1", "eslint-plugin-react": "^7.37.5", "globals": "^17.3.0", diff --git a/scripts/build-sea-unix.mjs b/scripts/build-sea-unix.mjs new file mode 100644 index 0000000..0662d3d --- /dev/null +++ b/scripts/build-sea-unix.mjs @@ -0,0 +1,132 @@ +/** + * Build a Node.js SEA (Single Executable Application) for the current + * Unix platform (Linux x64 or macOS x64/arm64). + * + * Run this script on the target platform — it uses process.execPath as + * the base binary and embeds the matching uWebSockets.js .node file. + * + * Produces: + * bin/simple-linux (when run on Linux) + * bin/simple-macos (when run on macOS) + */ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); + +const platform = process.platform; // 'linux' | 'darwin' +const arch = process.arch; // 'x64' | 'arm64' + +if (platform !== 'linux' && platform !== 'darwin') { + console.error('[SEA] This script is for Linux/macOS. Use build-sea-win.mjs on Windows.'); + process.exit(1); +} + +const outputName = platform === 'darwin' ? 'simple-macos' : 'simple-linux'; + +// ── 1. Build frontend and backend ──────────────────────────────────────────── + +console.log('\n[SEA] Building frontend...'); +execSync('npm run build:frontend', { cwd: root, stdio: 'inherit' }); + +console.log('\n[SEA] Building backend...'); +execSync('npm run build:backend', { cwd: root, stdio: 'inherit' }); + +// ── 2. Collect all dist/ files as SEA assets ───────────────────────────────── + +const distDir = path.join(root, 'dist'); + +function collectAssets(dir, base = distDir) { + const out = {}; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + Object.assign(out, collectAssets(full, base)); + } else { + const key = 'dist/' + path.relative(base, full); + const value = path.relative(root, full); + out[key] = value; + } + } + return out; +} + +const distAssets = collectAssets(distDir); +console.log(`\n[SEA] ${Object.keys(distAssets).length} frontend files collected.`); + +// ── 3. Add uWebSockets.js native module for this platform ──────────────────── + +const modulesVer = process.versions.modules; +const uwsFile = `uws_${platform}_${arch}_${modulesVer}.node`; +const uwsPath = path.join(root, 'node_modules', 'uWebSockets.js', uwsFile); + +if (!fs.existsSync(uwsPath)) { + console.error(`[SEA] ERROR: ${uwsFile} not found. Is uWebSockets.js installed?`); + process.exit(1); +} + +const assets = { + ...distAssets, + [uwsFile]: path.relative(root, uwsPath), +}; + +console.log(`[SEA] Including native module: ${uwsFile}`); + +// ── 4. Write sea-config.json ────────────────────────────────────────────────── + +const seaConfig = { + main: 'dist-api/index.cjs', + output: 'sea-prep.blob', + disableExperimentalSEAWarning: true, + assets, +}; + +fs.writeFileSync(path.join(root, 'sea-config.json'), JSON.stringify(seaConfig, null, 2)); +console.log(`[SEA] sea-config.json written (${Object.keys(assets).length} assets).`); + +// ── 5. Generate SEA blob ────────────────────────────────────────────────────── + +console.log('\n[SEA] Generating blob...'); +execSync('node --experimental-sea-config sea-config.json', { cwd: root, stdio: 'inherit' }); + +// ── 6. Copy node binary ─────────────────────────────────────────────────────── + +const outDir = path.join(root, 'bin'); +fs.mkdirSync(outDir, { recursive: true }); +const outBin = path.join(outDir, outputName); + +fs.copyFileSync(process.execPath, outBin); +fs.chmodSync(outBin, 0o755); +console.log(`\n[SEA] Copied node binary to: ${outBin}`); + +// ── 7. Inject SEA blob with postject ───────────────────────────────────────── +// +// macOS (Mach-O) requires --macho-segment-name NODE_SEA in addition to the +// standard section name argument. + +console.log('[SEA] Injecting blob with postject...'); +const machoFlag = platform === 'darwin' ? '--macho-segment-name NODE_SEA' : ''; +execSync( + `npx postject "${outBin}" NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite ${machoFlag}`.trim(), + { cwd: root, stdio: 'inherit' }, +); + +// ── 8. Sign the binary (macOS only) ────────────────────────────────────────── +// +// postject modifies the Mach-O binary, invalidating any existing signature. +// An ad-hoc re-sign is required for macOS to allow execution (especially on +// Apple Silicon where unsigned modified binaries are blocked by default). + +if (platform === 'darwin') { + console.log('[SEA] Ad-hoc signing binary...'); + execSync(`codesign --sign - "${outBin}"`, { cwd: root, stdio: 'inherit' }); +} + +const sizeMB = (fs.statSync(outBin).size / 1024 / 1024).toFixed(1); +console.log(`\n[SEA] Done!`); +console.log(` Output: ${outBin}`); +console.log(` Size: ${sizeMB} MB`); From 399049ee1daca49ef88824c52bb8058fcaf74073 Mon Sep 17 00:00:00 2001 From: Arthur Brugiere Date: Fri, 29 May 2026 13:34:09 +0700 Subject: [PATCH 10/12] ci: replace pkg cross-compilation with per-platform SEA jobs All three platforms now build on their native runner using Node.js SEA, removing the need for qemu, ldid, and cross-compilation from ubuntu. build-ci.yaml: - compilation-linux (ubuntu-latest): build:sea:linux - compilation-macos (macos-latest): build:sea:macos - compilation-windows (windows-latest): build:sea:win + Go release.yaml: - build-linux (ubuntu-latest) - build-macos (macos-latest) - build-windows (windows-latest) - release: waits for all three, collects artifacts, publishes --- .github/workflows/build-ci.yaml | 38 +- .github/workflows/release.yaml | 36 +- package-lock.json | 1084 ------------------------------- 3 files changed, 42 insertions(+), 1116 deletions(-) diff --git a/.github/workflows/build-ci.yaml b/.github/workflows/build-ci.yaml index cd5ec4b..ef73da3 100644 --- a/.github/workflows/build-ci.yaml +++ b/.github/workflows/build-ci.yaml @@ -4,7 +4,7 @@ on: push: jobs: - compilation-unix: + compilation-linux: runs-on: ubuntu-latest steps: @@ -13,20 +13,11 @@ jobs: with: node-version: 24 - - name: Get utils - run: | - sudo apt-get update - sudo apt-get install -y qemu-user-binfmt - - - uses: MOZGIII/install-ldid-action@v1 - with: - tag: v2.1.5-procursus7 - - name: Install npm modules run: npm i - - name: Package application - run: npm run build:executable + - name: Build Linux binary + run: npm run build:sea:linux - name: Export release for Linux uses: actions/upload-artifact@v4 @@ -35,18 +26,31 @@ jobs: if-no-files-found: error compression-level: 0 overwrite: true - path: | - ${{ github.workspace }}/bin/simple-linux* + path: ${{ github.workspace }}/bin/simple-linux + + compilation-macos: + runs-on: macos-latest + steps: + + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Install npm modules + run: npm i + + - name: Build macOS binary + run: npm run build:sea:macos - - name: Export release for MacOS + - name: Export release for macOS uses: actions/upload-artifact@v4 with: name: macos-compiled-archive if-no-files-found: error compression-level: 0 overwrite: true - path: | - ${{ github.workspace }}/bin/simple-macos* + path: ${{ github.workspace }}/bin/simple-macos compilation-windows: runs-on: windows-latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4a26482..0261f76 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,7 +4,7 @@ on: workflow_call: jobs: - build-unix: + build-linux: runs-on: ubuntu-latest steps: @@ -13,20 +13,11 @@ jobs: with: node-version: 24 - - name: Get utils - run: | - sudo apt-get update - sudo apt-get install -y qemu-user-binfmt - - - uses: MOZGIII/install-ldid-action@v1 - with: - tag: v2.1.5-procursus7 - - name: Install npm modules run: npm i - - name: Package application - run: npm run build:executable-compressed + - name: Build Linux binary + run: npm run build:sea:linux - name: Upload Linux artifact uses: actions/upload-artifact@v4 @@ -34,7 +25,22 @@ jobs: name: linux-release-binary if-no-files-found: error compression-level: 0 - path: ${{ github.workspace }}/bin/simple-linux* + path: ${{ github.workspace }}/bin/simple-linux + + build-macos: + runs-on: macos-latest + steps: + + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Install npm modules + run: npm i + + - name: Build macOS binary + run: npm run build:sea:macos - name: Upload macOS artifact uses: actions/upload-artifact@v4 @@ -42,7 +48,7 @@ jobs: name: macos-release-binary if-no-files-found: error compression-level: 0 - path: ${{ github.workspace }}/bin/simple-macos* + path: ${{ github.workspace }}/bin/simple-macos build-windows: runs-on: windows-latest @@ -71,7 +77,7 @@ jobs: path: ${{ github.workspace }}/bin/win/simple-win.exe release: - needs: [build-unix, build-windows] + needs: [build-linux, build-macos, build-windows] runs-on: ubuntu-latest steps: diff --git a/package-lock.json b/package-lock.json index c7a41f1..876fdac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,6 @@ "@eslint/js": "^9.39.3", "@eslint/json": "^1.0.0", "@types/express": "^5.0.6", - "@yao-pkg/pkg": "^6.12.0", "eslint": "^8.57.1", "eslint-plugin-react": "^7.37.5", "globals": "^17.3.0", @@ -1272,19 +1271,6 @@ "deprecated": "Use @eslint/object-schema instead", "license": "BSD-3-Clause" }, - "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/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2777,101 +2763,6 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/@yao-pkg/pkg": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/@yao-pkg/pkg/-/pkg-6.14.1.tgz", - "integrity": "sha512-kTtxr+r9/pi+POcuuJN9q2YRs1Ekp7iwsgU3du1XO6ozwjlp+F31ZF19WS2ZDIloWYGCvfbURrrXMgQJpWZb+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/generator": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", - "@yao-pkg/pkg-fetch": "3.5.32", - "esbuild": "^0.27.3", - "into-stream": "^9.1.0", - "minimist": "^1.2.6", - "multistream": "^4.1.0", - "picocolors": "^1.1.0", - "picomatch": "^4.0.2", - "prebuild-install": "^7.1.1", - "resolve": "^1.22.10", - "resolve.exports": "^2.0.3", - "stream-meter": "^1.0.4", - "tar": "^7.5.7", - "tinyglobby": "^0.2.11", - "unzipper": "^0.12.3" - }, - "bin": { - "pkg": "lib-es5/bin.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@yao-pkg/pkg-fetch": { - "version": "3.5.32", - "resolved": "https://registry.npmjs.org/@yao-pkg/pkg-fetch/-/pkg-fetch-3.5.32.tgz", - "integrity": "sha512-hS8zzze5VVyVAciZoNX4q3ZmCdn0gi34P2SElk4PFbzkA4LPs7qBJ3LhUKb4d4KGw1HVkFJJOMgGomH7cMqQ5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.6", - "picocolors": "^1.1.0", - "progress": "^2.0.3", - "semver": "^7.3.5", - "tar-fs": "^3.1.1", - "yargs": "^16.2.0" - }, - "bin": { - "pkg-fetch": "lib-es5/bin.js" - } - }, - "node_modules/@yao-pkg/pkg-fetch/node_modules/b4a": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", - "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "node_modules/@yao-pkg/pkg-fetch/node_modules/tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/@yao-pkg/pkg-fetch/node_modules/tar-stream": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", - "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/@yume-chan/adb": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@yume-chan/adb/-/adb-2.5.1.tgz", @@ -3038,19 +2929,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -3557,124 +3435,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/bare-events": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", - "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", - "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", - "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", - "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "streamx": "^2.25.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-abort-controller": "*", - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - }, - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", - "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "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/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -3717,50 +3477,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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/bl/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/bl/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/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.5", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", @@ -3864,31 +3580,6 @@ "node-int64": "^0.4.0" } }, - "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", @@ -4103,16 +3794,6 @@ "node": ">= 6" } }, - "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/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4161,18 +3842,6 @@ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "license": "ISC" }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -4603,22 +4272,6 @@ } } }, - "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/dedent": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", @@ -4634,16 +4287,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4714,16 +4357,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "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-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4855,16 +4488,6 @@ "node": ">= 0.8" } }, - "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/engine.io": { "version": "6.6.5", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", @@ -6047,16 +5670,6 @@ "es5-ext": "~0.10.14" } }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, "node_modules/evilscan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/evilscan/-/evilscan-1.9.1.tgz", @@ -6119,16 +5732,6 @@ "node": ">=0.10.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true, - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -6237,13 +5840,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6491,28 +6087,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" - }, - "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/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6749,13 +6323,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true, - "license": "MIT" - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7171,20 +6738,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -7230,27 +6783,6 @@ "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/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7332,13 +6864,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, "node_modules/inquirer": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", @@ -7474,19 +6999,6 @@ "node": ">= 0.4" } }, - "node_modules/into-stream": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-9.1.0.tgz", - "integrity": "sha512-DRsRnQrbzdFjaQ1oe4C6/EIUymIOEix1qROEJTF9dbMq+M4Zrm6VaLp6SD/B9IsiEjPZuBSnWWFN+udajugdWA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ip-address": { "version": "5.9.4", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz", @@ -8846,19 +8358,6 @@ "node": ">=6" } }, - "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/jsonify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", @@ -9276,19 +8775,6 @@ "node": ">=6" } }, - "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/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -9313,29 +8799,6 @@ "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/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", @@ -9348,13 +8811,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9370,56 +8826,6 @@ "duplexer2": "0.0.2" } }, - "node_modules/multistream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", - "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", - "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": { - "once": "^1.4.0", - "readable-stream": "^3.6.0" - } - }, - "node_modules/multistream/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/multistream/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/mute-stream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", @@ -9455,13 +8861,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "dev": true, - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -9483,19 +8882,6 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "license": "ISC" }, - "node_modules/node-abi": { - "version": "3.87.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", - "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", @@ -9531,27 +8917,6 @@ "semver": "bin/semver.js" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/node-hid": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/node-hid/-/node-hid-3.3.0.tgz", @@ -10317,34 +9682,6 @@ "node": "^12.20.0 || >=14" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10388,16 +9725,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "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/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10444,17 +9771,6 @@ "node": ">= 0.10" } }, - "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", @@ -10549,32 +9865,6 @@ "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -11426,53 +10716,6 @@ "dev": true, "license": "ISC" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "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/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "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": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -11669,68 +10912,6 @@ "node": ">= 0.4" } }, - "node_modules/stream-meter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", - "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.4" - } - }, - "node_modules/stream-meter/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/stream-meter/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/stream-meter/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/stream-meter/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/streamx": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz", - "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -12133,105 +11314,6 @@ "jiti": "bin/jiti.js" } }, - "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-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream/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/tar-stream/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/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/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -12271,31 +11353,6 @@ "node": "*" } }, - "node_modules/text-decoder": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", - "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/text-decoder/node_modules/b4a": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", - "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -12434,13 +11491,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -12493,19 +11543,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", @@ -12962,16 +11999,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "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/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -12981,70 +12008,6 @@ "node": ">= 0.8" } }, - "node_modules/unzipper": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", - "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bluebird": "~3.7.2", - "duplexer2": "~0.1.4", - "fs-extra": "^11.2.0", - "graceful-fs": "^4.2.2", - "node-int64": "^0.4.0" - } - }, - "node_modules/unzipper/node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/unzipper/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unzipper/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/unzipper/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/unzipper/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -13256,24 +12219,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13496,35 +12441,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", From 2eb62c30daa01e1d39fdc4883efa1b610993016c Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Fri, 29 May 2026 14:59:30 +0700 Subject: [PATCH 11/12] fix: Enforce binary compilation to use LTS 24 - My laptop uses latest NodeJS 26 over which the compilation is failing - Add a catch failing the compilation with explicit message --- .nvmrc | 1 + scripts/build-sea-unix.mjs | 12 ++++++++++++ scripts/build-sea-win.mjs | 7 +++++++ 3 files changed, 20 insertions(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/scripts/build-sea-unix.mjs b/scripts/build-sea-unix.mjs index 0662d3d..babb667 100644 --- a/scripts/build-sea-unix.mjs +++ b/scripts/build-sea-unix.mjs @@ -26,6 +26,18 @@ if (platform !== 'linux' && platform !== 'darwin') { process.exit(1); } +// Node.js v26 (Arch Linux package) has a broken SEA implementation: postject +// corrupts the ELF binary and the objcopy approach produces sections without +// program segments, so dl_iterate_phdr never finds the blob → SIGSEGV at boot. +// CI uses Node.js 24; local builds must match. +const nodeMajor = parseInt(process.versions.node.split('.')[0], 10); +if (nodeMajor !== 24) { + console.error(`[SEA] ERROR: Node.js v${process.versions.node} is not supported for SEA builds.`); + console.error('[SEA] SEA injection is only reliable on Node.js 24.'); + console.error('[SEA] Run: nvm use 24 (or: nvm install 24)'); + process.exit(1); +} + const outputName = platform === 'darwin' ? 'simple-macos' : 'simple-linux'; // ── 1. Build frontend and backend ──────────────────────────────────────────── diff --git a/scripts/build-sea-win.mjs b/scripts/build-sea-win.mjs index f752a54..7c97f2e 100644 --- a/scripts/build-sea-win.mjs +++ b/scripts/build-sea-win.mjs @@ -21,6 +21,13 @@ import { patchPe } from './patch-pe-no-cfg.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, '..'); +const nodeMajor = parseInt(process.versions.node.split('.')[0], 10); +if (nodeMajor !== 24) { + console.error(`[SEA] ERROR: Node.js v${process.versions.node} is not supported for SEA builds.`); + console.error('[SEA] SEA injection is only reliable on Node.js 24.'); + process.exit(1); +} + // ── 1. Build frontend and backend ──────────────────────────────────────────── console.log('\n[SEA] Building frontend...'); From 4d8f10911e21a77515667ed94d127a4906956089 Mon Sep 17 00:00:00 2001 From: RoiArthurB Date: Fri, 29 May 2026 15:08:51 +0700 Subject: [PATCH 12/12] feat: Make the webplatform shutdown stuff only if running on a mac mini - Strong assumption that a Mac Mini == M2L2 --- src/api/core/Controller.ts | 37 +++++++++++++++++---------------- src/api/index.ts | 2 ++ src/api/infra/DeviceDetector.ts | 17 +++++++++++++++ 3 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 src/api/infra/DeviceDetector.ts diff --git a/src/api/core/Controller.ts b/src/api/core/Controller.ts index 135c54d..bcc1581 100644 --- a/src/api/core/Controller.ts +++ b/src/api/core/Controller.ts @@ -9,6 +9,7 @@ import { JsonPlayerAsk, JsonOutput } from "./Constants.ts"; import { getLogger } from "@logtape/logtape"; import { spawnSync } from 'child_process'; import { UpsManager } from '../infra/ups/UpsManager.ts'; +import {isMacMini} from "../infra/DeviceDetector.ts"; const logger = getLogger(["core", "Controller"]); @@ -64,31 +65,31 @@ export class Controller { /** Called after 3 hours. Shuts down headsets, UPS, and host if UPS is on battery. */ private async handleSessionTimeout(): Promise { - logger.warn('3-hour session timer fired'); + if (isMacMini()) { + logger.warn('3-hour session timer fired'); - if (this.ups_service.isConnected() && this.ups_service.isOnAC()) { - logger.info('UPS is on AC power — no shutdown needed'); - return; - } + if (this.ups_service.isConnected() && this.ups_service.isOnAC()) { + logger.info('UPS is on AC power — no shutdown needed'); + return; + } - logger.warn('UPS is on battery or not connected — initiating shutdown sequence'); + logger.warn('UPS is on battery or not connected — initiating shutdown sequence'); - // (1) Power off all headsets - if (this.adb_manager) - await this.adb_manager.shutdownAllHeadsets(); + // (1) Power off all headsets + if (this.adb_manager) + await this.adb_manager.shutdownAllHeadsets(); - // (2) Arm UPS output cut in 2 minutes (only executes when on battery) - this.ups_service.armShutdown(120); + // (2) Arm UPS output cut in 2 minutes (only executes when on battery) + this.ups_service.armShutdown(120); - // (3) Shutdown host computer after 30s to allow headsets and UPS time to process - setTimeout(() => { + // (3) Shutdown host computer after 30s to allow headsets and UPS time to process logger.warn('Shutting down host computer now'); - if (process.platform === 'win32') { - spawnSync('shutdown', ['/s', '/t', '0']); - } else { + setTimeout(() => { spawnSync('shutdown', ['-h', 'now']); - } - }, 30_000); + }, 30_000); + } else { + logger.info("Not running on a M2L2 (Mac Mini), skipping shutdown mechanism"); + } } async restart() { diff --git a/src/api/index.ts b/src/api/index.ts index 13add38..50e92e5 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -15,6 +15,7 @@ import Controller from './core/Controller.ts'; import { StaticServer } from './infra/StaticServer.ts'; import path from 'path'; import { createRequire } from 'module'; +import {isMacMini} from "./infra/DeviceDetector.ts"; /* TOOLBOX ================================ @@ -177,6 +178,7 @@ async function start() { logger.debug(`Arch: ${process.arch}`); logger.debug(`Is Packaged: {isPackaged}`, {isPackaged: IS_PLATFORM_PACKAGED}); logger.debug(`NODE_ENV: ${process.env.NODE_ENV}`); + logger.debug(`Is running on a Mac Mini: ${isMacMini()}`); logger.trace(process.env); diff --git a/src/api/infra/DeviceDetector.ts b/src/api/infra/DeviceDetector.ts new file mode 100644 index 0000000..5fb5e10 --- /dev/null +++ b/src/api/infra/DeviceDetector.ts @@ -0,0 +1,17 @@ +import { spawnSync } from "child_process"; + +/** + * Detects whether the machine running this Node process is a Mac Mini. + * Works on macOS; returns false on Linux/Windows. + */ +export function isMacMini(): boolean { + if (process.platform !== "darwin") return false; + + const result = spawnSync("sysctl", ["-n", "hw.model"], { encoding: "utf-8" }); + if (result.status !== 0 || !result.stdout) return false; + + // Model identifiers for Mac Mini always start with "Macmini" + // e.g. "Macmini9,1" (M1), "Macmini8,1" (Intel 2018), etc. + const model = result.stdout.trim(); + return model.toLowerCase().startsWith("macmini"); +} \ No newline at end of file