aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-release/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bun-release/scripts')
-rw-r--r--packages/bun-release/scripts/npm-exec.ts13
-rw-r--r--packages/bun-release/scripts/npm-postinstall.ts10
-rw-r--r--packages/bun-release/scripts/upload-assets.ts113
-rw-r--r--packages/bun-release/scripts/upload-npm.ts191
4 files changed, 327 insertions, 0 deletions
diff --git a/packages/bun-release/scripts/npm-exec.ts b/packages/bun-release/scripts/npm-exec.ts
new file mode 100644
index 000000000..5b054ac86
--- /dev/null
+++ b/packages/bun-release/scripts/npm-exec.ts
@@ -0,0 +1,13 @@
+import { importBun } from "../src/npm/install";
+import { execFileSync } from "child_process";
+
+importBun()
+ .then((bun) => {
+ return execFileSync(bun, process.argv.slice(2), {
+ stdio: "inherit",
+ });
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/packages/bun-release/scripts/npm-postinstall.ts b/packages/bun-release/scripts/npm-postinstall.ts
new file mode 100644
index 000000000..bde044300
--- /dev/null
+++ b/packages/bun-release/scripts/npm-postinstall.ts
@@ -0,0 +1,10 @@
+import { importBun, optimizeBun } from "../src/npm/install";
+
+importBun()
+ .then((path) => {
+ optimizeBun(path);
+ })
+ .catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
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"));
+ }
+}