222 lines
8.5 KiB
TypeScript
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.",
|
|
);
|