From d5e834bc78b67408dc07e67fb8a16c8abf3414cb Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Sat, 1 Aug 2026 06:50:37 +0530 Subject: [PATCH] release: WRNexusJS 0.6.0 --- .publish/ui/component-catalog.json | 26 +- .publish/ui/component-reference.json | 742 +++++++++--------- CHANGES-0.6.0.md | 6 + FIXES-0.6.0-R12-RELEASE-REFERENCE-GATE.md | 33 + FOCUSED-VALIDATION-RESULTS-R12.json | 19 + RELEASE_NOTES-0.6.0.md | 4 + VALIDATION-0.6.0.md | 6 + editors/vscode/src/compiler.cjs | 393 ++-------- examples/component-showcase/app/routes.gen.ts | 8 - packages/ui/component-catalog.json | 26 +- packages/ui/component-reference.json | 742 +++++++++--------- scripts/generate-ui-component-reference.mjs | 10 +- scripts/release.ts | 24 +- scripts/validate-0.6.mjs | 28 + 14 files changed, 960 insertions(+), 1107 deletions(-) create mode 100644 FIXES-0.6.0-R12-RELEASE-REFERENCE-GATE.md create mode 100644 FOCUSED-VALIDATION-RESULTS-R12.json diff --git a/.publish/ui/component-catalog.json b/.publish/ui/component-catalog.json index 0c82d960..007b14ae 100644 --- a/.publish/ui/component-catalog.json +++ b/.publish/ui/component-catalog.json @@ -81,6 +81,11 @@ "category": "base", "purpose": "Theme-aware, responsive button group component." }, + { + "name": "CTASection", + "category": "marketing", + "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content." + }, { "name": "Card", "category": "base", @@ -151,11 +156,6 @@ "category": "advanced-forms", "purpose": "Theme-aware, responsive copy markup component." }, - { - "name": "CTASection", - "category": "marketing", - "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content." - }, { "name": "CustomScrollbar", "category": "layout", @@ -481,26 +481,26 @@ "category": "navigation", "purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events." }, - { - "name": "Textarea", - "category": "forms", - "purpose": "Theme-aware, responsive textarea component." - }, { "name": "TextLink", "category": "marketing", "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling." }, { - "name": "Timeline", - "category": "base", - "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata." + "name": "Textarea", + "category": "forms", + "purpose": "Theme-aware, responsive textarea component." }, { "name": "TimePicker", "category": "forms", "purpose": "Theme-aware, responsive time picker component." }, + { + "name": "Timeline", + "category": "base", + "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata." + }, { "name": "Toast", "category": "base", diff --git a/.publish/ui/component-reference.json b/.publish/ui/component-reference.json index d85433a7..258a50b0 100644 --- a/.publish/ui/component-reference.json +++ b/.publish/ui/component-reference.json @@ -2278,6 +2278,193 @@ "events": ["click", "select", "change"], "source": "components/ButtonGroup.wrn" }, + { + "name": "CTASection", + "mount": "CTASection", + "category": "marketing", + "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content.", + "props": [ + { + "name": "eyebrow", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "title", + "type": "string", + "required": false, + "default": "\"Ready to get started?\"", + "options": [] + }, + { + "name": "description", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "icon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "align", + "type": "string", + "required": false, + "default": "\"center\"", + "options": [] + }, + { + "name": "size", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "color", + "type": "string", + "required": false, + "default": "\"primary\"", + "options": [] + }, + { + "name": "variant", + "type": "string", + "required": false, + "default": "\"solid\"", + "options": [] + }, + { + "name": "primaryLabel", + "type": "string", + "required": false, + "default": "\"Get started\"", + "options": [] + }, + { + "name": "primaryHref", + "type": "string", + "required": false, + "default": "\"#\"", + "options": [] + }, + { + "name": "primaryIcon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "secondaryLabel", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "secondaryHref", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "secondaryIcon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "backgroundImage", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualImage", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualAlt", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualIcon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualTitle", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualDescription", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualItems", + "type": "unknown[]", + "required": false, + "default": "[]", + "options": [] + }, + { + "name": "visualPosition", + "type": "string", + "required": false, + "default": "\"right\"", + "options": [] + }, + { + "name": "maxWidth", + "type": "string", + "required": false, + "default": "\"xl\"", + "options": [] + }, + { + "name": "fullBleed", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "class", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + } + ], + "slots": ["default", "actions", "visual", "footer"], + "outputs": [], + "events": [], + "source": "components/CTASection.wrn" + }, { "name": "Card", "mount": "Card", @@ -4250,193 +4437,6 @@ "events": ["copy", "success", "error"], "source": "components/CopyMarkup.wrn" }, - { - "name": "CTASection", - "mount": "CTASection", - "category": "marketing", - "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content.", - "props": [ - { - "name": "eyebrow", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "title", - "type": "string", - "required": false, - "default": "\"Ready to get started?\"", - "options": [] - }, - { - "name": "description", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "icon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "align", - "type": "string", - "required": false, - "default": "\"center\"", - "options": [] - }, - { - "name": "size", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "color", - "type": "string", - "required": false, - "default": "\"primary\"", - "options": [] - }, - { - "name": "variant", - "type": "string", - "required": false, - "default": "\"solid\"", - "options": [] - }, - { - "name": "primaryLabel", - "type": "string", - "required": false, - "default": "\"Get started\"", - "options": [] - }, - { - "name": "primaryHref", - "type": "string", - "required": false, - "default": "\"#\"", - "options": [] - }, - { - "name": "primaryIcon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "secondaryLabel", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "secondaryHref", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "secondaryIcon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "backgroundImage", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualImage", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualAlt", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualIcon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualTitle", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualDescription", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualItems", - "type": "unknown[]", - "required": false, - "default": "[]", - "options": [] - }, - { - "name": "visualPosition", - "type": "string", - "required": false, - "default": "\"right\"", - "options": [] - }, - { - "name": "maxWidth", - "type": "string", - "required": false, - "default": "\"xl\"", - "options": [] - }, - { - "name": "fullBleed", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "class", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - } - ], - "slots": ["default", "actions", "visual", "footer"], - "outputs": [], - "events": [], - "source": "components/CTASection.wrn" - }, { "name": "CustomScrollbar", "mount": "CustomScrollbar", @@ -12144,6 +12144,129 @@ "events": [], "source": "components/Tabs.wrn" }, + { + "name": "TextLink", + "mount": "TextLink", + "category": "marketing", + "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling.", + "props": [ + { + "name": "label", + "type": "string", + "required": false, + "default": "\"Learn more\"", + "options": [] + }, + { + "name": "href", + "type": "string", + "required": false, + "default": "\"#\"", + "options": [] + }, + { + "name": "target", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "rel", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "external", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "icon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "iconPosition", + "type": "string", + "required": false, + "default": "\"start\"", + "options": [] + }, + { + "name": "showArrow", + "type": "boolean", + "required": false, + "default": "true", + "options": [] + }, + { + "name": "underline", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "size", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "color", + "type": "string", + "required": false, + "default": "\"primary\"", + "options": [] + }, + { + "name": "variant", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "disabled", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "class", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + } + ], + "slots": [], + "outputs": [ + { + "name": "click", + "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" + }, + { + "name": "focus", + "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" + }, + { + "name": "blur", + "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" + } + ], + "events": ["click", "focus", "blur"], + "source": "components/TextLink.wrn" + }, { "name": "Textarea", "mount": "Textarea", @@ -12338,190 +12461,6 @@ "events": ["input", "change", "focus", "blur", "invalid"], "source": "components/Textarea.wrn" }, - { - "name": "TextLink", - "mount": "TextLink", - "category": "marketing", - "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling.", - "props": [ - { - "name": "label", - "type": "string", - "required": false, - "default": "\"Learn more\"", - "options": [] - }, - { - "name": "href", - "type": "string", - "required": false, - "default": "\"#\"", - "options": [] - }, - { - "name": "target", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "rel", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "external", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "icon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "iconPosition", - "type": "string", - "required": false, - "default": "\"start\"", - "options": [] - }, - { - "name": "showArrow", - "type": "boolean", - "required": false, - "default": "true", - "options": [] - }, - { - "name": "underline", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "size", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "color", - "type": "string", - "required": false, - "default": "\"primary\"", - "options": [] - }, - { - "name": "variant", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "disabled", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "class", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - } - ], - "slots": [], - "outputs": [ - { - "name": "click", - "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" - }, - { - "name": "focus", - "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" - }, - { - "name": "blur", - "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" - } - ], - "events": ["click", "focus", "blur"], - "source": "components/TextLink.wrn" - }, - { - "name": "Timeline", - "mount": "Timeline", - "category": "base", - "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata.", - "props": [ - { - "name": "size", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "color", - "type": "string", - "required": false, - "default": "\"primary\"", - "options": [] - }, - { - "name": "title", - "type": "string", - "required": false, - "default": "\"Timeline\"", - "options": [] - }, - { - "name": "description", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "items", - "type": "unknown[]", - "required": false, - "default": "[]", - "options": [] - }, - { - "name": "variant", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "class", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - } - ], - "slots": ["default"], - "outputs": [], - "events": [], - "source": "components/Timeline.wrn" - }, { "name": "TimePicker", "mount": "TimePicker", @@ -12745,6 +12684,67 @@ "events": ["input", "change", "focus", "blur", "open", "close", "invalid"], "source": "components/TimePicker.wrn" }, + { + "name": "Timeline", + "mount": "Timeline", + "category": "base", + "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata.", + "props": [ + { + "name": "size", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "color", + "type": "string", + "required": false, + "default": "\"primary\"", + "options": [] + }, + { + "name": "title", + "type": "string", + "required": false, + "default": "\"Timeline\"", + "options": [] + }, + { + "name": "description", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "items", + "type": "unknown[]", + "required": false, + "default": "[]", + "options": [] + }, + { + "name": "variant", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "class", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + } + ], + "slots": ["default"], + "outputs": [], + "events": [], + "source": "components/Timeline.wrn" + }, { "name": "Toast", "mount": "Toast", diff --git a/CHANGES-0.6.0.md b/CHANGES-0.6.0.md index 2e91e84f..bc9bc25e 100644 --- a/CHANGES-0.6.0.md +++ b/CHANGES-0.6.0.md @@ -399,3 +399,9 @@ Comparison base: `WRNexusJS(4).zip`. Dependency directories, `.git`, and `.wrnex - Corrected the compiler regression test export-stripping regex from an over-escaped literal `\\s` match to the intended whitespace `\s` match. - 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 + +- 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. diff --git a/FIXES-0.6.0-R12-RELEASE-REFERENCE-GATE.md b/FIXES-0.6.0-R12-RELEASE-REFERENCE-GATE.md new file mode 100644 index 00000000..f17df961 --- /dev/null +++ b/FIXES-0.6.0-R12-RELEASE-REFERENCE-GATE.md @@ -0,0 +1,33 @@ +# R12 release reference gate fix + +## Failure + +`bun run scripts/release.ts publish` could stop with: + +```text +UI component reference is stale. +M packages/ui/component-reference.json +``` + +even when the generated content was current. + +## Root cause + +The release gate used `git status --short` immediately after the generator ran. On Windows, generated files are written with LF while an `autocrlf` checkout may use CRLF. Git status can report the worktree file as modified even when its canonical Git content is unchanged. + +The generator also writes `component-catalog.json`, but that file was not part of the release gate. Component ordering used `localeCompare`, which can vary by operating-system locale. + +## Fix + +- Compare generated files with `git diff --quiet`, which applies Git text normalization. +- Show real differences with `git diff --name-status`. +- Include all generated UI reference files: + - `packages/ui/component-catalog.json` + - `packages/ui/component-reference.json` + - `packages/ui/COMPONENTS.md` +- Replace locale-dependent sorting with a deterministic ASCII comparator. +- Add validation guards for the complete release gate. + +## Expected result + +A line-ending-only rewrite does not block publication. A real component-reference, catalog, or documentation difference still blocks publication and prints the exact changed files. diff --git a/FOCUSED-VALIDATION-RESULTS-R12.json b/FOCUSED-VALIDATION-RESULTS-R12.json new file mode 100644 index 00000000..a89f1544 --- /dev/null +++ b/FOCUSED-VALIDATION-RESULTS-R12.json @@ -0,0 +1,19 @@ +{ + "releaseReferenceGate": { + "deterministicGeneration": true, + "lineEndingOnlyDiffIgnored": true, + "regeneratedReferenceClean": true, + "realContentDifferenceDetected": true, + "generatedFiles": [ + "packages/ui/component-catalog.json", + "packages/ui/component-reference.json", + "packages/ui/COMPONENTS.md" + ] + }, + "validation": { + "passes": 20, + "warnings": 1, + "failures": 0, + "warning": "Bun is unavailable in the packaging environment; run the full Bun check locally." + } +} diff --git a/RELEASE_NOTES-0.6.0.md b/RELEASE_NOTES-0.6.0.md index 2bf2247f..75e1b95b 100644 --- a/RELEASE_NOTES-0.6.0.md +++ b/RELEASE_NOTES-0.6.0.md @@ -36,3 +36,7 @@ This archive also includes the focused fixes for generated browser identifier sa - Nested peer calls no longer have their state changes overwritten by stale outer aliases. - 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 + +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. diff --git a/VALIDATION-0.6.0.md b/VALIDATION-0.6.0.md index 34ecc6f8..192e6aa1 100644 --- a/VALIDATION-0.6.0.md +++ b/VALIDATION-0.6.0.md @@ -100,3 +100,9 @@ The complete `bun run check` command remains the authoritative local release gat - Nested peer calls no longer have their state changes overwritten by stale outer aliases. - 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 + +- 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`. diff --git a/editors/vscode/src/compiler.cjs b/editors/vscode/src/compiler.cjs index 840d9785..f5279ffd 100644 --- a/editors/vscode/src/compiler.cjs +++ b/editors/vscode/src/compiler.cjs @@ -145,51 +145,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.generateBrowserModule = generateBrowserModule; const syntax_1 = require("@wrnexus/syntax"); const RESERVED_BINDINGS = new Set([ - "await", - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "implements", - "import", - "in", - "instanceof", - "interface", - "let", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "static", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", + "await", "break", "case", "catch", "class", "const", "continue", "debugger", + "default", "delete", "do", "else", "enum", "export", "extends", "false", + "finally", "for", "function", "if", "implements", "import", "in", + "instanceof", "interface", "let", "new", "null", "package", "private", + "protected", "public", "return", "static", "super", "switch", "this", + "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield", ]); const RUNTIME_BINDINGS = new Set([ @@ -231,7 +192,9 @@ function functionEntry(ast, fn, availableFunctions) { : ""; const stateAliases = stateNames.length ? `let { ${stateNames.join(", ")} } = context.state;` : ""; const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : ""; - const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" "); + const syncStateToContext = stateNames + .map((name) => `context.state.${name} = ${name};`) + .join(" "); const syncStateFromContext = stateNames .map((name) => `${name} = context.state.${name};`) .join(" "); @@ -267,9 +230,7 @@ function functionEntry(ast, fn, availableFunctions) { !parameterNames.has("server") ? "const server = context.server;" : "", !parameterNames.has("props") ? "const props = context.props;" : "", !parameterNames.has("refs") ? "const refs = context.refs;" : "", - ] - .filter(Boolean) - .join("\n "); + ].filter(Boolean).join("\n "); return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(context${parameters ? `, ${parameters}` : ""}) { ${initialStateSnapshot} ${stateAliases} @@ -287,19 +248,18 @@ function generateBrowserModule(ast) { const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)); const functionNames = functions.map((fn) => fn.name); const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => entry.name); - const storeImports = ast.structuredImports.filter((entry) => !entry.typeOnly && entry.source.endsWith(".wrn") && /(?:^|\/)stores?\//.test(entry.source)); + const storeImports = ast.structuredImports + .filter((entry) => !entry.typeOnly && entry.source.endsWith(".wrn") && /(?:^|\/)stores?\//.test(entry.source)); const imports = ast.structuredImports .filter((entry) => !entry.typeOnly) .filter((entry) => !entry.source.endsWith(".wrn") || /(?:^|\/)stores?\//.test(entry.source)) .map((entry) => entry.raw) .join("\n"); - const importedBindings = storeImports - .flatMap((entry) => [ + const importedBindings = storeImports.flatMap((entry) => [ ...(entry.defaultImport ? [entry.defaultImport] : []), ...(entry.namespaceImport ? [entry.namespaceImport] : []), ...entry.namedImports.map((item) => item.local), - ]) - .filter(safeIdentifier); + ]).filter(safeIdentifier); return `// generated WRNexusJS browser module for ${ast.name} ${imports} export const __wrnexusClientFunctions = { @@ -994,12 +954,7 @@ function targetFunctions(ast, target) { .join("\n\n"); } function publicOutputNames(ast) { - return [ - ...new Set([ - ...ast.outputs.map((output) => output.name), - ...ast.events.map((event) => event.name), - ]), - ]; + return [...new Set([...ast.outputs.map((output) => output.name), ...ast.events.map((event) => event.name)])]; } function generate(ast) { if (ast.kind === "global-store" || ast.kind === "page-store") @@ -1700,7 +1655,9 @@ function generateComponent(ast) { }; const ctx = { stateNames, - functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)), + functionNames: new Set(ast.runtimeFunctions + .filter((fn) => fn.runtime !== "server") + .map((fn) => fn.name)), resolveExpr, eventNames: publicOutputNames(ast), }; @@ -2067,11 +2024,7 @@ exports.createComponentContract = createComponentContract; function unionOptions(type) { if (!type || !type.includes("|")) return undefined; - const values = type - .split("|") - .map((part) => part.trim()) - .filter((part) => /^(?:"[^"]*"|'[^']*')$/.test(part)) - .map((part) => part.slice(1, -1)); + const values = type.split("|").map((part) => part.trim()).filter((part) => /^(?:"[^"]*"|'[^']*')$/.test(part)).map((part) => part.slice(1, -1)); return values.length ? values : undefined; } function createComponentContract(ast) { @@ -2087,32 +2040,17 @@ function createComponentContract(ast) { })), outputs: ast.outputs.map((output) => ({ name: output.name, - ...(output.payload - ? { payloadName: output.payload.name, payloadType: output.payload.valueType } - : {}), + ...(output.payload ? { payloadName: output.payload.name, payloadType: output.payload.valueType } : {}), })), functions: ast.runtimeFunctions.map((fn) => ({ name: fn.name, runtime: fn.runtime, async: fn.async, - parameters: fn.parameters.map((param) => ({ - name: param.name, - type: param.valueType ?? "unknown", - optional: param.optional, - })), + parameters: fn.parameters.map((param) => ({ name: param.name, type: param.valueType ?? "unknown", optional: param.optional })), returnType: fn.returnType ?? (fn.async ? "Promise" : "unknown"), })), - states: ast.states.map((state) => ({ - name: state.name, - runtime: state.runtime, - type: state.valueType ?? "unknown", - initializer: state.expr, - })), - computed: ast.computed.map((entry) => ({ - name: entry.name, - type: entry.valueType ?? "unknown", - expression: entry.expr, - })), + states: ast.states.map((state) => ({ name: state.name, runtime: state.runtime, type: state.valueType ?? "unknown", initializer: state.expr })), + computed: ast.computed.map((entry) => ({ name: entry.name, type: entry.valueType ?? "unknown", expression: entry.expr })), imports: ast.structuredImports.map((entry) => ({ source: entry.source, typeOnly: entry.typeOnly, @@ -2131,37 +2069,19 @@ exports.resolveWrnImports = resolveWrnImports; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); function candidates(path) { - return (0, node_path_1.extname)(path) - ? [path] - : [ - path, - `${path}.wrn`, - `${path}.ts`, - `${path}.d.ts`, - (0, node_path_1.join)(path, "index.wrn"), - (0, node_path_1.join)(path, "index.ts"), - ]; + return (0, node_path_1.extname)(path) ? [path] : [path, `${path}.wrn`, `${path}.ts`, `${path}.d.ts`, (0, node_path_1.join)(path, "index.wrn"), (0, node_path_1.join)(path, "index.ts")]; } function resolveWrnImport(declaration, importer, options) { const source = declaration.source; if (!source.startsWith(".") && !source.startsWith("@/")) return { declaration, resolved: source }; const aliasRoot = options.aliases?.["@"] ?? "./app"; - const base = source.startsWith("@/") - ? (0, node_path_1.resolve)(options.appRoot, aliasRoot, source.slice(2)) - : (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source); + const base = source.startsWith("@/") ? (0, node_path_1.resolve)(options.appRoot, aliasRoot, source.slice(2)) : (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source); const found = candidates(base).find(node_fs_1.existsSync); if (found) return { declaration, resolved: (0, node_fs_1.realpathSync)(found) }; const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning"; - return { - declaration, - diagnostic: { - code: "WRN-IMPORT-NOT-FOUND", - message: `Cannot resolve import '${source}' from ${importer}`, - severity, - }, - }; + return { declaration, diagnostic: { code: "WRN-IMPORT-NOT-FOUND", message: `Cannot resolve import '${source}' from ${importer}`, severity } }; } function resolveWrnImports(declarations, importer, options) { return declarations.map((declaration) => resolveWrnImport(declaration, importer, options)); @@ -2585,12 +2505,7 @@ exports.createWrnSourceMap = createWrnSourceMap; function createWrnSourceMap(source, generated) { const sourceLines = source.split(/\r?\n/).length; const generatedLines = generated.split(/\r?\n/).length; - const mappings = Array.from({ length: Math.min(sourceLines, generatedLines) }, (_, index) => ({ - generatedLine: index + 1, - sourceLine: index + 1, - sourceColumn: 1, - kind: "line", - })); + const mappings = Array.from({ length: Math.min(sourceLines, generatedLines) }, (_, index) => ({ generatedLine: index + 1, sourceLine: index + 1, sourceColumn: 1, kind: "line" })); return { version: 1, source, generated, mappings }; } @@ -2604,52 +2519,12 @@ const syntax_1 = require("@wrnexus/syntax"); const type_codegen_ts_1 = require("./type-codegen.js"); const server_codegen_ts_1 = require("./server-codegen.js"); const RESERVED_BINDINGS = new Set([ - "await", - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "implements", - "import", - "in", - "instanceof", - "interface", - "let", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "static", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "yield", + "await", "break", "case", "catch", "class", "const", "continue", "debugger", + "default", "delete", "do", "else", "enum", "export", "extends", "false", + "finally", "for", "function", "if", "implements", "import", "in", + "instanceof", "interface", "let", "new", "null", "package", "private", + "protected", "public", "return", "static", "super", "switch", "this", + "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield", ]); function safeBinding(name) { return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name); @@ -2722,8 +2597,7 @@ function generateStoreModule(ast) { const lifecycle = lifecycleSource(ast, stateNames, false); const manifest = (0, server_codegen_ts_1.rpcManifest)(ast); const remoteFunctions = manifest.map((entry) => entry.function); - const rpcWrappers = remoteFunctions - .map((name) => `${JSON.stringify(name)}: async (...received) => { + const rpcWrappers = remoteFunctions.map((name) => `${JSON.stringify(name)}: async (...received) => { const rpcContext = received.pop(); if (!rpcContext || !rpcContext.request) throw new Error("WRN-RPC-CONTEXT: request context is required"); let container = __wrnexusRpcContainers.get(rpcContext.request); @@ -2736,8 +2610,7 @@ function generateStoreModule(ast) { const action = store.actions[${JSON.stringify(name)}]; if (typeof action !== "function") throw new Error(${JSON.stringify(`WRN-RPC-FUNCTION: store action '${name}' is unavailable on the server`)}); return action(...received); - }`) - .join(",\n"); + }`).join(",\n"); return `${ast.imports.join("\n")}\nimport { defineStore } from "@wrnexus/store";\nimport { createRequestStoreContainer } from "@wrnexus/store/server";\n\n${ast.types.join("\n\n")}\n\nexport const ${ast.name} = defineStore({\n name: ${JSON.stringify(ast.name)},\n kind: ${JSON.stringify(ast.storeKind)},\n createSharedState: () => (${stateObject(ast, "shared")}),\n createClientState: () => (${stateObject(ast, "client")}),\n createServerState: () => (${stateObject(ast, "server")}),\n computed: { ${computed} },\n actions: { ${actions} },\n persist: ${persistence},\n lifecycle: { ${lifecycle} },\n});\n\nexport default ${ast.name};\n\nconst __wrnexusRpcContainers = new WeakMap();\nexport const __wrnexusServerFunctions = {\n${rpcWrappers}\n};\nexport const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)};\n\n${(0, type_codegen_ts_1.generateDeclarations)(ast)}\n`; } /** Standalone browser artifact for an imported `.wrn` store. */ @@ -2989,12 +2862,8 @@ const type_codegen_ts_1 = require("./type-codegen.js"); const store_codegen_ts_1 = require("./store-codegen.js"); function generateTargets(ast) { return { - server: ast.kind === "global-store" || ast.kind === "page-store" - ? (0, store_codegen_ts_1.generateStoreModule)(ast) - : (0, server_codegen_ts_1.generateServerFunctionsModule)(ast), - browser: ast.kind === "global-store" || ast.kind === "page-store" - ? (0, store_codegen_ts_1.generateStoreBrowserModule)(ast) - : (0, client_codegen_ts_1.generateBrowserModule)(ast), + server: ast.kind === "global-store" || ast.kind === "page-store" ? (0, store_codegen_ts_1.generateStoreModule)(ast) : (0, server_codegen_ts_1.generateServerFunctionsModule)(ast), + browser: ast.kind === "global-store" || ast.kind === "page-store" ? (0, store_codegen_ts_1.generateStoreBrowserModule)(ast) : (0, client_codegen_ts_1.generateBrowserModule)(ast), declarations: (0, type_codegen_ts_1.generateDeclarations)(ast), contract: (0, component_contract_ts_1.createComponentContract)(ast), rpc: (0, server_codegen_ts_1.rpcManifest)(ast), @@ -3027,47 +2896,22 @@ __exportStar(require("@wrnexus/syntax/tokenizer"), exports); "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateDeclarations = generateDeclarations; -function member(name) { - return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name); -} +function member(name) { return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name); } function params(astParams) { - return astParams - .map((param) => `${member(param.name)}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`) - .join(", "); + return astParams.map((param) => `${member(param.name)}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`).join(", "); } function generateDeclarations(ast) { - const inline = ast.types - .map((body) => body.trim()) - .filter(Boolean) - .join("\n\n"); + const inline = ast.types.map((body) => body.trim()).filter(Boolean).join("\n\n"); if (ast.kind === "global-store" || ast.kind === "page-store") { - const state = ast.states - .filter((entry) => entry.runtime !== "server") - .map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`) - .join("\n"); - const computed = ast.computed - .map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`) - .join("\n"); - const actions = ast.runtimeFunctions - .filter((fn) => fn.runtime !== "server") - .map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`) - .join("\n"); + const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`).join("\n"); + const computed = ast.computed.map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`).join("\n"); + const actions = ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`).join("\n"); return `${inline ? `${inline}\n\n` : ""}export interface ${ast.name}State {\n${state}\n}\n\nexport interface ${ast.name}Computed {\n${computed}\n}\n\nexport interface ${ast.name}Actions {\n${actions}\n}\n\nexport interface ${ast.name}Instance extends ${ast.name}State, ${ast.name}Computed, ${ast.name}Actions {\n reset(): void;\n snapshot(): Readonly<${ast.name}State>;\n}\n\ndeclare const store: ${ast.name}Instance;\nexport default store;\n`; } - const props = ast.props - .map((prop) => ` readonly ${member(prop.name)}${prop.required ? "" : "?"}: ${prop.valueType ?? "unknown"};`) - .join("\n"); - const outputs = ast.outputs - .map((output) => ` ${member(output.name)}(${output.payload ? `${member(output.payload.name)}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`) - .join("\n"); - const clientFunctions = ast.runtimeFunctions - .filter((fn) => fn.runtime === "client" || fn.runtime === "shared" || fn.runtime === "legacy") - .map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`) - .join("\n"); - const serverFunctions = ast.runtimeFunctions - .filter((fn) => fn.runtime === "server") - .map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise>;`) - .join("\n"); + const props = ast.props.map((prop) => ` readonly ${member(prop.name)}${prop.required ? "" : "?"}: ${prop.valueType ?? "unknown"};`).join("\n"); + const outputs = ast.outputs.map((output) => ` ${member(output.name)}(${output.payload ? `${member(output.payload.name)}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`).join("\n"); + const clientFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "client" || fn.runtime === "shared" || fn.runtime === "legacy").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`).join("\n"); + const serverFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "server").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise>;`).join("\n"); return `${inline ? `${inline}\n\n` : ""}export interface ${ast.name}Props {\n${props}\n}\n\nexport interface ${ast.name}Outputs {\n${outputs}\n}\n\nexport interface ${ast.name}ClientFunctions {\n${clientFunctions}\n}\n\nexport interface ${ast.name}ServerCalls {\n${serverFunctions}\n}\n`; } @@ -3315,88 +3159,38 @@ function astDiagnostics(ast, options) { for (const call of fn.body.matchAll(/\boutput\.([A-Za-z_$][\w$]*)\s*\(/g)) { const outputName = call[1]; if (fn.runtime === "server") { - diagnostics.push({ - code: "WRN-OUTPUT-SERVER-CALL", - severity: "error", - message: `Server function '${fn.name}' cannot call output.${outputName}().`, - hint: "Return a typed value to the browser and call the output from a client function.", - file: options.file, - }); + diagnostics.push({ code: "WRN-OUTPUT-SERVER-CALL", severity: "error", message: `Server function '${fn.name}' cannot call output.${outputName}().`, hint: "Return a typed value to the browser and call the output from a client function.", file: options.file }); } else if (!outputs.has(outputName)) { - diagnostics.push({ - code: "WRN-OUTPUT-UNKNOWN", - severity: "error", - message: `Unknown output '${outputName}' called from '${fn.name}'.`, - hint: `Declare ${outputName}(payload) inside outputs { ... }.`, - file: options.file, - }); + diagnostics.push({ code: "WRN-OUTPUT-UNKNOWN", severity: "error", message: `Unknown output '${outputName}' called from '${fn.name}'.`, hint: `Declare ${outputName}(payload) inside outputs { ... }.`, file: options.file }); } } if (fn.runtime === "client" && /\b(?:process|Bun|Deno|__dirname|require)\b/.test(fn.body)) { - diagnostics.push({ - code: "WRN-CLIENT-SERVER-API", - severity: "error", - message: `Client function '${fn.name}' references a server-only API.`, - hint: "Move that operation into a server function and call it through server.name(...).", - file: options.file, - }); + diagnostics.push({ code: "WRN-CLIENT-SERVER-API", severity: "error", message: `Client function '${fn.name}' references a server-only API.`, hint: "Move that operation into a server function and call it through server.name(...).", file: options.file }); } - if (fn.runtime === "server" && - /\b(?:window|document|localStorage|sessionStorage|navigator)\b/.test(fn.body)) { - diagnostics.push({ - code: "WRN-SERVER-BROWSER-API", - severity: "error", - message: `Server function '${fn.name}' references a browser-only API.`, - hint: "Move that code into a client function.", - file: options.file, - }); + if (fn.runtime === "server" && /\b(?:window|document|localStorage|sessionStorage|navigator)\b/.test(fn.body)) { + diagnostics.push({ code: "WRN-SERVER-BROWSER-API", severity: "error", message: `Server function '${fn.name}' references a browser-only API.`, hint: "Move that code into a client function.", file: options.file }); } const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name)); for (const prop of ast.props) { if (containsReadonlyPropMutation(fn.body, prop.name, parameterNames)) { - diagnostics.push({ - code: "WRN-PROP-READONLY", - severity: "error", - message: `Function '${fn.name}' attempts to mutate readonly prop '${prop.name}'.`, - hint: "Copy the prop into state before mutating it.", - file: options.file, - }); + diagnostics.push({ code: "WRN-PROP-READONLY", severity: "error", message: `Function '${fn.name}' attempts to mutate readonly prop '${prop.name}'.`, hint: "Copy the prop into state before mutating it.", file: options.file }); } } } for (const state of ast.states) { - if (state.runtime === "shared" && - /^(?:new\s+(?:Map|Set|WeakMap|WeakSet)|(?:async\s+)?function\b|.*=>)/.test(state.expr.trim())) { - diagnostics.push({ - code: "WRN-STATE-NON-SERIALIZABLE", - severity: "error", - message: `Shared state '${state.name}' is not safely serializable.`, - hint: "Use JSON-compatible data or move the value into client/server state.", - file: options.file, - }); + if (state.runtime === "shared" && /^(?:new\s+(?:Map|Set|WeakMap|WeakSet)|(?:async\s+)?function\b|.*=>)/.test(state.expr.trim())) { + diagnostics.push({ code: "WRN-STATE-NON-SERIALIZABLE", severity: "error", message: `Shared state '${state.name}' is not safely serializable.`, hint: "Use JSON-compatible data or move the value into client/server state.", file: options.file }); } } if (ast.persist) { const stateNames = new Set(ast.states.filter((state) => state.runtime !== "server").map((state) => state.name)); for (const name of ast.persist.include) if (!stateNames.has(name)) - diagnostics.push({ - code: "WRN-PERSIST-UNKNOWN-FIELD", - severity: "error", - message: `Persist include references unknown or server-only state '${name}'.`, - hint: "Persist only declared shared/client state fields.", - file: options.file, - }); + diagnostics.push({ code: "WRN-PERSIST-UNKNOWN-FIELD", severity: "error", message: `Persist include references unknown or server-only state '${name}'.`, hint: "Persist only declared shared/client state fields.", file: options.file }); for (const name of ast.persist.include) if (/token|password|secret|otp|api.?key/i.test(name)) - diagnostics.push({ - code: "WRN-PERSIST-SENSITIVE", - severity: "error", - message: `Sensitive field '${name}' cannot be persisted.`, - hint: "Remove secrets, tokens, passwords, OTPs, and API keys from persistence.", - file: options.file, - }); + diagnostics.push({ code: "WRN-PERSIST-SENSITIVE", severity: "error", message: `Sensitive field '${name}' cannot be persisted.`, hint: "Remove secrets, tokens, passwords, OTPs, and API keys from persistence.", file: options.file }); } return diagnostics; } @@ -3623,8 +3417,7 @@ function parse(source) { try { // A file may contain a page, component, layout, global store, or page store. const opener = lx.next(); - if (opener.type !== "ident" || - !["page", "component", "layout", "global"].includes(opener.value)) { + if (opener.type !== "ident" || !["page", "component", "layout", "global"].includes(opener.value)) { throw new ParseError(`Expected 'page', 'component', 'layout', 'global store', or 'page store' but got '${opener.value || opener.type}' at offset ${opener.pos}`); } let kind; @@ -3636,9 +3429,7 @@ function parse(source) { storeKind = "global"; name = expect("ident").value; } - else if (opener.value === "page" && - lx.peek().type === "ident" && - lx.peek().value === "store") { + else if (opener.value === "page" && lx.peek().type === "ident" && lx.peek().value === "store") { lx.next(); kind = "page-store"; storeKind = "page"; @@ -3753,12 +3544,7 @@ function parse(source) { hasDefault = true; } const defaultValue = hasDefault ? lx.readPropInitializer() : "undefined"; - props.push({ - name: pName, - valueType, - required: !hasDefault && !optional, - default: defaultValue, - }); + props.push({ name: pName, valueType, required: !hasDefault && !optional, default: defaultValue }); } expect("rbrace"); break; @@ -3782,12 +3568,7 @@ function parse(source) { else { expect("eq"); } - states.push({ - name: sName, - valueType, - expr: lx.readPropInitializer(), - runtime: "shared", - }); + states.push({ name: sName, valueType, expr: lx.readPropInitializer(), runtime: "shared" }); break; } case "computed": { @@ -3892,9 +3673,7 @@ function parse(source) { const rawMode = kw.value; const mode = rawMode === "client" ? "client" : "ssr"; lx.next(); - if ((rawMode === "client" || rawMode === "server") && - lx.peek().type === "ident" && - lx.peek().value === "state") { + if ((rawMode === "client" || rawMode === "server") && lx.peek().type === "ident" && lx.peek().value === "state") { lx.next(); if (lx.peek().type !== "lbrace") throw new ParseError(`Expected a grouped ${rawMode} state block`); @@ -4440,13 +4219,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.WRN_DIAGNOSTIC_CODES = exports.WRN_RUNTIME_TARGETS = exports.WRN_HYDRATION_STRATEGIES = exports.WRN_ROOT_MEMBERS = exports.WRN_ROOT_KINDS = exports.WRN_LANGUAGE_VERSION = void 0; /** Canonical, machine-readable WRN language capabilities. */ exports.WRN_LANGUAGE_VERSION = "0.6"; -exports.WRN_ROOT_KINDS = [ - "page", - "component", - "layout", - "global-store", - "page-store", -]; +exports.WRN_ROOT_KINDS = ["page", "component", "layout", "global-store", "page-store"]; exports.WRN_ROOT_MEMBERS = [ "layout", "runtime", @@ -5009,9 +4782,7 @@ function findTopLevelChar(input, wanted) { return -1; } function parseParameters(source) { - return splitTopLevel(source) - .filter(Boolean) - .map((entry) => { + return splitTopLevel(source).filter(Boolean).map((entry) => { const eq = findTopLevelChar(entry, "="); const declaration = (eq >= 0 ? entry.slice(0, eq) : entry).trim(); const defaultValue = eq >= 0 ? entry.slice(eq + 1).trim() : undefined; @@ -5020,12 +4791,7 @@ function parseParameters(source) { const optional = rawName.endsWith("?"); const name = optional ? rawName.slice(0, -1).trim() : rawName; const valueType = colon >= 0 ? declaration.slice(colon + 1).trim() : undefined; - return { - name, - optional, - ...(valueType ? { valueType } : {}), - ...(defaultValue ? { default: defaultValue } : {}), - }; + return { name, optional, ...(valueType ? { valueType } : {}), ...(defaultValue ? { default: defaultValue } : {}) }; }); } function skipTrivia(source, start) { @@ -5203,15 +4969,7 @@ function parseOutputs(source) { throw new Error(`Output '${nameToken.word}' payload requires a type`); out.push({ name: nameToken.word, - ...(payload - ? { - payload: { - name: payload.name, - valueType: payload.valueType, - optional: payload.optional, - }, - } - : {}), + ...(payload ? { payload: { name: payload.name, valueType: payload.valueType, optional: payload.optional } } : {}), }); } return out; @@ -5244,11 +5002,7 @@ function parseStructuredImports(imports) { const localTypeOnly = /^type\s+/.test(item); const cleaned = item.replace(/^type\s+/, "").trim(); const [imported, local] = cleaned.split(/\s+as\s+/); - return { - imported: imported.trim(), - local: (local ?? imported).trim(), - typeOnly: typeOnly || localTypeOnly, - }; + return { imported: imported.trim(), local: (local ?? imported).trim(), typeOnly: typeOnly || localTypeOnly }; }); } return declaration; @@ -5274,12 +5028,7 @@ function parseStateDeclarations(source, runtime) { if (eq.type !== "eq") throw new tokenizer_ts_1.LexError(`Expected '=' after state '${nameToken.value}' at offset ${eq.pos}`); } - out.push({ - name: nameToken.value, - ...(valueType ? { valueType } : {}), - expr: lx.readPropInitializer(), - runtime, - }); + out.push({ name: nameToken.value, ...(valueType ? { valueType } : {}), expr: lx.readPropInitializer(), runtime }); } return out; } @@ -5303,11 +5052,7 @@ function parseComputedDeclarations(source) { if (eq.type !== "eq") throw new tokenizer_ts_1.LexError(`Expected '=' after computed '${nameToken.value}' at offset ${eq.pos}`); } - out.push({ - name: nameToken.value, - ...(valueType ? { valueType } : {}), - expr: lx.readPropInitializer(), - }); + out.push({ name: nameToken.value, ...(valueType ? { valueType } : {}), expr: lx.readPropInitializer() }); } return out; } diff --git a/examples/component-showcase/app/routes.gen.ts b/examples/component-showcase/app/routes.gen.ts index 5bbd26ab..da58280e 100644 --- a/examples/component-showcase/app/routes.gen.ts +++ b/examples/component-showcase/app/routes.gen.ts @@ -134,14 +134,6 @@ export interface Routes { "/tables": Record; "/templates": Record; "/themes": Record; - "/v06-functions": Record; - "/v06-imports": Record; - "/v06-migration": Record; - "/v06-outputs": Record; - "/v06-runtime": Record; - "/v06-state": Record; - "/v06-stores": Record; - "/v06-types": Record; } export type RoutePath = keyof Routes; diff --git a/packages/ui/component-catalog.json b/packages/ui/component-catalog.json index 0c82d960..007b14ae 100644 --- a/packages/ui/component-catalog.json +++ b/packages/ui/component-catalog.json @@ -81,6 +81,11 @@ "category": "base", "purpose": "Theme-aware, responsive button group component." }, + { + "name": "CTASection", + "category": "marketing", + "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content." + }, { "name": "Card", "category": "base", @@ -151,11 +156,6 @@ "category": "advanced-forms", "purpose": "Theme-aware, responsive copy markup component." }, - { - "name": "CTASection", - "category": "marketing", - "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content." - }, { "name": "CustomScrollbar", "category": "layout", @@ -481,26 +481,26 @@ "category": "navigation", "purpose": "Switch between related responsive content panels with horizontal or vertical orientation and selection events." }, - { - "name": "Textarea", - "category": "forms", - "purpose": "Theme-aware, responsive textarea component." - }, { "name": "TextLink", "category": "marketing", "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling." }, { - "name": "Timeline", - "category": "base", - "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata." + "name": "Textarea", + "category": "forms", + "purpose": "Theme-aware, responsive textarea component." }, { "name": "TimePicker", "category": "forms", "purpose": "Theme-aware, responsive time picker component." }, + { + "name": "Timeline", + "category": "base", + "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata." + }, { "name": "Toast", "category": "base", diff --git a/packages/ui/component-reference.json b/packages/ui/component-reference.json index d85433a7..258a50b0 100644 --- a/packages/ui/component-reference.json +++ b/packages/ui/component-reference.json @@ -2278,6 +2278,193 @@ "events": ["click", "select", "change"], "source": "components/ButtonGroup.wrn" }, + { + "name": "CTASection", + "mount": "CTASection", + "category": "marketing", + "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content.", + "props": [ + { + "name": "eyebrow", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "title", + "type": "string", + "required": false, + "default": "\"Ready to get started?\"", + "options": [] + }, + { + "name": "description", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "icon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "align", + "type": "string", + "required": false, + "default": "\"center\"", + "options": [] + }, + { + "name": "size", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "color", + "type": "string", + "required": false, + "default": "\"primary\"", + "options": [] + }, + { + "name": "variant", + "type": "string", + "required": false, + "default": "\"solid\"", + "options": [] + }, + { + "name": "primaryLabel", + "type": "string", + "required": false, + "default": "\"Get started\"", + "options": [] + }, + { + "name": "primaryHref", + "type": "string", + "required": false, + "default": "\"#\"", + "options": [] + }, + { + "name": "primaryIcon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "secondaryLabel", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "secondaryHref", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "secondaryIcon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "backgroundImage", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualImage", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualAlt", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualIcon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualTitle", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualDescription", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "visualItems", + "type": "unknown[]", + "required": false, + "default": "[]", + "options": [] + }, + { + "name": "visualPosition", + "type": "string", + "required": false, + "default": "\"right\"", + "options": [] + }, + { + "name": "maxWidth", + "type": "string", + "required": false, + "default": "\"xl\"", + "options": [] + }, + { + "name": "fullBleed", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "class", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + } + ], + "slots": ["default", "actions", "visual", "footer"], + "outputs": [], + "events": [], + "source": "components/CTASection.wrn" + }, { "name": "Card", "mount": "Card", @@ -4250,193 +4437,6 @@ "events": ["copy", "success", "error"], "source": "components/CopyMarkup.wrn" }, - { - "name": "CTASection", - "mount": "CTASection", - "category": "marketing", - "purpose": "Close a page or major section with conversion-focused copy, actions, and optional supporting visual content.", - "props": [ - { - "name": "eyebrow", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "title", - "type": "string", - "required": false, - "default": "\"Ready to get started?\"", - "options": [] - }, - { - "name": "description", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "icon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "align", - "type": "string", - "required": false, - "default": "\"center\"", - "options": [] - }, - { - "name": "size", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "color", - "type": "string", - "required": false, - "default": "\"primary\"", - "options": [] - }, - { - "name": "variant", - "type": "string", - "required": false, - "default": "\"solid\"", - "options": [] - }, - { - "name": "primaryLabel", - "type": "string", - "required": false, - "default": "\"Get started\"", - "options": [] - }, - { - "name": "primaryHref", - "type": "string", - "required": false, - "default": "\"#\"", - "options": [] - }, - { - "name": "primaryIcon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "secondaryLabel", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "secondaryHref", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "secondaryIcon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "backgroundImage", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualImage", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualAlt", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualIcon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualTitle", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualDescription", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "visualItems", - "type": "unknown[]", - "required": false, - "default": "[]", - "options": [] - }, - { - "name": "visualPosition", - "type": "string", - "required": false, - "default": "\"right\"", - "options": [] - }, - { - "name": "maxWidth", - "type": "string", - "required": false, - "default": "\"xl\"", - "options": [] - }, - { - "name": "fullBleed", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "class", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - } - ], - "slots": ["default", "actions", "visual", "footer"], - "outputs": [], - "events": [], - "source": "components/CTASection.wrn" - }, { "name": "CustomScrollbar", "mount": "CustomScrollbar", @@ -12144,6 +12144,129 @@ "events": [], "source": "components/Tabs.wrn" }, + { + "name": "TextLink", + "mount": "TextLink", + "category": "marketing", + "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling.", + "props": [ + { + "name": "label", + "type": "string", + "required": false, + "default": "\"Learn more\"", + "options": [] + }, + { + "name": "href", + "type": "string", + "required": false, + "default": "\"#\"", + "options": [] + }, + { + "name": "target", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "rel", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "external", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "icon", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "iconPosition", + "type": "string", + "required": false, + "default": "\"start\"", + "options": [] + }, + { + "name": "showArrow", + "type": "boolean", + "required": false, + "default": "true", + "options": [] + }, + { + "name": "underline", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "size", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "color", + "type": "string", + "required": false, + "default": "\"primary\"", + "options": [] + }, + { + "name": "variant", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "disabled", + "type": "boolean", + "required": false, + "default": "false", + "options": [] + }, + { + "name": "class", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + } + ], + "slots": [], + "outputs": [ + { + "name": "click", + "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" + }, + { + "name": "focus", + "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" + }, + { + "name": "blur", + "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" + } + ], + "events": ["click", "focus", "blur"], + "source": "components/TextLink.wrn" + }, { "name": "Textarea", "mount": "Textarea", @@ -12338,190 +12461,6 @@ "events": ["input", "change", "focus", "blur", "invalid"], "source": "components/Textarea.wrn" }, - { - "name": "TextLink", - "mount": "TextLink", - "category": "marketing", - "purpose": "Render an accessible text action with optional icon, arrow, underline, external state, and semantic styling.", - "props": [ - { - "name": "label", - "type": "string", - "required": false, - "default": "\"Learn more\"", - "options": [] - }, - { - "name": "href", - "type": "string", - "required": false, - "default": "\"#\"", - "options": [] - }, - { - "name": "target", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "rel", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "external", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "icon", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "iconPosition", - "type": "string", - "required": false, - "default": "\"start\"", - "options": [] - }, - { - "name": "showArrow", - "type": "boolean", - "required": false, - "default": "true", - "options": [] - }, - { - "name": "underline", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "size", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "color", - "type": "string", - "required": false, - "default": "\"primary\"", - "options": [] - }, - { - "name": "variant", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "disabled", - "type": "boolean", - "required": false, - "default": "false", - "options": [] - }, - { - "name": "class", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - } - ], - "slots": [], - "outputs": [ - { - "name": "click", - "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" - }, - { - "name": "focus", - "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" - }, - { - "name": "blur", - "payloadType": "{ sourceEvent?: Event; [key: string]: string | number | boolean | null | object }" - } - ], - "events": ["click", "focus", "blur"], - "source": "components/TextLink.wrn" - }, - { - "name": "Timeline", - "mount": "Timeline", - "category": "base", - "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata.", - "props": [ - { - "name": "size", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "color", - "type": "string", - "required": false, - "default": "\"primary\"", - "options": [] - }, - { - "name": "title", - "type": "string", - "required": false, - "default": "\"Timeline\"", - "options": [] - }, - { - "name": "description", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - }, - { - "name": "items", - "type": "unknown[]", - "required": false, - "default": "[]", - "options": [] - }, - { - "name": "variant", - "type": "string", - "required": false, - "default": "\"default\"", - "options": [] - }, - { - "name": "class", - "type": "string", - "required": false, - "default": "\"\"", - "options": [] - } - ], - "slots": ["default"], - "outputs": [], - "events": [], - "source": "components/Timeline.wrn" - }, { "name": "TimePicker", "mount": "TimePicker", @@ -12745,6 +12684,67 @@ "events": ["input", "change", "focus", "blur", "open", "close", "invalid"], "source": "components/TimePicker.wrn" }, + { + "name": "Timeline", + "mount": "Timeline", + "category": "base", + "purpose": "Present responsive chronological activity, milestones, or workflow status with rich item metadata.", + "props": [ + { + "name": "size", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "color", + "type": "string", + "required": false, + "default": "\"primary\"", + "options": [] + }, + { + "name": "title", + "type": "string", + "required": false, + "default": "\"Timeline\"", + "options": [] + }, + { + "name": "description", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + }, + { + "name": "items", + "type": "unknown[]", + "required": false, + "default": "[]", + "options": [] + }, + { + "name": "variant", + "type": "string", + "required": false, + "default": "\"default\"", + "options": [] + }, + { + "name": "class", + "type": "string", + "required": false, + "default": "\"\"", + "options": [] + } + ], + "slots": ["default"], + "outputs": [], + "events": [], + "source": "components/Timeline.wrn" + }, { "name": "Toast", "mount": "Toast", diff --git a/scripts/generate-ui-component-reference.mjs b/scripts/generate-ui-component-reference.mjs index 1630d493..9cf12499 100644 --- a/scripts/generate-ui-component-reference.mjs +++ b/scripts/generate-ui-component-reference.mjs @@ -95,6 +95,10 @@ function words(name) { return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase(); } +function compareText(left, right) { + return left < right ? -1 : left > right ? 1 : 0; +} + const sourceEntries = readdirSync(componentsDir) .filter((file) => file.endsWith(".wrn")) .map((file) => { @@ -150,7 +154,7 @@ const components = [...sourceByName.values()] source: `components/${file}`, }; }) - .sort((left, right) => left.name.localeCompare(right.name)); + .sort((left, right) => compareText(left.name, right.name)); const catalogComponents = components.map((component) => ({ name: component.name, @@ -180,7 +184,9 @@ const lines = [ `This reference is generated from the ${components.length} packaged \`.wrn\` component sources. Props marked required have no default; all others show their runtime default.`, "", ]; -for (const category of [...new Set(components.map((component) => component.category))].sort()) { +for (const category of [...new Set(components.map((component) => component.category))].sort( + compareText, +)) { lines.push(`## ${category[0].toUpperCase()}${category.slice(1)}`, ""); for (const component of components.filter((item) => item.category === category)) { const props = component.props.length diff --git a/scripts/release.ts b/scripts/release.ts index c34cc1a7..34c126d0 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -208,7 +208,11 @@ function prepare(all: PackageInfo[], version: string) { } function verifyUiComponentReference(): void { - const generatedFiles = ["packages/ui/component-reference.json", "packages/ui/COMPONENTS.md"]; + const generatedFiles = [ + "packages/ui/component-catalog.json", + "packages/ui/component-reference.json", + "packages/ui/COMPONENTS.md", + ]; const result = run( process.execPath, @@ -226,22 +230,32 @@ function verifyUiComponentReference(): void { throw new Error(`Failed to generate the UI component reference.${detail ? `\n${detail}` : ""}`); } - const changed = output("git", ["status", "--short", "--", ...generatedFiles], root).trim(); + // Do not use `git status --short` here. On Windows, the generator writes LF + // while an autocrlf checkout may contain CRLF. `git status` can report those + // files as modified even when their canonical Git content is identical. + // `git diff --quiet` applies Git's normal text conversion and only fails for + // a real generated-content difference. + const changedStatus = run("git", ["diff", "--quiet", "--", ...generatedFiles], root, { + capture: true, + allowFailure: true, + }).status; - if (!changed) { + if (changedStatus === 0) { return; } + const changed = output("git", ["diff", "--name-status", "--", ...generatedFiles], root); + throw new Error( [ "UI component reference is stale.", "", - changed, + changed || generatedFiles.join("\n"), "", "Run:", " bun run scripts/generate-ui-component-reference.mjs", " bun run format", - " git add packages/ui/component-reference.json packages/ui/COMPONENTS.md", + " git add packages/ui/component-catalog.json packages/ui/component-reference.json packages/ui/COMPONENTS.md", ' git commit -m "docs(ui): refresh component reference"', " git push origin main", ].join("\n"), diff --git a/scripts/validate-0.6.mjs b/scripts/validate-0.6.mjs index 676f4171..30b1ea52 100644 --- a/scripts/validate-0.6.mjs +++ b/scripts/validate-0.6.mjs @@ -395,6 +395,34 @@ if (!compilerTargetTestSource.includes('targets.browser.replace(/^export\\s+/gm, pass("R11 peer-function executable regression test uses the correct export-stripping regex"); } +const releaseSource = readFileSync(join(root, "scripts/release.ts"), "utf8"); +const referenceGeneratorSource = readFileSync( + join(root, "scripts/generate-ui-component-reference.mjs"), + "utf8", +); +for (const [label, condition] of [ + [ + "canonical generated-reference Git comparison", + releaseSource.includes('["diff", "--quiet", "--", ...generatedFiles]') && + releaseSource.includes('["diff", "--name-status", "--", ...generatedFiles]'), + ], + [ + "component catalog included in the release reference gate", + releaseSource.includes('"packages/ui/component-catalog.json"'), + ], + [ + "platform-independent component reference ordering", + referenceGeneratorSource.includes("function compareText(left, right)") && + referenceGeneratorSource.includes("compareText(left.name, right.name)") && + !referenceGeneratorSource.includes("localeCompare"), + ], +]) { + if (!condition) fail(`R12 regression: missing ${label}`); +} +if (!failures.some((item) => item.startsWith("R12 regression:"))) { + pass("R12 release reference gate is canonical, complete, and platform independent"); +} + const showcaseFiles = walk(join(root, "examples/component-showcase/app"), (path) => path.endsWith(".wrn"), );