Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113// Bump the Node compat target: moves .node-version and the two fields that must not// drift from it (the root package.json's volta.node, this package's nibling.node),// then re-records the surface manifest and goldens under the new Node.//// node scripts/bump-node.mjs <version>// <version> is a plain Node version, e.g. 24.22.2 (no leading "v").// 1. Writes <version> to .node-version, the root package.json's volta.node, and// this package's nibling.node.// 2. `volta fetch node@<version>` if Volta doesn't have it installed yet.// 3. `surface.mjs record --node=<bin>`, writing compat/node-<version>.json.// 4. Re-records every golden with `--write`. golden.mjs refuses to write under any// Node but the one .node-version just moved to, checked against its own// process.execPath — so this runs golden.mjs through the new Node's own binary// (found under Volta's tool image) rather than loosening that guard.// 5. Runs tests/surface.spec.ts. Clean, there is nothing to triage yet. Otherwise// runs `surface.mjs baseline` to list the new gaps as "untriaged" and says so:// open compat/divergences.json, tag each one, and re-run the spec until green.// Finishes by printing `surface.mjs delta <old> <new>`, the summary of what moved.import { execFileSync } from "node:child_process";import { existsSync, readFileSync, writeFileSync } from "node:fs";import { homedir } from "node:os";import { resolve } from "node:path";import { fileURLToPath } from "node:url";
const ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));const REPO_ROOT = resolve(ROOT, "../..");
const version = process.argv[2]?.replace(/^v/, "");if (!version || !/^\d+\.\d+\.\d+$/.test(version)) { console.error("usage: node scripts/bump-node.mjs <version> (e.g. 24.22.2)"); process.exit(1);}
const nodeVersionPath = resolve(REPO_ROOT, ".node-version");const rootPkgPath = resolve(REPO_ROOT, "package.json");const kernelPkgPath = resolve(ROOT, "package.json");const platformPath = resolve(ROOT, "src/shared/platform.ts");const readJson = (p) => JSON.parse(readFileSync(p, "utf8"));const writeJson = (p, v) => writeFileSync(p, `${JSON.stringify(v, null, 2)}\n`);
const from = readFileSync(nodeVersionPath, "utf8").trim();if (from === version) { console.error(`.node-version is already ${version}`); process.exit(1);}
// 1. Move the pin and every mirror pinned-node.mjs checks against it together, so// nothing in between sees a half-moved pin (it would throw on exactly that).writeFileSync(nodeVersionPath, `${version}\n`);const rootPkg = readJson(rootPkgPath);rootPkg.volta.node = version;writeJson(rootPkgPath, rootPkg);const kernelPkg = readJson(kernelPkgPath);kernelPkg.nibling = { ...kernelPkg.nibling, node: version };writeJson(kernelPkgPath, kernelPkg);// src/shared/platform.ts's NODE_VERSION ships inside the browser runtime bundle, so it// can't be derived at build time the way build.mjs's NODE_NOTICE is; pinned-node.mjs// only checks it matches, so the literal has to move here.const platformSrc = readFileSync(platformPath, "utf8");const platformMatch = platformSrc.match(/export const NODE_VERSION = "([^"]+)";/);if (!platformMatch) throw new Error(`${platformPath}: NODE_VERSION literal not found in the expected shape`);writeFileSync(platformPath, platformSrc.replace(platformMatch[0], `export const NODE_VERSION = "${version}";`));console.log(`.node-version, volta.node, nibling.node, platform.ts NODE_VERSION: ${from} -> ${version}`);
// 2. Make sure Volta has that Node installed.const bin = resolve(homedir(), ".volta/tools/image/node", version, "bin/node");if (!existsSync(bin)) { console.log(`volta fetch node@${version}…`); execFileSync("volta", ["fetch", `node@${version}`], { stdio: "inherit" });}if (!existsSync(bin)) throw new Error(`volta fetch node@${version} did not install ${bin}`);
// 3. Record the surface manifest under the new Node, by binary path — record() only// requires the pin to match when it's running on its own process.execPath.execFileSync(process.execPath, [resolve(ROOT, "scripts/surface.mjs"), "record", `--node=${bin}`], { cwd: ROOT, stdio: "inherit" });
// 4. Re-record goldens under the new Node. --write checks process.execPath against the// freshly-moved pin, so run golden.mjs through <bin> itself instead of this script's// own (still-old) Node.execFileSync(bin, [resolve(ROOT, "scripts/golden.mjs"), "--write"], { cwd: ROOT, stdio: "inherit" });
// 5. See whether the new surface needs triage. A clean run means no new gaps; a failing// one gets baselined so the untriaged entries are visible, and the user is told to// finish the job.const PORT = process.env.PORT ?? "8951";const playwright = resolve(ROOT, "node_modules/.bin/playwright");let specClean = true;try { execFileSync(playwright, ["test", "tests/surface.spec.ts", "--timeout=20000", "--global-timeout=220000"], { cwd: ROOT, env: { ...process.env, PORT }, stdio: "inherit", });} catch { specClean = false;}
if (specClean) { console.log("tests/surface.spec.ts is clean under the new Node: nothing to triage.");} else { console.log("tests/surface.spec.ts failed: new gaps need a reason in compat/divergences.json."); execFileSync(process.execPath, [resolve(ROOT, "scripts/surface.mjs"), "baseline"], { cwd: ROOT, stdio: "inherit" }); console.log( `Open compat/divergences.json and tag every "untriaged" entry (todo, refuse, internal, ` + `deprecated, or accepted — see tests/surface.spec.ts's header), then re-run ` + `\`PORT=${PORT} npx playwright test tests/surface.spec.ts\` until it passes.`, );}
// 6. The summary: what the surface gained or lost between the two pins.console.log(`\nsurface.mjs delta ${from} ${version}:`);execFileSync(process.execPath, [resolve(ROOT, "scripts/surface.mjs"), "delta", from, version], { cwd: ROOT, stdio: "inherit" });