diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 79f9c45604119..fb48c2adde531 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -22,7 +22,6 @@ defaults: shell: bash env: - ACTIONS_RUNNER_DEBUG: true NPM_CONFIG_PROVENANCE: true NPM_REGISTRY_URL: "https://registry.npmjs.org" @@ -98,14 +97,6 @@ jobs: node-version: "24" registry-url: "https://registry.npmjs.org" - - name: Install Dependencies - working-directory: ./npm - run: bun install --frozen-lockfile - - - name: Transpile TS -> JS - working-directory: ./npm - run: bun run build - - name: Derive RELEASE_VERSION id: release-version working-directory: ./npm @@ -159,7 +150,7 @@ jobs: fi echo "Staging binary $BIN into @foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" - PLATFORM_NAME=${{ matrix.os }} ARCH=${{ matrix.arch }} FORGE_BIN_PATH="$BIN" bun ./scripts/prepublish.ts + PLATFORM_NAME=${{ matrix.os }} ARCH=${{ matrix.arch }} FORGE_BIN_PATH="$BIN" bun ./scripts/prepublish.mjs - name: Sanity Check Binary working-directory: ./npm @@ -197,7 +188,7 @@ jobs: ls -la ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} - bun ./scripts/publish.ts ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} + bun ./scripts/publish.mjs ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} echo "Published @foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" @@ -240,7 +231,7 @@ jobs: - name: Publish Meta working-directory: ./npm - run: bun run ./scripts/publish.ts ./@foundry-rs/forge + run: bun run ./scripts/publish.mjs ./@foundry-rs/forge env: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} diff --git a/npm/.gitignore b/npm/.gitignore index 9d8d11def7774..bfb2b3fd4ae95 100644 --- a/npm/.gitignore +++ b/npm/.gitignore @@ -34,5 +34,9 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json .DS_Store forge/*/bin/forge +anvil/*/bin/anvil +cast/*/bin/cast +chisel/*/bin/chisel @foundry-rs/*/bin/ -test/workspace/bun.lock \ No newline at end of file +_ +build diff --git a/npm/@foundry-rs/anvil/README.md b/npm/@foundry-rs/anvil/README.md new file mode 100644 index 0000000000000..294aafe0b1e9f --- /dev/null +++ b/npm/@foundry-rs/anvil/README.md @@ -0,0 +1,4 @@ +# Anvil + +Anvil is a fast local Ethereum development node. +The cast binary can be used both within and outside of a Foundry project. diff --git a/npm/@foundry-rs/anvil/package.json b/npm/@foundry-rs/anvil/package.json new file mode 100644 index 0000000000000..9bd211af00d39 --- /dev/null +++ b/npm/@foundry-rs/anvil/package.json @@ -0,0 +1,34 @@ +{ + "name": "@foundry-rs/anvil", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/anvil", + "description": "Anvil is a fast local Ethereum development node", + "bin": { + "anvil": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "node ./dist/install.mjs" + }, + "optionalDependencies": { + "@foundry-rs/anvil-darwin-arm64": "0.0.0", + "@foundry-rs/anvil-darwin-amd64": "0.0.0", + "@foundry-rs/anvil-linux-arm64": "0.0.0", + "@foundry-rs/anvil-linux-amd64": "0.0.0", + "@foundry-rs/anvil-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/@foundry-rs/cast/README.md b/npm/@foundry-rs/cast/README.md new file mode 100644 index 0000000000000..5a7839d47437b --- /dev/null +++ b/npm/@foundry-rs/cast/README.md @@ -0,0 +1,5 @@ +# Cast + +Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. +You can make smart contract calls, send transactions, or retrieve any type of chain data - all from your command-line! +The cast binary can be used both within and outside of a Foundry project. diff --git a/npm/@foundry-rs/cast/package.json b/npm/@foundry-rs/cast/package.json new file mode 100644 index 0000000000000..fd48a721a61f0 --- /dev/null +++ b/npm/@foundry-rs/cast/package.json @@ -0,0 +1,34 @@ +{ + "name": "@foundry-rs/cast", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/cast", + "description": "Swiss Army knife for interacting with Ethereum applications from the command line", + "bin": { + "cast": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "node ./dist/install.mjs" + }, + "optionalDependencies": { + "@foundry-rs/cast-darwin-arm64": "0.0.0", + "@foundry-rs/cast-darwin-amd64": "0.0.0", + "@foundry-rs/cast-linux-arm64": "0.0.0", + "@foundry-rs/cast-linux-amd64": "0.0.0", + "@foundry-rs/cast-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/@foundry-rs/chisel/README.md b/npm/@foundry-rs/chisel/README.md new file mode 100644 index 0000000000000..50f47ebcdff3f --- /dev/null +++ b/npm/@foundry-rs/chisel/README.md @@ -0,0 +1,4 @@ +# Chisel + +Chisel is a fast, utilitarian, and verbose Solidity REPL. +The chisel binary can be used both within and outside of a Foundry project. diff --git a/npm/@foundry-rs/chisel/package.json b/npm/@foundry-rs/chisel/package.json new file mode 100644 index 0000000000000..95b8b95787d68 --- /dev/null +++ b/npm/@foundry-rs/chisel/package.json @@ -0,0 +1,34 @@ +{ + "name": "@foundry-rs/chisel", + "version": "0.0.0", + "type": "module", + "homepage": "https://getfoundry.sh/chisel", + "description": "Chisel is a fast, utilitarian, and verbose Solidity REPL", + "bin": { + "chisel": "./bin.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "node ./dist/install.mjs" + }, + "optionalDependencies": { + "@foundry-rs/chisel-darwin-arm64": "0.0.0", + "@foundry-rs/chisel-darwin-amd64": "0.0.0", + "@foundry-rs/chisel-linux-arm64": "0.0.0", + "@foundry-rs/chisel-linux-amd64": "0.0.0", + "@foundry-rs/chisel-win32-amd64": "0.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true, + "registry": "https://registry.npmjs.org" + }, + "license": "MIT OR Apache-2.0", + "repository": { + "directory": "npm", + "url": "https://github.com/foundry-rs/foundry" + } +} diff --git a/npm/@foundry-rs/forge-darwin-amd64/README.md b/npm/@foundry-rs/forge-darwin-amd64/README.md deleted file mode 100644 index 87b0255019198..0000000000000 --- a/npm/@foundry-rs/forge-darwin-amd64/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# forge - -This is the macOS 64-bit binary for `forge`, a CLI tool for testing, building, and deploying your smart contracts. -See for details. diff --git a/npm/@foundry-rs/forge-darwin-amd64/package.json b/npm/@foundry-rs/forge-darwin-amd64/package.json deleted file mode 100644 index f9785d7c0d161..0000000000000 --- a/npm/@foundry-rs/forge-darwin-amd64/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@foundry-rs/forge-darwin-amd64", - "version": "0.0.0", - "type": "module", - "homepage": "https://getfoundry.sh/forge", - "description": "Fast and flexible Ethereum testing framework (macOS amd64)", - "bin": { - "forge": "./bin/forge" - }, - "os": [ - "darwin" - ], - "cpu": [ - "x64" - ], - "files": [ - "bin" - ], - "engines": { - "node": ">=20" - }, - "license": "MIT OR Apache-2.0", - "repository": { - "directory": "npm", - "url": "https://github.com/foundry-rs/foundry" - }, - "keywords": [ - "foundry", - "testing", - "ethereum", - "solidity", - "blockchain", - "smart-contracts" - ], - "publishConfig": { - "provenance": true - } -} diff --git a/npm/@foundry-rs/forge-darwin-arm64/README.md b/npm/@foundry-rs/forge-darwin-arm64/README.md deleted file mode 100644 index 61f4dbb6e15c3..0000000000000 --- a/npm/@foundry-rs/forge-darwin-arm64/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# forge - -This is the macOS ARM 64-bit binary for `forge`, a CLI tool for testing, building, and deploying your smart contracts. -See for details. diff --git a/npm/@foundry-rs/forge-darwin-arm64/package.json b/npm/@foundry-rs/forge-darwin-arm64/package.json deleted file mode 100644 index e1a68b813c1fb..0000000000000 --- a/npm/@foundry-rs/forge-darwin-arm64/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@foundry-rs/forge-darwin-arm64", - "version": "0.0.0", - "type": "module", - "homepage": "https://getfoundry.sh/forge", - "description": "Fast and flexible Ethereum testing framework (macOS arm64)", - "bin": { - "forge": "./bin/forge" - }, - "os": [ - "darwin" - ], - "cpu": [ - "arm64" - ], - "files": [ - "bin" - ], - "engines": { - "node": ">=20" - }, - "license": "MIT OR Apache-2.0", - "repository": { - "directory": "npm", - "url": "https://github.com/foundry-rs/foundry" - }, - "keywords": [ - "foundry", - "testing", - "ethereum", - "solidity", - "blockchain", - "smart-contracts" - ], - "publishConfig": { - "provenance": true - } -} diff --git a/npm/@foundry-rs/forge-linux-amd64/README.md b/npm/@foundry-rs/forge-linux-amd64/README.md deleted file mode 100644 index c1731c7427772..0000000000000 --- a/npm/@foundry-rs/forge-linux-amd64/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# forge - -This is the Linux 64-bit binary for `forge`, a CLI tool for testing, building, and deploying your smart contracts. -See for details. diff --git a/npm/@foundry-rs/forge-linux-amd64/package.json b/npm/@foundry-rs/forge-linux-amd64/package.json deleted file mode 100644 index 477ee9ee32a63..0000000000000 --- a/npm/@foundry-rs/forge-linux-amd64/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@foundry-rs/forge-linux-amd64", - "version": "0.0.0", - "type": "module", - "homepage": "https://getfoundry.sh/forge", - "description": "Fast and flexible Ethereum testing framework (Linux amd64)", - "bin": { - "forge": "./bin/forge" - }, - "os": [ - "linux" - ], - "cpu": [ - "x64" - ], - "files": [ - "bin" - ], - "engines": { - "node": ">=20" - }, - "license": "MIT OR Apache-2.0", - "repository": { - "directory": "npm", - "url": "https://github.com/foundry-rs/foundry" - }, - "keywords": [ - "foundry", - "testing", - "ethereum", - "solidity", - "blockchain", - "smart-contracts" - ], - "publishConfig": { - "provenance": true - } -} diff --git a/npm/@foundry-rs/forge-linux-arm64/README.md b/npm/@foundry-rs/forge-linux-arm64/README.md deleted file mode 100644 index 2e209e9b965d6..0000000000000 --- a/npm/@foundry-rs/forge-linux-arm64/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# forge - -This is the Linux ARM 64-bit binary for `forge`, a CLI tool for testing, building, and deploying your smart contracts. -See for details. diff --git a/npm/@foundry-rs/forge-linux-arm64/package.json b/npm/@foundry-rs/forge-linux-arm64/package.json deleted file mode 100644 index 019bbd594945f..0000000000000 --- a/npm/@foundry-rs/forge-linux-arm64/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@foundry-rs/forge-linux-arm64", - "version": "0.0.0", - "type": "module", - "homepage": "https://getfoundry.sh/forge", - "description": "Fast and flexible Ethereum testing framework (Linux arm64)", - "bin": { - "forge": "./bin/forge" - }, - "os": [ - "linux" - ], - "cpu": [ - "arm64" - ], - "files": [ - "bin" - ], - "engines": { - "node": ">=20" - }, - "license": "MIT OR Apache-2.0", - "repository": { - "directory": "npm", - "url": "https://github.com/foundry-rs/foundry" - }, - "keywords": [ - "foundry", - "testing", - "ethereum", - "solidity", - "blockchain", - "smart-contracts" - ], - "publishConfig": { - "provenance": true - } -} diff --git a/npm/@foundry-rs/forge-win32-amd64/README.md b/npm/@foundry-rs/forge-win32-amd64/README.md deleted file mode 100644 index bec6364573068..0000000000000 --- a/npm/@foundry-rs/forge-win32-amd64/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# forge - -This is the Windows 64-bit binary for `forge`, a CLI tool for testing, building, and deploying your smart contracts. -See for details. diff --git a/npm/@foundry-rs/forge-win32-amd64/package.json b/npm/@foundry-rs/forge-win32-amd64/package.json deleted file mode 100644 index f4a1476f854f5..0000000000000 --- a/npm/@foundry-rs/forge-win32-amd64/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@foundry-rs/forge-win32-amd64", - "version": "0.0.0", - "type": "module", - "homepage": "https://getfoundry.sh/forge", - "description": "Fast and flexible Ethereum testing framework (Windows amd64)", - "bin": { - "forge": "./bin/forge.exe" - }, - "os": [ - "win32" - ], - "cpu": [ - "x64" - ], - "files": [ - "bin" - ], - "engines": { - "node": ">=20" - }, - "license": "MIT OR Apache-2.0", - "repository": { - "directory": "npm", - "url": "https://github.com/foundry-rs/foundry" - }, - "keywords": [ - "foundry", - "testing", - "ethereum", - "solidity", - "blockchain", - "smart-contracts" - ], - "publishConfig": { - "provenance": true - } -} diff --git a/npm/@foundry-rs/forge/README.md b/npm/@foundry-rs/forge/README.md index 775539ffc3811..e5719a5575aed 100644 --- a/npm/@foundry-rs/forge/README.md +++ b/npm/@foundry-rs/forge/README.md @@ -1,4 +1,4 @@ # Forge Forge is a command-line tool that ships with Foundry. Forge tests, builds, and deploys your smart contracts. -Forge is part of the Foundry suite and is installed alongside `cast`, `chisel`, and `anvil`. +The forge binary can be used both within and outside of a Foundry project. diff --git a/npm/@foundry-rs/forge/package.json b/npm/@foundry-rs/forge/package.json index 707efd7f7f03b..5ee8df3f19c2c 100644 --- a/npm/@foundry-rs/forge/package.json +++ b/npm/@foundry-rs/forge/package.json @@ -1,11 +1,11 @@ { "name": "@foundry-rs/forge", - "version": "1.3.2", + "version": "0.0.0", "type": "module", "homepage": "https://getfoundry.sh/forge", "description": "Fast and flexible Ethereum testing framework", "bin": { - "forge": "./bin/forge.mjs" + "forge": "./bin.mjs" }, "files": [ "bin", @@ -15,11 +15,11 @@ "postinstall": "node ./dist/install.mjs" }, "optionalDependencies": { - "@foundry-rs/forge-darwin-arm64": "1.3.2", - "@foundry-rs/forge-darwin-amd64": "1.3.2", - "@foundry-rs/forge-linux-arm64": "1.3.2", - "@foundry-rs/forge-linux-amd64": "1.3.2", - "@foundry-rs/forge-win32-amd64": "1.3.2" + "@foundry-rs/forge-darwin-arm64": "0.0.0", + "@foundry-rs/forge-darwin-amd64": "0.0.0", + "@foundry-rs/forge-linux-arm64": "0.0.0", + "@foundry-rs/forge-linux-amd64": "0.0.0", + "@foundry-rs/forge-win32-amd64": "0.0.0" }, "publishConfig": { "access": "public", diff --git a/npm/README.md b/npm/README.md index 3e2bcc376f819..b78cac02da6c5 100644 --- a/npm/README.md +++ b/npm/README.md @@ -1,5 +1,28 @@ # @foundry-rs npm Packages -The npm folder contains the meta packages for `forge` (`@foundry-rs/forge`) and per-arch packages (e.g. `@foundry-rs/forge-darwin-arm64`). +This folder contains npm publisher and installer scripts for `forge`, `cast`, `anvil`, and `chisel` and per-arch packages for each: -A guide on how to publish to a local registry and test via `npx`/`bunx` is available in [./test/README.md](./test/README.md). +- `@foundry-rs/forge`: + - `@foundry-rs/forge-darwin-arm64` + - `@foundry-rs/forge-darwin-amd64` + - `@foundry-rs/forge-linux-arm64` + - `@foundry-rs/forge-linux-amd64` + - `@foundry-rs/forge-win32-amd64` +- `@foundry-rs/cast`: + - `@foundry-rs/cast-darwin-arm64` + - `@foundry-rs/cast-darwin-amd64` + - `@foundry-rs/cast-linux-arm64` + - `@foundry-rs/cast-linux-amd64` + - `@foundry-rs/cast-win32-amd64` +- `@foundry-rs/anvil`: + - `@foundry-rs/anvil-darwin-arm64` + - `@foundry-rs/anvil-darwin-amd64` + - `@foundry-rs/anvil-linux-arm64` + - `@foundry-rs/anvil-linux-amd64` + - `@foundry-rs/anvil-win32-amd64` +- `@foundry-rs/chisel`: + - `@foundry-rs/chisel-darwin-arm64` + - `@foundry-rs/chisel-darwin-amd64` + - `@foundry-rs/chisel-linux-arm64` + - `@foundry-rs/chisel-linux-amd64` + - `@foundry-rs/chisel-win32-amd64` diff --git a/npm/bun.lock b/npm/bun.lock index b18f633ad448e..e66372c216dfc 100644 --- a/npm/bun.lock +++ b/npm/bun.lock @@ -4,163 +4,116 @@ "": { "dependencies": { "@types/bun": "^1.2.22", - "@types/node": "^24.4.0", - "tsdown": "^0.15.1", + "@types/node": "^24.5.2", + "bun": "^1.2.22", + "tsx": "^4.20.6", "typescript": "^5.9.2", }, }, }, "packages": { - "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], - "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], - "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], - "@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], - "@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.3", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@tybys/wasm-util": "^0.10.0" } }, "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], - "@oxc-project/runtime": ["@oxc-project/runtime@0.87.0", "", {}, "sha512-ky2Hqi2q/uGX36UfY79zxMbUqiNIl1RyKKVJfFenG70lbn+/fcaKBVTbhmUwn8a2wPyv2gNtDQxuDytbKX9giQ=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], - "@oxc-project/types": ["@oxc-project/types@0.87.0", "", {}, "sha512-ipZFWVGE9fADBVXXWJWY/cxpysc41Gt5upKDeb32F6WMgFyO7XETUMVq8UuREKCih+Km5E6p2VhEvf6Fuhey6g=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], - "@quansync/fs": ["@quansync/fs@0.1.5", "", { "dependencies": { "quansync": "^0.2.11" } }, "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.37", "", { "os": "android", "cpu": "arm64" }, "sha512-Pdr3USGBdoYzcygfJTSATHd7x476vVF3rnQ6SuUAh4YjhgGoNaI/ZycQ0RsonptwwU5NmQRWxfWv+aUPL6JlJg=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.37", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iDdmatSgbWhTYOq51G2CkJXwFayiuQpv/ywG7Bv3wKqy31L7d0LltUhWqAdfCl7eBG3gybfUm/iEXiTldH3jYA=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.37", "", { "os": "darwin", "cpu": "x64" }, "sha512-LQPpi3YJDtIprj6mwMbVM1gLM4BV2m9oqe9h3Y1UwAd20xs+imnzWJqWFpm4Hw9SiFmefIf3q4EPx2k6Nj2K7A=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.37", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9JnfSWfYd/YrZOu4Sj3rb2THBrCj70nJB/2FOSdg0O9ZoRrdTeB8b7Futo6N7HLWZM5uqqnJBX6VTpA0RZD+ow=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.37", "", { "os": "linux", "cpu": "arm" }, "sha512-eEmQTpvefEtHxc0vg5sOnWCqBcGQB/SIDlPkkzKR9ESKq9BsjQfHxssJWuNMyQ+rpr9CYaogddyQtZ9GHkp8vA=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.37", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ekv4OjDzQUl0X9kHM7M23N9hVRiYCYr89neLBNITCp7P4IHs1f6SNZiCIvvBVy6NIFzO1w9LZJGEeJYK5cQBVQ=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.37", "", { "os": "linux", "cpu": "arm64" }, "sha512-z8Aa5Kar5mhh0RVZEL+zKJwNz1cgcDISmwUMcTk0w986T8JZJOJCfJ/u9e8pqUTIJjxdM8SZq9/24nMgMlx5ng=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.37", "", { "os": "linux", "cpu": "x64" }, "sha512-e+fNseKhfE/socjOw6VrQcXrbNKfi2V/KZ+ssuLnmeaYNGuJWqPhvML56oYhGb3IgROEEc61lzr3Riy5BIqoMA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.37", "", { "os": "linux", "cpu": "x64" }, "sha512-dPZfB396PMIasd19X0ikpdCvjK/7SaJFO8y5/TxnozJEy70vOf4GESe/oKcsJPav/MSTWBYsHjJSO6vX0oAW8g=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.37", "", { "os": "none", "cpu": "arm64" }, "sha512-rFjLXoHpRqxJqkSBXHuyt6bhyiIFnvLD9X2iPmCYlfpEkdTbrY1AXg4ZbF8UMO5LM7DAAZm/7vPYPO1TKTA7Sg=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.37", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.3" }, "cpu": "none" }, "sha512-oQAe3lMaBGX6q0GSic0l3Obmd6/rX8R6eHLnRC8kyy/CvPLiCMV82MPGT8fxpPTo/ULFGrupSu2nV1zmOFBt/w=="], + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.2.22", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YCJkV2/vO5VVTQdwxLQrkW/yU4FAMWd3AXU3Z+TfoeYkHye5d2dIaBRXEPrOzrq1LQ2esN6ZhGfwYu2lVMTVRw=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.37", "", { "os": "win32", "cpu": "arm64" }, "sha512-ucO6CiZhpkNRiVAk7ybvA9pZaMreCtfHej3BtJcBL5S3aYmp4h0g6TvaXLD5YRJx5sXobp/9A//xU4wPMul3Bg=="], + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.2.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-LhazlsoNOhjirQT303zKG5cli65FR5WweZgGRL0LoxH/ZWTwlYxpTCOBJ6/euV8YLMaGDNQfIfRLFARK5NqXng=="], - "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.37", "", { "os": "win32", "cpu": "ia32" }, "sha512-Ya9DBWJe1EGHwil7ielI8CdE0ELCg6KyDvDQqIFllnTJEYJ1Rb74DK6mvlZo273qz6Mw8WrMm26urfDeZhCc3Q=="], + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.2.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-l8OHOXKZKCZaRDb5gxE8qRfccq6zi7j1xJiSI5P86qXW8jPoQbf+pPCoP8NgeyzeHqluWJoN0mqgCsSdp5dzWQ=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.37", "", { "os": "win32", "cpu": "x64" }, "sha512-r+RI+wMReoTIF/uXqQWJcD8xGWXzCzUyGdpLmQ8FC+MCyPHlkjEsFRv8OFIYI6HhiGAmbfWVYEGf+aeLJzkHGw=="], + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.2.22", "", { "os": "linux", "cpu": "arm64" }, "sha512-JdC5nvmQh0rbUC46FY5uSF4SKYcY2LX5S66ZZvWdFp8R+6WnNc3Jr1hd5NcRW9qBVQ/JHi8oedrky9BtT8tzMA=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.37", "", {}, "sha512-0taU1HpxFzrukvWIhLRI4YssJX2wOW5q1MxPXWztltsQ13TE51/larZIwhFdpyk7+K43TH7x6GJ8oEqAo+vDbA=="], + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.2.22", "", { "os": "linux", "cpu": "none" }, "sha512-Dc4/CsUgufxIwQKo8vVFtUvNSZIqVgogj7cg8GIXdNsanO/vckv8qspyLHuQB5E2Nye4nXorD76ixKuwkPTAMw=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-U3h5zPw0stPl1qi7sGk8hL1r2QXH73HJBTLBHpeJ+PlfhfX/QIWnL/qK2c5Prm4jh2e/Tkw8bwL7NZ4iE9cVEQ=="], - "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], - - "@types/node": ["@types/node@24.4.0", "", { "dependencies": { "undici-types": "~7.11.0" } }, "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ=="], - - "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-sww8Sqc0Zq94wa95ouNC5weMRXIFt32gB3+xXXw6o52Uf7TeNrYriQr+o68D7A5YXk9DSDFaTknwYTYwYw/lmQ=="], - "ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="], + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-h76y0mrs1dnpjVxZTzoREa9cRdf029aKP0TxRMgABH3aRm2UBgUfgh0qyTsRhnHd4+gl6X2Vn0nfStZTNWGEFQ=="], - "ast-kit": ["ast-kit@2.1.2", "", { "dependencies": { "@babel/parser": "^7.28.0", "pathe": "^2.0.3" } }, "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g=="], + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-iQgG4wCSkHQ0CrEPsLMsCWoM1hewybJHVP5d3UaASwHcfuvd7N7hODZyz59tfMaGxZygyxIXQhgz32p37zDsEg=="], - "birpc": ["birpc@2.5.0", "", {}, "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ=="], + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.2.22", "", { "os": "win32", "cpu": "x64" }, "sha512-u+MIs0yj8Euv2ScFuqmbL54n4uJ+ZMK2nkAwkzumu6oUG0wRzIaSxAv61bO70Q1lTWX4dXLfoJhADJ1HdiGpTQ=="], - "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.2.22", "", { "os": "win32", "cpu": "x64" }, "sha512-9NgPAoht79/rex2C4IJ4N9BFpNupXS5WdKMKda0tBB/xjQkEZbSZ01wpS7PF4yHPwWsUZI0g7xP8NcNHT3nDcw=="], - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="], - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "bun": ["bun@1.2.22", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.2.22", "@oven/bun-darwin-x64": "1.2.22", "@oven/bun-darwin-x64-baseline": "1.2.22", "@oven/bun-linux-aarch64": "1.2.22", "@oven/bun-linux-aarch64-musl": "1.2.22", "@oven/bun-linux-x64": "1.2.22", "@oven/bun-linux-x64-baseline": "1.2.22", "@oven/bun-linux-x64-musl": "1.2.22", "@oven/bun-linux-x64-musl-baseline": "1.2.22", "@oven/bun-windows-x64": "1.2.22", "@oven/bun-windows-x64-baseline": "1.2.22" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-NnU1TEiH9LLv1jE+84AJ7ZGimdQzLgzbZNvK3enNh5qUHqkgDm99SiA7tnJnzfJW5OWBdoZzKae2zXu0pwQ/kA=="], - "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], - "dts-resolver": ["dts-resolver@2.1.2", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], - "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], - - "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], - - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - "rolldown": ["rolldown@1.0.0-beta.37", "", { "dependencies": { "@oxc-project/runtime": "=0.87.0", "@oxc-project/types": "=0.87.0", "@rolldown/pluginutils": "1.0.0-beta.37", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.37", "@rolldown/binding-darwin-arm64": "1.0.0-beta.37", "@rolldown/binding-darwin-x64": "1.0.0-beta.37", "@rolldown/binding-freebsd-x64": "1.0.0-beta.37", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.37", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.37", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.37", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.37", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.37", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.37", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.37", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.37", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.37", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.37" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-KiTU6z1kHGaLvqaYjgsrv2LshHqNBn74waRZivlK8WbfN1obZeScVkQPKYunB66E/mxZWv/zyZlCv3xF2t0WOQ=="], - - "rolldown-plugin-dts": ["rolldown-plugin-dts@0.16.5", "", { "dependencies": { "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/types": "^7.28.4", "ast-kit": "^2.1.2", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.2", "get-tsconfig": "^4.10.1", "magic-string": "^0.30.19" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.0.3" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-bOAfJ7Tc11xK/Uou7KWYha25/Sy80G0DZkhX8WMYx6l8PUalR+bvVzQNuEqXafpKEisZfUHQrkhS2gZG76Xntw=="], - - "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - - "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - - "tsdown": ["tsdown@0.15.1", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "latest", "rolldown-plugin-dts": "^0.16.5", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig": "^7.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-USTr2wS5OIyohR8Sp09rp5mXVwOX4YvVRqarS9I9jIhF+PqgtVSMXG4vBrHGY1OficNcLT5Z75pSoxd2uU+BYg=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="], "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - "unconfig": ["unconfig@7.3.3", "", { "dependencies": { "@quansync/fs": "^0.1.5", "defu": "^6.1.4", "jiti": "^2.5.1", "quansync": "^0.2.11" } }, "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA=="], - - "undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="], - - "@babel/generator/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], - - "@babel/generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], - - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], - - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], - "ast-kit/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "bun-types/@types/node": ["@types/node@24.4.0", "", { "dependencies": { "undici-types": "~7.11.0" } }, "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ=="], - "ast-kit/@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "bun-types/@types/node/undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="], } } diff --git a/npm/env.d.ts b/npm/env.d.ts index 6cbc82710acdb..ecfc2bbae10b3 100644 --- a/npm/env.d.ts +++ b/npm/env.d.ts @@ -7,6 +7,10 @@ interface ImportMetaEnv { readonly PROVENANCE: 'true' | 'false' + readonly REGISTRY_URL: string + + readonly TARGET_TOOL: 'forge' | 'cast' | 'anvil' | 'chisel' + // release.yml#jobs:release:strategy:matrix:include:-|target readonly TARGET: | 'x86_64-unknown-linux-gnu' @@ -17,12 +21,12 @@ interface ImportMetaEnv { | 'aarch64-apple-darwin' | 'x86_64-pc-windows-msvc' // - readonly ARCH: 'amd64' | 'arm64' + readonly ARCH: 'amd64' | 'arm64' | 'aarch64' readonly IS_NIGHTLY: 'true' | 'false' // `${(env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name}` readonly VERSION_NAME: string // release.yml#jobs:release:strategy:matrix:include:-|platform - readonly PLATFORM_NAME: 'linux' | 'alpine' | 'darwin' | 'win32' + readonly PLATFORM_NAME: 'linux' | 'darwin' | 'win32' // `debug` / `release` / `maxperf` # <- always `maxperf` readonly PROFILE: 'debug' | 'release' | 'maxperf' diff --git a/npm/package.json b/npm/package.json index 23d091b1cec0c..078ddc87ecabc 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,19 +1,16 @@ { "private": true, + "type": "module", "imports": { - "#src/*": "./src/*", + "#*": "./src/*", "#scripts/*": "./scripts/*", "#package.json": "./package.json" }, - "scripts": { - "build": "tsdown --config='tsdown.config.ts'", - "clean": "rm -rf bin dist && rm -rf ./@foundry-rs/forge*/bin ./@foundry-rs/forge*/dist ./@foundry-rs/forge*/*.tgz" - }, "dependencies": { - "tsdown": "^0.15.1", - "typescript": "^5.9.2", "@types/bun": "^1.2.22", - "@types/node": "^24.4.0" + "@types/node": "^24.5.2", + "bun": "^1.2.22", + "typescript": "^5.9.2" }, "license": "MIT OR Apache-2.0", "$schema": "https://json.schemastore.org/package.json" diff --git a/npm/scripts/prepublish.ts b/npm/scripts/prepublish.mjs similarity index 73% rename from npm/scripts/prepublish.ts rename to npm/scripts/prepublish.mjs index 13edff756d8d4..d879b8bc4394a 100644 --- a/npm/scripts/prepublish.ts +++ b/npm/scripts/prepublish.mjs @@ -4,22 +4,29 @@ import * as NodeFS from 'node:fs' import * as NodePath from 'node:path' import * as NodeUtil from 'node:util' -import { colors } from '#const.ts' +import { colors } from '#const.mjs' +import { generateBinaryPackageJson } from '../src/generate-package-json.mjs' + +/** + * @typedef {import('#const.mjs').Arch} Arch + * @typedef {import('#const.mjs').Platform} Platform + * @typedef {import('#const.mjs').Profile} Profile + */ const PRESERVED_FILES = ['package.json', 'README.md'] -const PLATFORM_MAP = { +const PLATFORM_MAP = /** @type {const} */ (/** @type {Record} */ ({ linux: 'linux', darwin: 'darwin', win32: 'win32' -} as const +})) -const TARGET_MAP = { +const TARGET_MAP = /** @type {const} */ (/** @type {Record<`${Arch}-${Platform}`, string>} */ ({ 'amd64-linux': 'x86_64-unknown-linux-gnu', 'arm64-linux': 'aarch64-unknown-linux-gnu', 'amd64-darwin': 'x86_64-apple-darwin', 'arm64-darwin': 'aarch64-apple-darwin', 'amd64-win32': 'x86_64-pc-windows-msvc' -} as const +})) main().catch(error => { console.error(colors.red, error) @@ -35,16 +42,17 @@ async function main() { await NodeFS.promises.mkdir(packagePath, { recursive: true, mode: 0o755 }) console.info(colors.green, `Ensured package directory at ${packagePath}`, colors.reset) + await generateBinaryPackageJson({ tool: 'forge', platform, arch, packagePath }) + await cleanPackageDirectory(packagePath) - await buildScripts() await copyBinary(forgeBinPath, packagePath, platform) console.info(colors.green, 'Binary copy completed successfully!', colors.reset) } function getPlatformInfo() { - const platformEnv = Bun.env.PLATFORM_NAME as keyof typeof PLATFORM_MAP - const archEnv = (Bun.env.ARCH || '') as 'amd64' | 'arm64' | 'aarch64' + const platformEnv = Bun.env.PLATFORM_NAME + const archEnv = Bun.env.ARCH || '' if (!platformEnv || !archEnv) throw new Error('PLATFORM_NAME and ARCH environment variables are required') @@ -71,8 +79,15 @@ function getPlatformInfo() { return { platform, arch, forgeBinPath } } -function findForgeBinary(arch: string, platform: string, profile: string) { - const targetDir = TARGET_MAP[`${arch}-${platform}` as keyof typeof TARGET_MAP] +/** + * @param {Arch} arch + * @param {Platform} platform + * @param {Profile} profile + * @returns {string} + */ +function findForgeBinary(arch, platform, profile) { + // @ts-ignore + const targetDir = TARGET_MAP[`${arch}-${platform}`] const targetPath = NodePath.join(process.cwd(), '..', 'target', targetDir, profile, 'forge') if (NodeFS.existsSync(targetPath)) @@ -81,7 +96,11 @@ function findForgeBinary(arch: string, platform: string, profile: string) { return NodePath.join(process.cwd(), '..', 'target', 'release', 'forge') } -async function cleanPackageDirectory(packagePath: string) { +/** + * @param {string} packagePath + * @returns {Promise} + */ +async function cleanPackageDirectory(packagePath) { const items = await NodeFS.promises .readdir(packagePath, { withFileTypes: true, recursive: true }) .catch(() => []) @@ -98,16 +117,13 @@ async function cleanPackageDirectory(packagePath: string) { console.info(colors.green, 'Cleaned up package directory', colors.reset) } -async function buildScripts() { - const result = await Bun.$`bun x tsdown --config tsdown.config.ts`.nothrow().quiet() - - if (result.exitCode !== 0) - throw new Error(`Failed to build scripts: ${result.stderr.toString()}`) - - console.info(colors.green, result.stdout.toString(), colors.reset) -} - -async function copyBinary(forgeBinPath: string, packagePath: string, platform: string) { +/** + * @param {string} forgeBinPath + * @param {string} packagePath + * @param {Platform} platform + * @returns {Promise} + */ +async function copyBinary(forgeBinPath, packagePath, platform) { if (!(await Bun.file(forgeBinPath).exists())) throw new Error(`Source binary not found at ${forgeBinPath}`) diff --git a/npm/scripts/publish.ts b/npm/scripts/publish.mjs similarity index 76% rename from npm/scripts/publish.ts rename to npm/scripts/publish.mjs index cba6dc56bfa35..0f582c8ebc555 100644 --- a/npm/scripts/publish.ts +++ b/npm/scripts/publish.mjs @@ -1,8 +1,9 @@ #!/usr/bin/env bun + import * as NodeFS from 'node:fs' import * as NodePath from 'node:path' -import { colors } from '#const.ts' +import { colors } from '#const.mjs' const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' @@ -35,12 +36,15 @@ async function main() { await publishPackage(packagePath, packedFile, publishVersion) } +/** + * @returns {string} + */ function getPublishVersion() { const maybeVersion = (Bun.env.VERSION_NAME || '').replace(/^v/, '') - if (maybeVersion && isValidSemver(maybeVersion)) return maybeVersion + if (maybeVersion && Bun.semver.satisfies(maybeVersion, maybeVersion)) return maybeVersion const bump = (Bun.env.BUMP_VERSION || '').replace(/^v/, '') - if (bump && isValidSemver(bump)) return bump + if (bump && (!!bump && Bun.semver.satisfies(bump, bump))) return bump const releaseVersion = (Bun.env.RELEASE_VERSION || '').replace(/^v/, '') const isNightly = releaseVersion.toLowerCase() === 'nightly' || Bun.env.IS_NIGHTLY === 'true' @@ -54,6 +58,7 @@ function getPublishVersion() { if (!versionMatch) throw new Error('Version not found in Cargo.toml') const [, base] = versionMatch + if (!base) throw new Error('Version not found in Cargo.toml') if (!isNightly) return base const date = new Date() @@ -66,11 +71,12 @@ function getPublishVersion() { return `${base}-${suffix}` } -function isValidSemver(v: string) { - return !!v && Bun.semver.satisfies(v, v) -} - -async function updateOptionalDependencies(packagePath: string, version: string) { +/** + * @param {string} packagePath + * @param {string} version + * @returns {Promise} + */ +async function updateOptionalDependencies(packagePath, version) { const packageJsonPath = NodePath.join(packagePath, 'package.json') const packageJson = JSON.parse(NodeFS.readFileSync(packageJsonPath, 'utf-8')) @@ -83,17 +89,28 @@ async function updateOptionalDependencies(packagePath: string, version: string) } } -async function isMetaPackage(packagePath: string) { +/** + * @param {string} packagePath + * @returns {Promise} + */ +async function isMetaPackage(packagePath) { try { const packageJsonPath = NodePath.join(packagePath, 'package.json') const packageJson = JSON.parse(NodeFS.readFileSync(packageJsonPath, 'utf-8')) - return packageJson?.name === '@foundry-rs/forge' + return ['@foundry-rs/forge', '@foundry-rs/cast', '@foundry-rs/anvil', '@foundry-rs/chisel'].includes( + packageJson?.name + ) } catch { return false } } -async function setPackageVersion(packagePath: string, version: string) { +/** + * @param {string} packagePath + * @param {string} version + * @returns {Promise} + */ +async function setPackageVersion(packagePath, version) { console.info(colors.green, 'Setting package version:', version) const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` .cwd(packagePath) @@ -109,7 +126,11 @@ async function setPackageVersion(packagePath: string, version: string) { throw new Error(`Failed to set version: ${result.stderr}`) } -async function packPackage(packagePath: string) { +/** + * @param {string} packagePath + * @returns {Promise} + */ +async function packPackage(packagePath) { let packedFile = '' for await (const line of Bun.$`bun pm pack`.cwd(packagePath).lines()) @@ -119,7 +140,13 @@ async function packPackage(packagePath: string) { return packedFile } -async function publishPackage(packagePath: string, packedFile: string, version: string) { +/** + * @param {string} packagePath + * @param {string} packedFile + * @param {string} version + * @returns {Promise} + */ +async function publishPackage(packagePath, packedFile, version) { const tag = /-nightly(\.|$)/.test(version) ? 'nightly' : 'latest' const result = await Bun .$`npm publish ./${packedFile} --access=public --registry=${REGISTRY_URL} --tag=${tag} --provenance=${ diff --git a/npm/scripts/setup-local.sh b/npm/scripts/setup-local.sh deleted file mode 100644 index 509b182cf84a2..0000000000000 --- a/npm/scripts/setup-local.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -NPM_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -REPO_ROOT="$(cd "$NPM_DIR/.." && pwd)" - -REGISTRY_URL="${NPM_REGISTRY_URL:-http://localhost:4873}" -export NPM_REGISTRY_URL="$REGISTRY_URL" - -# Parse host:port for npm config lookups -REGISTRY_HOSTPORT=$(echo "$REGISTRY_URL" | sed -E 's#^https?://##; s#/$##') - -echo "Using registry: $REGISTRY_URL" - -ensure_verdaccio() { - echo "Checking Verdaccio at $REGISTRY_URL..." >&2 - if curl -fsS "$REGISTRY_URL/-/ping" >/dev/null 2>&1; then - echo "Verdaccio is up." >&2 - # Ensure large uploads allowed (best-effort) - if docker ps --format '{{.Names}}' | grep -qx 'verdaccio'; then - if ! docker exec verdaccio sh -c "grep -q '^max_body_size:' /verdaccio/conf/config.yaml" 2>/dev/null; then - echo "Configuring Verdaccio max_body_size: 300mb" >&2 - docker exec verdaccio sh -c "printf '\nmax_body_size: 300mb\n' >> /verdaccio/conf/config.yaml" || true - docker restart verdaccio >/dev/null || true - for i in {1..30}; do - if curl -fsS "$REGISTRY_URL/-/ping" >/dev/null 2>&1; then - break - fi - sleep 1 - done - fi - fi - return 0 - fi - - if ! command -v docker >/dev/null 2>&1; then - echo "Verdaccio not reachable and Docker not available to start it." >&2 - exit 1 - fi - - echo "Starting Verdaccio via Docker..." >&2 - # Reuse existing container if present - if docker ps -a --format '{{.Names}}' | grep -qx 'verdaccio'; then - docker start verdaccio >/dev/null - else - docker run -d --name verdaccio -p 4873:4873 verdaccio/verdaccio >/dev/null - fi - - echo "Waiting for Verdaccio to become ready..." >&2 - - for i in {1..60}; do - if curl -fsS "$REGISTRY_URL/-/ping" >/dev/null 2>&1; then - echo "Verdaccio is ready." >&2 - # Ensure large uploads allowed - if ! docker exec verdaccio sh -c "grep -q '^max_body_size:' /verdaccio/conf/config.yaml" 2>/dev/null; then - echo "Configuring Verdaccio max_body_size: 300mb" >&2 - docker exec verdaccio sh -c "printf '\nmax_body_size: 300mb\n' >> /verdaccio/conf/config.yaml" || true - docker restart verdaccio >/dev/null || true - for j in {1..30}; do - if curl -fsS "$REGISTRY_URL/-/ping" >/dev/null 2>&1; then - break - fi - sleep 1 - done - fi - return 0 - fi - sleep 1 - done - echo "Timed out waiting for Verdaccio at $REGISTRY_URL" >&2 - exit 1 -} - -ensure_npm_login() { - echo "Checking npm authentication..." >&2 - if npm whoami --registry "$REGISTRY_URL" >/dev/null 2>&1; then - echo "Already logged in to $REGISTRY_URL" >&2 - else - local user="${NPM_USER:-foundry-rs}" - local pass="${NPM_PASSWORD:-foundry-rs}" - local mail="${NPM_EMAIL:-foundry-rs@example.com}" - echo "Logging in to $REGISTRY_URL as '$user'..." >&2 - if ! printf "%s\n%s\n%s\n" "$user" "$pass" "$mail" | npm adduser --registry "$REGISTRY_URL" --scope=@foundry-rs; then - echo "npm adduser failed. You can set NPM_USER/NPM_PASSWORD/NPM_EMAIL and retry." >&2 - exit 1 - fi - fi - - # Export tokens for scripts that require them - local token - token=$(npm config get "//$REGISTRY_HOSTPORT/:_authToken" 2>/dev/null || true) - if [[ -z "$token" || "$token" == "undefined" ]]; then - echo "Could not read npm auth token from config for //$REGISTRY_HOSTPORT/." >&2 - echo "Continuing; npm may still use session auth, but scripts expect NPM_TOKEN." >&2 - token="localtesttoken" - fi - export NPM_TOKEN="$token" - export NODE_AUTH_TOKEN="$token" -} - -derive_platform() { - ARCH=$(uname -m | awk '{print tolower($0)}') - case "$ARCH" in - aarch64) ARCH="arm64" ;; - x86_64) ARCH="amd64" ;; - esac - PLATFORM=$(uname -s | awk '{print tolower($0)}') - FORGE_PACKAGE_NAME="@foundry-rs/forge-${PLATFORM}-${ARCH}" -} - -build_wrappers_and_binary() { - echo "Building npm wrappers (bun)" >&2 - (cd "$NPM_DIR" && bun install && bun run build) - - echo "Building forge binary (cargo)" >&2 - (cd "$REPO_ROOT" && cargo build --release -p forge) -} - -stage_binary_for_package() { - echo "Staging binary into $FORGE_PACKAGE_NAME" >&2 - (cd "$NPM_DIR" && PLATFORM_NAME="$PLATFORM" ARCH="$ARCH" bun ./scripts/prepublish.ts) -} - -unpublish_if_present() { - echo "Unpublishing from $REGISTRY_URL (if present)" >&2 - npm unpublish @foundry-rs/forge --registry "$REGISTRY_URL" --force || true - npm unpublish "$FORGE_PACKAGE_NAME" --registry "$REGISTRY_URL" --force || true -} - -publish_packages() { - # npm config set max_body_size 300mb --registry "$REGISTRY_URL" - - echo "Publishing to $REGISTRY_URL" >&2 - (cd "$NPM_DIR" && bun scripts/publish.ts "$FORGE_PACKAGE_NAME") - (cd "$NPM_DIR" && bun scripts/publish.ts @foundry-rs/forge) -} - -ensure_verdaccio -ensure_npm_login -derive_platform -build_wrappers_and_binary -stage_binary_for_package -unpublish_if_present -publish_packages - -echo "Done. Test with: npm_config_registry=$REGISTRY_URL npx @foundry-rs/forge --version" >&2 diff --git a/npm/src/forge.ts b/npm/src/bin.mjs similarity index 51% rename from npm/src/forge.ts rename to npm/src/bin.mjs index ae84d55787fa8..2f664d3f86c55 100644 --- a/npm/src/forge.ts +++ b/npm/src/bin.mjs @@ -1,23 +1,39 @@ +import { BINARY_NAME, colors, PLATFORM_SPECIFIC_PACKAGE_NAME, resolveTargetTool } from '#const.mjs' import * as NodeChildProcess from 'node:child_process' import * as NodeFS from 'node:fs' import * as NodeModule from 'node:module' import * as NodePath from 'node:path' import { fileURLToPath } from 'node:url' -import { BINARY_NAME, colors, PLATFORM_SPECIFIC_PACKAGE_NAME } from './const.js' const require = NodeModule.createRequire(import.meta.url) const __dirname = NodePath.dirname(fileURLToPath(import.meta.url)) +const targetTool = resolveTargetTool() +const binaryName = BINARY_NAME(targetTool) +const platformSpecificPackageName = PLATFORM_SPECIFIC_PACKAGE_NAME(targetTool) +if (!platformSpecificPackageName) { + console.error(colors.red, 'Platform not supported!') + console.error(colors.reset) + console.error(colors.yellow, `Platform: ${process.platform}, Architecture: ${process.arch}`) + console.error(colors.reset) + process.exit(1) +} + +/** + * @returns {string} + */ function getBinaryPath() { try { - const binaryPath = require.resolve(`${PLATFORM_SPECIFIC_PACKAGE_NAME}/bin/${BINARY_NAME}`) + const binaryPath = require.resolve( + `${platformSpecificPackageName}/bin/${binaryName}` + ) if (NodeFS.existsSync(binaryPath)) return binaryPath } catch { // Fall back to the binary written by postinstall into dist/ - return NodePath.join(__dirname, '..', 'dist', BINARY_NAME) + return NodePath.join(__dirname, '..', 'dist', binaryName) } - console.error(colors.red, `Platform-specific package ${PLATFORM_SPECIFIC_PACKAGE_NAME} not found.`) + console.error(colors.red, `Platform-specific package ${platformSpecificPackageName} not found.`) console.error(colors.yellow, 'This usually means the installation failed or your platform is not supported.') console.error(colors.reset) console.error(colors.yellow, `Platform: ${process.platform}, Architecture: ${process.arch}`) diff --git a/npm/src/const.mjs b/npm/src/const.mjs new file mode 100644 index 0000000000000..26ef5672ef944 --- /dev/null +++ b/npm/src/const.mjs @@ -0,0 +1,85 @@ +import * as NodePath from 'node:path' + +/** + * @typedef {'amd64' | 'arm64'} Arch + * @typedef {'linux' | 'darwin' | 'win32'} Platform + * @typedef {'forge' | 'cast' | 'anvil' | 'chisel'} Tool + * @typedef {'debug' | 'release' | 'maxperf'} Profile + */ + +/** @type {readonly Tool[]} */ +export const KNOWN_TOOLS = Object.freeze(['forge', 'cast', 'anvil', 'chisel']) + +const TOOL_SET = new Set(KNOWN_TOOLS) + +/** + * @param {string | undefined} [raw] + * @returns {Tool} + */ +export function resolveTargetTool(raw = process.env.TARGET_TOOL) { + const value = typeof raw === 'string' ? raw.trim() : '' + if (!value) + throw new Error('TARGET_TOOL must be set to one of: ' + KNOWN_TOOLS.join(', ')) + if (value !== NodePath.basename(value) || value.includes('..') || value.includes('/') || value.includes('\\')) + throw new Error('TARGET_TOOL contains invalid path segments') + // @ts-expect-error _ + if (!TOOL_SET.has(value)) + throw new Error(`TARGET_TOOL "${value}" is not supported. Expected: ${KNOWN_TOOLS.join(', ')}`) + return /** @type {Tool} */ (value) +} + +export function getRegistryUrl() { + // Prefer npm's configured registry (works with Verdaccio and custom registries) + // Fallback to REGISTRY_URL for tests/dev, then npmjs + return ( + process.env.npm_config_registry + || process.env.REGISTRY_URL + || 'https://registry.npmjs.org' + ) +} + +/** + * @param {Tool} tool + * @returns {Record>} + */ +export const BINARY_DISTRIBUTION_PACKAGES = tool => ({ + darwin: { + x64: `@foundry-rs/${tool}-darwin-amd64`, + arm64: `@foundry-rs/${tool}-darwin-arm64` + }, + linux: { + x64: `@foundry-rs/${tool}-linux-amd64`, + arm64: `@foundry-rs/${tool}-linux-arm64` + }, + win32: { + x64: `@foundry-rs/${tool}-win32-amd64` + } +}) + +/** + * @param {Tool} tool + * @returns {string} + */ +export const BINARY_NAME = tool => process.platform === 'win32' ? `${tool}.exe` : tool + +/** + * @param {Tool} tool + * @returns {string | undefined} + */ +export const PLATFORM_SPECIFIC_PACKAGE_NAME = tool => { + // @ts-ignore + const platformPackages = BINARY_DISTRIBUTION_PACKAGES(tool)[process.platform] + if (!platformPackages) return undefined + return platformPackages?.[process.arch] +} + +export const colors = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + reset: '\x1b[0m' +} diff --git a/npm/src/const.ts b/npm/src/const.ts deleted file mode 100644 index 1ee67efd33835..0000000000000 --- a/npm/src/const.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type * as Process from 'node:process' - -export function getRegistryUrl() { - // Prefer npm's configured registry (works with Verdaccio and custom registries) - // Fallback to REGISTRY_URL for tests/dev, then npmjs - return ( - process.env.npm_config_registry - || process.env.REGISTRY_URL - || 'https://registry.npmjs.org' - ) -} - -export type Architecture = Extract<(typeof Process)['arch'], 'arm64' | 'x64'> -export type Platform = Extract< - (typeof Process)['platform'], - 'darwin' | 'linux' | 'win32' -> - -/** - * foundry doesn't ship arm64 binaries for windows - */ -export type ArchitecturePlatform = Exclude< - `${Platform}-${Architecture}`, - 'win32-arm64' -> - -export const BINARY_DISTRIBUTION_PACKAGES = { - darwin: { - x64: '@foundry-rs/forge-darwin-amd64', - arm64: '@foundry-rs/forge-darwin-arm64' - }, - linux: { - x64: '@foundry-rs/forge-linux-amd64', - arm64: '@foundry-rs/forge-linux-arm64' - }, - win32: { - x64: '@foundry-rs/forge-win32-amd64' - } -} as const - -export const BINARY_NAME = process.platform === 'win32' ? 'forge.exe' : 'forge' -// @ts-expect-error -export const PLATFORM_SPECIFIC_PACKAGE_NAME = BINARY_DISTRIBUTION_PACKAGES[process.platform][process.arch] - -export const colors = { - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - magenta: '\x1b[35m', - cyan: '\x1b[36m', - white: '\x1b[37m', - reset: '\x1b[0m' -} diff --git a/npm/src/generate-package-json.mjs b/npm/src/generate-package-json.mjs new file mode 100644 index 0000000000000..75e40cb27def1 --- /dev/null +++ b/npm/src/generate-package-json.mjs @@ -0,0 +1,108 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs/promises' +import * as NodePath from 'node:path' +import * as NodeUtil from 'node:util' + +import { colors } from '#const.mjs' + +/** + * @typedef {import('#const.mjs').Tool} Tool + * @typedef {import('#const.mjs').Arch} Arch + * @typedef {import('#const.mjs').Platform} Platform + * @typedef {{ + * tool: Tool + * platform: Platform + * arch: Arch + * packagePath: string + * }} GenerateOptions + */ + +const TOOL_META = /** @type {const} */ (/** @type {Record} */ ({ + forge: { + homepage: 'https://getfoundry.sh/forge', + description: 'Fast and flexible Ethereum testing framework' + }, + cast: { + homepage: 'https://getfoundry.sh/cast', + description: 'Swiss Army knife for interacting with Ethereum applications from the command line' + }, + anvil: { + homepage: 'https://getfoundry.sh/anvil', + description: 'Anvil is a fast local Ethereum development node' + }, + chisel: { + homepage: 'https://getfoundry.sh/chisel', + description: 'Chisel is a fast, utilitarian, and verbose Solidity REPL' + } +})) + +/** + * @param {GenerateOptions} options + * @returns {Promise} + */ +export async function generateBinaryPackageJson({ + tool, + platform, + arch, + packagePath +}) { + const packageJsonPath = NodePath.join(packagePath, 'package.json') + + const cpu = arch === 'amd64' ? 'x64' : 'arm64' + const isWindows = platform === 'win32' + const binName = isWindows ? `${tool}.exe` : tool + const humanPlatform = platform === 'darwin' ? 'macOS' : platform === 'win32' ? 'Windows' : 'Linux' + const { homepage, description } = TOOL_META[tool] + + const pkg = { + name: `@foundry-rs/${tool}-${platform}-${arch}`, + version: '0.0.0', + type: 'module', + homepage, + description: `${description} (${humanPlatform} ${cpu})`, + bin: { [tool]: binName }, + os: [platform], + cpu: [cpu], + files: ['bin'], + engines: { node: '>=20' }, + license: 'MIT OR Apache-2.0', + repository: { + directory: 'npm', + url: 'https://github.com/foundry-rs/foundry' + }, + keywords: ['foundry', 'testing', 'ethereum', 'solidity', 'blockchain', 'smart-contracts'], + publishConfig: { provenance: true } + } + + await Bun.write(packageJsonPath, JSON.stringify(pkg, null, 2)) + console.info(colors.green, `Wrote ${NodePath.relative(process.cwd(), packageJsonPath)}`, colors.reset) +} + +// CLI entrypoint so CI can call directly if desired +if (import.meta.main) { + const { values } = NodeUtil.parseArgs({ + args: Bun.argv, + options: { + tool: { type: 'string', default: Bun.env.TARGET_TOOL }, + platform: { type: 'string' }, + arch: { type: 'string' }, + out: { type: 'string' } + }, + strict: true + }) + + const tool = /** @type {Tool} */ (String(values.tool || Bun.env.TARGET_TOOL)) + const platform = /** @type {Platform} */ (values.platform || Bun.env.PLATFORM_NAME) + const arch = /** @type {Arch} */ (values.arch || Bun.env.ARCH) + const out = /** @type {string} */ (values.out || '') + + if (!platform || !arch) + throw new Error('platform and arch are required (flags or env PLATFORM_NAME/ARCH)') + if (!out) + throw new Error('out is required (path to per-arch package directory)') + + // Ensure the directory exists + await NodeFS.mkdir(out, { recursive: true }) + await generateBinaryPackageJson({ tool, platform, arch, packagePath: out }) +} diff --git a/npm/src/install.mjs b/npm/src/install.mjs new file mode 100644 index 0000000000000..34c036bea526e --- /dev/null +++ b/npm/src/install.mjs @@ -0,0 +1,360 @@ +import * as Bun from 'bun' +import * as NodeCrypto from 'node:crypto' +import * as NodeFS from 'node:fs' +import * as NodeHttp from 'node:http' +import * as NodeHttps from 'node:https' +import * as NodeModule from 'node:module' +import * as NodePath from 'node:path' +import { fileURLToPath } from 'node:url' +import * as NodeZlib from 'node:zlib' +import { BINARY_NAME, getRegistryUrl, PLATFORM_SPECIFIC_PACKAGE_NAME, resolveTargetTool } from './const.mjs' + +const __dirname = NodePath.dirname(fileURLToPath(import.meta.url)) +const targetTool = resolveTargetTool() +const binaryName = BINARY_NAME(targetTool) +const fallbackBinaryPath = NodePath.join(__dirname, binaryName) +const platformSpecificPackageName = PLATFORM_SPECIFIC_PACKAGE_NAME(targetTool) + +const expectedTarEntryPath = `package/bin/${binaryName}` + +if (NodePath.relative(__dirname, fallbackBinaryPath).startsWith('..')) + throw new Error('Resolved binary path escapes package directory') + +if (!platformSpecificPackageName) throw new Error('Platform not supported!') + +const require = NodeModule.createRequire(import.meta.url) + +/** + * Enforce HTTPS except for localhost, unless explicitly allowed + * @param {string} urlString + * @param {string} purpose + * @returns {void} + */ +function ensureSecureUrl(urlString, purpose) { + try { + const url = new URL(urlString) + if (url.protocol === 'http:') { + const allowInsecure = process.env.ALLOW_INSECURE_REGISTRY === 'true' + if ( + // Accept typical localhost variants by default + !['localhost', '127.0.0.1', '::1'].includes(url.hostname) + && !allowInsecure + ) { + throw new Error( + `Refusing to use insecure HTTP for ${purpose}: ${urlString}. ` + + `Set ALLOW_INSECURE_REGISTRY=true to override (not recommended).` + ) + } + } + } catch { + // If parsing fails, the request will fail so no need to do anything here + } +} + +const MAX_REDIRECTS = 10 +const REQUEST_TIMEOUT = 30_000 // 30s +const MAX_METADATA_BYTES = 5 * 1024 * 1024 // 5 MiB +const MAX_TARBALL_BYTES = 200 * 1024 * 1024 // 200 MiB +const MAX_BINARY_BYTES = 500 * 1024 * 1024 // 500 MiB + +/** + * @param {string} url + * @param {{ + * parentSignal?: AbortSignal + * redirectDepth?: number + * visited?: Set + * maxBytes?: number + * collect?: boolean + * onChunk?: (chunk: Buffer, response: import('node:http').IncomingMessage) => void + * } | undefined} options + * @returns {Promise} + */ +export function makeRequest(url, options = {}) { + const { + parentSignal, + redirectDepth = 0, + visited = new Set(), + maxBytes, + collect = true, + onChunk + } = options + + if (redirectDepth > MAX_REDIRECTS) throw new Error('Maximum redirect depth exceeded') + + if (visited.has(url)) throw new Error('Circular redirect detected') + visited.add(url) + + ensureSecureUrl(url, 'HTTP request') + + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT) + const signal = parentSignal + ? AbortSignal.any([parentSignal, controller.signal]) + : controller.signal + + return new Promise((resolve, reject) => { + const client = url.startsWith('https:') ? NodeHttps : NodeHttp + const request = client.get(url, { signal }, response => { + /** + * @param {Error | null} error + * @param {Buffer | undefined} value + */ + const finish = (error, value = undefined) => { + clearTimeout(timer) + if (error) reject(error) + else resolve(value) + } + + if ( + response?.statusCode + && response.statusCode >= 200 + && response.statusCode < 300 + ) { + let totalBytes = 0 + /** @type {Array | undefined} */ + const chunks = collect ? [] : undefined + + response.on('data', chunk => { + totalBytes += chunk.length + if (maxBytes && totalBytes > maxBytes) { + response.destroy(new Error('Response exceeded maximum allowed size')) + return + } + + if (chunks) chunks.push(chunk) + + if (onChunk) { + try { + onChunk(chunk, response) + } catch (error) { + response.destroy( + error instanceof Error ? error : new Error(`Error occurred: ${error}`) + ) + } + } + }) + + response.on('end', () => { + finish(null, chunks ? Buffer.concat(chunks) : undefined) + }) + + response.on('error', finish) + } else if ( + response?.statusCode + && response.statusCode >= 300 + && response.statusCode < 400 + && response.headers.location + ) { + clearTimeout(timer) + const nextUrl = new URL(response.headers.location, url).href + + return makeRequest(nextUrl, { + parentSignal: signal, + redirectDepth: redirectDepth + 1, + visited, + maxBytes, + collect, + onChunk + }).then(resolve, reject) + } else { + finish( + new Error( + `Package registry responded with status code ${ + response?.statusCode ?? '(none)' + } when downloading the package.` + ) + ) + } + }) + + request.on('error', error => { + clearTimeout(timer) + reject(error) + }) + }) +} + +/** + * Tar archives are organized in 512 byte blocks. + * Blocks can either be header blocks or data blocks. + * Header blocks contain file names of the archive in the first 100 bytes, terminated by a null byte. + * The size of a file is contained in bytes 124-135 of a header block and in octal format. + * The following blocks will be data blocks containing the file. + * @param {Buffer} tarballBuffer + * @param {string} filepath + * @returns {Buffer} + */ +function extractFileFromTarball(tarballBuffer, filepath) { + let offset = 0 + while (offset < tarballBuffer.length) { + const header = tarballBuffer.subarray(offset, offset + 512) + offset += 512 + + const fileName = header.toString('utf-8', 0, 100).replace(/\0.*/g, '') + const fileSize = Number.parseInt( + header.toString('utf-8', 124, 136).replace(/\0.*/g, ''), + 8 + ) + + if (fileName === filepath) { + if (!Number.isFinite(fileSize) || Number.isNaN(fileSize) || fileSize < 0) + throw new Error(`Invalid size for ${filepath} in tarball`) + if (fileSize > MAX_BINARY_BYTES) + throw new Error(`Binary size for ${filepath} exceeds maximum allowed threshold`) + return tarballBuffer.subarray(offset, offset + fileSize) + } + + // Clamp offset to the uppoer multiple of 512 + offset = (offset + fileSize + 511) & ~511 + } + throw new Error(`File ${filepath} not found in tarball`) +} + +async function downloadBinaryFromRegistry() { + if (!platformSpecificPackageName) + throw new Error('Platform-specific package name is not defined') + + const registryUrl = getRegistryUrl().replace(/\/$/, '') + ensureSecureUrl(registryUrl, 'registry URL') + + // Scoped package names should be percent-encoded + const encodedName = platformSpecificPackageName.startsWith('@') + ? encodeURIComponent(platformSpecificPackageName) + : platformSpecificPackageName + + // Determine which version to fetch: prefer the version pinned in optionalDependencies + /** @type {string | undefined} */ + let desiredVersion + try { + const pkgJsonPath = NodePath.join(__dirname, '..', 'package.json') + const pkgJson = JSON.parse(NodeFS.readFileSync(pkgJsonPath, 'utf8')) + desiredVersion = pkgJson?.optionalDependencies[platformSpecificPackageName] + || pkgJson?.version + } catch {} + + // Fetch metadata for the platform-specific package + const metaUrl = `${registryUrl}/${encodedName}` + const metaBuffer = await makeRequest(metaUrl, { maxBytes: MAX_METADATA_BYTES }) + if (!metaBuffer) + throw new Error('Failed to download package metadata') + const metadata = JSON.parse(metaBuffer.toString('utf8')) + + const version = desiredVersion || metadata?.['dist-tags']?.latest + const versionMeta = metadata?.versions?.[version] + const dist = versionMeta?.dist + if (!dist?.tarball) { + throw new Error( + `Could not find tarball for ${platformSpecificPackageName}@${version} from ${metaUrl}` + ) + } + + // Guard tarball URL scheme + ensureSecureUrl(dist.tarball, 'tarball URL') + + console.info( + Bun.color('green', 'ansi'), + 'Downloading binary from:\n', + dist.tarball, + '\n', + Bun.color('reset', 'ansi') + ) + + /** + * Download the tarball of the right binary distribution package + * Verify integrity: prefer SRI integrity (sha512/sha256/sha1), + * fallback to legacy dist.shasum (sha1 hex). Fail if neither unless explicitly allowed. + */ + const integrity = typeof dist.integrity === 'string' ? dist.integrity : '' + const sriMatch = integrity.match(/^([a-z0-9]+)-([A-Za-z0-9+/=]+)$/i) + const allowedSRIAlgorithms = new Set(['sha512', 'sha256', 'sha1']) + const sriAlgo = sriMatch && allowedSRIAlgorithms.has(sriMatch[1].toLowerCase()) + ? sriMatch[1].toLowerCase() + : undefined + const expectedSri = sriAlgo ? sriMatch?.[2] : undefined + const sriHasher = sriAlgo ? NodeCrypto.createHash(sriAlgo) : undefined + + const expectedSha1Hex = typeof dist.shasum === 'string' && dist.shasum.length === 40 + ? dist.shasum.toLowerCase() + : undefined + const sha1Hasher = expectedSha1Hex ? NodeCrypto.createHash('sha1') : undefined + + const tarballDownloadBuffer = await makeRequest(dist.tarball, { + maxBytes: MAX_TARBALL_BYTES, + onChunk: chunk => { + sriHasher?.update(chunk) + sha1Hasher?.update(chunk) + } + }) + + if (!tarballDownloadBuffer) + throw new Error('Failed to download tarball contents') + + let verified = false + + if (sriHasher && expectedSri) { + const actual = sriHasher.digest('base64') + if (expectedSri !== actual) { + throw new Error( + `Downloaded tarball failed integrity check (${sriAlgo} mismatch)` + ) + } + verified = true + } + + if (!verified && sha1Hasher && expectedSha1Hex) { + const actualSha1Hex = sha1Hasher.digest('hex') + if (expectedSha1Hex !== actualSha1Hex) { + throw new Error( + 'Downloaded tarball failed integrity check (sha1 shasum mismatch)' + ) + } + verified = true + } + + if (!verified) { + const allowNoIntegrity = process.env.ALLOW_NO_INTEGRITY === 'true' + || process.env.ALLOW_UNVERIFIED_TARBALL === 'true' + if (!allowNoIntegrity) { + throw new Error( + 'No integrity metadata found for downloaded tarball. ' + + 'Set ALLOW_NO_INTEGRITY=true to bypass (not recommended).' + ) + } + console.warn( + Bun.color('yellow', 'ansi'), + 'Warning: proceeding without integrity verification (explicitly allowed).', + Bun.color('reset', 'ansi') + ) + } + + // Unpack and write binary + const tarballBuffer = NodeZlib.gunzipSync(tarballDownloadBuffer) + + NodeFS.writeFileSync( + fallbackBinaryPath, + extractFileFromTarball(tarballBuffer, expectedTarEntryPath), + { mode: 0o755 } // Make binary file executable + ) +} + +function isPlatformSpecificPackageInstalled() { + try { + // Resolving will fail if the optionalDependency was not installed + require.resolve(`${platformSpecificPackageName}/bin/${binaryName}`) + return true + } catch { + return false + } +} + +// Skip downloading the binary if it was already installed via optionalDependencies +if (!isPlatformSpecificPackageInstalled()) { + console.log( + 'Platform specific package not found. Will manually download binary.' + ) + downloadBinaryFromRegistry() +} else { + console.log( + 'Platform specific package already installed. Skipping manual download.' + ) +} diff --git a/npm/src/install.ts b/npm/src/install.ts deleted file mode 100644 index b93a4e1d4ee54..0000000000000 --- a/npm/src/install.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { BINARY_NAME, colors, getRegistryUrl, PLATFORM_SPECIFIC_PACKAGE_NAME } from '#const.ts' -import * as NodeCrypto from 'node:crypto' -import * as NodeFS from 'node:fs' -import * as NodeHttp from 'node:http' -import * as NodeHttps from 'node:https' -import * as NodeModule from 'node:module' -import * as NodePath from 'node:path' -import { fileURLToPath } from 'node:url' -import * as NodeZlib from 'node:zlib' - -const __dirname = NodePath.dirname(fileURLToPath(import.meta.url)) -const fallbackBinaryPath = NodePath.join(__dirname, BINARY_NAME) - -const require = NodeModule.createRequire(import.meta.url) - -// Accept typical localhost variants by default -const isLocalhostHost = (hostname: string) => ( - hostname === 'localhost' - || hostname === '127.0.0.1' - || hostname === '::1' -) - -// Enforce HTTPS except for localhost, unless explicitly allowed -function ensureSecureUrl(urlString: string, purpose: string) { - try { - const url = new URL(urlString) - if (url.protocol === 'http:') { - const allowInsecure = process.env.ALLOW_INSECURE_REGISTRY === 'true' - if (!isLocalhostHost(url.hostname) && !allowInsecure) { - throw new Error( - `Refusing to use insecure HTTP for ${purpose}: ${urlString}. ` - + `Set ALLOW_INSECURE_REGISTRY=true to override (not recommended).` - ) - } - } - } catch { - // If parsing fails, the request will fail so no need to do anything here - } -} - -function makeRequest(url: string): Promise { - return new Promise((resolve, reject) => { - ensureSecureUrl(url, 'HTTP request') - - const client = url.startsWith('https:') ? NodeHttps : NodeHttp - client - .get(url, response => { - if (response?.statusCode && response.statusCode >= 200 && response.statusCode < 300) { - const chunks: Array = [] - - response.on('data', chunk => chunks.push(chunk)) - response.on('end', () => resolve(Buffer.concat(chunks))) - } else if ( - response?.statusCode - && response.statusCode >= 300 - && response.statusCode < 400 - && response.headers.location - ) { - // Follow redirects - const redirected = (() => { - try { - return new URL(response.headers.location, url).href - } catch { - return response.headers.location - } - })() - makeRequest(redirected).then(resolve, reject) - } else { - reject( - new Error( - `Package registry responded with status code ${response.statusCode} when downloading the package.` - ) - ) - } - }) - .on('error', error => reject(error)) - }) -} - -/** - * Scoped package names should be percent-encoded - * e.g. @scope/pkg -> %40scope%2Fpkg - */ -const encodePackageNameForRegistry = (name: string) => name.startsWith('@') ? encodeURIComponent(name) : name - -/** - * Tar archives are organized in 512 byte blocks. - * Blocks can either be header blocks or data blocks. - * Header blocks contain file names of the archive in the first 100 bytes, terminated by a null byte. - * The size of a file is contained in bytes 124-135 of a header block and in octal format. - * The following blocks will be data blocks containing the file. - */ -function extractFileFromTarball( - tarballBuffer: Buffer, - filepath: string -): Buffer { - let offset = 0 - while (offset < tarballBuffer.length) { - const header = tarballBuffer.subarray(offset, offset + 512) - offset += 512 - - const fileName = header.toString('utf-8', 0, 100).replace(/\0.*/g, '') - const fileSize = Number.parseInt(header.toString('utf-8', 124, 136).replace(/\0.*/g, ''), 8) - - if (fileName === filepath) - return tarballBuffer.subarray(offset, offset + fileSize) - - // Clamp offset to the uppoer multiple of 512 - offset = (offset + fileSize + 511) & ~511 - } - throw new Error(`File ${filepath} not found in tarball`) -} - -async function downloadBinaryFromRegistry() { - const registryUrl = getRegistryUrl().replace(/\/$/, '') - ensureSecureUrl(registryUrl, 'registry URL') - - const encodedName = encodePackageNameForRegistry(PLATFORM_SPECIFIC_PACKAGE_NAME) - - // Determine which version to fetch: prefer the version pinned in optionalDependencies - let desiredVersion: string | undefined - try { - const pkgJsonPath = NodePath.join(__dirname, '..', 'package.json') - const pkgJson = JSON.parse(NodeFS.readFileSync(pkgJsonPath, 'utf8')) - desiredVersion = pkgJson?.optionalDependencies?.[PLATFORM_SPECIFIC_PACKAGE_NAME] || pkgJson?.version - } catch {} - - // Fetch metadata for the platform-specific package - const metaUrl = `${registryUrl}/${encodedName}` - const metaBuffer = await makeRequest(metaUrl) - const metadata = JSON.parse(metaBuffer.toString('utf8')) - - const version = desiredVersion || metadata?.['dist-tags']?.latest - const versionMeta = metadata?.versions?.[version] - const dist = versionMeta?.dist - if (!dist?.tarball) { - throw new Error( - `Could not find tarball for ${PLATFORM_SPECIFIC_PACKAGE_NAME}@${version} from ${metaUrl}` - ) - } - - // Guard tarball URL scheme - ensureSecureUrl(dist.tarball, 'tarball URL') - - console.info( - colors.green, - 'Downloading binary from:\n', - dist.tarball, - '\n', - colors.reset - ) - - /** - * Download the tarball of the right binary distribution package - * Verify integrity: prefer SRI integrity (sha512/sha256/sha1), - * fallback to legacy dist.shasum (sha1 hex). Fail if neither unless explicitly allowed. - */ - const tarballDownloadBuffer = await makeRequest(dist.tarball) - ;(() => { - let verified = false - - const integrity = typeof dist.integrity === 'string' ? dist.integrity : '' - const sriMatch = integrity.match(/^([a-z0-9]+)-([A-Za-z0-9+/=]+)$/i) - if (sriMatch) { - const algo = sriMatch[1].toLowerCase() - const expected = sriMatch[2] - const allowed = new Set(['sha512', 'sha256', 'sha1']) - if (allowed.has(algo)) { - const actual = NodeCrypto.createHash(algo as 'sha512') - .update(tarballDownloadBuffer) - .digest('base64') - if (expected !== actual) - throw new Error(`Downloaded tarball failed integrity check (${algo} mismatch)`) - verified = true - } - } - - if (!verified && typeof dist.shasum === 'string' && dist.shasum.length === 40) { - const expectedSha1Hex = dist.shasum.toLowerCase() - const actualSha1Hex = NodeCrypto.createHash('sha1') - .update(tarballDownloadBuffer) - .digest('hex') - if (expectedSha1Hex !== actualSha1Hex) - throw new Error('Downloaded tarball failed integrity check (sha1 shasum mismatch)') - verified = true - } - - if (!verified) { - const allowNoIntegrity = process.env.ALLOW_NO_INTEGRITY === 'true' - || process.env.ALLOW_UNVERIFIED_TARBALL === 'true' - if (!allowNoIntegrity) { - throw new Error( - 'No integrity metadata found for downloaded tarball. ' - + 'Set ALLOW_NO_INTEGRITY=true to bypass (not recommended).' - ) - } - console.warn( - colors.yellow, - 'Warning: proceeding without integrity verification (explicitly allowed).', - colors.reset - ) - } - })() - - // Unpack and write binary - const tarballBuffer = NodeZlib.gunzipSync(tarballDownloadBuffer) - - NodeFS.writeFileSync( - fallbackBinaryPath, - extractFileFromTarball(tarballBuffer, `package/bin/${BINARY_NAME}`), - { mode: 0o755 } // Make binary file executable - ) -} - -function isPlatformSpecificPackageInstalled() { - try { - // Resolving will fail if the optionalDependency was not installed - require.resolve(`${PLATFORM_SPECIFIC_PACKAGE_NAME}/bin/${BINARY_NAME}`) - return true - } catch (_error) { - return false - } -} - -if (!PLATFORM_SPECIFIC_PACKAGE_NAME) - throw new Error('Platform not supported!') - -// Skip downloading the binary if it was already installed via optionalDependencies -if (!isPlatformSpecificPackageInstalled()) { - console.log('Platform specific package not found. Will manually download binary.') - downloadBinaryFromRegistry() -} else { - console.log('Platform specific package already installed. Skipping manual download.') -} diff --git a/npm/tsconfig.json b/npm/tsconfig.json index da5cf01414aaa..4dd43e87de48a 100644 --- a/npm/tsconfig.json +++ b/npm/tsconfig.json @@ -32,14 +32,6 @@ "bun", "node" ], - "paths": { - "#*": [ - "./src/*" - ], - "#package.json": [ - "./package.json" - ] - } }, "exclude": [ "dist", @@ -48,11 +40,12 @@ ], "include": [ "./src/**/*", + "./src/*.mjs", + "./scripts/**/*" ], "files": [ "env.d.ts", - "package.json", - "tsdown.config.ts" + "package.json" ] } diff --git a/npm/tsdown.config.ts b/npm/tsdown.config.ts deleted file mode 100644 index fb11867871338..0000000000000 --- a/npm/tsdown.config.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as NodeFS from 'node:fs' -import * as NodePath from 'node:path' -import { defineConfig, type UserConfig } from 'tsdown' - -const shebang = /* sh */ `#!/usr/bin/env node -` - -const config = { - dts: false, - clean: true, - format: ['es'], - target: 'node20', - platform: 'node', - skipNodeModulesBundle: true, - outExtensions: () => ({ js: '.mjs' }), - onSuccess: ({ name }) => console.info(`🎉 [${name}] Build complete!`), - hooks: { - 'build:before': ({ options }) => { - const packagePath = options.env?.PACKAGE_PATH - if (!packagePath) return - - NodeFS.readdirSync(packagePath, { withFileTypes: true }) - .filter(item => !['package.json', 'README.md'].includes(item.name)) - .forEach(item => { - NodeFS.rmSync(NodePath.join(packagePath, item.name), { - recursive: true, - force: true - }) - }) - }, - 'build:done': ({ options }) => { - // prepend shebang to the file - const normalizedPath = NodePath.join( - options.outDir, - `${options.name}.mjs` - ) - NodeFS.writeFileSync( - normalizedPath, - shebang + NodeFS.readFileSync(normalizedPath, { encoding: 'utf8' }), - { encoding: 'utf8' } - ) - } - } -} as const satisfies UserConfig - -export default [ - defineConfig({ - ...config, - name: 'forge', - env: { - PACKAGE_PATH: './@foundry-rs/forge' - }, - outDir: './@foundry-rs/forge/bin', - entry: ['./src/forge.ts'] - }), - defineConfig({ - ...config, - name: 'install', - outDir: './@foundry-rs/forge/dist', - outExtensions: () => ({ js: '.mjs' }), - entry: ['./src/install.ts'] - }) -]