diff --git a/.github/workflows/bun-release-test.yml b/.github/workflows/bun-release-test.yml new file mode 100644 index 0000000000..c331c47e93 --- /dev/null +++ b/.github/workflows/bun-release-test.yml @@ -0,0 +1,44 @@ +# This workflow tests bun-release's code and the packages to ensure that npm, +# yarn, and pnpm can install bun on all platforms. This does not test that bun +# itself works as it hardcodes 1.1.0 as the version to package. +name: bun-release-test +concurrency: release-test + +on: + pull_request: + paths: + - "packages/bun-release/**" + - ".github/workflows/bun-release-test.yml" + +jobs: + test-release-script: + name: Test Release Script + strategy: + matrix: + machine: [namespace-profile-bun-linux-x64, linux-arm64, macos-arm64, macos-12-large, windows-latest] + fail-fast: false + runs-on: ${{ matrix.machine }} + permissions: + contents: read + defaults: + run: + working-directory: packages/bun-release + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: "1.1.0" + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install Dependencies + run: bun install && npm i -g pnpm yarn npm + + - name: Release + run: bun upload-npm -- 1.1.0 test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 32c68dc08c..3d3c40b760 100644 --- a/.gitignore +++ b/.gitignore @@ -140,4 +140,4 @@ txt.js x64 yarn.lock zig-cache -zig-out +zig-out \ No newline at end of file diff --git a/packages/bun-release/.gitignore b/packages/bun-release/.gitignore index 4c021cb09b..81f3bc7fe7 100644 --- a/packages/bun-release/.gitignore +++ b/packages/bun-release/.gitignore @@ -1,6 +1,8 @@ -.DS_Store -.env -node_modules -/npm/**/bin -/npm/**/*.js -/npm/**/.npmrc +.DS_Store +.env +node_modules +/npm/**/bin +/npm/**/*.js +/npm/**/package.json +/npm/**/.npmrc +*.tgz diff --git a/packages/bun-release/bun.lockb b/packages/bun-release/bun.lockb index ab68b3a5de..5a23cb2ffb 100755 Binary files a/packages/bun-release/bun.lockb and b/packages/bun-release/bun.lockb differ diff --git a/packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json b/packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json deleted file mode 100644 index 701f4c92b8..0000000000 --- a/packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@oven/bun-darwin-aarch64", - "version": "0.5.3", - "description": "This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime.", - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "repository": "https://github.com/oven-sh/bun", - "preferUnplugged": true, - "os": [ - "darwin" - ], - "cpu": [ - "arm64" - ] -} diff --git a/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/package.json b/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/package.json deleted file mode 100644 index b14492c1c7..0000000000 --- a/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@oven/bun-darwin-x64-baseline", - "version": "0.5.3", - "description": "This is the macOS x64 binary for Bun, a fast all-in-one JavaScript runtime.", - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "repository": "https://github.com/oven-sh/bun", - "preferUnplugged": true, - "os": [ - "darwin" - ], - "cpu": [ - "x64" - ] -} diff --git a/packages/bun-release/npm/@oven/bun-darwin-x64/package.json b/packages/bun-release/npm/@oven/bun-darwin-x64/package.json deleted file mode 100644 index 82934da0aa..0000000000 --- a/packages/bun-release/npm/@oven/bun-darwin-x64/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@oven/bun-darwin-x64", - "version": "0.5.3", - "description": "This is the macOS x64 binary for Bun, a fast all-in-one JavaScript runtime.", - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "repository": "https://github.com/oven-sh/bun", - "preferUnplugged": true, - "os": [ - "darwin" - ], - "cpu": [ - "x64" - ] -} diff --git a/packages/bun-release/npm/@oven/bun-linux-aarch64/package.json b/packages/bun-release/npm/@oven/bun-linux-aarch64/package.json deleted file mode 100644 index 4f68fec41b..0000000000 --- a/packages/bun-release/npm/@oven/bun-linux-aarch64/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@oven/bun-linux-aarch64", - "version": "0.5.3", - "description": "This is the Linux arm64 binary for Bun, a fast all-in-one JavaScript runtime.", - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "repository": "https://github.com/oven-sh/bun", - "preferUnplugged": true, - "os": [ - "linux" - ], - "cpu": [ - "arm64" - ] -} diff --git a/packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json b/packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json deleted file mode 100644 index 1767be0dc5..0000000000 --- a/packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@oven/bun-linux-x64-baseline", - "version": "0.5.3", - "description": "This is the Linux x64 binary for Bun, a fast all-in-one JavaScript runtime.", - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "repository": "https://github.com/oven-sh/bun", - "preferUnplugged": true, - "os": [ - "linux" - ], - "cpu": [ - "x64" - ] -} diff --git a/packages/bun-release/npm/@oven/bun-linux-x64/package.json b/packages/bun-release/npm/@oven/bun-linux-x64/package.json deleted file mode 100644 index 16f9309f5d..0000000000 --- a/packages/bun-release/npm/@oven/bun-linux-x64/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@oven/bun-linux-x64", - "version": "0.5.3", - "description": "This is the Linux x64 binary for Bun, a fast all-in-one JavaScript runtime.", - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "repository": "https://github.com/oven-sh/bun", - "preferUnplugged": true, - "os": [ - "linux" - ], - "cpu": [ - "x64" - ] -} diff --git a/packages/bun-release/npm/@oven/bun-windows-x64-baseline/README.md b/packages/bun-release/npm/@oven/bun-windows-x64-baseline/README.md new file mode 100644 index 0000000000..0e383e2d8d --- /dev/null +++ b/packages/bun-release/npm/@oven/bun-windows-x64-baseline/README.md @@ -0,0 +1,5 @@ +# Bun + +This is the Windows x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh + +_Note: "Baseline" builds are for machines that do not support [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) instructions._ diff --git a/packages/bun-release/npm/@oven/bun-windows-x64/README.md b/packages/bun-release/npm/@oven/bun-windows-x64/README.md new file mode 100644 index 0000000000..1fa7a287ba --- /dev/null +++ b/packages/bun-release/npm/@oven/bun-windows-x64/README.md @@ -0,0 +1,3 @@ +# Bun + +This is the Windows x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh diff --git a/packages/bun-release/npm/bun/package.json b/packages/bun-release/npm/bun/package.json deleted file mode 100644 index 374518b855..0000000000 --- a/packages/bun-release/npm/bun/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "bun", - "version": "0.5.3", - "description": "Bun is a fast all-in-one JavaScript runtime.", - "keywords": [ - "bun", - "bun.js", - "node", - "node.js", - "runtime", - "bundler", - "transpiler", - "typescript" - ], - "homepage": "https://bun.sh", - "bugs": "https://github.com/oven-sh/issues", - "license": "MIT", - "bin": { - "bun": "bin/bun", - "bunx": "bin/bun" - }, - "repository": "https://github.com/oven-sh/bun", - "scripts": { - "postinstall": "node install.js" - }, - "optionalDependencies": { - "@oven/bun-darwin-aarch64": "0.5.3", - "@oven/bun-darwin-x64": "0.5.3", - "@oven/bun-darwin-x64-baseline": "0.5.3", - "@oven/bun-linux-aarch64": "0.5.3", - "@oven/bun-linux-x64": "0.5.3", - "@oven/bun-linux-x64-baseline": "0.5.3" - }, - "os": [ - "darwin", - "linux" - ], - "cpu": [ - "arm64", - "x64" - ] -} diff --git a/packages/bun-release/package.json b/packages/bun-release/package.json index c0ed4bad38..1c4719ec60 100644 --- a/packages/bun-release/package.json +++ b/packages/bun-release/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@octokit/types": "^8.1.1", - "bun-types": "^0.4.0", + "bun-types": "^1.1.0", "prettier": "^2.8.2" }, "scripts": { diff --git a/packages/bun-release/scripts/npm-exec.ts b/packages/bun-release/scripts/npm-exec.ts index 517f296cea..5938f43837 100644 --- a/packages/bun-release/scripts/npm-exec.ts +++ b/packages/bun-release/scripts/npm-exec.ts @@ -1,4 +1,4 @@ -import { importBun } from "../src/npm/install"; +import { importBun, optimizeBun } from "../src/npm/install"; import { execFileSync } from "child_process"; importBun() diff --git a/packages/bun-release/scripts/upload-npm.ts b/packages/bun-release/scripts/upload-npm.ts index 3cd8cd9d5d..fbb99afb79 100644 --- a/packages/bun-release/scripts/upload-npm.ts +++ b/packages/bun-release/scripts/upload-npm.ts @@ -1,4 +1,8 @@ import { join, copy, exists, chmod, write, writeJson } from "../src/fs"; +import { mkdtemp } from "fs/promises"; +import { rmSync, mkdirSync } from "fs"; +import { tmpdir } from "os"; +import { dirname } from "path"; import { fetch } from "../src/fetch"; import { spawn } from "../src/spawn"; import type { Platform } from "../src/platform"; @@ -10,41 +14,51 @@ import { buildSync, formatMessagesSync } from "esbuild"; import type { JSZipObject } from "jszip"; import { loadAsync } from "jszip"; import { debug, log, error } from "../src/console"; +import { expect } from "bun:test"; const module = "bun"; const owner = "@oven"; -let version: string; const [tag, action] = process.argv.slice(2); -await build(tag); +const release = await getRelease(tag); +const version = await getSemver(release.tag_name); + +if (action !== "test-only") await build(); + if (action === "publish") { await publish(); } else if (action === "dry-run") { await publish(true); +} else if (action === "test") { + await publish(true); + await test(); +} else if (action === "test-only") { + await test(); } else if (action) { throw new Error(`Unknown action: ${action}`); } process.exit(0); // HACK -async function build(tag?: string): Promise { - const release = await getRelease(tag); - version = await getSemver(release.tag_name); +async function build(): Promise { await buildRootModule(); for (const platform of platforms) { + if (action !== "publish" && (platform.os !== process.platform || platform.arch !== process.arch)) continue; await buildModule(release, platform); } } async function publish(dryRun?: boolean): Promise { - const modules = platforms.map(({ bin }) => `${owner}/${bin}`); + const modules = platforms + .filter(({ os, arch }) => action === "publish" || (os === process.platform && arch === process.arch)) + .map(({ bin }) => `${owner}/${bin}`); modules.push(module); for (const module of modules) { publishModule(module, dryRun); } } -async function buildRootModule() { +async function buildRootModule(dryRun?: boolean) { log("Building:", `${module}@${version}`); const cwd = join("npm", module); const define = { @@ -54,28 +68,53 @@ async function buildRootModule() { }; bundle(join("scripts", "npm-postinstall.ts"), join(cwd, "install.js"), { define, - }); - bundle(join("scripts", "npm-exec.ts"), join(cwd, "bin", "bun"), { - define, banner: { - js: "#!/usr/bin/env node", + js: "// Source code: https://github.com/oven-sh/bun/blob/main/packages/bun-release/scripts/npm-postinstall.ts", }, }); + write(join(cwd, "bin", "bun.exe"), ""); + write( + join(cwd, "bin", "README.txt"), + `The 'bun.exe' file is a placeholder for the binary file, which +is replaced by Bun's 'postinstall' script. For this to work, make +sure that you do not use --ignore-scripts while installing. + +The postinstall script is responsible for linking the binary file +directly into 'node_modules/.bin' and avoiding a Node.js wrapper +script being called on every invocation of 'bun'. If this wasn't +done, Bun would seem to be slower than Node.js, because it would +be executing a copy of Node.js every time! + +Unfortunately, it is not possible to fix all cases on all platforms +without *requiring* a postinstall script. +`, + ); const os = [...new Set(platforms.map(({ os }) => os))]; const cpu = [...new Set(platforms.map(({ arch }) => arch))]; writeJson(join(cwd, "package.json"), { name: module, + description: "Bun is a fast all-in-one JavaScript runtime.", version: version, scripts: { postinstall: "node install.js", }, - optionalDependencies: Object.fromEntries(platforms.map(({ bin }) => [`${owner}/${bin}`, version])), + optionalDependencies: Object.fromEntries( + platforms.map(({ bin }) => [ + `${owner}/${bin}`, + dryRun ? `file:./oven-${bin.replaceAll("/", "-") + "-" + version + ".tgz"}` : version, + ]), + ), bin: { - bun: "bin/bun", - bunx: "bin/bun", + bun: "bin/bun.exe", + bunx: "bin/bun.exe", }, os, cpu, + keywords: ["bun", "bun.js", "node", "node.js", "runtime", "bundler", "transpiler", "typescript"], + homepage: "https://bun.sh", + bugs: "https://github.com/oven-sh/issues", + license: "MIT", + repository: "https://github.com/oven-sh/bun", }); if (exists(".npmrc")) { copy(".npmrc", join(cwd, ".npmrc")); @@ -95,11 +134,17 @@ async function buildModule( } const bun = await extractFromZip(asset.browser_download_url, `${bin}/bun`); const cwd = join("npm", module); + mkdirSync(dirname(join(cwd, exe)), { recursive: true }); write(join(cwd, exe), await bun.async("arraybuffer")); chmod(join(cwd, exe), 0o755); writeJson(join(cwd, "package.json"), { name: module, version: version, + description: "This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime.", + homepage: "https://bun.sh", + bugs: "https://github.com/oven-sh/issues", + license: "MIT", + repository: "https://github.com/oven-sh/bun", preferUnplugged: true, os: [os], cpu: [arch], @@ -111,22 +156,33 @@ async function buildModule( function publishModule(name: string, dryRun?: boolean): void { log(dryRun ? "Dry-run Publishing:" : "Publishing:", `${name}@${version}`); - const { exitCode, stdout, stderr } = spawn( - "npm", - [ - "publish", - "--access", - "public", - "--tag", - version.includes("canary") ? "canary" : "latest", - ...(dryRun ? ["--dry-run"] : []), - ], - { - cwd: join("npm", name), - }, - ); - if (exitCode === 0) { + if (!dryRun) { + const { exitCode, stdout, stderr } = spawn( + "npm", + [ + "publish", + "--access", + "public", + "--tag", + version.includes("canary") ? "canary" : "latest", + ...(dryRun ? ["--dry-run"] : []), + ], + { + cwd: join("npm", name), + }, + ); error(stderr || stdout); + if (exitCode !== 0) { + throw new Error("npm publish failed with code " + exitCode); + } + } else { + const { exitCode, stdout, stderr } = spawn("npm", ["pack"], { + cwd: join("npm", name), + }); + error(stderr || stdout); + if (exitCode !== 0) { + throw new Error("npm pack failed with code " + exitCode); + } } } @@ -162,3 +218,86 @@ function bundle(src: string, dst: string, options: BuildOptions = {}): void { throw new Error(messages.join("\n")); } } + +async function test() { + const root = await mkdtemp(join(tmpdir(), "bun-release-test-")); + const $ = new Bun.$.Shell().cwd(root); + + for (const platform of platforms) { + if (platform.os !== process.platform) continue; + if (platform.arch !== process.arch) continue; + copy( + join( + import.meta.dir, + "../npm/@oven/", + platform.bin, + "oven-" + platform.bin.replaceAll("/", "-") + `-${version}.tgz`, + ), + join(root, `${platform.bin}-${version}.tgz`), + ); + } + + copy(join(import.meta.dir, "../npm", "bun", "bun-" + version + ".tgz"), join(root, "bun-" + version + ".tgz")); + + console.log(root); + for (const [install, exec] of [ + ["npm i", "npm exec"], + ["yarn set version berry; yarn add", "yarn"], + ["yarn set version latest; yarn add", "yarn"], + ["pnpm i", "pnpm"], + ["bun i", "bun run"], + ]) { + rmSync(join(root, "node_modules"), { recursive: true, force: true }); + rmSync(join(root, "package-lock.json"), { recursive: true, force: true }); + rmSync(join(root, "package.json"), { recursive: true, force: true }); + rmSync(join(root, "pnpm-lock.yaml"), { recursive: true, force: true }); + rmSync(join(root, "yarn.lock"), { recursive: true, force: true }); + writeJson(join(root, "package.json"), { + name: "bun-release-test", + }); + + console.log("Testing", install + " bun"); + await $`${{ raw: install }} ./bun-${version}.tgz`; + + console.log("Running " + exec + " bun"); + + // let output = await $`${{ + // raw: exec, + // }} bun -- -e "console.log(JSON.stringify([Bun.version, process.platform, process.arch, process.execPath]))"`.text(); + const split = exec.split(" "); + let { + stdout: output, + stderr, + exitCode, + } = spawn( + split[0], + [ + ...split.slice(1), + "--", + "bun", + "-e", + "console.log(JSON.stringify([Bun.version, process.platform, process.arch, process.execPath]))", + ], + { + cwd: root, + }, + ); + if (exitCode !== 0) { + console.error(stderr); + throw new Error("Failed to run " + exec + " bun, exit code: " + exitCode); + } + + try { + output = JSON.parse(output); + } catch (e) { + console.log({ output }); + throw e; + } + + expect(output[0]).toBe(version); + expect(output[1]).toBe(process.platform); + expect(output[2]).toBe(process.arch); + expect(output[3]).toStartWith(root); + expect(output[3]).toInclude("bun"); + } +} diff --git a/packages/bun-release/src/npm/install.ts b/packages/bun-release/src/npm/install.ts index fcb829c70f..37ed2c3165 100644 --- a/packages/bun-release/src/npm/install.ts +++ b/packages/bun-release/src/npm/install.ts @@ -121,16 +121,9 @@ async function downloadBun(platform: Platform, dst: string): Promise { } export function optimizeBun(path: string): void { - const installScript = - os === "win32" ? 'powershell -c "irm bun.sh/install.ps1 | iex"' : "curl -fsSL https://bun.sh/install | bash"; - const { npm_config_user_agent } = process.env; - if (npm_config_user_agent && /\byarn\//.test(npm_config_user_agent)) { - throw new Error( - `Yarn does not support bun, because it does not allow linking to binaries. To use bun, install using the following command: ${installScript}`, - ); - } + const installScript = os === "win32" ? 'powershell -c "irm bun.sh/install.ps1 | iex"' : "curl -fsSL https://bun.sh/install | bash"; try { - rename(path, join(__dirname, "bin", "bun")); + rename(path, join(__dirname, "bin", "bun.exe")); return; } catch (error) { debug("optimizeBun failed", error); diff --git a/packages/bun-release/src/platform.ts b/packages/bun-release/src/platform.ts index e38de526cf..fbe4d9d373 100644 --- a/packages/bun-release/src/platform.ts +++ b/packages/bun-release/src/platform.ts @@ -106,6 +106,15 @@ function isRosetta2(): boolean { } function isWindowsAVX2(): boolean { - // TODO: Implement AVX2 detection on Windows - return false; + try { + return ( + spawn("powershell", [ + "-c", + `(Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' -Name 'Kernel32' -Namespace 'Win32' -PassThru)::IsProcessorFeaturePresent(40);`, + ]).stdout == "True" + ); + } catch (error) { + debug("isWindowsAVX2 failed", error); + return false; + } }