fix(release): stabilize generated UI references
This commit is contained in:
+3
-4
@@ -400,8 +400,7 @@ Comparison base: `WRNexusJS(4).zip`. Dependency directories, `.git`, and `.wrnex
|
||||
- Confirmed the generated peer-function browser module executes and updates shared state from `0` to `1`.
|
||||
- Added a release validator guard for the exact test source regression.
|
||||
|
||||
## R12 release reference gate
|
||||
## R13 release reference stability
|
||||
|
||||
- Fixed false stale component-reference failures on Windows/autocrlf checkouts.
|
||||
- The release gate now uses canonical Git diffs and validates all three generated UI reference files.
|
||||
- Component-reference ordering is deterministic across operating systems and locales.
|
||||
- Prevented the UI reference generator from rewriting current files solely because of CRLF/LF differences.
|
||||
- Added an executable regression test proving line-ending-only differences are ignored and real stale content is repaired.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# WRNexusJS 0.6.0 R13 - UI reference release stability
|
||||
|
||||
## Problem
|
||||
|
||||
`release:private` regenerated the UI component catalog and reference before publishing. On Windows, the generator always wrote LF output. When the checked-out JSON files used CRLF, Git reported both generated JSON files as modified even though their normalized content was already current.
|
||||
|
||||
## Fix
|
||||
|
||||
- Added normalized newline comparison to `scripts/generate-ui-component-reference.mjs`.
|
||||
- Generated files are no longer rewritten when their content differs only by line endings.
|
||||
- Real generated-content differences are still written and remain visible to the release gate.
|
||||
- Added `scripts/test-ui-reference-generation.mjs` to execute both the CRLF no-op case and the genuine stale-content repair case.
|
||||
- Wired the executable regression into `scripts/validate-0.6.mjs`.
|
||||
|
||||
## Verified behavior
|
||||
|
||||
- Current CRLF catalog/reference files remain byte-for-byte unchanged.
|
||||
- A stale reference count is regenerated to the correct value.
|
||||
- Release verification still blocks real generated-content drift.
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"releaseReference": {
|
||||
"crlfCurrentFilesUntouched": true,
|
||||
"staleContentRegenerated": true,
|
||||
"realDriftStillDetected": true
|
||||
},
|
||||
"rootValidator": {
|
||||
"passed": 20,
|
||||
"warnings": 1,
|
||||
"failed": 0,
|
||||
"warning": "Bun unavailable in packaging environment"
|
||||
},
|
||||
"vscode": {
|
||||
"testsPassed": 28,
|
||||
"testsFailed": 0,
|
||||
"validationPassed": true
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,6 @@ This archive also includes the focused fixes for generated browser identifier sa
|
||||
- Synchronous, asynchronous, and throwing peer calls refresh shared state correctly.
|
||||
- Added an executable `validate:0.6` regression probe for the exact compiler failure.
|
||||
|
||||
### R12 release workflow correction
|
||||
### R13 release verification correction
|
||||
|
||||
The private publish workflow now compares generated UI reference files using canonical Git diffs, includes the component catalog, and uses platform-independent generator ordering. This prevents line-ending-only rewrites from being reported as stale references on Windows.
|
||||
The UI component reference generator now leaves already-current CRLF files untouched on Windows. The private release gate therefore remains clean for line-ending-only differences while continuing to reject genuinely stale generated content.
|
||||
|
||||
+6
-4
@@ -101,8 +101,10 @@ The complete `bun run check` command remains the authoritative local release gat
|
||||
- Synchronous, asynchronous, and throwing peer calls refresh shared state correctly.
|
||||
- Added an executable `validate:0.6` regression probe for the exact compiler failure.
|
||||
|
||||
## R12 release-reference validation
|
||||
## R13 validation
|
||||
|
||||
- Release verification uses `git diff --quiet` instead of `git status --short` for generated UI references.
|
||||
- `component-catalog.json`, `component-reference.json`, and `COMPONENTS.md` are all covered.
|
||||
- Generator ordering no longer depends on `localeCompare`.
|
||||
- UI reference CRLF no-op test: passed.
|
||||
- Genuine stale-reference regeneration test: passed.
|
||||
- Root v0.6 validator: 20 passed, 0 failed.
|
||||
- VS Code editor tests: 28 passed, 0 failed.
|
||||
- VS Code extension validation: passed.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "wrnexus",
|
||||
"version": "0.6.0",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "latest",
|
||||
"@types/bun": "latest",
|
||||
@@ -153,7 +154,6 @@
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/store": "workspace:*",
|
||||
"@wrnexus/syntax": "workspace:*",
|
||||
},
|
||||
},
|
||||
@@ -186,7 +186,6 @@
|
||||
"@wrnexus/pubsub": "workspace:*",
|
||||
"@wrnexus/router": "workspace:*",
|
||||
"@wrnexus/ssr": "workspace:*",
|
||||
"@wrnexus/store": "workspace:*",
|
||||
"@wrnexus/styles": "workspace:*",
|
||||
"@wrnexus/ui": "workspace:*",
|
||||
"@wrnexus/uploader": "workspace:*",
|
||||
@@ -270,13 +269,8 @@
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/store": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/store": {
|
||||
"name": "@wrnexus/store",
|
||||
"version": "0.6.0",
|
||||
},
|
||||
"packages/styles": {
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.6.0",
|
||||
@@ -298,14 +292,6 @@
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.6.0",
|
||||
},
|
||||
"packages/typecheck": {
|
||||
"name": "@wrnexus/typecheck",
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/syntax": "workspace:*",
|
||||
"typescript": "^5.5.0",
|
||||
},
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.6.0",
|
||||
@@ -324,6 +310,19 @@
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.6.0",
|
||||
},
|
||||
"packages/store": {
|
||||
"name": "@wrnexus/store",
|
||||
"version": "0.6.0",
|
||||
},
|
||||
"packages/typecheck": {
|
||||
"name": "@wrnexus/typecheck",
|
||||
"version": "0.6.0",
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "workspace:*",
|
||||
"@wrnexus/syntax": "workspace:*",
|
||||
"typescript": "^5.5.0",
|
||||
},
|
||||
},
|
||||
"services/managed-captcha": {
|
||||
"name": "@wrnexus/managed-captcha-service",
|
||||
"version": "0.6.0",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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) =>
|
||||
|
||||
Reference in New Issue
Block a user