diff options
author | 2023-01-22 15:47:25 -0800 | |
---|---|---|
committer | 2023-01-22 15:47:25 -0800 | |
commit | 43403f693fa128004f4f0df6a1740b46cbf03808 (patch) | |
tree | 3b76d3b8dc649918570bef65e50f88e47bed1516 | |
parent | 188f472ed2956c94e41842d24ae1fb4fe58b953f (diff) | |
download | bun-43403f693fa128004f4f0df6a1740b46cbf03808.tar.gz bun-43403f693fa128004f4f0df6a1740b46cbf03808.tar.zst bun-43403f693fa128004f4f0df6a1740b46cbf03808.zip |
Add bun-npm package to publish and install Bun via npm
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 Binary files differnew file mode 100755 index 000000000..c87d1c9cf --- /dev/null +++ b/packages/bun-npm/bun.lockb 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" + ] +} |