aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/bun-npm-release.yml47
-rw-r--r--packages/bun-npm/.gitignore5
-rw-r--r--packages/bun-npm/.npmrc1
-rw-r--r--packages/bun-npm/README.md15
-rwxr-xr-xpackages/bun-npm/bun.lockbbin0 -> 14600 bytes
-rw-r--r--packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md3
-rw-r--r--packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json16
-rw-r--r--packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/README.md5
-rw-r--r--packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/package.json16
-rw-r--r--packages/bun-npm/npm/@oven/bun-darwin-x64/README.md3
-rw-r--r--packages/bun-npm/npm/@oven/bun-darwin-x64/package.json16
-rw-r--r--packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md3
-rw-r--r--packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json16
-rw-r--r--packages/bun-npm/npm/@oven/bun-linux-x64-baseline/README.md5
-rw-r--r--packages/bun-npm/npm/@oven/bun-linux-x64-baseline/package.json16
-rw-r--r--packages/bun-npm/npm/@oven/bun-linux-x64/README.md3
-rw-r--r--packages/bun-npm/npm/@oven/bun-linux-x64/package.json16
-rw-r--r--packages/bun-npm/npm/bun/README.md31
-rw-r--r--packages/bun-npm/npm/bun/package.json41
-rw-r--r--packages/bun-npm/package.json15
-rw-r--r--packages/bun-npm/scripts/npm-build.ts226
-rw-r--r--packages/bun-npm/scripts/npm-exec.ts13
-rw-r--r--packages/bun-npm/scripts/npm-postinstall.ts10
-rw-r--r--packages/bun-npm/src/install.ts159
-rw-r--r--packages/bun-npm/src/platform.ts100
-rw-r--r--packages/bun-npm/src/util.ts191
-rw-r--r--packages/bun-npm/tsconfig.json17
27 files changed, 989 insertions, 0 deletions
diff --git a/.github/workflows/bun-npm-release.yml b/.github/workflows/bun-npm-release.yml
new file mode 100644
index 000000000..7fde74464
--- /dev/null
+++ b/.github/workflows/bun-npm-release.yml
@@ -0,0 +1,47 @@
+name: Release bun to npm
+on:
+ release:
+ types:
+ - published
+ - edited # canary only
+ workflow_dispatch:
+ inputs:
+ tag:
+ type: string
+ description: The tag to publish, defaults to 'canary' if empty
+ default: canary
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ if: github.repository_owner == 'oven-sh'
+ defaults:
+ run:
+ working-directory: packages/bun-npm
+ steps:
+ - id: checkout
+ name: Checkout
+ uses: actions/checkout@v3
+ - id: setup-env
+ name: Setup Environment
+ run: |
+ TAG="${{ github.event.inputs.tag }}"
+ TAG="${TAG:-"${{ github.event.release.tag_name }}"}"
+ TAG="${TAG:-"canary"}"
+ echo "Setup tag: ${TAG}"
+ echo "TAG=${TAG}" >> ${GITHUB_ENV}
+ - id: setup-bun
+ name: Setup Bun
+ uses: oven-sh/setup-bun@v0.1.8
+ with:
+ bun-version: canary
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ - id: bun-install
+ name: Install Dependencies
+ run: bun install
+ - id: bun-run
+ name: Release
+ run: bun run npm -- publish "${{ env.TAG }}"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/packages/bun-npm/.gitignore b/packages/bun-npm/.gitignore
new file mode 100644
index 000000000..39931eeba
--- /dev/null
+++ b/packages/bun-npm/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+.env
+node_modules
+/npm/**/bin
+/npm/**/*.js
diff --git a/packages/bun-npm/.npmrc b/packages/bun-npm/.npmrc
new file mode 100644
index 000000000..ae643592e
--- /dev/null
+++ b/packages/bun-npm/.npmrc
@@ -0,0 +1 @@
+//registry.npmjs.org/:_authToken=${NPM_TOKEN}
diff --git a/packages/bun-npm/README.md b/packages/bun-npm/README.md
new file mode 100644
index 000000000..4556f0f4d
--- /dev/null
+++ b/packages/bun-npm/README.md
@@ -0,0 +1,15 @@
+# bun-npm
+
+Scripts that allow Bun to be installed with `npm install`.
+
+### Running
+
+```sh
+bun run npm # build assets for the latest release
+bun run npm -- <release> # build assets for the provided release
+bun run npm -- <release> [dry-run|publish] # build and publish assets to npm
+```
+
+### Credits
+
+- [esbuild](https://github.com/evanw/esbuild), for its npm scripts which this was largely based off of.
diff --git a/packages/bun-npm/bun.lockb b/packages/bun-npm/bun.lockb
new file mode 100755
index 000000000..41a60a1ec
--- /dev/null
+++ b/packages/bun-npm/bun.lockb
Binary files differ
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md b/packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md
new file mode 100644
index 000000000..3b03fdd9f
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md
@@ -0,0 +1,3 @@
+# Bun
+
+This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json b/packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json
new file mode 100644
index 000000000..46da9b168
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-darwin-aarch64",
+ "version": "0.5.1",
+ "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-npm/npm/@oven/bun-darwin-x64-baseline/README.md b/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/README.md
new file mode 100644
index 000000000..e451cccfe
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/README.md
@@ -0,0 +1,5 @@
+# Bun
+
+This is the macOS 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-npm/npm/@oven/bun-darwin-x64-baseline/package.json b/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/package.json
new file mode 100644
index 000000000..a9a330f42
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-darwin-x64-baseline",
+ "version": "0.5.1",
+ "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-npm/npm/@oven/bun-darwin-x64/README.md b/packages/bun-npm/npm/@oven/bun-darwin-x64/README.md
new file mode 100644
index 000000000..be73fd7a2
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-x64/README.md
@@ -0,0 +1,3 @@
+# Bun
+
+This is the macOS x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-x64/package.json b/packages/bun-npm/npm/@oven/bun-darwin-x64/package.json
new file mode 100644
index 000000000..d83283425
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-x64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-darwin-x64",
+ "version": "0.5.1",
+ "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-npm/npm/@oven/bun-linux-aarch64/README.md b/packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md
new file mode 100644
index 000000000..e727d8008
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md
@@ -0,0 +1,3 @@
+# Bun
+
+This is the Linux arm64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
diff --git a/packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json b/packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json
new file mode 100644
index 000000000..c2feaf708
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-linux-aarch64",
+ "version": "0.5.1",
+ "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-npm/npm/@oven/bun-linux-x64-baseline/README.md b/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/README.md
new file mode 100644
index 000000000..6b2ce0fcf
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/README.md
@@ -0,0 +1,5 @@
+# Bun
+
+This is the Linux 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-npm/npm/@oven/bun-linux-x64-baseline/package.json b/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/package.json
new file mode 100644
index 000000000..e3333b134
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-linux-x64-baseline",
+ "version": "0.5.1",
+ "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-npm/npm/@oven/bun-linux-x64/README.md b/packages/bun-npm/npm/@oven/bun-linux-x64/README.md
new file mode 100644
index 000000000..e0fac1347
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-x64/README.md
@@ -0,0 +1,3 @@
+# Bun
+
+This is the Linux x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
diff --git a/packages/bun-npm/npm/@oven/bun-linux-x64/package.json b/packages/bun-npm/npm/@oven/bun-linux-x64/package.json
new file mode 100644
index 000000000..71228ad14
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-x64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-linux-x64",
+ "version": "0.5.1",
+ "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-npm/npm/bun/README.md b/packages/bun-npm/npm/bun/README.md
new file mode 100644
index 000000000..cf4c3c759
--- /dev/null
+++ b/packages/bun-npm/npm/bun/README.md
@@ -0,0 +1,31 @@
+# Bun
+
+Bun is a fast all-in-one JavaScript runtime. https://bun.sh
+
+### Install
+
+```sh
+npm install -g bun
+```
+
+### Upgrade
+
+```sh
+bun upgrade
+```
+
+### Supported Platforms
+
+- [macOS, arm64 (Apple Silicon)](https://www.npmjs.com/package/@oven/bun-darwin-aarch64)
+- [macOS, x64](<(https://www.npmjs.com/package/@oven/bun-darwin-x64)>)
+- [macOS, x64 (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-darwin-x64-baseline)
+- [Linux, arm64](https://www.npmjs.com/package/@oven/bun-linux-aarch64)
+- [Linux, x64](https://www.npmjs.com/package/@oven/bun-linux-x64)
+- [Linux, x64 (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-linux-x64-baseline)
+- [Windows (using Windows Subsystem for Linux, aka. "WSL")](https://relatablecode.com/how-to-set-up-bun-on-a-windows-machine)
+
+### Future Platforms
+
+- [Windows](https://github.com/oven-sh/bun/issues/43)
+- Unix-like variants such as FreeBSD, OpenBSD, etc.
+- Android and iOS
diff --git a/packages/bun-npm/npm/bun/package.json b/packages/bun-npm/npm/bun/package.json
new file mode 100644
index 000000000..7cb62f499
--- /dev/null
+++ b/packages/bun-npm/npm/bun/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "bun",
+ "version": "0.5.1",
+ "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"
+ },
+ "repository": "https://github.com/oven-sh/bun",
+ "scripts": {
+ "postinstall": "node install.js"
+ },
+ "optionalDependencies": {
+ "@oven/bun-darwin-aarch64": "0.5.1",
+ "@oven/bun-darwin-x64": "0.5.1",
+ "@oven/bun-darwin-x64-baseline": "0.5.1",
+ "@oven/bun-linux-aarch64": "0.5.1",
+ "@oven/bun-linux-x64": "0.5.1",
+ "@oven/bun-linux-x64-baseline": "0.5.1"
+ },
+ "os": [
+ "darwin",
+ "linux"
+ ],
+ "cpu": [
+ "arm64",
+ "x64"
+ ]
+}
diff --git a/packages/bun-npm/package.json b/packages/bun-npm/package.json
new file mode 100644
index 000000000..33de39c3c
--- /dev/null
+++ b/packages/bun-npm/package.json
@@ -0,0 +1,15 @@
+{
+ "private": true,
+ "dependencies": {},
+ "devDependencies": {
+ "@octokit/types": "^8.1.1",
+ "bun-types": "^0.4.0",
+ "prettier": "^2.8.2",
+ "esbuild": "^0.17.3",
+ "jszip": "^3.10.1"
+ },
+ "scripts": {
+ "format": "prettier --write src scripts",
+ "npm": "bun scripts/npm-build.ts"
+ }
+}
diff --git a/packages/bun-npm/scripts/npm-build.ts b/packages/bun-npm/scripts/npm-build.ts
new file mode 100644
index 000000000..c8fcb7d86
--- /dev/null
+++ b/packages/bun-npm/scripts/npm-build.ts
@@ -0,0 +1,226 @@
+import type { Endpoints } from "@octokit/types";
+import { fetch, spawn } from "../src/util";
+import type { JSZipObject } from "jszip";
+import { loadAsync } from "jszip";
+import { join } from "node:path";
+import { chmod, read, write } from "../src/util";
+import type { BuildOptions } from "esbuild";
+import { buildSync, formatMessagesSync } from "esbuild";
+import type { Platform } from "../src/platform";
+import { platforms } from "../src/platform";
+
+type Release =
+ Endpoints["GET /repos/{owner}/{repo}/releases/latest"]["response"]["data"];
+
+const npmPackage = "bun";
+const npmOwner = "@oven";
+let npmVersion: string;
+
+const [tag, action] = process.argv.slice(2);
+
+await build(tag);
+if (action === "publish") {
+ await publish();
+} else if (action === "dry-run") {
+ await publish(true);
+} else if (action) {
+ throw new Error(`Unknown action: ${action}`);
+}
+
+async function build(version: string): Promise<void> {
+ const release = await getRelease(version);
+ if (release.tag_name === "canary") {
+ const { tag_name } = await getRelease();
+ const sha = await getSha(tag_name);
+ npmVersion = `${tag_name.replace("bun-v", "")}+canary.${sha}`;
+ } else {
+ npmVersion = release.tag_name.replace("bun-v", "");
+ }
+ await buildBasePackage();
+ for (const platform of platforms) {
+ await buildPackage(release, platform);
+ }
+}
+
+async function publish(dryRun?: boolean): Promise<void> {
+ const npmPackages = platforms.map(({ bin }) => `${npmOwner}/${bin}`);
+ npmPackages.push(npmPackage);
+ for (const npmPackage of npmPackages) {
+ publishPackage(npmPackage, dryRun);
+ }
+}
+
+async function buildBasePackage() {
+ const done = log("Building:", `${npmPackage}@${npmVersion}`);
+ const cwd = join("npm", npmPackage);
+ const define = {
+ npmVersion: `"${npmVersion}"`,
+ npmPackage: `"${npmPackage}"`,
+ npmOwner: `"${npmOwner}"`,
+ };
+ buildJs(join("scripts", "npm-postinstall.ts"), join(cwd, "install.js"), {
+ define,
+ });
+ buildJs(join("scripts", "npm-exec.ts"), join(cwd, "bin", "bun"), {
+ define,
+ banner: {
+ js: "#!/usr/bin/env node",
+ },
+ });
+ const os = [...new Set(platforms.map(({ os }) => os))];
+ const cpu = [...new Set(platforms.map(({ arch }) => arch))];
+ patchJson(join(cwd, "package.json"), {
+ name: npmPackage,
+ version: npmVersion,
+ scripts: {
+ postinstall: "node install.js",
+ },
+ optionalDependencies: Object.fromEntries(
+ platforms.map(({ bin }) => [`${npmOwner}/${bin}`, npmVersion]),
+ ),
+ bin: {
+ bun: "bin/bun",
+ },
+ os,
+ cpu,
+ });
+ done();
+}
+
+async function buildPackage(
+ release: Release,
+ { bin, exe, os, arch }: Platform,
+): Promise<void> {
+ const npmPackage = `${npmOwner}/${bin}`;
+ const done = log("Building:", `${npmPackage}@${npmVersion}`);
+ const asset = release.assets.find(({ name }) => name === `${bin}.zip`);
+ if (!asset) {
+ throw new Error(`No asset found: ${bin}`);
+ }
+ const bun = await extractFromZip(asset.browser_download_url, `${bin}/bun`);
+ const cwd = join("npm", npmPackage);
+ write(join(cwd, exe), await bun.async("arraybuffer"));
+ chmod(join(cwd, exe), 0o755);
+ patchJson(join(cwd, "package.json"), {
+ name: npmPackage,
+ version: npmVersion,
+ preferUnplugged: true,
+ os: [os],
+ cpu: [arch],
+ });
+ done();
+}
+
+function publishPackage(name: string, dryRun?: boolean): void {
+ const done = log(dryRun ? "Dry-run Publishing:" : "Publishing:", name);
+ const { exitCode, stdout, stderr } = spawn(
+ "npm",
+ [
+ "publish",
+ "--access",
+ "public",
+ "--tag",
+ npmVersion.startsWith("canary") ? "canary" : "latest",
+ ...(dryRun ? ["--dry-run"] : []),
+ ],
+ {
+ cwd: join("npm", name),
+ },
+ );
+ if (exitCode === 0) {
+ done();
+ return;
+ }
+ throw new Error(stdout || stderr);
+}
+
+async function extractFromZip(
+ url: string,
+ filename: string,
+): Promise<JSZipObject> {
+ const response = await fetch(url);
+ const buffer = await response.arrayBuffer();
+ const zip = await loadAsync(buffer);
+ for (const [name, file] of Object.entries(zip.files)) {
+ if (!file.dir && name.startsWith(filename)) {
+ return file;
+ }
+ }
+ console.warn("Found files:", Object.keys(zip.files));
+ throw new Error(`File not found: ${filename}`);
+}
+
+async function getRelease(version?: string | null): Promise<Release> {
+ const response = await fetchGithub(
+ version ? `releases/tags/${formatTag(version)}` : `releases/latest`,
+ );
+ return response.json();
+}
+
+async function getSha(version: string): Promise<string> {
+ const response = await fetchGithub(`git/ref/tags/${formatTag(version)}`);
+ const {
+ object,
+ }: Endpoints["GET /repos/{owner}/{repo}/git/ref/{ref}"]["response"]["data"] =
+ await response.json();
+ return object.sha.substring(0, 7);
+}
+
+async function fetchGithub(path: string) {
+ const headers = new Headers();
+ const token = process.env.GITHUB_TOKEN;
+ if (token) {
+ headers.set("Authorization", `Bearer ${token}`);
+ }
+ const url = new URL(path, "https://api.github.com/repos/oven-sh/bun/");
+ return fetch(url.toString());
+}
+
+function formatTag(version: string): string {
+ if (version.startsWith("canary") || version.startsWith("bun-v")) {
+ return version;
+ }
+ return `bun-v${version}`;
+}
+
+function patchJson(path: string, patch: object): void {
+ let value;
+ try {
+ const existing = JSON.parse(read(path));
+ value = {
+ ...existing,
+ ...patch,
+ };
+ } catch {
+ value = patch;
+ }
+ write(path, `${JSON.stringify(value, undefined, 2)}\n`);
+}
+
+function buildJs(src: string, dst: string, options: BuildOptions = {}): void {
+ const { errors } = buildSync({
+ bundle: true,
+ treeShaking: true,
+ keepNames: true,
+ minifySyntax: true,
+ pure: ["console.debug"],
+ platform: "node",
+ target: "es6",
+ format: "cjs",
+ entryPoints: [src],
+ outfile: dst,
+ ...options,
+ });
+ if (errors?.length) {
+ const messages = formatMessagesSync(errors, { kind: "error" });
+ throw new Error(messages.join("\n"));
+ }
+}
+
+function log(...args: any[]): () => void {
+ console.write(Bun.inspect(...args));
+ const start = Date.now();
+ return () => {
+ console.write(` [${(Date.now() - start).toFixed()} ms]\n`);
+ };
+}
diff --git a/packages/bun-npm/scripts/npm-exec.ts b/packages/bun-npm/scripts/npm-exec.ts
new file mode 100644
index 000000000..3937247cb
--- /dev/null
+++ b/packages/bun-npm/scripts/npm-exec.ts
@@ -0,0 +1,13 @@
+import { importBun } from "../src/install";
+import { execFileSync } from "child_process";
+
+importBun()
+ .then((bun) => {
+ return execFileSync(bun, process.argv.slice(2), {
+ stdio: "inherit",
+ });
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/packages/bun-npm/scripts/npm-postinstall.ts b/packages/bun-npm/scripts/npm-postinstall.ts
new file mode 100644
index 000000000..c22f2c669
--- /dev/null
+++ b/packages/bun-npm/scripts/npm-postinstall.ts
@@ -0,0 +1,10 @@
+import { importBun, optimizeBun } from "../src/install";
+
+importBun()
+ .then((path) => {
+ optimizeBun(path);
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/packages/bun-npm/src/install.ts b/packages/bun-npm/src/install.ts
new file mode 100644
index 000000000..9eabd2c41
--- /dev/null
+++ b/packages/bun-npm/src/install.ts
@@ -0,0 +1,159 @@
+import { fetch, chmod, join, rename, rm, tmp, write, spawn } from "./util";
+import { unzipSync } from "zlib";
+import type { Platform } from "./platform";
+import { os, arch, supportedPlatforms } from "./platform";
+
+declare const npmVersion: string;
+declare const npmPackage: string;
+declare const npmOwner: string;
+
+export async function importBun(): Promise<string> {
+ if (!supportedPlatforms.length) {
+ throw new Error(`Unsupported platform: ${os} ${arch}`);
+ }
+ for (const platform of supportedPlatforms) {
+ try {
+ return await requireBun(platform);
+ } catch (error) {
+ console.debug("requireBun failed", error);
+ }
+ }
+ throw new Error(`Failed to install package "${npmPackage}"`);
+}
+
+async function requireBun(platform: Platform): Promise<string> {
+ const npmPackage = `${npmOwner}/${platform.bin}`;
+ function resolveBun() {
+ const exe = require.resolve(join(npmPackage, platform.exe));
+ const { exitCode, stderr, stdout } = spawn(exe, ["--version"]);
+ if (exitCode === 0) {
+ return exe;
+ }
+ throw new Error(stderr || stdout);
+ }
+ try {
+ return resolveBun();
+ } catch (error) {
+ console.debug("resolveBun failed", error);
+ console.error(
+ `Failed to find package "${npmPackage}".`,
+ `You may have used the "--no-optional" flag when running "npm install".`,
+ );
+ }
+ const cwd = join("node_modules", npmPackage);
+ try {
+ installBun(platform, cwd);
+ } catch (error) {
+ console.debug("installBun failed", error);
+ console.error(
+ `Failed to install package "${npmPackage}" using "npm install".`,
+ error,
+ );
+ try {
+ await downloadBun(platform, cwd);
+ } catch (error) {
+ console.debug("downloadBun failed", error);
+ console.error(
+ `Failed to download package "${npmPackage}" from "registry.npmjs.org".`,
+ error,
+ );
+ }
+ }
+ return resolveBun();
+}
+
+function installBun(platform: Platform, dst: string): void {
+ const npmPackage = `${npmOwner}/${platform.bin}`;
+ const cwd = tmp();
+ try {
+ write(join(cwd, "package.json"), "{}");
+ const { exitCode } = spawn(
+ "npm",
+ [
+ "install",
+ "--loglevel=error",
+ "--prefer-offline",
+ "--no-audit",
+ "--progress=false",
+ `${npmPackage}@${npmVersion}`,
+ ],
+ {
+ cwd,
+ stdio: "pipe",
+ env: {
+ ...process.env,
+ npm_config_global: undefined,
+ },
+ },
+ );
+ if (exitCode === 0) {
+ rename(join(cwd, "node_modules", npmPackage), dst);
+ }
+ } finally {
+ try {
+ rm(cwd);
+ } catch (error) {
+ console.debug("rm failed", error);
+ // There is nothing to do if the directory cannot be cleaned up.
+ }
+ }
+}
+
+async function downloadBun(platform: Platform, dst: string): Promise<void> {
+ const response = await fetch(
+ `https://registry.npmjs.org/${npmOwner}/${platform.bin}/-/${platform.bin}-${npmVersion}.tgz`,
+ );
+ const tgz = await response.arrayBuffer();
+ let buffer: Buffer;
+ try {
+ buffer = unzipSync(tgz);
+ } catch (cause) {
+ throw new Error("Invalid gzip data", { cause });
+ }
+ function str(i: number, n: number): string {
+ return String.fromCharCode(...buffer.subarray(i, i + n)).replace(
+ /\0.*$/,
+ "",
+ );
+ }
+ let offset = 0;
+ while (offset < buffer.length) {
+ const name = str(offset, 100).replace("package/", "");
+ const size = parseInt(str(offset + 124, 12), 8);
+ offset += 512;
+ if (!isNaN(size)) {
+ write(join(dst, name), buffer.subarray(offset, offset + size));
+ if (name === platform.exe) {
+ try {
+ chmod(join(dst, name), 0o755);
+ } catch (error) {
+ console.debug("chmod failed", error);
+ }
+ }
+ offset += (size + 511) & ~511;
+ }
+ }
+}
+
+export function optimizeBun(path: string): void {
+ if (os === "win32") {
+ throw new Error(
+ "You must use Windows Subsystem for Linux, aka. WSL, to run bun. Learn more: https://learn.microsoft.com/en-us/windows/wsl/install",
+ );
+ }
+ 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: curl -fsSL https://bun.sh/install | bash",
+ );
+ }
+ try {
+ rename(path, join(__dirname, "bin", "bun"));
+ return;
+ } catch (error) {
+ console.debug("optimizeBun failed", error);
+ }
+ throw new Error(
+ "Your package manager doesn't seem to support bun. To use bun, install using the following command: curl -fsSL https://bun.sh/install | bash",
+ );
+}
diff --git a/packages/bun-npm/src/platform.ts b/packages/bun-npm/src/platform.ts
new file mode 100644
index 000000000..a01cc3ddc
--- /dev/null
+++ b/packages/bun-npm/src/platform.ts
@@ -0,0 +1,100 @@
+import { read, spawn } from "./util";
+
+export const os = process.platform;
+
+export const arch =
+ os === "darwin" && process.arch === "x64" && isRosetta2()
+ ? "arm64"
+ : process.arch;
+
+export const avx2 =
+ (arch === "x64" && os === "linux" && isLinuxAVX2()) ||
+ (os === "darwin" && isDarwinAVX2());
+
+export type Platform = {
+ os: string;
+ arch: string;
+ avx2?: boolean;
+ bin: string;
+ exe: string;
+};
+
+export const platforms: Platform[] = [
+ {
+ os: "darwin",
+ arch: "arm64",
+ bin: "bun-darwin-aarch64",
+ exe: "bin/bun",
+ },
+ {
+ os: "darwin",
+ arch: "x64",
+ avx2: true,
+ bin: "bun-darwin-x64",
+ exe: "bin/bun",
+ },
+ {
+ os: "darwin",
+ arch: "x64",
+ bin: "bun-darwin-x64-baseline",
+ exe: "bin/bun",
+ },
+ {
+ os: "linux",
+ arch: "arm64",
+ bin: "bun-linux-aarch64",
+ exe: "bin/bun",
+ },
+ {
+ os: "linux",
+ arch: "x64",
+ avx2: true,
+ bin: "bun-linux-x64",
+ exe: "bin/bun",
+ },
+ {
+ os: "linux",
+ arch: "x64",
+ bin: "bun-linux-x64-baseline",
+ exe: "bin/bun",
+ },
+];
+
+export const supportedPlatforms: Platform[] = platforms
+ .filter(
+ (platform) =>
+ platform.os === os && platform.arch === arch && (!platform.avx2 || avx2),
+ )
+ .sort((a, b) => (a.avx2 === b.avx2 ? 0 : a.avx2 ? -1 : 1));
+
+function isLinuxAVX2(): boolean {
+ try {
+ return read("/proc/cpuinfo").includes("avx2");
+ } catch (error) {
+ console.debug("isLinuxAVX2 failed", error);
+ return false;
+ }
+}
+
+function isDarwinAVX2(): boolean {
+ try {
+ const { exitCode, stdout } = spawn("sysctl", ["-n", "machdep.cpu"]);
+ return exitCode === 0 && stdout.includes("AVX2");
+ } catch (error) {
+ console.debug("isDarwinAVX2 failed", error);
+ return false;
+ }
+}
+
+function isRosetta2(): boolean {
+ try {
+ const { exitCode, stdout } = spawn("sysctl", [
+ "-n",
+ "sysctl.proc_translated",
+ ]);
+ return exitCode === 0 && stdout.includes("1");
+ } catch (error) {
+ console.debug("isRosetta2 failed", error);
+ return false;
+ }
+}
diff --git a/packages/bun-npm/src/util.ts b/packages/bun-npm/src/util.ts
new file mode 100644
index 000000000..c36bda2b7
--- /dev/null
+++ b/packages/bun-npm/src/util.ts
@@ -0,0 +1,191 @@
+import fs from "fs";
+import path, { dirname } from "path";
+import { tmpdir } from "os";
+import child_process from "child_process";
+
+if (process.env["DEBUG"] !== "1") {
+ console.debug = () => {};
+}
+
+export function join(...paths: (string | string[])[]): string {
+ return path.join(...paths.flat(2));
+}
+
+export function tmp(): string {
+ const path = fs.mkdtempSync(join(tmpdir(), "bun-"));
+ console.debug("tmp", path);
+ return path;
+}
+
+export function rm(path: string): void {
+ console.debug("rm", path);
+ try {
+ fs.rmSync(path, { recursive: true });
+ return;
+ } catch (error) {
+ console.debug("rmSync failed", error);
+ // Did not exist before Node.js v14.
+ // Attempt again with older, slower implementation.
+ }
+ let stats: fs.Stats;
+ try {
+ stats = fs.lstatSync(path);
+ } catch (error) {
+ console.debug("lstatSync failed", error);
+ // The file was likely deleted, so return early.
+ return;
+ }
+ if (!stats.isDirectory()) {
+ fs.unlinkSync(path);
+ return;
+ }
+ try {
+ fs.rmdirSync(path, { recursive: true });
+ return;
+ } catch (error) {
+ console.debug("rmdirSync failed", error);
+ // Recursive flag did not exist before Node.js X.
+ // Attempt again with older, slower implementation.
+ }
+ for (const filename of fs.readdirSync(path)) {
+ rm(join(path, filename));
+ }
+ fs.rmdirSync(path);
+}
+
+export function rename(path: string, newPath: string): void {
+ console.debug("rename", path, newPath);
+ try {
+ fs.renameSync(path, newPath);
+ return;
+ } catch (error) {
+ console.debug("renameSync failed", error);
+ // If there is an error, delete the new path and try again.
+ }
+ try {
+ rm(newPath);
+ } catch (error) {
+ console.debug("rm failed", error);
+ // The path could have been deleted already.
+ }
+ fs.renameSync(path, newPath);
+}
+
+export function write(
+ path: string,
+ content: string | ArrayBuffer | ArrayBufferView,
+): void {
+ console.debug("write", path);
+ try {
+ fs.writeFileSync(path, content);
+ return;
+ } catch (error) {
+ console.debug("writeFileSync failed", error);
+ // If there is an error, ensure the parent directory
+ // exists and try again.
+ try {
+ fs.mkdirSync(dirname(path), { recursive: true });
+ } catch (error) {
+ console.debug("mkdirSync failed", error);
+ // The directory could have been created already.
+ }
+ fs.writeFileSync(path, content);
+ }
+}
+
+export function read(path: string): string {
+ console.debug("read", path);
+ return fs.readFileSync(path, "utf-8");
+}
+
+export function chmod(path: string, mode: fs.Mode): void {
+ console.debug("chmod", path, mode);
+ fs.chmodSync(path, mode);
+}
+
+export function spawn(
+ cmd: string,
+ args: string[],
+ options: child_process.SpawnOptions = {},
+): {
+ exitCode: number;
+ stdout: string;
+ stderr: string;
+} {
+ console.debug("spawn", [cmd, ...args].join(" "));
+ const { status, stdout, stderr } = child_process.spawnSync(cmd, args, {
+ stdio: "pipe",
+ encoding: "utf-8",
+ ...options,
+ });
+ return {
+ exitCode: status ?? 1,
+ stdout,
+ stderr,
+ };
+}
+
+export type Response = {
+ readonly status: number;
+ arrayBuffer(): Promise<ArrayBuffer>;
+ json<T>(): Promise<T>;
+};
+
+export const fetch = "fetch" in globalThis ? webFetch : nodeFetch;
+
+async function webFetch(url: string, assert?: boolean): Promise<Response> {
+ const response = await globalThis.fetch(url);
+ console.debug("fetch", url, response.status);
+ if (assert !== false && !isOk(response.status)) {
+ throw new Error(`${response.status}: ${url}`);
+ }
+ return response;
+}
+
+async function nodeFetch(url: string, assert?: boolean): Promise<Response> {
+ const { get } = await import("node:http");
+ return new Promise((resolve, reject) => {
+ get(url, (response) => {
+ console.debug("get", url, response.statusCode);
+ const status = response.statusCode ?? 501;
+ if (response.headers.location && isRedirect(status)) {
+ return nodeFetch(url).then(resolve, reject);
+ }
+ if (assert !== false && !isOk(status)) {
+ return reject(new Error(`${status}: ${url}`));
+ }
+ const body: Buffer[] = [];
+ response.on("data", (chunk) => {
+ body.push(chunk);
+ });
+ response.on("end", () => {
+ resolve({
+ status,
+ async arrayBuffer() {
+ return Buffer.concat(body).buffer as ArrayBuffer;
+ },
+ async json() {
+ const text = Buffer.concat(body).toString("utf-8");
+ return JSON.parse(text);
+ },
+ });
+ });
+ }).on("error", reject);
+ });
+}
+
+function isOk(status: number): boolean {
+ return status === 200;
+}
+
+function isRedirect(status: number): boolean {
+ switch (status) {
+ case 301: // Moved Permanently
+ case 308: // Permanent Redirect
+ case 302: // Found
+ case 307: // Temporary Redirect
+ case 303: // See Other
+ return true;
+ }
+ return false;
+}
diff --git a/packages/bun-npm/tsconfig.json b/packages/bun-npm/tsconfig.json
new file mode 100644
index 000000000..9272b9920
--- /dev/null
+++ b/packages/bun-npm/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "lib": ["esnext"],
+ "module": "esnext",
+ "target": "esnext",
+ "moduleResolution": "node",
+ "types": ["bun-types"],
+ "esModuleInterop": true,
+ "allowJs": true,
+ "strict": true,
+ "resolveJsonModule": true
+ },
+ "include": [
+ "src",
+ "scripts"
+ ]
+}