Files
hyperframes/scripts/catalog-drift.ts
T
Miguel Ángel 7b85b28406 fix(ci): keep the catalog publish PR when main changes nothing it generates (#5372)
* fix(ci): keep the catalog publish PR when main changes nothing it generates

The bot compared the whole branch tree with main, so every main push rewrote
the standing PR and dismissed its approval. It now compares only generated
catalog files, with search vectors judged by the drift check's cosine rule.

* fix(ci): republish when main edits generated files or the branch carries others

* docs(registry): motion-blur copies explain their inline styles in three lines

* refactor(ci): split the kept-snapshot check into named conditions
2026-10-09 22:19:38 +00:00

88 lines
3.7 KiB
TypeScript

// Source PRs validate generated output; the publish PR also checks its committed snapshot.
import { execFileSync } from "node:child_process";
import { cpSync, mkdirSync, readdirSync, readFileSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, relative, resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { generateCatalog } from "./generate-catalog.ts";
import { GENERATED_CATALOG_PATHS } from "./catalog-generated-paths.mjs";
import { VECTORS, vectorsAgree } from "./catalog-vectors.mjs";
import { runAsCommand } from "./entrypoint.ts";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const MAX_LISTED = 40;
function filesUnder(root: string): string[] {
return readdirSync(root, { recursive: true, withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => relative(root, join(entry.parentPath, entry.name)))
.sort();
}
/** One line per file that is missing, extra or different between the generated and committed trees. */
export function treeDifferences(generatedRoot: string, committedRoot: string): string[] {
const generated = new Set(filesUnder(generatedRoot));
const committed = new Set(filesUnder(committedRoot));
const missing = [...generated].filter((file) => !committed.has(file));
const extra = [...committed].filter((file) => !generated.has(file));
const changed = [...generated].filter(
(file) => committed.has(file) && !sameContent(generatedRoot, committedRoot, file),
);
return [
...missing.map((file) => `not committed: ${file}`),
...extra.map((file) => `no longer generated: ${file}`),
...changed.map((file) => `stale: ${file}`),
];
}
function sameContent(generatedRoot: string, committedRoot: string, file: string): boolean {
const generated = readFileSync(join(generatedRoot, file));
const committed = readFileSync(join(committedRoot, file));
if (generated.equals(committed)) return true;
if (!VECTORS.test(file)) return false;
const meta = join(generatedRoot, file.replace(/\.bin$/, ".json"));
const { dimensions } = JSON.parse(readFileSync(meta, "utf8")) as { dimensions: number };
return vectorsAgree(generated, committed, dimensions);
}
async function main(): Promise<void> {
await generateCatalog();
for (const script of [
"scripts/check-docs-catalog.mjs",
"scripts/catalog/check-artifact-coverage.ts",
])
execFileSync("bun", [script], { cwd: repoRoot, stdio: "inherit" });
if (process.argv.includes("--committed")) checkCommittedCatalog();
else
console.log("Catalog sources generate successfully; derived files belong to the publish PR.");
}
function checkCommittedCatalog(): void {
const base = mkdtempSync(join(tmpdir(), "catalog-drift-"));
try {
const committed = join(base, "committed");
const generated = join(base, "generated");
mkdirSync(committed);
mkdirSync(generated);
const archive = execFileSync("git", ["archive", "HEAD", ...GENERATED_CATALOG_PATHS], {
cwd: repoRoot,
maxBuffer: 2 ** 31 - 1,
});
execFileSync("tar", ["-x", "-C", committed], { input: archive });
for (const path of GENERATED_CATALOG_PATHS) {
mkdirSync(dirname(join(generated, path)), { recursive: true });
cpSync(join(repoRoot, path), join(generated, path), { recursive: true });
}
const differences = treeDifferences(generated, committed);
if (differences.length > 0)
throw new Error(
`Published catalog differs from generation:\n${differences.slice(0, MAX_LISTED).join("\n")}`,
);
console.log("Committed catalog matches generation.");
} finally {
rmSync(base, { recursive: true, force: true });
}
}
runAsCommand(import.meta.url, main);