mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
312 lines
9.4 KiB
TypeScript
312 lines
9.4 KiB
TypeScript
import { expect } from "bun:test";
|
|
import type { BuildOptions } from "esbuild";
|
|
import { buildSync, formatMessagesSync } from "esbuild";
|
|
import { mkdirSync, rmSync } from "fs";
|
|
import { mkdtemp } from "fs/promises";
|
|
import type { JSZipObject } from "jszip";
|
|
import { loadAsync } from "jszip";
|
|
import { tmpdir } from "os";
|
|
import { dirname } from "path";
|
|
import { debug, error, log } from "../src/console";
|
|
import { fetch } from "../src/fetch";
|
|
import { chmod, copy, exists, join, write, writeJson } from "../src/fs";
|
|
import { getRelease, getSemver } from "../src/github";
|
|
import type { Platform } from "../src/platform";
|
|
import { platforms } from "../src/platform";
|
|
import { spawn } from "../src/spawn";
|
|
|
|
const module = "bun";
|
|
const owner = "@oven";
|
|
|
|
const [tag, action] = process.argv.slice(2);
|
|
|
|
const release = await getRelease(tag);
|
|
const version = await getSemver(release.tag_name);
|
|
|
|
if (action !== "test-only") await build();
|
|
|
|
if (action === "publish") {
|
|
await publish();
|
|
} else if (action === "dry-run") {
|
|
await publish(true);
|
|
} else if (action === "test") {
|
|
await publish(true);
|
|
await test();
|
|
} else if (action === "test-only") {
|
|
await test();
|
|
} else if (action) {
|
|
throw new Error(`Unknown action: ${action}`);
|
|
}
|
|
process.exit(0); // HACK
|
|
|
|
async function build(): Promise<void> {
|
|
await buildRootModule();
|
|
for (const platform of platforms) {
|
|
if (action !== "publish" && (platform.os !== process.platform || platform.arch !== process.arch)) continue;
|
|
await buildModule(release, platform);
|
|
}
|
|
}
|
|
|
|
async function publish(dryRun?: boolean): Promise<void> {
|
|
const modules = platforms
|
|
.filter(({ os, arch }) => action === "publish" || (os === process.platform && arch === process.arch))
|
|
.map(({ bin }) => `${owner}/${bin}`);
|
|
modules.push(module);
|
|
for (const module of modules) {
|
|
publishModule(module, dryRun);
|
|
}
|
|
}
|
|
|
|
async function buildRootModule(dryRun?: boolean) {
|
|
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,
|
|
banner: {
|
|
js: "// Source code: https://github.com/oven-sh/bun/blob/main/packages/bun-release/scripts/npm-postinstall.ts",
|
|
},
|
|
});
|
|
write(join(cwd, "bin", "bun.exe"), "");
|
|
write(join(cwd, "bin", "bunx.exe"), "");
|
|
write(
|
|
join(cwd, "bin", "README.txt"),
|
|
`The 'bun.exe' file is a placeholder for the binary file, which
|
|
is replaced by Bun's 'postinstall' script. For this to work, make
|
|
sure that you do not use --ignore-scripts while installing.
|
|
|
|
The postinstall script is responsible for linking the binary file
|
|
directly into 'node_modules/.bin' and avoiding a Node.js wrapper
|
|
script being called on every invocation of 'bun'. If this wasn't
|
|
done, Bun would seem to be slower than Node.js, because it would
|
|
be executing a copy of Node.js every time!
|
|
|
|
Unfortunately, it is not possible to fix all cases on all platforms
|
|
without *requiring* a postinstall script.
|
|
`,
|
|
);
|
|
const os = [...new Set(platforms.map(({ os }) => os))];
|
|
const cpu = [...new Set(platforms.map(({ arch }) => arch))];
|
|
writeJson(join(cwd, "package.json"), {
|
|
name: module,
|
|
description: "Bun is a fast all-in-one JavaScript runtime.",
|
|
version: version,
|
|
scripts: {
|
|
postinstall: "node install.js",
|
|
},
|
|
optionalDependencies: Object.fromEntries(
|
|
platforms.map(({ bin }) => [
|
|
`${owner}/${bin}`,
|
|
dryRun ? `file:./oven-${bin.replaceAll("/", "-") + "-" + version + ".tgz"}` : version,
|
|
]),
|
|
),
|
|
bin: {
|
|
bun: "bin/bun.exe",
|
|
bunx: "bin/bunx.exe",
|
|
},
|
|
os,
|
|
cpu,
|
|
keywords: ["bun", "bun.js", "node", "node.js", "runtime", "bundler", "transpiler", "typescript"],
|
|
homepage: "https://bun.com",
|
|
bugs: "https://github.com/oven-sh/issues",
|
|
license: "MIT",
|
|
repository: "https://github.com/oven-sh/bun",
|
|
});
|
|
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);
|
|
mkdirSync(dirname(join(cwd, exe)), { recursive: true });
|
|
write(join(cwd, exe), await bun.async("arraybuffer"));
|
|
chmod(join(cwd, exe), 0o755);
|
|
writeJson(join(cwd, "package.json"), {
|
|
name: module,
|
|
version: version,
|
|
description: "This is the macOS arm64 binary for Bun, a fast all-in-one JavaScript runtime.",
|
|
homepage: "https://bun.com",
|
|
bugs: "https://github.com/oven-sh/issues",
|
|
license: "MIT",
|
|
repository: "https://github.com/oven-sh/bun",
|
|
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}`);
|
|
if (!dryRun) {
|
|
const { exitCode, stdout, stderr } = spawn(
|
|
"npm",
|
|
[
|
|
"publish",
|
|
"--access",
|
|
"public",
|
|
"--tag",
|
|
version.includes("canary") ? "canary" : "latest",
|
|
...(dryRun ? ["--dry-run"] : []),
|
|
],
|
|
{
|
|
cwd: join("npm", name),
|
|
},
|
|
);
|
|
error(stderr || stdout);
|
|
if (exitCode !== 0) {
|
|
if (
|
|
stdout.includes("You cannot publish over the previously published version") ||
|
|
stderr.includes("You cannot publish over the previously published version")
|
|
) {
|
|
console.warn("Ignoring npm publish error:", stdout, stderr);
|
|
return;
|
|
}
|
|
|
|
throw new Error("npm publish failed with code " + exitCode);
|
|
}
|
|
} else {
|
|
const { exitCode, stdout, stderr } = spawn("npm", ["pack"], {
|
|
cwd: join("npm", name),
|
|
});
|
|
error(stderr || stdout);
|
|
if (exitCode !== 0) {
|
|
throw new Error("npm pack failed with code " + exitCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
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}`);
|
|
}
|
|
|
|
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"));
|
|
}
|
|
}
|
|
|
|
async function test() {
|
|
const root = await mkdtemp(join(tmpdir(), "bun-release-test-"));
|
|
const $ = new Bun.$.Shell().cwd(root);
|
|
|
|
for (const platform of platforms) {
|
|
if (platform.os !== process.platform) continue;
|
|
if (platform.arch !== process.arch) continue;
|
|
copy(
|
|
join(
|
|
import.meta.dir,
|
|
"../npm/@oven/",
|
|
platform.bin,
|
|
"oven-" + platform.bin.replaceAll("/", "-") + `-${version}.tgz`,
|
|
),
|
|
join(root, `${platform.bin}-${version}.tgz`),
|
|
);
|
|
}
|
|
|
|
copy(join(import.meta.dir, "../npm", "bun", "bun-" + version + ".tgz"), join(root, "bun-" + version + ".tgz"));
|
|
|
|
console.log(root);
|
|
for (const [install, exec] of [
|
|
["npm i", "npm exec"],
|
|
["yarn set version berry; yarn add", "yarn"],
|
|
["yarn set version latest; yarn add", "yarn"],
|
|
["pnpm i", "pnpm"],
|
|
["bun i", "bun run"],
|
|
]) {
|
|
rmSync(join(root, "node_modules"), { recursive: true, force: true });
|
|
rmSync(join(root, "package-lock.json"), { recursive: true, force: true });
|
|
rmSync(join(root, "package.json"), { recursive: true, force: true });
|
|
rmSync(join(root, "pnpm-lock.yaml"), { recursive: true, force: true });
|
|
rmSync(join(root, "yarn.lock"), { recursive: true, force: true });
|
|
writeJson(join(root, "package.json"), {
|
|
name: "bun-release-test",
|
|
});
|
|
|
|
console.log("Testing", install + " bun");
|
|
await $`${{ raw: install }} ./bun-${version}.tgz`;
|
|
|
|
console.log("Running " + exec + " bun");
|
|
|
|
// let output = await $`${{
|
|
// raw: exec,
|
|
// }} bun -- -e "console.log(JSON.stringify([Bun.version, process.platform, process.arch, process.execPath]))"`.text();
|
|
const split = exec.split(" ");
|
|
let {
|
|
stdout: output,
|
|
stderr,
|
|
exitCode,
|
|
} = spawn(
|
|
split[0],
|
|
[
|
|
...split.slice(1),
|
|
"--",
|
|
"bun",
|
|
"-e",
|
|
"console.log(JSON.stringify([Bun.version, process.platform, process.arch, process.execPath]))",
|
|
],
|
|
{
|
|
cwd: root,
|
|
},
|
|
);
|
|
if (exitCode !== 0) {
|
|
console.error(stderr);
|
|
throw new Error("Failed to run " + exec + " bun, exit code: " + exitCode);
|
|
}
|
|
|
|
try {
|
|
output = JSON.parse(output);
|
|
} catch (e) {
|
|
console.log({ output });
|
|
throw e;
|
|
}
|
|
|
|
expect(output[0]).toBe(version);
|
|
expect(output[1]).toBe(process.platform);
|
|
expect(output[2]).toBe(process.arch);
|
|
expect(output[3]).toStartWith(root);
|
|
expect(output[3]).toInclude("bun");
|
|
}
|
|
}
|