Files
WRNexusJS/scripts/design-release-consumer.ts
T

222 lines
8.5 KiB
TypeScript

/** Copied into an isolated tarball consumer by prepare-design-release.ts. Package imports only. */
import assert from "node:assert/strict";
import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { CORE_DESIGN_SYSTEMS, compileDesignSystem } from "@wrnexus/design-system";
import { createDesignPackage, readDesignPackage } from "@wrnexus/design-system/package";
import { StudioSession } from "@wrnexus/design-studio";
import { planDesignUpgrade, applyDesignUpgrade } from "@wrnexus/design-studio/upgrades";
import { createStudioHandler } from "@wrnexus/design-studio/server";
import { createFrameworkMcpServer } from "@wrnexus/mcp";
import { uiComponentNames } from "@wrnexus/ui/registry";
import { resolveThemeConfig } from "@wrnexus/styles/theme";
import schema from "@wrnexus/design-system/schema.json";
assert.equal(uiComponentNames().length, 104);
assert.ok(schema);
const system = structuredClone(CORE_DESIGN_SYSTEMS.square);
const original = await createDesignPackage(system);
const next = structuredClone(system);
next.version = "1.1.0";
next.tokens["color.primary"]!.value = "#abcdef";
const upgraded = await createDesignPackage(next);
const source = JSON.stringify(upgraded);
const sessions: StudioSession[] = [];
const applications = [];
for (const name of ["application-one", "application-two"]) {
const root = resolve(name);
mkdirSync(root, { recursive: true });
writeFileSync(join(root, "wrn.config.ts"), "export default { existing: true };");
const storage = {
value: "",
getItem() {
return this.value || null;
},
setItem(_key: string, value: string) {
this.value = value;
},
};
const session = new StudioSession(system, storage);
sessions.push(session);
session.setLocks(["/tokens/shape.control"]);
const review = await planDesignUpgrade(session, source);
assert.equal(review.canApply, true);
await applyDesignUpgrade(session, source, review.baseRevision);
assert.equal(session.valid.version, "1.1.0");
session.undo();
assert.equal(session.valid.version, "1.0.0");
session.redo();
session.edit("{unfinished");
const restored = new StudioSession(undefined, storage);
assert.equal(restored.restore(), true);
assert.equal(restored.source, "{unfinished");
restored.undo();
assert.equal(restored.valid.version, "1.1.0");
const server = createFrameworkMcpServer(root, { design: true, designWrite: true });
let draft = (await server.call("wrnexus_design_create", {
baseRevision: "none",
source: JSON.stringify(system),
})) as { revision: string };
const migration = (await server.call("wrnexus_design_upgrade_plan", { source })) as {
canApply: boolean;
};
assert.equal(migration.canApply, true);
draft = (await server.call("wrnexus_design_upgrade", {
baseRevision: draft.revision,
source,
})) as typeof draft;
const page = {
id: "account",
title: "Account settings",
components: [
{ component: "Input", props: { id: "name", label: "Display name" } },
{ component: "Button", props: { label: "Save profile", variant: "default" } },
],
};
draft = (await server.call("wrnexus_design_page", {
baseRevision: draft.revision,
page,
})) as typeof draft;
const preview = (await server.call("wrnexus_design_preview", { pageId: "account" })) as {
html: string;
};
assert.ok(preview.html.includes('data-wrn-component="Button"'));
assert.ok(preview.html.includes("Save profile"));
const pinned = (await server.call("wrnexus_design_package")) as { package: typeof upgraded };
assert.equal(pinned.package.integrity.digest, upgraded.integrity.digest);
await server.call("wrnexus_design_apply", {
baseRevision: draft.revision,
baseApplicationRevision: "none",
});
const state = (await server.call("wrnexus_design_inspect")) as {
application: { revision: string };
};
assert.notEqual(state.application.revision, "none");
assert.equal(
readFileSync(join(root, "wrn.config.ts"), "utf8"),
"export default { existing: true };",
);
applications.push({
name,
digest: pinned.package.integrity.digest,
applied: state.application.revision,
});
}
assert.equal(applications[0]!.digest, applications[1]!.digest);
assert.equal(applications[0]!.applied, applications[1]!.applied);
assert.equal(
(await readDesignPackage(JSON.stringify(original))).package.integrity.digest,
original.integrity.digest,
);
for (const system of Object.values(CORE_DESIGN_SYSTEMS)) {
assert.ok(compileDesignSystem(system).css);
assert.ok(resolveThemeConfig({ designSystem: { system } }).recipeCss);
}
const handler = await createStudioHandler({ applicationRoot: resolve("application-one") });
assert.equal((await handler(new Request("http://127.0.0.1:4320/"))).status, 200);
const client = await handler(new Request("http://127.0.0.1:4320/studio.js"));
assert.equal(client.status, 200);
assert.ok((await client.text()).length > 1000);
const req = await handler(
new Request("http://127.0.0.1:4320/api/preview", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ system: CORE_DESIGN_SYSTEMS.angular, mode: "dark" }),
}),
);
assert.equal(req.status, 200);
const result = await req.json();
assert.ok(
(await (await handler(new Request("http://127.0.0.1:4320" + result.url))).text()).includes(
'data-wrn-component="Button"',
),
);
assert.equal(existsSync(resolve("node_modules/@wrnexus/design-studio/src")), false);
assert.equal(
existsSync(resolve("node_modules/@wrnexus/design-studio/assets/studio-client.js")),
true,
);
writeFileSync(
"browser-probe.ts",
'import {StudioSession} from "@wrnexus/design-studio"; import {createDesignPackage} from "@wrnexus/design-system/package"; import {planDesignUpgrade} from "@wrnexus/design-studio/upgrades"; console.log(new StudioSession(),createDesignPackage,planDesignUpgrade);',
);
const browser = await Bun.build({ entrypoints: [resolve("browser-probe.ts")], target: "browser" });
assert.equal(browser.success, true, browser.logs.join("\n"));
assert.ok(browser.outputs.length);
const defaultServer = createFrameworkMcpServer(resolve("application-one"));
assert.equal(defaultServer.tools().length, 8);
const stdio = resolve("node_modules/@wrnexus/mcp/dist/stdio.js");
const child = Bun.spawn(
[process.execPath, stdio, "--root=" + resolve("application-one"), "--design"],
{ stdin: "pipe", stdout: "pipe", stderr: "pipe" },
);
child.stdin.write(
JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: { name: "wrnexus_design_context", arguments: { components: ["Button"] } },
}) + "\n",
);
child.stdin.end();
const protocol = JSON.parse((await new Response(child.stdout).text()).trim());
assert.equal(await child.exited, 0);
assert.ok(protocol.result.structuredContent.value.components.Button);
const cli = Bun.spawn(
[
process.execPath,
resolve("node_modules/@wrnexus/design-studio/dist/cli.js"),
"--app-root",
resolve("application-one"),
],
{ stdout: "pipe", stderr: "pipe", env: { ...process.env, DESIGN_STUDIO_PORT: "0" } },
);
let startupTimer: ReturnType<typeof setTimeout> | undefined;
try {
const startup = await Promise.race([
cli.stdout.getReader().read(),
new Promise<never>((_resolve, reject) => {
startupTimer = setTimeout(
() => reject(Error("Installed Studio CLI startup timed out.")),
10000,
);
}),
]);
const line = new TextDecoder().decode(startup.value);
const address = /http:\/\/127\.0\.0\.1:\d+\//.exec(line)?.[0];
assert.ok(address, line);
assert.equal((await fetch(address)).status, 200);
} finally {
if (startupTimer) clearTimeout(startupTimer);
cli.kill();
await cli.exited;
}
writeFileSync(
process.env.WRNEXUS_DESIGN_REPORT!,
JSON.stringify(
{
packagingVerified: false,
published: false,
applications,
checks: [
"104 shipped component sources",
"JSON schema asset",
"browser-safe root and upgrades bundle",
"prebuilt Studio client without src files",
"real composed and agent SSR previews",
"shared pin and CSS consistency across two applications",
"invalid draft reload and undo/redo",
"reviewed package upgrades and MCP operations",
"unchanged default MCP tools and installed stdio protocol",
"existing application configuration preserved",
"installed Studio CLI starts without source files",
],
},
null,
2,
),
);
console.log(
"Installed tarball consumers passed for two applications, Studio browser/server assets and MCP.",
);