aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ashcon Partovi <ashcon@partovi.net> 2023-01-22 15:47:25 -0800
committerGravatar Ashcon Partovi <ashcon@partovi.net> 2023-01-22 15:47:25 -0800
commit43403f693fa128004f4f0df6a1740b46cbf03808 (patch)
tree3b76d3b8dc649918570bef65e50f88e47bed1516
parent188f472ed2956c94e41842d24ae1fb4fe58b953f (diff)
downloadbun-43403f693fa128004f4f0df6a1740b46cbf03808.tar.gz
bun-43403f693fa128004f4f0df6a1740b46cbf03808.tar.zst
bun-43403f693fa128004f4f0df6a1740b46cbf03808.zip
Add bun-npm package to publish and install Bun via npm
-rw-r--r--packages/bun-npm/.gitignore5
-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.ts190
-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.ts151
-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
25 files changed, 897 insertions, 0 deletions
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/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..c87d1c9cf
--- /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..e4dd34f7c
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-darwin-aarch64",
+ "version": "0.1.7",
+ "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"
+ ]
+} \ No newline at end of file
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..16af70345
--- /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.1.7",
+ "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"
+ ]
+} \ No newline at end of file
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..3ae5c4c31
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-darwin-x64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-darwin-x64",
+ "version": "0.1.7",
+ "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"
+ ]
+} \ No newline at end of file
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..138c870c6
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-linux-aarch64",
+ "version": "0.1.7",
+ "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"
+ ]
+} \ No newline at end of file
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..89500a8e5
--- /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.1.7",
+ "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"
+ ]
+} \ No newline at end of file
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..c4332e3e0
--- /dev/null
+++ b/packages/bun-npm/npm/@oven/bun-linux-x64/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@oven/bun-linux-x64",
+ "version": "0.1.7",
+ "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"
+ ]
+} \ No newline at end of file
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..b88097d74
--- /dev/null
+++ b/packages/bun-npm/npm/bun/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "bun",
+ "version": "0.1.7",
+ "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.1.7",
+ "@oven/bun-darwin-x64": "0.1.7",
+ "@oven/bun-darwin-x64-baseline": "0.1.7",
+ "@oven/bun-linux-aarch64": "0.1.7",
+ "@oven/bun-linux-x64": "0.1.7",
+ "@oven/bun-linux-x64-baseline": "0.1.7"
+ },
+ "os": [
+ "darwin",
+ "linux"
+ ],
+ "cpu": [
+ "arm64",
+ "x64"
+ ]
+} \ No newline at end of file
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..95178d77e
--- /dev/null
+++ b/packages/bun-npm/scripts/npm-build.ts
@@ -0,0 +1,190 @@
+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";
+
+const tag = process.argv[2];
+const release = await getRelease(tag);
+const version = release.tag_name.replace("bun-v", "");
+const npmPackage = "bun";
+const npmOwner = "@oven";
+
+await buildBasePackage();
+for (const platform of platforms) {
+ await buildPackage(platform);
+}
+const publish = process.argv[3] === "publish";
+const dryRun = process.argv[3] === "dry-run";
+if (publish || dryRun) {
+ 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}@${version}`);
+ const cwd = join("npm", npmPackage);
+ const define = {
+ npmVersion: `"${version}"`,
+ 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,
+ scripts: {
+ postinstall: "node install.js",
+ },
+ optionalDependencies: Object.fromEntries(
+ platforms.map(({ bin }) => [`${npmOwner}/${bin}`, version]),
+ ),
+ bin: {
+ bun: "bin/bun",
+ },
+ os,
+ cpu,
+ });
+ done();
+}
+
+async function buildPackage({ bin, exe, os, arch }: Platform): Promise<void> {
+ const npmPackage = `${npmOwner}/${bin}`;
+ const done = log("Building:", `${npmPackage}@${version}`);
+ 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,
+ 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",
+ version === "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<
+ Endpoints["GET /repos/{owner}/{repo}/releases/latest"]["response"]["data"]
+> {
+ const tag = version
+ ? version === "canary" || version.startsWith("bun-v")
+ ? version
+ : `bun-v${version}`
+ : null;
+ const response = await fetch(
+ tag
+ ? `https://api.github.com/repos/oven-sh/bun/releases/tags/${tag}`
+ : `https://api.github.com/repos/oven-sh/bun/releases/latest`,
+ );
+ return response.json();
+}
+
+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));
+}
+
+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..fbf7a76bf
--- /dev/null
+++ b/packages/bun-npm/src/install.ts
@@ -0,0 +1,151 @@
+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") {
+ return;
+ }
+ const { npm_config_user_agent } = process.env;
+ if (npm_config_user_agent && /\byarn\//.test(npm_config_user_agent)) {
+ return;
+ }
+ try {
+ rename(path, join(__dirname, "bin", "bun"));
+ } catch (error) {
+ console.debug("optimizeBun failed", error);
+ }
+}
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"
+ ]
+}