aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/bun-linux-build.yml35
-rw-r--r--.github/workflows/bun-mac-aarch64.yml35
-rw-r--r--.github/workflows/bun-mac-x64-baseline.yml35
-rw-r--r--.github/workflows/bun-mac-x64.yml35
-rw-r--r--.github/workflows/bun-release.yml6
-rw-r--r--.scripts/sign-release.ts93
-rwxr-xr-xpackages/bun-npm/bun.lockbbin14600 -> 0 bytes
-rw-r--r--packages/bun-npm/scripts/npm-build.ts256
-rw-r--r--packages/bun-npm/src/util.ts212
-rw-r--r--packages/bun-release/.gitignore (renamed from packages/bun-npm/.gitignore)0
-rw-r--r--packages/bun-release/.npmrc (renamed from packages/bun-npm/.npmrc)0
-rw-r--r--packages/bun-release/README.md (renamed from packages/bun-npm/README.md)4
-rwxr-xr-xpackages/bun-release/bun.lockbbin0 -> 22796 bytes
-rw-r--r--packages/bun-release/npm/@oven/bun-darwin-aarch64/README.md (renamed from packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md)0
-rw-r--r--packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json (renamed from packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json)2
-rw-r--r--packages/bun-release/npm/@oven/bun-darwin-x64-baseline/README.md (renamed from packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/README.md)0
-rw-r--r--packages/bun-release/npm/@oven/bun-darwin-x64-baseline/package.json (renamed from packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/package.json)2
-rw-r--r--packages/bun-release/npm/@oven/bun-darwin-x64/README.md (renamed from packages/bun-npm/npm/@oven/bun-darwin-x64/README.md)0
-rw-r--r--packages/bun-release/npm/@oven/bun-darwin-x64/package.json (renamed from packages/bun-npm/npm/@oven/bun-darwin-x64/package.json)2
-rw-r--r--packages/bun-release/npm/@oven/bun-linux-aarch64/README.md (renamed from packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md)0
-rw-r--r--packages/bun-release/npm/@oven/bun-linux-aarch64/package.json (renamed from packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json)2
-rw-r--r--packages/bun-release/npm/@oven/bun-linux-x64-baseline/README.md (renamed from packages/bun-npm/npm/@oven/bun-linux-x64-baseline/README.md)0
-rw-r--r--packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json (renamed from packages/bun-npm/npm/@oven/bun-linux-x64-baseline/package.json)2
-rw-r--r--packages/bun-release/npm/@oven/bun-linux-x64/README.md (renamed from packages/bun-npm/npm/@oven/bun-linux-x64/README.md)0
-rw-r--r--packages/bun-release/npm/@oven/bun-linux-x64/package.json (renamed from packages/bun-npm/npm/@oven/bun-linux-x64/package.json)2
-rw-r--r--packages/bun-release/npm/bun/README.md (renamed from packages/bun-npm/npm/bun/README.md)0
-rw-r--r--packages/bun-release/npm/bun/package.json (renamed from packages/bun-npm/npm/bun/package.json)14
-rw-r--r--packages/bun-release/package.json (renamed from packages/bun-npm/package.json)13
-rw-r--r--packages/bun-release/scripts/npm-exec.ts (renamed from packages/bun-npm/scripts/npm-exec.ts)2
-rw-r--r--packages/bun-release/scripts/npm-postinstall.ts (renamed from packages/bun-npm/scripts/npm-postinstall.ts)2
-rw-r--r--packages/bun-release/scripts/upload-assets.ts113
-rw-r--r--packages/bun-release/scripts/upload-npm.ts191
-rw-r--r--packages/bun-release/src/console.ts77
-rw-r--r--packages/bun-release/src/fetch.ts73
-rw-r--r--packages/bun-release/src/fs.ts166
-rw-r--r--packages/bun-release/src/github.ts113
-rw-r--r--packages/bun-release/src/npm/install.ts (renamed from packages/bun-npm/src/install.ts)66
-rw-r--r--packages/bun-release/src/platform.ts (renamed from packages/bun-npm/src/platform.ts)10
-rw-r--r--packages/bun-release/src/spawn.ts24
-rw-r--r--packages/bun-release/tsconfig.json (renamed from packages/bun-npm/tsconfig.json)6
40 files changed, 826 insertions, 767 deletions
diff --git a/.github/workflows/bun-linux-build.yml b/.github/workflows/bun-linux-build.yml
index cfe79ef02..39382f22b 100644
--- a/.github/workflows/bun-linux-build.yml
+++ b/.github/workflows/bun-linux-build.yml
@@ -146,38 +146,3 @@ jobs:
name: "Canary (${{github.sha}})"
tag: "canary"
artifacts: "${{runner.temp}}/release/bun-${{matrix.tag}}.zip,${{runner.temp}}/release/bun-${{matrix.tag}}-profile.zip"
- - id: setup-bun
- name: Setup Bun
- uses: oven-sh/setup-bun@v0.1.8
- if: |
- github.repository_owner == 'oven-sh'
- && github.ref == 'refs/heads/main'
- with:
- bun-version: canary
- github-token: ${{ secrets.GITHUB_TOKEN }}
- # - name: Sign Release
- # id: sign-release
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # env:
- # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- # run: |
- # echo "$GPG_PASSPHRASE" | bun run .scripts/sign-release.ts
- # - name: Release Checksum
- # id: release-checksum
- # uses: ncipollo/release-action@v1
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # with:
- # prerelease: true
- # body: "This canary release of Bun corresponds to the commit [${{ github.sha }}]"
- # allowUpdates: true
- # replacesArtifacts: true
- # generateReleaseNotes: true
- # artifactErrorsFailBuild: true
- # token: ${{ secrets.GITHUB_TOKEN }}
- # name: "Canary (${{github.sha}})"
- # tag: "canary"
- # artifacts: "SHASUMS256.txt,SHASUMS256.txt.asc"
diff --git a/.github/workflows/bun-mac-aarch64.yml b/.github/workflows/bun-mac-aarch64.yml
index 3b2ce89ca..923f30a5c 100644
--- a/.github/workflows/bun-mac-aarch64.yml
+++ b/.github/workflows/bun-mac-aarch64.yml
@@ -384,38 +384,3 @@ jobs:
name: "Canary (${{github.sha}})"
tag: "canary"
artifacts: "${{runner.temp}}/release/${{matrix.tag}}.zip,${{runner.temp}}/release/${{matrix.tag}}-profile.zip"
- - id: setup-bun
- name: Setup Bun
- uses: oven-sh/setup-bun@v0.1.8
- if: |
- github.repository_owner == 'oven-sh'
- && github.ref == 'refs/heads/main'
- with:
- bun-version: canary
- github-token: ${{ secrets.GITHUB_TOKEN }}
- # - name: Sign Release
- # id: sign-release
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # env:
- # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- # run: |
- # echo "$GPG_PASSPHRASE" | bun run .scripts/sign-release.ts
- # - name: Release Checksum
- # id: release-checksum
- # uses: ncipollo/release-action@v1
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # with:
- # prerelease: true
- # body: "This canary release of Bun corresponds to the commit [${{ github.sha }}]"
- # allowUpdates: true
- # replacesArtifacts: true
- # generateReleaseNotes: true
- # artifactErrorsFailBuild: true
- # token: ${{ secrets.GITHUB_TOKEN }}
- # name: "Canary (${{github.sha}})"
- # tag: "canary"
- # artifacts: "SHASUMS256.txt,SHASUMS256.txt.asc"
diff --git a/.github/workflows/bun-mac-x64-baseline.yml b/.github/workflows/bun-mac-x64-baseline.yml
index b7261b7d7..f28d10947 100644
--- a/.github/workflows/bun-mac-x64-baseline.yml
+++ b/.github/workflows/bun-mac-x64-baseline.yml
@@ -388,38 +388,3 @@ jobs:
name: "Canary (${{github.sha}})"
tag: "canary"
artifacts: "${{runner.temp}}/release/${{matrix.tag}}.zip,${{runner.temp}}/release/${{matrix.tag}}-profile.zip"
- - id: setup-bun
- name: Setup Bun
- uses: oven-sh/setup-bun@v0.1.8
- if: |
- github.repository_owner == 'oven-sh'
- && github.ref == 'refs/heads/main'
- with:
- bun-version: canary
- github-token: ${{ secrets.GITHUB_TOKEN }}
- # - name: Sign Release
- # id: sign-release
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # env:
- # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- # run: |
- # echo "$GPG_PASSPHRASE" | bun run .scripts/sign-release.ts
- # - name: Release Checksum
- # id: release-checksum
- # uses: ncipollo/release-action@v1
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # with:
- # prerelease: true
- # body: "This canary release of Bun corresponds to the commit [${{ github.sha }}]"
- # allowUpdates: true
- # replacesArtifacts: true
- # generateReleaseNotes: true
- # artifactErrorsFailBuild: true
- # token: ${{ secrets.GITHUB_TOKEN }}
- # name: "Canary (${{github.sha}})"
- # tag: "canary"
- # artifacts: "SHASUMS256.txt,SHASUMS256.txt.asc"
diff --git a/.github/workflows/bun-mac-x64.yml b/.github/workflows/bun-mac-x64.yml
index f094c7b1e..67e07ffe0 100644
--- a/.github/workflows/bun-mac-x64.yml
+++ b/.github/workflows/bun-mac-x64.yml
@@ -390,38 +390,3 @@ jobs:
name: "Canary (${{github.sha}})"
tag: "canary"
artifacts: "${{runner.temp}}/release/${{matrix.tag}}.zip,${{runner.temp}}/release/${{matrix.tag}}-profile.zip"
- - id: setup-bun
- name: Setup Bun
- uses: oven-sh/setup-bun@v0.1.8
- if: |
- github.repository_owner == 'oven-sh'
- && github.ref == 'refs/heads/main'
- with:
- bun-version: canary
- github-token: ${{ secrets.GITHUB_TOKEN }}
- # - name: Sign Release
- # id: sign-release
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # env:
- # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- # run: |
- # echo "$GPG_PASSPHRASE" | bun run .scripts/sign-release.ts
- # - name: Release Checksum
- # id: release-checksum
- # uses: ncipollo/release-action@v1
- # if: |
- # github.repository_owner == 'oven-sh'
- # && github.ref == 'refs/heads/main'
- # with:
- # prerelease: true
- # body: "This canary release of Bun corresponds to the commit [${{ github.sha }}]"
- # allowUpdates: true
- # replacesArtifacts: true
- # generateReleaseNotes: true
- # artifactErrorsFailBuild: true
- # token: ${{ secrets.GITHUB_TOKEN }}
- # name: "Canary (${{github.sha}})"
- # tag: "canary"
- # artifacts: "SHASUMS256.txt,SHASUMS256.txt.asc"
diff --git a/.github/workflows/bun-release.yml b/.github/workflows/bun-release.yml
index 141725379..b25c0d17b 100644
--- a/.github/workflows/bun-release.yml
+++ b/.github/workflows/bun-release.yml
@@ -4,7 +4,6 @@ on:
release:
types:
- published
- - edited # canary only
workflow_dispatch:
inputs:
tag:
@@ -17,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
- working-directory: packages/bun-npm
+ working-directory: packages/bun-release
steps:
- id: checkout
name: Checkout
@@ -41,7 +40,7 @@ jobs:
run: bun install
- id: bun-run
name: Release
- run: bun run npm -- "${{ env.TAG }}" publish
+ run: bun upload-npm -- "${{ env.TAG }}" publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -73,7 +72,6 @@ jobs:
uses: docker/metadata-action@v4
with:
images: oven/bun
- flavor: latest=false # TODO
tags: |
type=match,pattern=(bun-v)?(canary|\d.\d.\d),group=2,value=${{ env.TAG }}
type=match,pattern=(bun-v)?(canary|\d.\d.\d),group=2,value=${{ env.TAG }}
diff --git a/.scripts/sign-release.ts b/.scripts/sign-release.ts
deleted file mode 100644
index 9ff189659..000000000
--- a/.scripts/sign-release.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/// <reference types="bun-types" />
-import { SHA256, which, write, spawnSync } from "bun";
-import { isatty } from "node:tty";
-import { createInterface } from "node:readline";
-
-const tag = process.argv[2];
-const url = tag
- ? `https://api.github.com/repos/oven-sh/bun/releases/tags/${tag}`
- : "https://api.github.com/repos/oven-sh/bun/releases/latest";
-const response = await fetch(url);
-if (response.status === 404) {
- throw new Error(`Release not found: ${tag}`);
-}
-if (!response.ok) {
- throw new Error(`Failed to find release: ${tag} [status: ${response.status}]`);
-}
-const release: any = await response.json();
-if (release.assets.find(({ name }) => name === "SHA256SUMS.txt.asc")) {
- throw new Error(`Release already signed: ${tag}`);
-}
-const sha256s = await Promise.all(
- release.assets.map(async ({ name, browser_download_url }) => {
- return `${await sha256(browser_download_url)} ${name}`;
- }),
-);
-await write("SHASUMS256.txt", sha256s.join("\n"));
-await sign("SHASUMS256.txt");
-
-async function sha256(url: string): Promise<string> {
- const response = await fetch(url);
- if (!response.ok) {
- throw new Error(`Failed to find asset: ${url} [status: ${response.status}]`);
- }
- const body = await response.arrayBuffer();
- const sha256 = SHA256.hash(body);
- return Buffer.from(sha256).toString("hex");
-}
-
-async function sign(path: string): Promise<void> {
- // https://www.gnupg.org/gph/en/manual/x135.html
- if (!which("gpg")) {
- throw new Error("Command not found: gpg");
- }
- const { stdout } = spawnSync(
- [
- "gpg",
- "--list-secret-keys",
- "--keyid-format",
- "long"
- ],
- {
- stdout: "pipe",
- stderr: "pipe",
- }
- );
- if (!stdout.includes("F3DCC08A8572C0749B3E18888EAB4D40A7B22B59")) {
- console.warn("Signature is likely wrong, key not found: robobun@oven.sh");
- }
- const passphrase = await prompt("Passphrase:");
- spawnSync(
- [
- "gpg",
- "--batch",
- "--yes",
- "--clearsign",
- "--output",
- `${path}.asc`,
- path
- ],
- {
- stdin: new TextEncoder().encode(passphrase),
- stdout: "inherit",
- stderr: "inherit",
- }
- );
-}
-
-async function prompt(question: string): Promise<string> {
- if (isatty(process.stdout.fd)) {
- return globalThis.prompt(question) || "";
- }
- const reader = createInterface({
- input: process.stdin,
- terminal: false
- });
- let buffer = "";
- reader.on("line", (line) => {
- buffer += line;
- });
- return new Promise((resolve) => {
- reader.once("close", () => resolve(buffer));
- });
-}
diff --git a/packages/bun-npm/bun.lockb b/packages/bun-npm/bun.lockb
deleted file mode 100755
index 41a60a1ec..000000000
--- a/packages/bun-npm/bun.lockb
+++ /dev/null
Binary files differ
diff --git a/packages/bun-npm/scripts/npm-build.ts b/packages/bun-npm/scripts/npm-build.ts
deleted file mode 100644
index 9f3547565..000000000
--- a/packages/bun-npm/scripts/npm-build.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-import type { Endpoints } from "@octokit/types";
-import { copy, exists, 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") {
- npmVersion = await getCanaryVersion();
- } 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",
- bunx: "bin/bun",
- },
- os,
- cpu,
- });
- if (exists(".npmrc")) {
- copy(".npmrc", join(cwd, ".npmrc"));
- }
- 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) {
- console.warn(`No asset found: ${bin}`);
- return;
- }
- 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],
- });
- if (exists(".npmrc")) {
- copy(".npmrc", join(cwd, ".npmrc"));
- }
- done();
-}
-
-function publishPackage(name: string, dryRun?: boolean): void {
- const done = log(
- dryRun ? "Dry-run Publishing:" : "Publishing:",
- `${name}@${npmVersion}`,
- );
- const { exitCode, stdout, stderr } = spawn(
- "npm",
- [
- "publish",
- "--access",
- "public",
- "--tag",
- npmVersion.includes("canary") ? "canary" : "latest",
- ...(dryRun ? ["--dry-run"] : []),
- ],
- {
- cwd: join("npm", name),
- },
- );
- if (exitCode === 0) {
- done();
- return;
- }
- console.warn(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());
-}
-
-async function getCanaryVersion(): Promise<string> {
- const date = new Date().toISOString().split("T")[0].replace(/-/g, "");
- const semver = `${Bun.version}-canary.${date}`;
- try {
- const sha = await getSha("canary");
- const response = await fetch(
- `https://registry.npmjs.org/-/package/${npmPackage}/dist-tags`,
- );
- const { canary }: { canary: string } = await response.json();
- if (canary.startsWith(semver)) {
- const match = /canary.[0-9]{8}\.([0-9]+)+?/.exec(canary);
- const build = 1 + (match ? parseInt(match[1]) : 0);
- return `${semver}.${build}+${sha}`;
- }
- return `${semver}.1+${sha}`;
- } catch (error) {
- console.warn("Failed to calculate canary version", error);
- }
- return `${semver}.1`;
-}
-
-function formatTag(version: string): string {
- if (version.includes("canary") || version.startsWith("bun-v")) {
- return version;
- }
- return `bun-v${version}`;
-}
-
-function patchJson(path: string, patch: object): void {
- let value;
- try {
- const existing = JSON.parse(read(path));
- value = {
- ...existing,
- ...patch,
- };
- } catch {
- value = patch;
- }
- write(path, `${JSON.stringify(value, undefined, 2)}\n`);
-}
-
-function buildJs(src: string, dst: string, options: BuildOptions = {}): void {
- const { errors } = buildSync({
- bundle: true,
- treeShaking: true,
- keepNames: true,
- minifySyntax: true,
- pure: ["console.debug"],
- platform: "node",
- target: "es6",
- format: "cjs",
- entryPoints: [src],
- outfile: dst,
- ...options,
- });
- if (errors?.length) {
- const messages = formatMessagesSync(errors, { kind: "error" });
- throw new Error(messages.join("\n"));
- }
-}
-
-function log(...args: any[]): () => void {
- console.write(Bun.inspect(...args));
- const start = Date.now();
- return () => {
- console.write(` [${(Date.now() - start).toFixed()} ms]\n`);
- };
-}
diff --git a/packages/bun-npm/src/util.ts b/packages/bun-npm/src/util.ts
deleted file mode 100644
index e2cf19ee0..000000000
--- a/packages/bun-npm/src/util.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-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 copy(path: string, newPath: string): void {
- console.debug("copy", path, newPath);
- try {
- fs.copyFileSync(path, newPath);
- return;
- } catch (error) {
- console.debug("copyFileSync failed", error);
- }
- write(newPath, read(path));
-}
-
-export function exists(path: string): boolean {
- console.debug("exists", path);
- try {
- return fs.existsSync(path);
- } catch (error) {
- console.debug("existsSync failed", error);
- }
- return false;
-}
-
-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/.gitignore b/packages/bun-release/.gitignore
index 4c021cb09..4c021cb09 100644
--- a/packages/bun-npm/.gitignore
+++ b/packages/bun-release/.gitignore
diff --git a/packages/bun-npm/.npmrc b/packages/bun-release/.npmrc
index aa0ab1f07..aa0ab1f07 100644
--- a/packages/bun-npm/.npmrc
+++ b/packages/bun-release/.npmrc
diff --git a/packages/bun-npm/README.md b/packages/bun-release/README.md
index 4556f0f4d..9a01bdbfd 100644
--- a/packages/bun-npm/README.md
+++ b/packages/bun-release/README.md
@@ -1,6 +1,6 @@
-# bun-npm
+# bun-release
-Scripts that allow Bun to be installed with `npm install`.
+Scripts that release Bun to npm, Dockerhub, Homebrew, etc.
### Running
diff --git a/packages/bun-release/bun.lockb b/packages/bun-release/bun.lockb
new file mode 100755
index 000000000..df506ae60
--- /dev/null
+++ b/packages/bun-release/bun.lockb
Binary files differ
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md b/packages/bun-release/npm/@oven/bun-darwin-aarch64/README.md
index 3b03fdd9f..3b03fdd9f 100644
--- a/packages/bun-npm/npm/@oven/bun-darwin-aarch64/README.md
+++ b/packages/bun-release/npm/@oven/bun-darwin-aarch64/README.md
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json b/packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json
index 46da9b168..701f4c92b 100644
--- a/packages/bun-npm/npm/@oven/bun-darwin-aarch64/package.json
+++ b/packages/bun-release/npm/@oven/bun-darwin-aarch64/package.json
@@ -1,6 +1,6 @@
{
"name": "@oven/bun-darwin-aarch64",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime.",
"homepage": "https://bun.sh",
"bugs": "https://github.com/oven-sh/issues",
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/README.md b/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/README.md
index e451cccfe..e451cccfe 100644
--- a/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/README.md
+++ b/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/README.md
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/package.json b/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/package.json
index a9a330f42..b14492c1c 100644
--- a/packages/bun-npm/npm/@oven/bun-darwin-x64-baseline/package.json
+++ b/packages/bun-release/npm/@oven/bun-darwin-x64-baseline/package.json
@@ -1,6 +1,6 @@
{
"name": "@oven/bun-darwin-x64-baseline",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "This is the macOS x64 binary for Bun, a fast all-in-one JavaScript runtime.",
"homepage": "https://bun.sh",
"bugs": "https://github.com/oven-sh/issues",
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-x64/README.md b/packages/bun-release/npm/@oven/bun-darwin-x64/README.md
index be73fd7a2..be73fd7a2 100644
--- a/packages/bun-npm/npm/@oven/bun-darwin-x64/README.md
+++ b/packages/bun-release/npm/@oven/bun-darwin-x64/README.md
diff --git a/packages/bun-npm/npm/@oven/bun-darwin-x64/package.json b/packages/bun-release/npm/@oven/bun-darwin-x64/package.json
index d83283425..82934da0a 100644
--- a/packages/bun-npm/npm/@oven/bun-darwin-x64/package.json
+++ b/packages/bun-release/npm/@oven/bun-darwin-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@oven/bun-darwin-x64",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "This is the macOS x64 binary for Bun, a fast all-in-one JavaScript runtime.",
"homepage": "https://bun.sh",
"bugs": "https://github.com/oven-sh/issues",
diff --git a/packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md b/packages/bun-release/npm/@oven/bun-linux-aarch64/README.md
index e727d8008..e727d8008 100644
--- a/packages/bun-npm/npm/@oven/bun-linux-aarch64/README.md
+++ b/packages/bun-release/npm/@oven/bun-linux-aarch64/README.md
diff --git a/packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json b/packages/bun-release/npm/@oven/bun-linux-aarch64/package.json
index c2feaf708..4f68fec41 100644
--- a/packages/bun-npm/npm/@oven/bun-linux-aarch64/package.json
+++ b/packages/bun-release/npm/@oven/bun-linux-aarch64/package.json
@@ -1,6 +1,6 @@
{
"name": "@oven/bun-linux-aarch64",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "This is the Linux arm64 binary for Bun, a fast all-in-one JavaScript runtime.",
"homepage": "https://bun.sh",
"bugs": "https://github.com/oven-sh/issues",
diff --git a/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/README.md b/packages/bun-release/npm/@oven/bun-linux-x64-baseline/README.md
index 6b2ce0fcf..6b2ce0fcf 100644
--- a/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/README.md
+++ b/packages/bun-release/npm/@oven/bun-linux-x64-baseline/README.md
diff --git a/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/package.json b/packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json
index e3333b134..1767be0dc 100644
--- a/packages/bun-npm/npm/@oven/bun-linux-x64-baseline/package.json
+++ b/packages/bun-release/npm/@oven/bun-linux-x64-baseline/package.json
@@ -1,6 +1,6 @@
{
"name": "@oven/bun-linux-x64-baseline",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "This is the Linux x64 binary for Bun, a fast all-in-one JavaScript runtime.",
"homepage": "https://bun.sh",
"bugs": "https://github.com/oven-sh/issues",
diff --git a/packages/bun-npm/npm/@oven/bun-linux-x64/README.md b/packages/bun-release/npm/@oven/bun-linux-x64/README.md
index e0fac1347..e0fac1347 100644
--- a/packages/bun-npm/npm/@oven/bun-linux-x64/README.md
+++ b/packages/bun-release/npm/@oven/bun-linux-x64/README.md
diff --git a/packages/bun-npm/npm/@oven/bun-linux-x64/package.json b/packages/bun-release/npm/@oven/bun-linux-x64/package.json
index 71228ad14..16f9309f5 100644
--- a/packages/bun-npm/npm/@oven/bun-linux-x64/package.json
+++ b/packages/bun-release/npm/@oven/bun-linux-x64/package.json
@@ -1,6 +1,6 @@
{
"name": "@oven/bun-linux-x64",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "This is the Linux x64 binary for Bun, a fast all-in-one JavaScript runtime.",
"homepage": "https://bun.sh",
"bugs": "https://github.com/oven-sh/issues",
diff --git a/packages/bun-npm/npm/bun/README.md b/packages/bun-release/npm/bun/README.md
index f344b258e..f344b258e 100644
--- a/packages/bun-npm/npm/bun/README.md
+++ b/packages/bun-release/npm/bun/README.md
diff --git a/packages/bun-npm/npm/bun/package.json b/packages/bun-release/npm/bun/package.json
index caf298447..374518b85 100644
--- a/packages/bun-npm/npm/bun/package.json
+++ b/packages/bun-release/npm/bun/package.json
@@ -1,6 +1,6 @@
{
"name": "bun",
- "version": "0.5.1",
+ "version": "0.5.3",
"description": "Bun is a fast all-in-one JavaScript runtime.",
"keywords": [
"bun",
@@ -24,12 +24,12 @@
"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"
+ "@oven/bun-darwin-aarch64": "0.5.3",
+ "@oven/bun-darwin-x64": "0.5.3",
+ "@oven/bun-darwin-x64-baseline": "0.5.3",
+ "@oven/bun-linux-aarch64": "0.5.3",
+ "@oven/bun-linux-x64": "0.5.3",
+ "@oven/bun-linux-x64-baseline": "0.5.3"
},
"os": [
"darwin",
diff --git a/packages/bun-npm/package.json b/packages/bun-release/package.json
index 33de39c3c..6c28a229f 100644
--- a/packages/bun-npm/package.json
+++ b/packages/bun-release/package.json
@@ -1,15 +1,18 @@
{
"private": true,
- "dependencies": {},
+ "dependencies": {
+ "@octokit/rest": "^19.0.7",
+ "esbuild": "^0.17.3",
+ "jszip": "^3.10.1"
+ },
"devDependencies": {
"@octokit/types": "^8.1.1",
"bun-types": "^0.4.0",
- "prettier": "^2.8.2",
- "esbuild": "^0.17.3",
- "jszip": "^3.10.1"
+ "prettier": "^2.8.2"
},
"scripts": {
"format": "prettier --write src scripts",
- "npm": "bun scripts/npm-build.ts"
+ "upload-npm": "bun scripts/upload-npm.ts",
+ "upload-assets": "bun scripts/upload-assets.ts"
}
}
diff --git a/packages/bun-npm/scripts/npm-exec.ts b/packages/bun-release/scripts/npm-exec.ts
index 3937247cb..5b054ac86 100644
--- a/packages/bun-npm/scripts/npm-exec.ts
+++ b/packages/bun-release/scripts/npm-exec.ts
@@ -1,4 +1,4 @@
-import { importBun } from "../src/install";
+import { importBun } from "../src/npm/install";
import { execFileSync } from "child_process";
importBun()
diff --git a/packages/bun-npm/scripts/npm-postinstall.ts b/packages/bun-release/scripts/npm-postinstall.ts
index c22f2c669..bde044300 100644
--- a/packages/bun-npm/scripts/npm-postinstall.ts
+++ b/packages/bun-release/scripts/npm-postinstall.ts
@@ -1,4 +1,4 @@
-import { importBun, optimizeBun } from "../src/install";
+import { importBun, optimizeBun } from "../src/npm/install";
importBun()
.then((path) => {
diff --git a/packages/bun-release/scripts/upload-assets.ts b/packages/bun-release/scripts/upload-assets.ts
new file mode 100644
index 000000000..27b7ba01a
--- /dev/null
+++ b/packages/bun-release/scripts/upload-assets.ts
@@ -0,0 +1,113 @@
+import { getRelease, uploadAsset } from "../src/github";
+import { fetch } from "../src/fetch";
+import { spawn } from "../src/spawn";
+import { confirm, exit, log, stdin, warn } from "../src/console";
+import { hash, join, rm, tmp, write, basename, blob } from "../src/fs";
+
+const [tag, ...paths] = process.argv.slice(2);
+
+if (!tag) {
+ exit("Invalid arguments: [tag] [...assets]");
+}
+
+const { tag_name, assets } = await getRelease(tag);
+log("Release:", tag_name, "\n");
+log("Existing assets:\n", ...assets.map(({ name }) => `- ${name}\n`));
+log("Updating assets:\n", ...paths.map((path) => `+ ${basename(path)}\n`));
+await confirm();
+
+log("Hashing assets...\n");
+const existing: Map<string, string> = new Map();
+for (const { name, browser_download_url } of assets) {
+ if (name.startsWith("SHASUMS256.txt")) {
+ continue;
+ }
+ const response = await fetch(browser_download_url);
+ const buffer = Buffer.from(await response.arrayBuffer());
+ existing.set(name, await hash(buffer));
+}
+const updated: Map<string, string> = new Map();
+for (const path of paths) {
+ const name = basename(path);
+ updated.set(name, await hash(path));
+}
+log(
+ "Unchanged hashes:\n",
+ ...Array.from(existing.entries())
+ .filter(([name]) => !updated.has(name))
+ .map(([name, sha256]) => ` - ${sha256} => ${name}\n`),
+);
+log(
+ "Changed hashes:\n",
+ ...Array.from(updated.entries()).map(
+ ([name, sha256]) => ` + ${sha256} => ${name}\n`,
+ ),
+);
+await confirm();
+
+log("Signing assets...\n");
+const cwd = tmp();
+const path = join(cwd, "SHASUMS256.txt");
+const signedPath = `${path}.asc`;
+write(
+ path,
+ [
+ ...Array.from(updated.entries()),
+ ...Array.from(existing.entries()).filter(([name]) => !updated.has(name)),
+ ]
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(([name, sha256]) => `${sha256} ${name}`)
+ .join("\n"),
+);
+const { stdout: keys } = spawn("gpg", [
+ "--list-secret-keys",
+ "--keyid-format",
+ "long",
+]);
+const verifiedKeys = [
+ "F3DCC08A8572C0749B3E18888EAB4D40A7B22B59", // robobun@oven.sh
+];
+if (!verifiedKeys.find((key) => keys.includes(key))) {
+ warn("Signature is probably wrong, key not found: robobun@oven.sh");
+}
+const passphrase = await stdin("Passphrase:");
+log();
+const { exitCode, stdout, stderr } = spawn(
+ "gpg",
+ [
+ "--pinentry-mode",
+ "loopback",
+ "--passphrase-fd",
+ "0",
+ "--clearsign",
+ "--output",
+ signedPath,
+ path,
+ ],
+ {
+ // @ts-ignore
+ input: passphrase,
+ stdout: "inherit",
+ stderr: "inherit",
+ },
+);
+if (exitCode !== 0) {
+ exit(stdout || stderr);
+}
+
+const uploads = [...paths, path, signedPath];
+log("Uploading assets:\n", ...uploads.map((path) => ` + ${basename(path)}\n`));
+await confirm();
+
+for (const path of uploads) {
+ const name = basename(path);
+ await uploadAsset(tag_name, name, blob(path));
+}
+try {
+ rm(cwd);
+} catch {
+ warn("Failed to cleanup:", cwd, "\n");
+}
+log("Done");
+
+process.exit(0); // FIXME
diff --git a/packages/bun-release/scripts/upload-npm.ts b/packages/bun-release/scripts/upload-npm.ts
new file mode 100644
index 000000000..8309d7e57
--- /dev/null
+++ b/packages/bun-release/scripts/upload-npm.ts
@@ -0,0 +1,191 @@
+import { join, copy, exists, chmod, write, writeJson } from "../src/fs";
+import { fetch } from "../src/fetch";
+import { spawn } from "../src/spawn";
+import type { Platform } from "../src/platform";
+import { platforms } from "../src/platform";
+import { getSemver } from "../src/github";
+import { getRelease } from "../src/github";
+import type { BuildOptions } from "esbuild";
+import { buildSync, formatMessagesSync } from "esbuild";
+import type { JSZipObject } from "jszip";
+import { loadAsync } from "jszip";
+import { debug, log, error } from "../src/console";
+
+const module = "bun";
+const owner = "@oven";
+let version: 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}`);
+}
+process.exit(0); // HACK
+
+async function build(tag?: string): Promise<void> {
+ const release = await getRelease(tag);
+ if (release.tag_name === "canary") {
+ version = await getCanarySemver();
+ } else {
+ version = release.tag_name.replace("bun-v", "");
+ }
+ await buildRootModule();
+ for (const platform of platforms) {
+ await buildModule(release, platform);
+ }
+}
+
+async function publish(dryRun?: boolean): Promise<void> {
+ const modules = platforms.map(({ bin }) => `${owner}/${bin}`);
+ modules.push(module);
+ for (const module of modules) {
+ publishModule(module, dryRun);
+ }
+}
+
+async function buildRootModule() {
+ log("Building:", `${module}@${version}`);
+ const cwd = join("npm", module);
+ const define = {
+ version: `"${version}"`,
+ module: `"${module}"`,
+ owner: `"${owner}"`,
+ };
+ bundle(join("scripts", "npm-postinstall.ts"), join(cwd, "install.js"), {
+ define,
+ });
+ bundle(join("scripts", "npm-exec.ts"), join(cwd, "bin", "bun"), {
+ define,
+ banner: {
+ js: "#!/usr/bin/env node",
+ },
+ });
+ const os = [...new Set(platforms.map(({ os }) => os))];
+ const cpu = [...new Set(platforms.map(({ arch }) => arch))];
+ writeJson(join(cwd, "package.json"), {
+ name: module,
+ version: version,
+ scripts: {
+ postinstall: "node install.js",
+ },
+ optionalDependencies: Object.fromEntries(
+ platforms.map(({ bin }) => [`${owner}/${bin}`, version]),
+ ),
+ bin: {
+ bun: "bin/bun",
+ bunx: "bin/bun",
+ },
+ os,
+ cpu,
+ });
+ if (exists(".npmrc")) {
+ copy(".npmrc", join(cwd, ".npmrc"));
+ }
+}
+
+async function buildModule(
+ release: Awaited<ReturnType<typeof getRelease>>,
+ { bin, exe, os, arch }: Platform,
+): Promise<void> {
+ const module = `${owner}/${bin}`;
+ log("Building:", `${module}@${version}`);
+ const asset = release.assets.find(({ name }) => name === `${bin}.zip`);
+ if (!asset) {
+ error(`No asset found: ${bin}`);
+ return;
+ }
+ const bun = await extractFromZip(asset.browser_download_url, `${bin}/bun`);
+ const cwd = join("npm", module);
+ write(join(cwd, exe), await bun.async("arraybuffer"));
+ chmod(join(cwd, exe), 0o755);
+ writeJson(join(cwd, "package.json"), {
+ name: module,
+ version: version,
+ preferUnplugged: true,
+ os: [os],
+ cpu: [arch],
+ });
+ if (exists(".npmrc")) {
+ copy(".npmrc", join(cwd, ".npmrc"));
+ }
+}
+
+function publishModule(name: string, dryRun?: boolean): void {
+ log(dryRun ? "Dry-run Publishing:" : "Publishing:", `${name}@${version}`);
+ const { exitCode, stdout, stderr } = spawn(
+ "npm",
+ [
+ "publish",
+ "--access",
+ "public",
+ "--tag",
+ version.includes("canary") ? "canary" : "latest",
+ ...(dryRun ? ["--dry-run"] : []),
+ ],
+ {
+ cwd: join("npm", name),
+ },
+ );
+ if (exitCode === 0) {
+ error(stderr || stdout);
+ }
+}
+
+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;
+ }
+ }
+ debug("Found files:", Object.keys(zip.files));
+ throw new Error(`File not found: ${filename}`);
+}
+
+async function getCanarySemver(): Promise<string> {
+ const date = new Date().toISOString().split("T")[0].replace(/-/g, "");
+ try {
+ const response = await fetch(
+ `https://registry.npmjs.org/-/package/${module}/dist-tags`,
+ );
+ const { canary }: { canary: string } = await response.json();
+ if (canary.includes(date)) {
+ const match = /canary.[0-9]{8}\.([0-9]+)+?/.exec(canary);
+ const build = 1 + (match ? parseInt(match[1]) : 0);
+ return getSemver("canary", build);
+ }
+ } catch (error) {
+ debug("getCanarySemver failed", error);
+ }
+ return getSemver("canary");
+}
+
+function bundle(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"));
+ }
+}
diff --git a/packages/bun-release/src/console.ts b/packages/bun-release/src/console.ts
new file mode 100644
index 000000000..69e0911d6
--- /dev/null
+++ b/packages/bun-release/src/console.ts
@@ -0,0 +1,77 @@
+import { isatty } from "tty";
+import { createInterface } from "readline";
+
+export const isAction = !!process.env["GITHUB_ACTION"];
+
+export const isDebug =
+ process.env["DEBUG"] === "1" ||
+ process.env["LOG_LEVEL"] === "debug" ||
+ process.env["RUNNER_DEBUG"] === "1";
+
+export function debug(...message: any[]): void {
+ if (isAction) {
+ console.debug("::debug::", ...message);
+ } else if (isDebug) {
+ console.debug(...message);
+ }
+}
+
+export function log(...message: any[]): void {
+ console.log(...message);
+}
+
+export function warn(...message: any[]): void {
+ if (isAction) {
+ console.warn("::warning::", ...message);
+ } else {
+ console.warn(...message);
+ }
+}
+
+export function error(...message: any[]): void {
+ if (isAction) {
+ console.error("::error::", ...message);
+ } else {
+ console.error(...message);
+ }
+}
+
+export function exit(...message: any[]): never {
+ error(...message);
+ process.exit(1);
+}
+
+export function isTty(): boolean {
+ return isatty(process.stdout.fd);
+}
+
+export async function stdin(question: string): Promise<string> {
+ if (isTty()) {
+ return prompt(question) || "";
+ }
+ const reader = createInterface({
+ input: process.stdin,
+ terminal: false,
+ });
+ let buffer = "";
+ reader.on("line", (line) => {
+ buffer += line;
+ });
+ return new Promise((resolve) => {
+ reader.once("close", () => resolve(buffer));
+ });
+}
+
+export async function confirm(message?: string): Promise<void> {
+ if (!isTty()) {
+ return;
+ }
+ const question = message ?? "Confirm?";
+ switch (prompt(`${question} [y/n]`)) {
+ case "y":
+ case "Y":
+ log();
+ return;
+ }
+ exit();
+}
diff --git a/packages/bun-release/src/fetch.ts b/packages/bun-release/src/fetch.ts
new file mode 100644
index 000000000..d9c85a5f3
--- /dev/null
+++ b/packages/bun-release/src/fetch.ts
@@ -0,0 +1,73 @@
+import { debug, isDebug } from "./console";
+
+export const fetch = "fetch" in globalThis ? webFetch : nodeFetch;
+
+type Options = RequestInit & { assert?: boolean };
+
+async function webFetch(url: string, options: Options = {}): Promise<Response> {
+ debug("fetch request", url, options);
+ const response = await globalThis.fetch(url, options, { verbose: isDebug });
+ debug("fetch response", response);
+ if (options?.assert !== false && !isOk(response.status)) {
+ try {
+ debug(await response.text());
+ } catch {}
+ throw new Error(`${response.status}: ${url}`);
+ }
+ return response;
+}
+
+async function nodeFetch(
+ url: string,
+ options: Options = {},
+): Promise<Response> {
+ const { get } = await import("node:http");
+ return new Promise((resolve, reject) => {
+ get(url, (response) => {
+ debug("http.get", url, response.statusCode);
+ const status = response.statusCode ?? 501;
+ if (response.headers.location && isRedirect(status)) {
+ return nodeFetch(url).then(resolve, reject);
+ }
+ if (options?.assert !== false && !isOk(status)) {
+ return reject(new Error(`${status}: ${url}`));
+ }
+ const body: Buffer[] = [];
+ response.on("data", (chunk) => {
+ body.push(chunk);
+ });
+ response.on("end", () => {
+ resolve({
+ ok: isOk(status),
+ status,
+ async arrayBuffer() {
+ return Buffer.concat(body).buffer as ArrayBuffer;
+ },
+ async text() {
+ return Buffer.concat(body).toString("utf-8");
+ },
+ async json() {
+ const text = Buffer.concat(body).toString("utf-8");
+ return JSON.parse(text);
+ },
+ } as Response);
+ });
+ }).on("error", reject);
+ });
+}
+
+function isOk(status: number): boolean {
+ return status >= 200 && status <= 204;
+}
+
+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-release/src/fs.ts b/packages/bun-release/src/fs.ts
new file mode 100644
index 000000000..ef20c224a
--- /dev/null
+++ b/packages/bun-release/src/fs.ts
@@ -0,0 +1,166 @@
+import path from "path";
+import fs from "fs";
+import os from "os";
+import crypto from "crypto";
+import { debug } from "./console";
+
+export function join(...paths: (string | string[])[]): string {
+ return path.join(...paths.flat(2));
+}
+
+export function basename(...paths: (string | string[])[]): string {
+ return path.basename(join(...paths));
+}
+
+export function tmp(): string {
+ const tmpdir = process.env["RUNNER_TEMP"] ?? os.tmpdir();
+ const dir = fs.mkdtempSync(join(tmpdir, "bun-"));
+ debug("tmp", dir);
+ return dir;
+}
+
+export function rm(path: string): void {
+ debug("rm", path);
+ try {
+ fs.rmSync(path, { recursive: true });
+ return;
+ } catch (error) {
+ debug("fs.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) {
+ debug("fs.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) {
+ debug("fs.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 {
+ debug("rename", path, newPath);
+ try {
+ fs.renameSync(path, newPath);
+ return;
+ } catch (error) {
+ debug("fs.renameSync failed", error);
+ // If there is an error, delete the new path and try again.
+ }
+ try {
+ rm(newPath);
+ } catch (error) {
+ debug("rm failed", error);
+ // The path could have been deleted already.
+ }
+ fs.renameSync(path, newPath);
+}
+
+export function write(
+ dst: string,
+ content: string | ArrayBuffer | ArrayBufferView,
+): void {
+ debug("write", dst);
+ try {
+ fs.writeFileSync(dst, content);
+ return;
+ } catch (error) {
+ debug("fs.writeFileSync failed", error);
+ // If there is an error, ensure the parent directory
+ // exists and try again.
+ try {
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
+ } catch (error) {
+ debug("fs.mkdirSync failed", error);
+ // The directory could have been created already.
+ }
+ fs.writeFileSync(dst, content);
+ }
+}
+
+export function writeJson(path: string, json: object, force?: boolean): void {
+ let value = json;
+ if (!force && exists(path)) {
+ try {
+ const existing = JSON.parse(read(path));
+ value = {
+ ...existing,
+ ...json,
+ };
+ } catch {
+ value = json;
+ }
+ }
+ write(path, `${JSON.stringify(value, undefined, 2)}\n`);
+}
+
+export function read(path: string): string {
+ debug("read", path);
+ return fs.readFileSync(path, "utf-8");
+}
+
+export function blob(path: string): Blob {
+ debug("blob", path);
+ if ("Bun" in globalThis) {
+ return Bun.file(path);
+ }
+ const buffer = fs.readFileSync(path);
+ return new Blob([buffer], {
+ type: path.endsWith(".zip")
+ ? "application/zip"
+ : path.endsWith(".txt")
+ ? "text/plain"
+ : "application/octet-stream",
+ });
+}
+
+export function hash(content: string | crypto.BinaryLike): string {
+ debug("hash", content);
+ return crypto
+ .createHash("sha256")
+ .update(typeof content === "string" ? fs.readFileSync(content) : content)
+ .digest("hex");
+}
+
+export function chmod(path: string, mode: fs.Mode): void {
+ debug("chmod", path, mode);
+ fs.chmodSync(path, mode);
+}
+
+export function copy(path: string, newPath: string): void {
+ debug("copy", path, newPath);
+ try {
+ fs.copyFileSync(path, newPath);
+ return;
+ } catch (error) {
+ debug("fs.copyFileSync failed", error);
+ }
+ write(newPath, read(path));
+}
+
+export function exists(path: string): boolean {
+ debug("exists", path);
+ try {
+ return fs.existsSync(path);
+ } catch (error) {
+ debug("fs.existsSync failed", error);
+ }
+ return false;
+}
diff --git a/packages/bun-release/src/github.ts b/packages/bun-release/src/github.ts
new file mode 100644
index 000000000..fbbbbf267
--- /dev/null
+++ b/packages/bun-release/src/github.ts
@@ -0,0 +1,113 @@
+import type { Endpoints, RequestParameters, Route } from "@octokit/types";
+import { Octokit } from "octokit";
+import { fetch } from "./fetch";
+import { debug, log, warn, error } from "./console";
+
+const [owner, repo] = process.env["GITHUB_REPOSITORY"]?.split("/") ?? [
+ "oven-sh",
+ "bun",
+];
+
+const octokit = new Octokit({
+ auth: process.env["GITHUB_TOKEN"],
+ request: {
+ fetch,
+ },
+ log: {
+ debug,
+ info: log,
+ warn,
+ error,
+ },
+});
+
+export async function github<R extends Route>(
+ url: R | keyof Endpoints,
+ options?: Omit<
+ R extends keyof Endpoints
+ ? Endpoints[R]["parameters"] & RequestParameters
+ : RequestParameters,
+ "owner" | "repo"
+ >,
+): Promise<
+ R extends keyof Endpoints ? Endpoints[R]["response"]["data"] : unknown
+> {
+ // @ts-ignore
+ const { data } = await octokit.request(url, {
+ owner,
+ repo,
+ ...options,
+ });
+ return data;
+}
+
+export async function getRelease(tag?: string) {
+ if (!tag) {
+ return github("GET /repos/{owner}/{repo}/releases/latest");
+ }
+ return github("GET /repos/{owner}/{repo}/releases/tags/{tag}", {
+ tag: formatTag(tag),
+ });
+}
+
+export async function uploadAsset(tag: string, name: string, blob: Blob) {
+ const release = await getRelease(tag);
+ const asset = release.assets.find((asset) => asset.name === name);
+ // Github requires that existing assets are deleted before uploading
+ // a new asset, but does not provide a rename or re-upload API?!?
+ if (asset) {
+ await github("DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}", {
+ asset_id: asset.id,
+ });
+ }
+ return github(
+ "POST {origin}/repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}",
+ {
+ baseUrl: "https://uploads.github.com",
+ release_id: release.id,
+ name,
+ headers: {
+ "content-type": blob.type,
+ "content-length": blob.size,
+ },
+ data: Buffer.from(await blob.arrayBuffer()),
+ },
+ );
+}
+
+export async function downloadAsset(tag: string, name: string): Promise<Blob> {
+ const release = await getRelease(tag);
+ const asset = release.assets.find((asset) => asset.name === name);
+ if (!asset) {
+ throw new Error(`Asset not found: ${name}`);
+ }
+ const response = await fetch(asset.browser_download_url);
+ return response.blob();
+}
+
+export async function getSha(tag: string, format?: "short" | "long") {
+ const { sha } = await github("GET /repos/{owner}/{repo}/git/tags/{tag_sha}", {
+ tag_sha: formatTag(tag),
+ });
+ return format === "short" ? sha.substring(0, 7) : sha;
+}
+
+export async function getSemver(tag?: string, build?: number): Promise<string> {
+ const { tag_name } = await getRelease(tag);
+ if (tag_name !== "canary") {
+ return tag_name.replace("bun-v", "");
+ }
+ const sha = await getSha(tag_name, "short");
+ const date = new Date().toISOString().split("T")[0].replace(/-/g, "");
+ return `${Bun.version}-canary.${date}.${build ?? 1}+${sha}`;
+}
+
+export function formatTag(tag: string): string {
+ if (tag === "canary" || tag.startsWith("bun-v")) {
+ return tag;
+ }
+ if (tag.startsWith("v")) {
+ return tag.slice(1);
+ }
+ return `bun-v${tag}`;
+}
diff --git a/packages/bun-npm/src/install.ts b/packages/bun-release/src/npm/install.ts
index 9eabd2c41..ac6f2e670 100644
--- a/packages/bun-npm/src/install.ts
+++ b/packages/bun-release/src/npm/install.ts
@@ -1,11 +1,14 @@
-import { fetch, chmod, join, rename, rm, tmp, write, spawn } from "./util";
+import { fetch } from "../fetch";
+import { spawn } from "../spawn";
+import { chmod, join, rename, rm, tmp, write } from "../fs";
import { unzipSync } from "zlib";
-import type { Platform } from "./platform";
-import { os, arch, supportedPlatforms } from "./platform";
+import type { Platform } from "../platform";
+import { os, arch, supportedPlatforms } from "../platform";
+import { debug, error } from "../console";
-declare const npmVersion: string;
-declare const npmPackage: string;
-declare const npmOwner: string;
+declare const version: string;
+declare const module: string;
+declare const owner: string;
export async function importBun(): Promise<string> {
if (!supportedPlatforms.length) {
@@ -15,16 +18,16 @@ export async function importBun(): Promise<string> {
try {
return await requireBun(platform);
} catch (error) {
- console.debug("requireBun failed", error);
+ debug("requireBun failed", error);
}
}
- throw new Error(`Failed to install package "${npmPackage}"`);
+ throw new Error(`Failed to install package "${module}"`);
}
async function requireBun(platform: Platform): Promise<string> {
- const npmPackage = `${npmOwner}/${platform.bin}`;
+ const module = `${owner}/${platform.bin}`;
function resolveBun() {
- const exe = require.resolve(join(npmPackage, platform.exe));
+ const exe = require.resolve(join(module, platform.exe));
const { exitCode, stderr, stdout } = spawn(exe, ["--version"]);
if (exitCode === 0) {
return exe;
@@ -33,29 +36,26 @@ async function requireBun(platform: Platform): Promise<string> {
}
try {
return resolveBun();
- } catch (error) {
- console.debug("resolveBun failed", error);
- console.error(
- `Failed to find package "${npmPackage}".`,
+ } catch (cause) {
+ debug("resolveBun failed", cause);
+ error(
+ `Failed to find package "${module}".`,
`You may have used the "--no-optional" flag when running "npm install".`,
);
}
- const cwd = join("node_modules", npmPackage);
+ const cwd = join("node_modules", module);
try {
installBun(platform, cwd);
- } catch (error) {
- console.debug("installBun failed", error);
- console.error(
- `Failed to install package "${npmPackage}" using "npm install".`,
- error,
- );
+ } catch (cause) {
+ debug("installBun failed", cause);
+ error(`Failed to install package "${module}" using "npm install".`, cause);
try {
await downloadBun(platform, cwd);
- } catch (error) {
- console.debug("downloadBun failed", error);
- console.error(
- `Failed to download package "${npmPackage}" from "registry.npmjs.org".`,
- error,
+ } catch (cause) {
+ debug("downloadBun failed", cause);
+ error(
+ `Failed to download package "${module}" from "registry.npmjs.org".`,
+ cause,
);
}
}
@@ -63,7 +63,7 @@ async function requireBun(platform: Platform): Promise<string> {
}
function installBun(platform: Platform, dst: string): void {
- const npmPackage = `${npmOwner}/${platform.bin}`;
+ const module = `${owner}/${platform.bin}`;
const cwd = tmp();
try {
write(join(cwd, "package.json"), "{}");
@@ -75,7 +75,7 @@ function installBun(platform: Platform, dst: string): void {
"--prefer-offline",
"--no-audit",
"--progress=false",
- `${npmPackage}@${npmVersion}`,
+ `${module}@${version}`,
],
{
cwd,
@@ -87,13 +87,13 @@ function installBun(platform: Platform, dst: string): void {
},
);
if (exitCode === 0) {
- rename(join(cwd, "node_modules", npmPackage), dst);
+ rename(join(cwd, "node_modules", module), dst);
}
} finally {
try {
rm(cwd);
} catch (error) {
- console.debug("rm failed", error);
+ debug("rm failed", error);
// There is nothing to do if the directory cannot be cleaned up.
}
}
@@ -101,7 +101,7 @@ function installBun(platform: Platform, dst: string): void {
async function downloadBun(platform: Platform, dst: string): Promise<void> {
const response = await fetch(
- `https://registry.npmjs.org/${npmOwner}/${platform.bin}/-/${platform.bin}-${npmVersion}.tgz`,
+ `https://registry.npmjs.org/${owner}/${platform.bin}/-/${platform.bin}-${version}.tgz`,
);
const tgz = await response.arrayBuffer();
let buffer: Buffer;
@@ -127,7 +127,7 @@ async function downloadBun(platform: Platform, dst: string): Promise<void> {
try {
chmod(join(dst, name), 0o755);
} catch (error) {
- console.debug("chmod failed", error);
+ debug("chmod failed", error);
}
}
offset += (size + 511) & ~511;
@@ -151,7 +151,7 @@ export function optimizeBun(path: string): void {
rename(path, join(__dirname, "bin", "bun"));
return;
} catch (error) {
- console.debug("optimizeBun failed", error);
+ debug("optimizeBun failed", error);
}
throw new Error(
"Your package manager doesn't seem to support bun. To use bun, install using the following command: curl -fsSL https://bun.sh/install | bash",
diff --git a/packages/bun-npm/src/platform.ts b/packages/bun-release/src/platform.ts
index a01cc3ddc..fb438d824 100644
--- a/packages/bun-npm/src/platform.ts
+++ b/packages/bun-release/src/platform.ts
@@ -1,4 +1,6 @@
-import { read, spawn } from "./util";
+import { spawn } from "./spawn";
+import { read } from "./fs";
+import { debug } from "./console";
export const os = process.platform;
@@ -71,7 +73,7 @@ function isLinuxAVX2(): boolean {
try {
return read("/proc/cpuinfo").includes("avx2");
} catch (error) {
- console.debug("isLinuxAVX2 failed", error);
+ debug("isLinuxAVX2 failed", error);
return false;
}
}
@@ -81,7 +83,7 @@ function isDarwinAVX2(): boolean {
const { exitCode, stdout } = spawn("sysctl", ["-n", "machdep.cpu"]);
return exitCode === 0 && stdout.includes("AVX2");
} catch (error) {
- console.debug("isDarwinAVX2 failed", error);
+ debug("isDarwinAVX2 failed", error);
return false;
}
}
@@ -94,7 +96,7 @@ function isRosetta2(): boolean {
]);
return exitCode === 0 && stdout.includes("1");
} catch (error) {
- console.debug("isRosetta2 failed", error);
+ debug("isRosetta2 failed", error);
return false;
}
}
diff --git a/packages/bun-release/src/spawn.ts b/packages/bun-release/src/spawn.ts
new file mode 100644
index 000000000..7a04dc3f2
--- /dev/null
+++ b/packages/bun-release/src/spawn.ts
@@ -0,0 +1,24 @@
+import child_process from "child_process";
+import { debug } from "./console";
+
+export function spawn(
+ cmd: string,
+ args: string[],
+ options: child_process.SpawnOptions = {},
+): {
+ exitCode: number;
+ stdout: string;
+ stderr: string;
+} {
+ 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,
+ };
+}
diff --git a/packages/bun-npm/tsconfig.json b/packages/bun-release/tsconfig.json
index 9272b9920..b7194ece3 100644
--- a/packages/bun-npm/tsconfig.json
+++ b/packages/bun-release/tsconfig.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
- "lib": ["esnext"],
- "module": "esnext",
- "target": "esnext",
+ "lib": ["ESNext"],
+ "module": "ESNext",
+ "target": "ESNext",
"moduleResolution": "node",
"types": ["bun-types"],
"esModuleInterop": true,