diff --git a/CHANGES-0.6.0.md b/CHANGES-0.6.0.md index bc9bc25e..0a718b38 100644 --- a/CHANGES-0.6.0.md +++ b/CHANGES-0.6.0.md @@ -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. diff --git a/FIXES-0.6.0-R13-REFERENCE-LINE-ENDINGS.md b/FIXES-0.6.0-R13-REFERENCE-LINE-ENDINGS.md new file mode 100644 index 00000000..a1846334 --- /dev/null +++ b/FIXES-0.6.0-R13-REFERENCE-LINE-ENDINGS.md @@ -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. diff --git a/FOCUSED-VALIDATION-RESULTS-R13.json b/FOCUSED-VALIDATION-RESULTS-R13.json new file mode 100644 index 00000000..496d7dee --- /dev/null +++ b/FOCUSED-VALIDATION-RESULTS-R13.json @@ -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 + } +} diff --git a/RELEASE_NOTES-0.6.0.md b/RELEASE_NOTES-0.6.0.md index 75e1b95b..af62f29b 100644 --- a/RELEASE_NOTES-0.6.0.md +++ b/RELEASE_NOTES-0.6.0.md @@ -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. diff --git a/VALIDATION-0.6.0.md b/VALIDATION-0.6.0.md index 192e6aa1..15e79f43 100644 --- a/VALIDATION-0.6.0.md +++ b/VALIDATION-0.6.0.md @@ -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. diff --git a/bun.lock b/bun.lock index 00449210..7376ac28 100644 --- a/bun.lock +++ b/bun.lock @@ -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", diff --git a/scripts/generate-ui-component-reference.mjs b/scripts/generate-ui-component-reference.mjs index 9cf12499..89181b2e 100644 --- a/scripts/generate-ui-component-reference.mjs +++ b/scripts/generate-ui-component-reference.mjs @@ -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) { diff --git a/scripts/test-ui-reference-generation.mjs b/scripts/test-ui-reference-generation.mjs new file mode 100644 index 00000000..1a741104 --- /dev/null +++ b/scripts/test-ui-reference-generation.mjs @@ -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 }); +} diff --git a/scripts/validate-0.6.mjs b/scripts/validate-0.6.mjs index 30b1ea52..9fb95995 100644 --- a/scripts/validate-0.6.mjs +++ b/scripts/validate-0.6.mjs @@ -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) =>