fix(release): stabilize generated UI references

This commit is contained in:
2026-08-01 07:02:22 +05:30
parent d5e834bc78
commit e2f7bea299
9 changed files with 178 additions and 28 deletions
@@ -7,8 +7,22 @@ const catalogPath = join(uiRoot, "component-catalog.json");
const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
const metadata = new Map(catalog.components.map((entry) => [entry.name, entry]));
function normalizeNewlines(source) {
return source.replace(/\r\n?/g, "\n");
}
async function writeFormatted(path, source) {
const current = readFileSync(path, "utf8");
// Do not rewrite a generated file when only its working-tree line endings
// differ. This keeps release verification clean on Windows CRLF checkouts
// while still rewriting files whose generated content is genuinely stale.
if (normalizeNewlines(current) === normalizeNewlines(source)) {
return false;
}
writeFileSync(path, source);
return true;
}
function block(source, keyword) {
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env node
import console from "node:console";
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import process from "node:process";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const temporaryRoot = mkdtempSync(join(tmpdir(), "wrnexus-ui-reference-"));
function runGenerator() {
const result = spawnSync(
process.execPath,
[join(temporaryRoot, "scripts", "generate-ui-component-reference.mjs")],
{
encoding: "utf8",
},
);
if (result.status !== 0) {
throw new Error(result.stderr || result.stdout || "UI component reference generator failed");
}
}
try {
mkdirSync(join(temporaryRoot, "scripts"), { recursive: true });
mkdirSync(join(temporaryRoot, "packages", "ui"), { recursive: true });
cpSync(
join(root, "scripts", "generate-ui-component-reference.mjs"),
join(temporaryRoot, "scripts", "generate-ui-component-reference.mjs"),
);
cpSync(
join(root, "packages", "ui", "components"),
join(temporaryRoot, "packages", "ui", "components"),
{
recursive: true,
},
);
for (const name of ["component-catalog.json", "component-reference.json", "COMPONENTS.md"]) {
cpSync(join(root, "packages", "ui", name), join(temporaryRoot, "packages", "ui", name));
}
const jsonFiles = ["component-catalog.json", "component-reference.json"];
const before = new Map();
for (const name of jsonFiles) {
const path = join(temporaryRoot, "packages", "ui", name);
const crlf = readFileSync(path, "utf8").replace(/\r\n?/g, "\n").replace(/\n/g, "\r\n");
writeFileSync(path, crlf);
before.set(name, readFileSync(path));
}
runGenerator();
for (const name of jsonFiles) {
const path = join(temporaryRoot, "packages", "ui", name);
if (!readFileSync(path).equals(before.get(name))) {
throw new Error(`${name} was rewritten even though only line endings differed`);
}
}
const referencePath = join(temporaryRoot, "packages", "ui", "component-reference.json");
const stale = readFileSync(referencePath, "utf8").replace('"count": 108', '"count": 107');
writeFileSync(referencePath, stale);
runGenerator();
const restored = JSON.parse(readFileSync(referencePath, "utf8"));
if (restored.count !== 108) {
throw new Error("genuinely stale component reference content was not regenerated");
}
console.log("UI reference generation is line-ending stable and still repairs stale content.");
} finally {
rmSync(temporaryRoot, { recursive: true, force: true });
}
+26 -3
View File
@@ -416,11 +416,34 @@ for (const [label, condition] of [
referenceGeneratorSource.includes("compareText(left.name, right.name)") &&
!referenceGeneratorSource.includes("localeCompare"),
],
[
"line-ending-stable generated reference writes",
referenceGeneratorSource.includes("function normalizeNewlines(source)") &&
referenceGeneratorSource.includes(
"normalizeNewlines(current) === normalizeNewlines(source)",
) &&
referenceGeneratorSource.includes("return false"),
],
]) {
if (!condition) fail(`R12 regression: missing ${label}`);
if (!condition) fail(`R13 regression: missing ${label}`);
}
if (!failures.some((item) => item.startsWith("R12 regression:"))) {
pass("R12 release reference gate is canonical, complete, and platform independent");
if (!failures.some((item) => item.startsWith("R13 regression:"))) {
const referenceGenerationTest = spawnSync(
process.execPath,
[join(root, "scripts/test-ui-reference-generation.mjs")],
{ encoding: "utf8" },
);
if (referenceGenerationTest.status !== 0) {
fail(
`R13 regression: generated-reference line-ending test failed: ${
referenceGenerationTest.stderr.trim() || referenceGenerationTest.stdout.trim()
}`,
);
} else {
pass(
"R13 release reference gate is canonical, complete, platform independent, and line-ending stable",
);
}
}
const showcaseFiles = walk(join(root, "examples/component-showcase/app"), (path) =>