mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
7 Commits
fix-node-h
...
feat/npm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eb7142c9f | ||
|
|
f52831ba42 | ||
|
|
ee7fe5892c | ||
|
|
028b3f0aff | ||
|
|
edd583e481 | ||
|
|
de90a5d935 | ||
|
|
43403f693f |
47
.github/workflows/bun-npm-release.yml
vendored
Normal file
47
.github/workflows/bun-npm-release.yml
vendored
Normal file
@@ -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 }}
|
||||
5
packages/bun-npm/.gitignore
vendored
Normal file
5
packages/bun-npm/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
.env
|
||||
node_modules
|
||||
/npm/**/bin
|
||||
/npm/**/*.js
|
||||
1
packages/bun-npm/.npmrc
Normal file
1
packages/bun-npm/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
15
packages/bun-npm/README.md
Normal file
15
packages/bun-npm/README.md
Normal file
@@ -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.
|
||||
BIN
packages/bun-npm/bun.lockb
Executable file
BIN
packages/bun-npm/bun.lockb
Executable file
Binary file not shown.
3
packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md
Normal file
3
packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bun
|
||||
|
||||
This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
|
||||
16
packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json
Normal file
16
packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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._
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
3
packages/bun-npm/npm/@oven/bun-darwin-x64/README.md
Normal file
3
packages/bun-npm/npm/@oven/bun-darwin-x64/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bun
|
||||
|
||||
This is the macOS x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
|
||||
16
packages/bun-npm/npm/@oven/bun-darwin-x64/package.json
Normal file
16
packages/bun-npm/npm/@oven/bun-darwin-x64/package.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
3
packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md
Normal file
3
packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bun
|
||||
|
||||
This is the Linux arm64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
|
||||
16
packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json
Normal file
16
packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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._
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
3
packages/bun-npm/npm/@oven/bun-linux-x64/README.md
Normal file
3
packages/bun-npm/npm/@oven/bun-linux-x64/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bun
|
||||
|
||||
This is the Linux x64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.sh
|
||||
16
packages/bun-npm/npm/@oven/bun-linux-x64/package.json
Normal file
16
packages/bun-npm/npm/@oven/bun-linux-x64/package.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
31
packages/bun-npm/npm/bun/README.md
Normal file
31
packages/bun-npm/npm/bun/README.md
Normal file
@@ -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
|
||||
41
packages/bun-npm/npm/bun/package.json
Normal file
41
packages/bun-npm/npm/bun/package.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
15
packages/bun-npm/package.json
Normal file
15
packages/bun-npm/package.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
227
packages/bun-npm/scripts/npm-build.ts
Normal file
227
packages/bun-npm/scripts/npm-build.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
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);
|
||||
// Note: this needs to be run using canary
|
||||
npmVersion = `${Bun.version}-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`);
|
||||
};
|
||||
}
|
||||
13
packages/bun-npm/scripts/npm-exec.ts
Normal file
13
packages/bun-npm/scripts/npm-exec.ts
Normal file
@@ -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);
|
||||
});
|
||||
10
packages/bun-npm/scripts/npm-postinstall.ts
Normal file
10
packages/bun-npm/scripts/npm-postinstall.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { importBun, optimizeBun } from "../src/install";
|
||||
|
||||
importBun()
|
||||
.then((path) => {
|
||||
optimizeBun(path);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
159
packages/bun-npm/src/install.ts
Normal file
159
packages/bun-npm/src/install.ts
Normal file
@@ -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",
|
||||
);
|
||||
}
|
||||
100
packages/bun-npm/src/platform.ts
Normal file
100
packages/bun-npm/src/platform.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
191
packages/bun-npm/src/util.ts
Normal file
191
packages/bun-npm/src/util.ts
Normal file
@@ -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;
|
||||
}
|
||||
17
packages/bun-npm/tsconfig.json
Normal file
17
packages/bun-npm/tsconfig.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user