diff options
Diffstat (limited to 'packages/bun-release/scripts')
-rw-r--r-- | packages/bun-release/scripts/npm-exec.ts | 13 | ||||
-rw-r--r-- | packages/bun-release/scripts/npm-postinstall.ts | 10 | ||||
-rw-r--r-- | packages/bun-release/scripts/upload-assets.ts | 113 | ||||
-rw-r--r-- | packages/bun-release/scripts/upload-npm.ts | 191 |
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")); + } +} |