mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
regen-generated.mjs resolved each conflicted generated file with `git checkout <ref> --`, which also staged it, so a failing generator left the conflict resolved with the branch's stale file. It now writes the chosen side's file to the working tree only and stages it after both generators succeed. It also always took MERGE_HEAD's ledger, so merging a branch with an older Node pin into one with a newer pin regenerated from a ledger for the older pin and failed. It now takes the side (index stage 2 or 3) whose ledger is for the merged pin in node-v24.json, and when neither is, stops and says to run pnpm node-compat, which fetches the pinned docs.
86 lines
3.7 KiB
JavaScript
86 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/* Regenerates the committed generated files from their sources, so a merge
|
|
* never resolves one by hand:
|
|
*
|
|
* packages/compiler/surface-manifest.json pnpm manifest
|
|
* internal/compatibility/generated/node-v24-internal.json
|
|
* docs/src/generated/node-v24-compatibility.json pnpm node-compat --local
|
|
*
|
|
* During a merge, rebase or cherry-pick, resolve the conflicts in source
|
|
* files first. Each generated file git still reports as conflicted is then
|
|
* written from one side of the conflict, every file is regenerated from the
|
|
* merged sources, and only then are the conflicted ones staged: when a
|
|
* generator fails they stay unmerged. The side is the one whose ledger is
|
|
* for the merged Node pin (node-v24.json), which offline regeneration
|
|
* needs; when neither is, run `pnpm node-compat`, which fetches the pinned
|
|
* Node docs, instead.
|
|
*
|
|
* node scripts/regen-generated.mjs
|
|
*/
|
|
import { spawnSync } from "node:child_process";
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
const GENERATED = [
|
|
"packages/compiler/surface-manifest.json",
|
|
"internal/compatibility/generated/node-v24-internal.json",
|
|
"docs/src/generated/node-v24-compatibility.json",
|
|
];
|
|
|
|
function run(command, args) {
|
|
const result = spawnSync(command, args, { cwd: root, stdio: ["ignore", "pipe", "inherit"] });
|
|
if (result.error) throw result.error;
|
|
if (result.status !== 0) {
|
|
process.stdout.write(result.stdout);
|
|
throw new Error(`${command} ${args.join(" ")} exited with ${result.status}`);
|
|
}
|
|
return result.stdout.toString();
|
|
}
|
|
|
|
const git = (...args) => run("git", args);
|
|
const LEDGER = "internal/compatibility/generated/node-v24-internal.json";
|
|
|
|
/** A conflicted file's content on one side: index stage 2 (ours) or 3 (theirs), or null if absent there. */
|
|
function side(stage, path) {
|
|
const result = spawnSync("git", ["show", `:${stage}:${path}`], { cwd: root, maxBuffer: 1 << 30 });
|
|
return result.status === 0 ? result.stdout : null;
|
|
}
|
|
|
|
/** The side (2 or 3) to take the conflicted files from: the one whose ledger is for the merged pin. */
|
|
function chooseSide(conflicted) {
|
|
if (!conflicted.includes(LEDGER)) return 2;
|
|
const pin = JSON.parse(readFileSync(join(root, "internal/compatibility/node-v24.json"), "utf8"));
|
|
const forPin = (stage) => {
|
|
const text = side(stage, LEDGER);
|
|
if (text === null) return false;
|
|
const ledger = JSON.parse(text.toString("utf8"));
|
|
return ledger.nodeVersion === pin.version && ledger.nodeCommit === pin.commit;
|
|
};
|
|
if (forPin(2)) return 2;
|
|
if (forPin(3)) return 3;
|
|
throw new Error(
|
|
`neither side's ledger is for Node ${pin.version} (${pin.commit.slice(0, 12)}); run 'pnpm manifest' and 'pnpm node-compat', which fetches the pinned Node docs, then stage the generated files`,
|
|
);
|
|
}
|
|
|
|
const unmerged = new Set(git("diff", "--name-only", "--diff-filter=U").split("\n"));
|
|
const conflicted = GENERATED.filter((path) => unmerged.has(path));
|
|
if (conflicted.length > 0) {
|
|
const stage = chooseSide(conflicted);
|
|
// Written to the working tree only: the index keeps the conflict until
|
|
// regeneration succeeds.
|
|
for (const path of conflicted) {
|
|
const text = side(stage, path) ?? side(5 - stage, path);
|
|
if (text !== null) writeFileSync(join(root, path), text);
|
|
}
|
|
console.log(`took ${stage === 2 ? "ours" : "theirs"} for ${conflicted.join(", ")}`);
|
|
}
|
|
process.stdout.write(run("pnpm", ["--silent", "manifest"]));
|
|
process.stdout.write(run(process.execPath, ["internal/compatibility/src/generate.mjs", "--local"]));
|
|
if (conflicted.length > 0) {
|
|
git("add", "--", ...conflicted);
|
|
console.log(`staged ${conflicted.join(", ")}`);
|
|
}
|