diff --git a/.gitattributes b/.gitattributes index c3c54b1cfce95..9269e20e90012 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ crates/cheatcodes/assets/*.json linguist-generated testdata/cheats/Vm.sol linguist-generated +bun.lock linguist-generated # See *.rs diff=rust diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5aa6e98a1c389..d7b5a7ee02905 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -272,6 +272,60 @@ jobs: ${{ steps.artifacts.outputs.foundry_attestation }} ${{ steps.man.outputs.foundry_man }} + - name: Setup Bun (npm) + uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + with: + bun-version: "latest" + scope: "@foundry-rs" + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + working-directory: ./npm + run: bun install --frozen-lockfile + + - name: Publish @foundry-rs/forge-${{ matrix.platform }}-${{ matrix.arch }} (npm) + shell: bash + working-directory: ./npm + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_REGISTRY_URL: "https://registry.npmjs.org" + ARCH: ${{ matrix.arch }} + TARGET: ${{ matrix.target }} + PLATFORM: ${{ matrix.platform }} + OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + run: |- + bun ./scripts/prepublish.ts + bun run ./scripts/publish.ts ./@foundry-rs/forge-${{ env.PLATFORM }}-${{ env.ARCH }} + + publish-npm: + name: Publish @foundry-rs/forge + runs-on: ubuntu-latest + needs: release + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 + with: + bun-version: "latest" + scope: "@foundry-rs" + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + working-directory: ./npm + run: bun install --frozen-lockfile + + - name: Publish @foundry-rs/forge + shell: bash + working-directory: ./npm + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_REGISTRY_URL: "https://registry.npmjs.org" + run: |- + bun ./scripts/prepublish.ts + bun run ./scripts/publish.ts ./@foundry-rs/forge + cleanup: name: Release cleanup runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 64af508ada43c..52c326c7c494a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ CLAUDE.md node_modules dist bin -_ \ No newline at end of file +_ +*.tgz diff --git a/dprint.json b/dprint.json index c534f9106fd29..b9b42a69b6378 100644 --- a/dprint.json +++ b/dprint.json @@ -1,29 +1,25 @@ { "$schema": "https://raw.githubusercontent.com/dprint/dprint/refs/heads/main/website/src/assets/schemas/v0.json", - "incremental": true, "indentWidth": 2, "useTabs": false, - "includes": [ - "*.md", - "*.{toml}", - "Dockerfile", - "*.{yml,yaml}", - "*.{json,jsonc}", - "*.{js,cjs,mjs,d.ts,d.cts,d.mts,ts,tsx,jsx}" - ], "excludes": [ + "!npm/**/*.{json,md,toml}", + "!npm/**/*.{js,cjs,mjs,d.ts,d.cts,d.mts,ts,tsx,jsx}", + "**/_", + "dprint.json", + "**/abi", "**/build", - "**/abi/**", "**/target", "**/test/**", "**/*.min.*", "**/dist/**", "testdata/**", "**/tests/**", + "node_modules", "changelog.json", "**/test-data/**", - "**/node_modules", "**/cheatcodes/**", + "**/node_modules/**", ".github/scripts/**", ".github/ISSUE_TEMPLATE/**" ], @@ -40,13 +36,15 @@ "textWrap": "maintain" }, "toml": { - "columnWidth": 100 + "lineWidth": 100 }, "json": { - "useTabs": false, + "lineWidth": 1, "indentWidth": 2, + "useTabs": false, "trailingCommas": "never", - "array.preferSingleLine": true + "preferSingleLine": false, + "array.preferSingleLine": false }, "typescript": { "useTabs": false, @@ -59,4 +57,4 @@ "exportDeclaration.sortTypeOnlyExports": "none", "importDeclaration.sortTypeOnlyImports": "none" } -} +} \ No newline at end of file diff --git a/npm/.env.example b/npm/.env.example new file mode 100644 index 0000000000000..1459b209934c9 --- /dev/null +++ b/npm/.env.example @@ -0,0 +1,8 @@ +NODE_ENV="development" + +NPM_TOKEN="" +NPM_REGISTRY_URL="" +NPM_USERNAME="foundry-rs" + +PLATFORM_NAME="" +ARCH="" \ No newline at end of file diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 0000000000000..9d8d11def7774 --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1,38 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +forge/*/bin/forge +@foundry-rs/*/bin/ +test/workspace/bun.lock \ No newline at end of file diff --git a/npm/@foundry-rs/forge-darwin-amd64/README.md b/npm/@foundry-rs/forge-darwin-amd64/README.md new file mode 100644 index 0000000000000..87b0255019198 --- /dev/null +++ b/npm/@foundry-rs/forge-darwin-amd64/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000000..16533db33e8d8 --- /dev/null +++ b/npm/@foundry-rs/forge-darwin-amd64/package.json @@ -0,0 +1,35 @@ +{ + "name": "@foundry-rs/forge-darwin-amd64", + "version": "1.2.3", + "type": "module", + "homepage": "https://getfoundry.sh", + "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" + ] +} diff --git a/npm/@foundry-rs/forge-darwin-arm64/README.md b/npm/@foundry-rs/forge-darwin-arm64/README.md new file mode 100644 index 0000000000000..61f4dbb6e15c3 --- /dev/null +++ b/npm/@foundry-rs/forge-darwin-arm64/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000000..6c37ac13a434f --- /dev/null +++ b/npm/@foundry-rs/forge-darwin-arm64/package.json @@ -0,0 +1,35 @@ +{ + "name": "@foundry-rs/forge-darwin-arm64", + "version": "1.3.0", + "type": "module", + "homepage": "https://getfoundry.sh", + "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" + ] +} diff --git a/npm/@foundry-rs/forge-linux-amd64/README.md b/npm/@foundry-rs/forge-linux-amd64/README.md new file mode 100644 index 0000000000000..c1731c7427772 --- /dev/null +++ b/npm/@foundry-rs/forge-linux-amd64/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000000..4c18aef4b0c44 --- /dev/null +++ b/npm/@foundry-rs/forge-linux-amd64/package.json @@ -0,0 +1,35 @@ +{ + "name": "@foundry-rs/forge-linux-amd64", + "version": "1.2.3", + "type": "module", + "homepage": "https://getfoundry.sh", + "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" + ] +} diff --git a/npm/@foundry-rs/forge-linux-arm64/README.md b/npm/@foundry-rs/forge-linux-arm64/README.md new file mode 100644 index 0000000000000..2e209e9b965d6 --- /dev/null +++ b/npm/@foundry-rs/forge-linux-arm64/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000000..69d6ad050fd9e --- /dev/null +++ b/npm/@foundry-rs/forge-linux-arm64/package.json @@ -0,0 +1,35 @@ +{ + "name": "@foundry-rs/forge-linux-arm64", + "version": "1.2.3", + "type": "module", + "homepage": "https://getfoundry.sh", + "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" + ] +} diff --git a/npm/@foundry-rs/forge-win32-amd64/README.md b/npm/@foundry-rs/forge-win32-amd64/README.md new file mode 100644 index 0000000000000..bec6364573068 --- /dev/null +++ b/npm/@foundry-rs/forge-win32-amd64/README.md @@ -0,0 +1,4 @@ +# 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 new file mode 100644 index 0000000000000..3b488b1a64cbb --- /dev/null +++ b/npm/@foundry-rs/forge-win32-amd64/package.json @@ -0,0 +1,35 @@ +{ + "name": "@foundry-rs/forge-win32-amd64", + "version": "1.2.3", + "type": "module", + "homepage": "https://getfoundry.sh", + "description": "Fast and flexible Ethereum testing framework (Windows amd64)", + "bin": { + "forge": "./bin/forge" + }, + "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" + ] +} diff --git a/npm/@foundry-rs/forge/README.md b/npm/@foundry-rs/forge/README.md new file mode 100644 index 0000000000000..459d0ac64956d --- /dev/null +++ b/npm/@foundry-rs/forge/README.md @@ -0,0 +1,4 @@ +# forge + +A CLI tool for testing, building, and deploying your smart contracts. +See for details. diff --git a/npm/@foundry-rs/forge/package.json b/npm/@foundry-rs/forge/package.json new file mode 100644 index 0000000000000..72b92df66815d --- /dev/null +++ b/npm/@foundry-rs/forge/package.json @@ -0,0 +1,37 @@ +{ + "name": "@foundry-rs/forge", + "version": "1.3.0", + "type": "module", + "homepage": "https://getfoundry.sh", + "description": "Fast and flexible Ethereum testing framework", + "main": "./dist/index.mjs", + "engines": { + "node": ">=20" + }, + "bin": { + "forge": "./bin/forge.mjs" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "postinstall": "node ./dist/install.mjs" + }, + "optionalDependencies": { + "@foundry-rs/forge-darwin-arm64": "1.3.0", + "@foundry-rs/forge-darwin-amd64": "1.3.0", + "@foundry-rs/forge-linux-arm64": "1.3.0", + "@foundry-rs/forge-linux-amd64": "1.3.0", + "@foundry-rs/forge-win32-amd64": "1.3.0" + }, + "publishConfig": { + "access": "public", + "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/bun.lock b/npm/bun.lock new file mode 100644 index 0000000000000..1e8418cc95339 --- /dev/null +++ b/npm/bun.lock @@ -0,0 +1,158 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "dependencies": { + "@types/bun": "^1.2.18", + "@types/node": "^24.0.15", + "tsdown": "^0.12.9", + "typescript": "^5.8.3", + }, + }, + }, + "packages": { + "@babel/generator": ["@babel/generator@7.27.5", "", { "dependencies": { "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], + + "@babel/types": ["@babel/types@7.28.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg=="], + + "@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" } }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" } }, "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA=="], + + "@oxc-project/runtime": ["@oxc-project/runtime@0.71.0", "", {}, "sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw=="], + + "@oxc-project/types": ["@oxc-project/types@0.71.0", "", {}, "sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w=="], + + "@quansync/fs": ["@quansync/fs@0.1.3", "", { "dependencies": { "quansync": "^0.2.10" } }, "sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "darwin", "cpu": "x64" }, "sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm" }, "sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "arm64" }, "sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5", "", { "os": "linux", "cpu": "x64" }, "sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.4" }, "cpu": "none" }, "sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w=="], + + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "ia32" }, "sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5", "", { "os": "win32", "cpu": "x64" }, "sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5", "", {}, "sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="], + + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + + "@types/node": ["@types/node@24.0.15", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA=="], + + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], + + "ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="], + + "ast-kit": ["ast-kit@2.1.0", "", { "dependencies": { "@babel/parser": "^7.27.3", "pathe": "^2.0.3" } }, "sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew=="], + + "birpc": ["birpc@2.4.0", "", {}, "sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg=="], + + "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + + "dts-resolver": ["dts-resolver@2.1.1", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw=="], + + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + + "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], + + "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.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "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.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="], + + "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.9-commit.d91dfb5", "", { "dependencies": { "@oxc-project/runtime": "0.71.0", "@oxc-project/types": "0.71.0", "@rolldown/pluginutils": "1.0.0-beta.9-commit.d91dfb5", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-darwin-arm64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-darwin-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-freebsd-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.9-commit.d91dfb5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.13.13", "", { "dependencies": { "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.7", "@babel/types": "^7.27.7", "ast-kit": "^2.1.0", "birpc": "^2.4.0", "debug": "^4.4.1", "dts-resolver": "^2.1.1", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~2.2.0" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-Nchx9nQoa4IpfQ/BJzodKMvtJ3H3dT322siAJSp3uvQJ+Pi1qgEjOp7hSQwGSQRhaC5gC+9hparbWEH5oiAL9Q=="], + + "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.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + + "tsdown": ["tsdown@0.12.9", "", { "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": "^1.0.0-beta.19", "rolldown-plugin-dts": "^0.13.12", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "unconfig": "^7.3.2" }, "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-MfrXm9PIlT3saovtWKf/gCJJ/NQCdE0SiREkdNC+9Qy6UHhdeDPxnkFaBD7xttVUmgp0yUHtGirpoLB+OVLuLA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "unconfig": ["unconfig@7.3.2", "", { "dependencies": { "@quansync/fs": "^0.1.1", "defu": "^6.1.4", "jiti": "^2.4.2", "quansync": "^0.2.8" } }, "sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg=="], + + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + + "@babel/generator/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="], + + "@babel/generator/@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="], + + "ast-kit/@babel/parser": ["@babel/parser@7.27.5", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg=="], + + "bun-types/@types/node": ["@types/node@24.0.12", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-LtOrbvDf5ndC9Xi+4QZjVL0woFymF/xSTKZKPgrrl7H7XoeDvnD+E2IclKVDyaK9UM756W/3BXqSU+JEHopA9g=="], + + "ast-kit/@babel/parser/@babel/types": ["@babel/types@7.27.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q=="], + } +} diff --git a/npm/bunfig.toml b/npm/bunfig.toml new file mode 100644 index 0000000000000..20f02ac0fb481 --- /dev/null +++ b/npm/bunfig.toml @@ -0,0 +1,11 @@ +telemetry = false + +run.bun = true + +[install] +auto = "auto" + +[install.scopes] +"@foundry-rs" = { url = "$NPM_REGISTRY_URL", username = "$NPM_USERNAME" } + +logLevel = "debug" diff --git a/npm/env.d.ts b/npm/env.d.ts new file mode 100644 index 0000000000000..a08a874c35c1e --- /dev/null +++ b/npm/env.d.ts @@ -0,0 +1,42 @@ +interface ImportMetaEnv { + readonly CI: string + readonly NPM_TOKEN: string + readonly BUN_AUTH_TOKEN: string + + readonly NODE_ENV: 'development' | 'production' + + // release.yml#jobs:release:strategy:matrix:include:-|target + readonly TARGET: + | 'x86_64-unknown-linux-gnu' + | 'x86_64-unknown-linux-musl' + | 'aarch64-unknown-linux-gnu' + | 'aarch64-unknown-linux-musl' + | 'x86_64-apple-darwin' + | 'aarch64-apple-darwin' + | 'x86_64-pc-windows-msvc' + // + readonly ARCH: 'amd64' | 'arm64' + // `target/$TARGET/$PROFILE` + readonly OUT_DIR: `target/${TARGET}/${PROFILE}` + 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' + // `$OUT_DIR/forge$ext # <- .exe or empty string` + readonly EXT: '.exe' | '' + // `debug` / `release` / `maxperf` # <- always `maxperf` + readonly PROFILE: 'debug' | 'release' | 'maxperf' +} + +declare namespace NodeJS { + interface ProcessEnv extends ImportMetaEnv {} +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + +declare namespace Bun { + interface Env extends ImportMetaEnv {} +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000000000..641a8da68ebe3 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "scripts": { + "build": "tsdown --config='tsdown.config.ts'", + "prepublishOnly": "bun build && bun run ./scripts/prepublish.ts" + }, + "dependencies": { + "tsdown": "^0.12.9", + "typescript": "^5.8.3", + "@types/bun": "^1.2.18", + "@types/node": "^24.0.15" + }, + "license": "MIT OR Apache-2.0", + "$schema": "https://json.schemastore.org/package.json" +} diff --git a/npm/scripts/clean.sh b/npm/scripts/clean.sh new file mode 100644 index 0000000000000..942b85eff8a16 --- /dev/null +++ b/npm/scripts/clean.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -eou pipefail + +rm -rf bin dist +rm -rf ./@foundry-rs/forge*/bin ./@foundry-rs/forge*/dist ./@foundry-rs/forge*/*.tgz \ No newline at end of file diff --git a/npm/scripts/prepublish.ts b/npm/scripts/prepublish.ts new file mode 100644 index 0000000000000..3c1a29742ed7b --- /dev/null +++ b/npm/scripts/prepublish.ts @@ -0,0 +1,126 @@ +#!/usr/bin/env bun + +import * as NodeFS from 'node:fs' +import * as NodePath from 'node:path' +import * as NodeUtil from 'node:util' +import { colors } from '../src/utilities.ts' + +const PRESERVED_FILES = ['package.json', 'README.md'] +const PLATFORM_MAP = { + linux: 'linux', + alpine: 'linux', + darwin: 'darwin', + win32: 'win32' +} as const + +const TARGET_MAP = { + 'amd64-linux': 'x86_64-unknown-linux-gnu', + 'amd64-alpine': 'x86_64-unknown-linux-musl', + 'arm64-linux': 'aarch64-unknown-linux-gnu', + 'arm64-alpine': 'aarch64-unknown-linux-musl', + 'amd64-darwin': 'x86_64-apple-darwin', + 'arm64-darwin': 'aarch64-apple-darwin', + 'amd64-win32': 'x86_64-pc-windows-msvc' +} as const + +const ARCH_MAP = { amd64: 'x64', arm64: 'arm64', aarch64: 'arm64' } as const + +main().catch(error => { + console.error(colors.red, error) + process.exit(1) +}) + +async function main() { + const { platform, arch, forgeBinPath } = getPlatformInfo() + const distribution = `${platform}-${arch}` + const packagePath = NodePath.join(process.cwd(), '@foundry-rs', `forge-${distribution}`) + + await cleanPackageDirectory(packagePath) + await buildScripts() + await copyBinary(forgeBinPath, packagePath, platform) + + console.info(colors.green, 'Binary copy completed successfully!', colors.reset) +} + +function getPlatformInfo() { + const platform = Bun.env.PLATFORM_NAME as keyof typeof PLATFORM_MAP + const arch = Bun.env.ARCH as keyof typeof ARCH_MAP + + if (!platform || !arch) + throw new Error('PLATFORM_NAME and ARCH environment variables are required') + + const npmPlatform = PLATFORM_MAP[platform] + const npmArch = ARCH_MAP[arch] + + if (!npmPlatform || !npmArch) + throw new Error('Invalid platform or architecture') + + const { values } = NodeUtil.parseArgs({ + args: Bun.argv, + strict: true, + allowPositionals: true, + options: { + 'forge-bin-path': { type: 'string', default: Bun.env.FORGE_BIN_PATH } + } + }) + + const profile = Bun.env.NODE_ENV === 'production' ? 'release' : Bun.env.PROFILE || 'release' + const forgeBinPath = values['forge-bin-path'] || findForgeBinary(arch, platform, profile) + + return { platform: npmPlatform, arch: npmArch, forgeBinPath } +} + +function findForgeBinary(arch: string, platform: string, profile: string): string { + const targetDir = TARGET_MAP[`${arch}-${platform}` as keyof typeof TARGET_MAP] + const targetPath = NodePath.join(process.cwd(), '..', 'target', targetDir, profile, 'forge') + + if (NodeFS.existsSync(targetPath)) + return targetPath + + return NodePath.join(process.cwd(), '..', 'target', 'release', 'forge') +} + +async function cleanPackageDirectory(packagePath: string) { + const items = await NodeFS.promises.readdir(packagePath, { + withFileTypes: true, + recursive: true + }) + + items + .filter(item => !PRESERVED_FILES.includes(item.name)) + .forEach(item => + NodeFS.rmSync(NodePath.join(packagePath, item.name), { + recursive: true, + force: true + }) + ) + + 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) { + if (!(await Bun.file(forgeBinPath).exists())) + throw new Error(`Source binary not found at ${forgeBinPath}`) + + const binaryName = platform === 'win32' ? 'forge.exe' : 'forge' + const targetDir = NodePath.join('@foundry-rs', NodePath.basename(packagePath), 'bin') + + NodeFS.mkdirSync(targetDir, { recursive: true }) + + const targetPath = NodePath.join(targetDir, binaryName) + console.info(colors.green, `Copying ${forgeBinPath} to ${targetPath}`, colors.reset) + + await Bun.write(targetPath, Bun.file(forgeBinPath)) + + if (platform !== 'win32') + NodeFS.chmodSync(targetPath, 0o755) +} diff --git a/npm/scripts/publish.ts b/npm/scripts/publish.ts new file mode 100644 index 0000000000000..c674db9b0f43e --- /dev/null +++ b/npm/scripts/publish.ts @@ -0,0 +1,96 @@ +#!/usr/bin/env bun +import { colors } from '#utilities.ts' +import * as NodeFS from 'node:fs' +import * as NodePath from 'node:path' + +const REGISTRY_URL = Bun.env.NPM_REGISTRY_URL || 'https://registry.npmjs.org' + +const NPM_TOKEN = Bun.env.NPM_TOKEN +if (!NPM_TOKEN) throw new Error('NPM_TOKEN is required') + +main().catch(error => { + console.error(error) + process.exit(1) +}) + +async function main() { + const npmToken = Bun.env.NPM_TOKEN + if (!npmToken) throw new Error('NPM_TOKEN is required') + + const packagePath = Bun.argv[2] + if (!packagePath) throw new Error('Package path is required') + console.info(colors.green, 'Package path:', packagePath) + + const publishVersion = getPublishVersion() + console.info(colors.green, 'Publish version:', publishVersion) + + if (packagePath === '@foundry-rs/forge') + await updateOptionalDependencies(packagePath, publishVersion) + + await setPackageVersion(packagePath, publishVersion, npmToken) + const packedFile = await packPackage(packagePath) + await publishPackage(packagePath, packedFile) +} + +function getPublishVersion() { + if (Bun.env.VERSION_NAME) return Bun.env.VERSION_NAME.replace(/^v/, '') + if (Bun.env.BUMP_VERSION) return Bun.env.BUMP_VERSION + + const cargoToml = NodeFS.readFileSync( + NodePath.join(import.meta.dirname, '..', '..', 'Cargo.toml'), + 'utf-8' + ) + + const versionMatch = cargoToml.match(/^version\s*=\s*"([^"]+)"/m) + if (!versionMatch) throw new Error('Version not found in Cargo.toml') + + return versionMatch[1] +} + +async function updateOptionalDependencies(packagePath: string, version: string) { + const packageJsonPath = NodePath.join(packagePath, 'package.json') + const packageJson = JSON.parse(NodeFS.readFileSync(packageJsonPath, 'utf-8')) + + if (packageJson.optionalDependencies) { + Object.keys(packageJson.optionalDependencies).forEach(key => { + packageJson.optionalDependencies[key] = version + }) + + await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2)) + } +} + +async function setPackageVersion(packagePath: string, version: string, npmToken: string) { + console.info(colors.green, 'Setting package version:', version) + const result = await Bun.$`npm version ${version} --allow-same-version --no-git-tag-version` + .cwd(packagePath) + .env({ + ...Bun.env, + ...process.env, + NPM_TOKEN + }) + .quiet() + .nothrow() + + if (result.exitCode !== 0) + throw new Error(`Failed to set version: ${result.stderr}`) +} + +async function packPackage(packagePath: string) { + let packedFile = '' + + for await (const line of Bun.$`bun pm pack`.cwd(packagePath).lines()) + if (line.endsWith('.tgz')) packedFile = line + + if (!packedFile) throw new Error('Failed to pack package') + return packedFile +} + +async function publishPackage(packagePath: string, packedFile: string) { + const result = await Bun.$`npm publish ./${packedFile} --access=public --registry=${REGISTRY_URL}` + .cwd(packagePath) + .nothrow() + + if (result.exitCode !== 0) + throw new Error(`Publish failed: ${result.stderr}`) +} diff --git a/npm/scripts/setup-local.sh b/npm/scripts/setup-local.sh new file mode 100644 index 0000000000000..e7669b56c67ec --- /dev/null +++ b/npm/scripts/setup-local.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -eou pipefail + +# build +npm run build + +# unpublish +npm unpublish @foundry-rs/forge --registry http://localhost:4873 --force || true +npm unpublish @foundry-rs/forge-darwin-arm64 --registry http://localhost:4873 --force || true + +# publish +bun scripts/publish.ts @foundry-rs/forge +bun scripts/publish.ts @foundry-rs/forge-darwin-arm64 diff --git a/npm/src/const.ts b/npm/src/const.ts new file mode 100644 index 0000000000000..5026600baa6ce --- /dev/null +++ b/npm/src/const.ts @@ -0,0 +1,48 @@ +import type * as Process from 'node:process' + +export function getRegistryUrl() { + if (process.env.NODE_ENV !== 'production') return process.env.REGISTRY_URL ?? 'https://registry.npmjs.org' + + return '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' +> + +const referenceMap = { + 'darwin-x64': 'x86_64-apple-darwin', + 'darwin-arm64': 'aarch64-apple-darwin', + 'linux-x64': 'x86_64-unknown-linux-gnu', + 'linux-arm64': 'aarch64-unknown-linux-gnu', + 'win32-x64': 'x86_64-pc-windows-msvc' +} as const satisfies Record + +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', + arm64: '@foundry-rs/forge-win32-arm64' + } +} + +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] diff --git a/npm/src/forge.ts b/npm/src/forge.ts new file mode 100644 index 0000000000000..838132c5db9ac --- /dev/null +++ b/npm/src/forge.ts @@ -0,0 +1,62 @@ +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 { BINARY_NAME } from './const.js' + +const require = NodeModule.createRequire(import.meta.url) + +function getBinaryPath() { + const { platform, arch } = process + + let packageName: string | undefined + let binaryName = 'forge' + + switch (platform) { + case 'win32': + binaryName += '.exe' + if (arch === 'x64') packageName = '@foundry-rs/forge-win32-amd64' + break + case 'darwin': + if (arch === 'x64') packageName = '@foundry-rs/forge-darwin-amd64' + else if (arch === 'arm64') packageName = '@foundry-rs/forge-darwin-arm64' + break + case 'linux': + if (arch === 'x64') packageName = '@foundry-rs/forge-linux-amd64' + else if (arch === 'arm64') packageName = '@foundry-rs/forge-linux-arm64' + break + default: + throw new Error(`Unsupported platform: ${platform}-${arch}`) + } + + if (!packageName) { + console.error(`Unsupported platform: ${platform}-${arch}`) + process.exit(1) + } + + // Try to find the binary in the platform-specific package + try { + const binaryPath = require.resolve(`${packageName}/bin/${binaryName}`) + if (NodeFS.existsSync(binaryPath)) return binaryPath + } catch (error) { + return NodePath.join( + import.meta.dirname, + '..', + '..', + BINARY_NAME + '-' + process.platform + '-' + process.arch, + 'bin', + BINARY_NAME + ) + } + + console.error(`Platform-specific package ${packageName} not found.`) + console.error( + 'This usually means the installation failed or your platform is not supported.' + ) + console.error(`Platform: ${platform}, Architecture: ${arch}`) + process.exit(1) +} + +NodeChildProcess.spawn(getBinaryPath(), process.argv.slice(2), { + stdio: 'inherit' +}) diff --git a/npm/src/index.ts b/npm/src/index.ts new file mode 100644 index 0000000000000..da9a23acdb231 --- /dev/null +++ b/npm/src/index.ts @@ -0,0 +1,28 @@ +import NodeChildProcess from 'node:child_process' +import NodeModule from 'node:module' +import NodePath from 'node:path' +import { BINARY_NAME, PLATFORM_SPECIFIC_PACKAGE_NAME } from './const.js' + +const require = NodeModule.createRequire(import.meta.url) + +function getBinaryPath() { + try { + return require.resolve(`${PLATFORM_SPECIFIC_PACKAGE_NAME}/bin/${BINARY_NAME}`) + } catch (_error) { + return NodePath.join( + import.meta.dirname, + '..', + '..', + 'bin', + BINARY_NAME + ) + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + NodeChildProcess.execFileSync( + getBinaryPath(), + process.argv.slice(2), + { stdio: 'inherit' } + ) +} diff --git a/npm/src/install.ts b/npm/src/install.ts new file mode 100644 index 0000000000000..1273094e2ce53 --- /dev/null +++ b/npm/src/install.ts @@ -0,0 +1,115 @@ +import { BINARY_NAME, getRegistryUrl, PLATFORM_SPECIFIC_PACKAGE_NAME } from '#const.ts' +import { colors } from '#utilities.ts' +import * as NodeFS from 'node:fs' +import * as NodeHttps from 'node:https' +import * as NodeModule from 'node:module' +import * as NodePath from 'node:path' +import * as NodeZlib from 'node:zlib' + +const fallbackBinaryPath = NodePath.join(import.meta.dirname, BINARY_NAME) + +const require = NodeModule.createRequire(import.meta.url) + +function makeRequest(url: string): Promise { + return new Promise((resolve, reject) => { + NodeHttps + .get(url, response => { + if (response?.statusCode && response.statusCode >= 200 && response.statusCode < 300) { + const chunks: Buffer[] = [] + 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 + makeRequest(response.headers.location).then(resolve, reject) + } else { + reject( + new Error( + `npm responded with status code ${response.statusCode} when downloading the package!` + ) + ) + } + }) + .on('error', error => { + reject(error) + }) + }) +} + +function extractFileFromTarball( + tarballBuffer: Buffer, + filepath: string +): Buffer { + // 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. + 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 downloadBinaryFromNpm() { + const registryUrl = getRegistryUrl() + const url = `${registryUrl}/${PLATFORM_SPECIFIC_PACKAGE_NAME}/-/${PLATFORM_SPECIFIC_PACKAGE_NAME}.tgz` + console.info( + colors.green, + 'Downloading binary from:\n', + url, + '\n', + colors.reset + ) + // Download the tarball of the right binary distribution package + const tarballDownloadBuffer = await makeRequest(url) + + const tarballBuffer = NodeZlib.unzipSync(tarballDownloadBuffer) + + // Extract binary from package and write to disk + 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.') + downloadBinaryFromNpm() +} else { + console.log( + 'Platform specific package already installed. Will fall back to manually downloading binary.' + ) +} diff --git a/npm/src/utilities.ts b/npm/src/utilities.ts new file mode 100644 index 0000000000000..5f633692e34c0 --- /dev/null +++ b/npm/src/utilities.ts @@ -0,0 +1,10 @@ +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/test/workspace/.npmrc b/npm/test/workspace/.npmrc new file mode 100644 index 0000000000000..9ddac6ee0b8ba --- /dev/null +++ b/npm/test/workspace/.npmrc @@ -0,0 +1 @@ +registry = "http://localhost:4873" \ No newline at end of file diff --git a/npm/test/workspace/README.md b/npm/test/workspace/README.md new file mode 100644 index 0000000000000..cc4d76d037853 --- /dev/null +++ b/npm/test/workspace/README.md @@ -0,0 +1,25 @@ +# Test package deployment + +## Run local package registry server + +```bash +docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio +``` + +## Clean up previous test + +```bash +/bin/bash ./scripts/setup.sh +``` + +## Install forge + +```bash +bun add @foundry-rs/forge --no-cache --force +``` + +## Run forge + +```bash +bun x forge --version +``` diff --git a/npm/test/workspace/bunfig.toml b/npm/test/workspace/bunfig.toml new file mode 100644 index 0000000000000..c55a65072f7c6 --- /dev/null +++ b/npm/test/workspace/bunfig.toml @@ -0,0 +1,4 @@ +install.registry.url = "http://localhost:4873" + +[install.cache] +disable = true diff --git a/npm/test/workspace/package.json b/npm/test/workspace/package.json new file mode 100644 index 0000000000000..e2495035c1ad0 --- /dev/null +++ b/npm/test/workspace/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "trustedDependencies": [], + "type": "module", + "dependencies": {} +} \ No newline at end of file diff --git a/npm/test/workspace/scripts/setup.sh b/npm/test/workspace/scripts/setup.sh new file mode 100644 index 0000000000000..5ab1db6af1f09 --- /dev/null +++ b/npm/test/workspace/scripts/setup.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eou pipefail + +echo "Deleting node_modules and bun.lock..." + +rm -rf node_modules +rm -rf bun.lock + +bun remove @foundry-rs/forge + +echo "Cleanup complete." diff --git a/npm/tsconfig.json b/npm/tsconfig.json new file mode 100644 index 0000000000000..da5cf01414aaa --- /dev/null +++ b/npm/tsconfig.json @@ -0,0 +1,58 @@ +{ + "schema": "https://json.schemastore.org/tsconfig.json", + "compilerOptions": { + "strict": true, + "baseUrl": ".", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "lib": [ + "ESNext" + ], + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true, + "alwaysStrict": true, + "esModuleInterop": true, + "isolatedModules": true, + "strictNullChecks": true, + "resolveJsonModule": true, + "verbatimModuleSyntax": true, + "moduleResolution": "Bundler", + "useDefineForClassFields": true, + "noUncheckedIndexedAccess": true, + "resolvePackageJsonImports": true, + "resolvePackageJsonExports": true, + "useUnknownInCatchVariables": true, + "allowImportingTsExtensions": true, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "types": [ + "bun", + "node" + ], + "paths": { + "#*": [ + "./src/*" + ], + "#package.json": [ + "./package.json" + ] + } + }, + "exclude": [ + "dist", + "**/_/**", + "node_modules" + ], + "include": [ + "./src/**/*", + "./scripts/**/*" + ], + "files": [ + "env.d.ts", + "package.json", + "tsdown.config.ts" + ] +} diff --git a/npm/tsdown.config.ts b/npm/tsdown.config.ts new file mode 100644 index 0000000000000..26602cf959879 --- /dev/null +++ b/npm/tsdown.config.ts @@ -0,0 +1,70 @@ +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: 'index', + outDir: './@foundry-rs/forge/dist', + outExtensions: () => ({ js: '.mjs' }), + entry: ['./src/index.ts'] + }), + defineConfig({ + ...config, + name: 'install', + outDir: './@foundry-rs/forge/dist', + outExtensions: () => ({ js: '.mjs' }), + entry: ['./src/install.ts'] + }) +]