Files
webstudio/packages/cli/scripts/check-package-boundaries.test.ts
Oleg Isonen d291e4b671 feat: Builder api runtime contracts guardrails for MCP (#5857)
This PR expands Webstudio MCP and CLI into a more complete agent
authoring interface. Agents can discover project capabilities, edit real
Webstudio content, use data/resources, verify visual output, and report
useful issues when workflows fail.

## MCP User Capabilities

- Create, update, duplicate, reorder, and delete pages.
- Create pages from templates and manage reusable page templates.
- Inspect project pages, instances, props, styles, assets, variables,
resources, and metadata.
- Insert Webstudio components and authored JSX fragments into pages.
- Edit text, props, styles, breakpoints, design tokens, and CSS
variables.
- Update page metadata, SEO fields, redirects, marketplace metadata, and
project settings.
- Create and use static data variables.
- Create and use dynamic resources fetched over HTTP.
- Use REST, GraphQL, and built-in system resources in authored pages.
- Bind data variables and resources to visible page content.
- Create resource-backed pages such as sitemap XML, structured SEO data,
and API-driven content sections.
- Preview current project state without requiring a publish step.
- Capture screenshots for visual verification.
- Verify responsive designs across desktop, tablet, and mobile
breakpoints.
- Use screenshot diff and OCR-style checks to inspect rendered output.
- Reset and refresh local MCP project state when needed.
- Discover available CLI/MCP commands from generated manuals.
- Use clearer examples for resources, data variables, JSX authoring,
previewing, and visual checks.
- Read marketplace product metadata without requiring a full project
snapshot.
- Get safer guidance when using low-level patch tools.
- Get a ready-to-copy Discord report template when MCP/CLI behavior is
confusing or broken.
2026-07-10 02:13:22 +02:00

89 lines
2.7 KiB
TypeScript

import { execFile } from "node:child_process";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { afterEach, beforeEach, expect, test } from "vitest";
const execFileAsync = promisify(execFile);
const scriptPath = fileURLToPath(
new URL("../../../scripts/check-package-boundaries.mjs", import.meta.url)
);
let tempDir: string;
const writeJson = async (path: string, value: unknown) => {
await mkdir(join(tempDir, path, ".."), { recursive: true });
await writeFile(join(tempDir, path), JSON.stringify(value, undefined, 2));
};
const runCheck = async () =>
await execFileAsync("node", [scriptPath], { cwd: tempDir });
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "webstudio-package-boundaries-"));
await mkdir(join(tempDir, "packages"), { recursive: true });
await mkdir(join(tempDir, "apps"), { recursive: true });
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
test("passes when public packages depend only on public packages at runtime", async () => {
await writeJson("packages/public-a/package.json", {
name: "@webstudio-is/public-a",
private: false,
dependencies: {
"@webstudio-is/public-b": "workspace:*",
},
});
await writeJson("packages/public-b/package.json", {
name: "@webstudio-is/public-b",
private: false,
});
await expect(runCheck()).resolves.toMatchObject({
stdout: "Package boundaries are valid.\n",
});
});
test("passes when public packages use private packages only for dev-time generation", async () => {
await writeJson("packages/public/package.json", {
name: "@webstudio-is/public",
private: false,
devDependencies: {
"@webstudio-is/private": "workspace:*",
},
});
await writeJson("packages/private/package.json", {
name: "@webstudio-is/private",
private: true,
});
await expect(runCheck()).resolves.toMatchObject({
stdout: "Package boundaries are valid.\n",
});
});
test("fails when public packages depend on private packages at runtime", async () => {
await writeJson("packages/public/package.json", {
name: "@webstudio-is/public",
private: false,
dependencies: {
"@webstudio-is/private": "workspace:*",
},
});
await writeJson("packages/private/package.json", {
name: "@webstudio-is/private",
private: true,
});
await expect(runCheck()).rejects.toMatchObject({
stderr: expect.stringContaining(
"@webstudio-is/public (packages/public/package.json) lists private workspace package @webstudio-is/private in dependencies"
),
});
});