feat: release design system engine, studio and component contracts

This commit is contained in:
Clintchiz committed 2026-10-19 13:09:03 +05:30
1 parent ff1065fb27
commit 3943b946d6
693 files changed
+175546 -3894

No files matched your search

+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/cli",
"version": "0.8.76",
"version": "0.8.77",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -16,6 +16,7 @@
"src"
],
"dependencies": {
"@wrnexus/design-system": "workspace:*",
"@wrnexus/core": "workspace:*",
"@wrnexus/router": "workspace:*",
"@wrnexus/csr": "workspace:*",
+27 -8
View File
@@ -23,7 +23,13 @@ import {
} from "node:fs";
import { basename, dirname, extname, join, relative, resolve } from "node:path";
import { buildRouter, type Route } from "@wrnexus/router";
import { getComponentControllerRuntime, getReactiveRuntime, getNavRuntime, getActionRuntime, getRealtimeRuntime } from "@wrnexus/csr";
import {
getComponentControllerRuntime,
getReactiveRuntime,
getNavRuntime,
getActionRuntime,
getRealtimeRuntime,
} from "@wrnexus/csr";
import { getIslandRuntime } from "@wrnexus/react/runtime";
import {
analyzeRuntimeImports,
@@ -351,7 +357,11 @@ export async function runBuild(appRoot: string): Promise<void> {
const bundledClientOut = join(clientModulesDir, `${clientStem}-${bundleHash}.mjs`);
writeFileSync(bundledClientOut, browserSource, "utf8");
clientFiles.set(file, bundledClientOut);
writeFileSync(out, code.replaceAll(clientUrl, `/__wrnexus/client/${basename(bundledClientOut)}`), "utf8");
writeFileSync(
out,
code.replaceAll(clientUrl, `/__wrnexus/client/${basename(bundledClientOut)}`),
"utf8",
);
rmSync(clientEntry, { force: true });
} catch (error) {
compiledFiles.delete(file);
@@ -724,14 +734,17 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
const runtimeConfigPath = ["wrnexus.config.ts", "wrnexus.config.js", "wrnexus.config.mjs"]
.map((name) => resolve(appRoot, name))
.find(existsSync);
if (runtimeConfigPath && config.auth) {
if (runtimeConfigPath && "auth" in config && config.auth) {
// Importing config creates the engine, but does not run plugin.configure.
// Initialize route options before route/middleware modules are evaluated.
// Credentials are read by the config at server startup, never serialized here.
writeFileSync(join(distDir, ".auth-runtime.ts"),
writeFileSync(
join(distDir, ".auth-runtime.ts"),
`import config from ${JSON.stringify(fwd(runtimeConfigPath))};\n` +
`import { configureAuthRuntime } from "@wrnexus/auth/plugin";\n` +
`configureAuthRuntime(config.auth);\n`, "utf8");
`import { configureAuthRuntime } from "@wrnexus/auth/plugin";\n` +
`configureAuthRuntime(config.auth);\n`,
"utf8",
);
imports.push(`import "./.auth-runtime.ts";`);
} else if (runtimeConfigPath) imports.push(`import ${JSON.stringify(fwd(runtimeConfigPath))};`);
@@ -1072,8 +1085,14 @@ async function emitPluginAssets(
// Keep that alias revalidatable while the HTML uses the immutable URL.
const stablePath = runtime.publicPath ?? `/__wrnexus/assets/${safeAssetName(runtime.id)}.js`;
if (stablePath !== publicPath) {
assets.push({ id: runtime.id, publicPath: stablePath, file,
contentType: "text/javascript; charset=utf-8", runtime: true, immutable: false });
assets.push({
id: runtime.id,
publicPath: stablePath,
file,
contentType: "text/javascript; charset=utf-8",
runtime: true,
immutable: false,
});
}
// Keep an empty source marker so @wrnexus/plugin can normalize this
// already-emitted descriptor during production HTML rendering without
+46
View File
@@ -0,0 +1,46 @@
import { readFileSync, statSync } from "node:fs";
import { resolve } from "node:path";
import {
assertDesignSystem,
compileDesignSystem,
DesignSystemError,
resolveDesignSystem,
validateDesignSystem,
type DesignSystem,
type ResolveOptions,
} from "@wrnexus/design-system";
function readJson(file: string): unknown {
const path = resolve(file);
if (statSync(path).size > 1024 * 1024)
throw new Error("WRN-DS-LIMIT: Design input exceeds 1 MiB.");
return JSON.parse(readFileSync(path, "utf8"));
}
/** Data-only CLI: never imports executable user configuration or writes files. */
export async function runDesignCommand(args: string[]): Promise<string> {
const [command, file, ...flags] = args;
if (!file || !["validate", "inspect", "compile"].includes(command ?? ""))
throw new Error(
"Usage: wrnexus design <validate|inspect|compile> <system.json> [--mode=name] [--density=name] [--registry=systems.json]",
);
const options: ResolveOptions = {};
for (const flag of flags) {
const match = /^--(mode|density|registry)=(.+)$/.exec(flag);
if (!match) throw new Error(`WRN-DS-OPTION: Unknown design option '${flag}'.`);
if (match[1] === "registry") {
const registry = readJson(match[2]!);
if (!registry || typeof registry !== "object" || Array.isArray(registry))
throw new Error("WRN-DS-SCHEMA: Registry must map identifiers to systems.");
for (const value of Object.values(registry)) assertDesignSystem(value);
options.registry = registry as Record<string, DesignSystem>;
} else options[match[1] as "mode" | "density"] = match[2]!;
}
const system = readJson(file);
const diagnostics = validateDesignSystem(system, options);
if (diagnostics.length) throw new DesignSystemError(diagnostics);
assertDesignSystem(system);
if (command === "compile") return compileDesignSystem(system, options).css.trimEnd();
if (command === "inspect") return JSON.stringify(resolveDesignSystem(system, options), null, 2);
return JSON.stringify({ valid: true, id: system.id, schemaVersion: system.schemaVersion });
}
+7
View File
@@ -39,6 +39,8 @@ function help(): void {
console.log(`wrnexus — WrNexus CLI
Usage:
wrnexus design <validate|inspect|compile> <system.json> [--mode=light] [--density=compact]
Validate or compile an opt-in design system
wrnexus dev [app-dir] [--port=3000] [--host=::]
Start the development server (live reload)
wrnexus dev [app-dir] --services Start local production-service simulators with the app
@@ -121,6 +123,11 @@ async function main(): Promise<void> {
notifyIfUpdateAvailable(currentCliVersion(), command);
switch (command) {
case "design": {
const { runDesignCommand } = await import("./design.ts");
console.log(await runDesignCommand(rest));
break;
}
case "dev": {
const appRoot = rest.find((a) => !a.startsWith("--")) ?? ".";
const portArg = rest.find((a) => a.startsWith("--port="));
@@ -1,24 +1,33 @@
import {afterAll,expect,test} from "bun:test";
import {mkdirSync,mkdtempSync,readFileSync,readdirSync,rmSync,writeFileSync} from "node:fs";
import {join} from "node:path";
import {runBuild} from "../src/build.ts";
const scratch=join(import.meta.dir,".tmp-client-dependency");
mkdirSync(scratch,{recursive:true});
afterAll(()=>rmSync(scratch,{recursive:true,force:true}));
test("changing a bundled dependency changes the immutable client URL",async()=>{
const root=mkdtempSync(join(scratch,"app-"));
mkdirSync(join(root,"app/pages"),{recursive:true});
writeFileSync(join(root,"package.json"),JSON.stringify({name:"client-cache-fixture"}));
writeFileSync(join(root,"wrnexus.config.ts"),"export default {};\n");
writeFileSync(join(root,"app/pages/index.wrn"),'import {message} from "../message.ts"\npage Home { functions { client function show(){alert(message)} } view { <button type="button" on:click={show()}>Show message</button> } }');
const files:string[]=[];
for(const message of ["first dependency","updated dependency"]){
writeFileSync(join(root,"app/message.ts"),`export const message=${JSON.stringify(message)};`);
import { afterAll, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { runBuild } from "../src/build.ts";
const scratch = join(import.meta.dir, ".tmp-client-dependency");
mkdirSync(scratch, { recursive: true });
afterAll(() => rmSync(scratch, { recursive: true, force: true }));
test("changing a bundled dependency changes the immutable client URL", async () => {
const root = mkdtempSync(join(scratch, "app-"));
mkdirSync(join(root, "app/pages"), { recursive: true });
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "client-cache-fixture" }));
writeFileSync(join(root, "wrnexus.config.ts"), "export default {};\n");
writeFileSync(
join(root, "app/pages/index.wrn"),
'import {message} from "../message.ts"\npage Home { functions { client function show(){alert(message)} } view { <button type="button" on:click={show()}>Show message</button> } }',
);
const files: string[] = [];
for (const message of ["first dependency", "updated dependency"]) {
writeFileSync(join(root, "app/message.ts"), `export const message=${JSON.stringify(message)};`);
await runBuild(root);
const file=readdirSync(join(root,"dist/client")).find(name=>name.startsWith("index-")&&readFileSync(join(root,"dist/client",name),"utf8").includes(message));
const file = readdirSync(join(root, "dist/client")).find(
(name) =>
name.startsWith("index-") &&
readFileSync(join(root, "dist/client", name), "utf8").includes(message),
);
expect(file).toBeDefined();
files.push(file!);
expect(readFileSync(join(root,"dist/server.js"),"utf8")).toContain(`/__wrnexus/client/${file}`);
expect(readFileSync(join(root, "dist/server.js"), "utf8")).toContain(
`/__wrnexus/client/${file}`,
);
}
expect(files[0]).not.toBe(files[1]);
},120000);
}, 120000);
+33
View File
@@ -0,0 +1,33 @@
import { test, expect } from "bun:test";
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { runDesignCommand } from "../src/design.ts";
test("design CLI validates data, inspects provenance and compiles without modifying input", async () => {
const directory = mkdtempSync(join(tmpdir(), "wrn-design-cli-"));
try {
const file = join(directory, "system.json");
const source = JSON.stringify({
schemaVersion: 1,
id: "square",
name: "Square",
version: "1",
tokens: { radius: { type: "dimension", value: "0", legacy: "radius-sm" } },
});
writeFileSync(file, source);
expect(JSON.parse(await runDesignCommand(["validate", file])).valid).toBe(true);
expect(JSON.parse(await runDesignCommand(["inspect", file])).tokens.radius.source).toBe(
"square.tokens.radius",
);
expect(await runDesignCommand(["compile", file])).toContain("--wrn-radius-sm:0;");
expect(await Bun.file(file).text()).toBe(source);
await expect(runDesignCommand(["compile", file, "--unknown=x"])).rejects.toThrow(
"WRN-DS-OPTION",
);
writeFileSync(file, source.replace('"0"', '"url(bad)"'));
await expect(runDesignCommand(["validate", file])).rejects.toThrow("WRN-DS-VALUE");
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
+6 -2
View File
@@ -11,10 +11,14 @@ test("production CSS inlining uses a configurable UTF-8 byte budget", () => {
});
test("relative asset URLs stay external so their base does not change", () => {
for (const url of ['./font.woff2', '../font.woff2', 'font.woff2', '#mask']) {
for (const url of ["./font.woff2", "../font.woff2", "font.woff2", "#mask"]) {
expect(shouldInlineCss(`a{background:url("${url}")}`, undefined)).toBe(false);
}
for (const url of ['/font.woff2', 'https://cdn.example/font.woff2', 'data:image/svg+xml;base64,AAAA']) {
for (const url of [
"/font.woff2",
"https://cdn.example/font.woff2",
"data:image/svg+xml;base64,AAAA",
]) {
expect(shouldInlineCss(`a{background:url('${url}')}`, undefined)).toBe(true);
}
});
@@ -38,8 +38,10 @@ export default {
join(root, "app", "pages", "index.wrn"),
`import RuntimeProbe from "../components/RuntimeProbe.wrn"\npage Home { view { <RuntimeProbe /> } }\n`,
);
writeFileSync(join(root, "app", "pages", "[id].wrn"),
`page Dynamic { functions { client function selectPanel() { console.log('panel-ready') } } view { <button @click="selectPanel()">Panel</button> } }`);
writeFileSync(
join(root, "app", "pages", "[id].wrn"),
`page Dynamic { functions { client function selectPanel() { console.log('panel-ready') } } view { <button @click="selectPanel()">Panel</button> } }`,
);
await runBuild(root);
@@ -3,7 +3,10 @@ import { productionConnections } from "../src/runtime-connections.ts";
test("deployment connections override build placeholders without accepting server settings", () => {
const db = { driver: "postgres", url: "postgres://runtime:secret@db/app" };
expect(productionConnections(JSON.stringify({ db, realtime: { scale: true } }))).toEqual({ db, realtime: { scale: true } });
expect(productionConnections(JSON.stringify({ db, realtime: { scale: true } }))).toEqual({
db,
realtime: { scale: true },
});
expect(productionConnections("")).toEqual({});
for (const value of ["null", "[]", "invalid", '{"port":80}', '{"db":null}', '{"db":[]}'])
expect(() => productionConnections(value)).toThrow();