release: WRNexusJS 0.6.0

This commit is contained in:
2026-08-01 06:50:37 +05:30
parent 687d345882
commit d5e834bc78
14 changed files with 960 additions and 1107 deletions
+13 -13
View File
@@ -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",
+371 -371
View File
@@ -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",
+6
View File
@@ -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.
+33
View File
@@ -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.
+19
View File
@@ -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."
}
}
+4
View File
@@ -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.
+6
View File
@@ -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`.
+69 -324
View File
@@ -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>" : "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>" : "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>" : "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>" : "unknown")};`)
.join("\n");
const serverFunctions = ast.runtimeFunctions
.filter((fn) => fn.runtime === "server")
.map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise<Awaited<${fn.returnType ?? "unknown"}>>;`)
.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>" : "unknown")};`).join("\n");
const serverFunctions = ast.runtimeFunctions.filter((fn) => fn.runtime === "server").map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise<Awaited<${fn.returnType ?? "unknown"}>>;`).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;
}
@@ -134,14 +134,6 @@ export interface Routes {
"/tables": Record<string, never>;
"/templates": Record<string, never>;
"/themes": Record<string, never>;
"/v06-functions": Record<string, never>;
"/v06-imports": Record<string, never>;
"/v06-migration": Record<string, never>;
"/v06-outputs": Record<string, never>;
"/v06-runtime": Record<string, never>;
"/v06-state": Record<string, never>;
"/v06-stores": Record<string, never>;
"/v06-types": Record<string, never>;
}
export type RoutePath = keyof Routes;
+13 -13
View File
@@ -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",
+371 -371
View File
@@ -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",
+8 -2
View File
@@ -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
+19 -5
View File
@@ -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"),
+28
View File
@@ -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"),
);