diff --git a/.github/workflows/build-ci.yaml b/.github/workflows/build-ci.yaml index 1ae3362e..ef73da3c 100644 --- a/.github/workflows/build-ci.yaml +++ b/.github/workflows/build-ci.yaml @@ -4,7 +4,7 @@ on: push: jobs: - compilation: + compilation-linux: runs-on: ubuntu-latest steps: @@ -13,22 +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 - - # Publish + - name: Build Linux binary + run: npm run build:sea:linux - name: Export release for Linux uses: actions/upload-artifact@v4 @@ -37,25 +26,55 @@ jobs: if-no-files-found: error compression-level: 0 overwrite: true - path: | - ${{ github.workspace }}/bin/simple-linux* + path: ${{ github.workspace }}/bin/simple-linux - - name: Export release for Windows + 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 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* + path: ${{ github.workspace }}/bin/simple-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: Export release for MacOS + - 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 ca2056e3..0261f769 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,7 +4,7 @@ on: workflow_call: jobs: - compilation: + build-linux: runs-on: ubuntu-latest steps: @@ -13,28 +13,95 @@ jobs: with: node-version: 24 - - name: Get utils - run: | - sudo apt-get update - sudo apt-get install -y qemu-user-binfmt + - name: Install npm modules + run: npm i + + - name: Build Linux binary + run: npm run build:sea:linux + + - 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 - - uses: MOZGIII/install-ldid-action@v1 + build-macos: + runs-on: macos-latest + steps: + + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - tag: v2.1.5-procursus7 + node-version: 24 - 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: Build macOS binary + run: npm run build:sea:macos + + - 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 - - name: Package application - run: npm run build:executable-compressed + release: + needs: [build-linux, build-macos, build-windows] + runs-on: ubuntu-latest + steps: - # Publish + - 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 +112,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 diff --git a/.gitignore b/.gitignore index 17ee22e5..4cf402ce 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 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..a45fd52c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/package-lock.json b/package-lock.json index ca7d6cc7..876fdacf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,12 +61,12 @@ "@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", "jest": "^29.7.0", "jiti": "^2.6.1", + "postject": "^1.0.0-alpha.6", "typescript-eslint": "^8.56.0" } }, @@ -1271,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", @@ -2776,58 +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/@yume-chan/adb": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@yume-chan/adb/-/adb-2.5.1.tgz", @@ -2994,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", @@ -3513,120 +3435,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "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==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "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==", - "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.7.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.7.1.tgz", - "integrity": "sha512-ebvMaS5BgZKmJlvuWh14dg9rbUI84QeV3WlWn6Ph6lFI8jJoh7ADtVTyD2c93euwbe+zgi0DVrl4YmqXeM9aIA==", - "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.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "streamx": "^2.21.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "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==", - "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", @@ -3669,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", @@ -3816,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", @@ -4055,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", @@ -4113,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", @@ -4555,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", @@ -4586,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", @@ -4666,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", @@ -4807,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", @@ -5999,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", @@ -6071,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", @@ -6189,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", @@ -6443,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", @@ -6701,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", @@ -7123,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", @@ -7182,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", @@ -7284,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", @@ -7426,23 +6999,10 @@ "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", - "integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==", + "node_modules/ip-address": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz", + "integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==", "license": "MIT", "dependencies": { "jsbn": "1.1.0", @@ -8798,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", @@ -9228,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", @@ -9265,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", @@ -9300,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", @@ -9322,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", @@ -9407,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", @@ -9435,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", @@ -9483,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", @@ -10243,94 +9656,30 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "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.", + "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": { - "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" + "commander": "^9.4.0" }, "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "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" + "postject": "dist/cli.js" }, "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": ">=14.0.0" } }, - "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==", + "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", - "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": "^12.20.0 || >=14" } }, "node_modules/prelude-ls": { @@ -10376,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", @@ -10432,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", @@ -10537,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", @@ -11414,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", @@ -11657,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.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "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", @@ -12121,86 +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": "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/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", - "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/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==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "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", @@ -12240,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.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", - "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", @@ -12403,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", @@ -12462,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", @@ -12931,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", @@ -12950,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", @@ -13225,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", @@ -13465,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", diff --git a/package.json b/package.json index 303cd4b3..f4a6a73e 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: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-windows-x64", - "node24-macos-x64" - ], - "outputPath": "bin" - }, "dependencies": { "@homebridge/ciao": "^1.1.8", "@logtape/file": "^2.0.4", @@ -77,12 +66,12 @@ "@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", "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-unix.mjs b/scripts/build-sea-unix.mjs new file mode 100644 index 00000000..babb667f --- /dev/null +++ b/scripts/build-sea-unix.mjs @@ -0,0 +1,144 @@ +/** + * 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); +} + +// 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 ──────────────────────────────────────────── + +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`); diff --git a/scripts/build-sea-win.mjs b/scripts/build-sea-win.mjs new file mode 100644 index 00000000..7c97f2ec --- /dev/null +++ b/scripts/build-sea-win.mjs @@ -0,0 +1,190 @@ +/** + * 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, '..'); + +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...'); +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/launcher-win.go b/scripts/launcher-win.go new file mode 100644 index 00000000..7ef87c7e --- /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 +} diff --git a/scripts/patch-pe-no-cfg.mjs b/scripts/patch-pe-no-cfg.mjs new file mode 100644 index 00000000..1662982a --- /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`); +} diff --git a/src/api/android/adb/DeviceFinder.ts b/src/api/android/adb/DeviceFinder.ts index ad174f7b..9f6fbb60 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 e574fb60..bcc1581e 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,27 +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'); - spawnSync('shutdown', ['-h', 'now']); - }, 30_000); + setTimeout(() => { + spawnSync('shutdown', ['-h', 'now']); + }, 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 b71e78cb..50e92e54 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -14,6 +14,8 @@ 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'; +import {isMacMini} from "./infra/DeviceDetector.ts"; /* TOOLBOX ================================ @@ -42,10 +44,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")) ; @@ -165,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 00000000..5fb5e10b --- /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 diff --git a/src/api/infra/StaticServer.ts b/src/api/infra/StaticServer.ts index dc6ab186..7b7e26c2 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=========================================`); + }); + } } diff --git a/vite.backend.config.ts b/vite.backend.config.ts index a66881d5..cac56f13 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,