mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Regenerate the compatibility files offline, and after merges
pnpm node-compat fetched Node's docs on every run, even though only the classification changes between pins. pnpm node-compat --local now reclassifies the committed ledger rows from the current sources and rewrites the ledger and the public artifact, the same files a fetching run writes while the pin is unchanged. scripts/regen-generated.mjs uses it after a merge: for each generated file still in conflict it takes the merged-in branch's version, regenerates the surface manifest and the compatibility files from the merged sources, and stages them, so these files never need a hand merge.
This commit is contained in:
@@ -142,7 +142,9 @@ Never hand-edit these files:
|
||||
- `internal/compatibility/generated/node-v24-internal.json`
|
||||
- `docs/src/generated/node-v24-compatibility.json`
|
||||
|
||||
Change compiler decision sources or the implementation-owned compatibility manifests, then regenerate. Use `pnpm manifest` when compiler decision tables change and `pnpm node-compat` whenever the Node pin, compatibility manifests, compiler surface manifest, changes.
|
||||
Change compiler decision sources or the implementation-owned compatibility manifests, then regenerate. Use `pnpm manifest` when compiler decision tables change and `pnpm node-compat` whenever the Node pin, compatibility manifests, compiler surface manifest, changes. `pnpm node-compat --local` reclassifies the committed ledger without fetching Node's docs; it writes the same files as `pnpm node-compat` while the Node pin is unchanged.
|
||||
|
||||
Never resolve a merge conflict in these files by hand. Resolve the source conflicts, then run `node scripts/regen-generated.mjs`: it takes the merged-in branch's version of each conflicted generated file, regenerates all of them from the merged sources, and stages the ones that conflicted.
|
||||
|
||||
The public docs artifact must not expose repository test paths, internal source identifiers, engineering evidence bookkeeping, documentation/metadata headings, command-line or configuration-only entries, or rows that are N/A in the native tier. Those remain in the complete internal census under `internal/compatibility/generated/`.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Statuses are evidence-aware: `supported` and `partial` require test evidence; `r
|
||||
|
||||
Run `pnpm node-compat:backlog` from the repository root for a summary. The filters `--tier`, `--action`, `--priority`, `--chapter`, and `--status` compose; use `--format=json` or `--format=tsv` for tooling.
|
||||
|
||||
Run `pnpm node-compat` from the repository root after changing the Node pin or the support manifest. `pnpm node-compat:check` is the offline drift/evidence gate; `pnpm --filter @internal/compatibility check:upstream` additionally verifies the pinned upstream inputs over the network.
|
||||
Run `pnpm node-compat` from the repository root after changing the Node pin or the support manifest. `pnpm node-compat --local` does the same without network access while the pin is unchanged, by reclassifying the committed ledger rows; after a merge, `node scripts/regen-generated.mjs` regenerates every generated file instead of a hand merge. `pnpm node-compat:check` is the offline drift/evidence gate; `pnpm --filter @internal/compatibility check:upstream` additionally verifies the pinned upstream inputs over the network.
|
||||
|
||||
## npm packages that compile as builtins
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
*
|
||||
* pnpm node-compat fetch latest-v24.x and rewrite the snapshot
|
||||
* pnpm node-compat --check fetch and fail if the snapshot would change
|
||||
* pnpm node-compat --local rewrite the snapshot without fetching: reclassify
|
||||
* the committed rows from the current sources
|
||||
* pnpm node-compat:check offline implementation/profile drift check
|
||||
*/
|
||||
|
||||
@@ -1013,6 +1015,34 @@ function publicSnapshot(snapshot) {
|
||||
};
|
||||
}
|
||||
|
||||
/** The committed ledger with every row's classification recomputed from
|
||||
* the current sources. The rows themselves come from the pinned Node docs,
|
||||
* so while the pin is unchanged this is what a fetching run writes. */
|
||||
function reclassify(snapshot) {
|
||||
if (snapshot.nodeVersion !== pin.version || snapshot.nodeCommit !== pin.commit) {
|
||||
throw new Error(
|
||||
`the ledger targets Node ${snapshot.nodeVersion}, the pin ${pin.version}; run 'pnpm node-compat' to fetch the new docs`,
|
||||
);
|
||||
}
|
||||
const ctx = classificationContext();
|
||||
const ancestors = [];
|
||||
for (const row of snapshot.rows) {
|
||||
ancestors.length = row.depth;
|
||||
const parentSignature = ancestors[row.depth - 1]?.signature;
|
||||
row.static = classifyStatic(row, null, ctx, parentSignature);
|
||||
ancestors[row.depth] = row;
|
||||
}
|
||||
for (const chapter of snapshot.chapters) {
|
||||
const chapterRows = snapshot.rows.filter((row) => row.chapter === chapter.slug);
|
||||
chapterRows[0].static = summarizeTier(
|
||||
chapterRows,
|
||||
"static",
|
||||
ctx.compilerCompat.chapterPolicies[chapter.slug],
|
||||
);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
function checkLocal() {
|
||||
for (const path of retiredOutputPaths) {
|
||||
if (existsSync(path))
|
||||
@@ -1109,6 +1139,15 @@ async function fetchText(url) {
|
||||
async function main() {
|
||||
const args = new Set(process.argv.slice(2));
|
||||
if (args.has("--check-local")) return checkLocal();
|
||||
if (args.has("--local")) {
|
||||
const snapshot = reclassify(JSON.parse(readFileSync(internalOutputPath, "utf8")));
|
||||
writeFileSync(internalOutputPath, render(snapshot));
|
||||
writeFileSync(publicOutputPath, render(publicSnapshot(snapshot)));
|
||||
console.log(
|
||||
`reclassified the compatibility ledger and public artifact (${snapshot.rows.length} source rows, ${projectPublicMatrix(snapshot).rows.length} public matrix rows, ${pin.tag})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const [allText, indexMarkdown] = await Promise.all([
|
||||
fetchText(pin.allJsonUrl),
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const check = process.argv.slice(2).includes("--check");
|
||||
const args = check ? ["--check"] : [];
|
||||
const args = process.argv.slice(2).filter((arg) => arg === "--check" || arg === "--local");
|
||||
|
||||
for (const script of ["generate.mjs"]) {
|
||||
const result = spawnSync(process.execPath, [`src/${script}`, ...args], { stdio: "inherit" });
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/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, resolve the conflicts in source files first. Each
|
||||
* generated file git still reports as conflicted is then replaced by the
|
||||
* merged-in branch's version (MERGE_HEAD; HEAD during a rebase or
|
||||
* cherry-pick, where HEAD is the branch being built on), every file is
|
||||
* regenerated from the merged sources, and the conflicted ones are staged.
|
||||
*
|
||||
* node scripts/regen-generated.mjs
|
||||
*/
|
||||
import { spawnSync } from "node:child_process";
|
||||
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 hasRef = (ref) =>
|
||||
spawnSync("git", ["rev-parse", "-q", "--verify", ref], { cwd: root }).status === 0;
|
||||
|
||||
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 from = hasRef("MERGE_HEAD") ? "MERGE_HEAD" : "HEAD";
|
||||
git("checkout", from, "--", ...conflicted);
|
||||
console.log(`took ${from}'s ${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(", ")}`);
|
||||
}
|
||||
Reference in New Issue
Block a user