@cogitato/kit-cli 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +34 -2
  3. package/bin/kit.js +4 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +245 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/doctor.d.ts +23 -0
  8. package/dist/doctor.js +86 -0
  9. package/dist/doctor.js.map +1 -0
  10. package/dist/errors.d.ts +8 -0
  11. package/dist/errors.js +9 -0
  12. package/dist/errors.js.map +1 -0
  13. package/dist/export-flows.d.ts +8 -0
  14. package/dist/export-flows.js +15 -0
  15. package/dist/export-flows.js.map +1 -0
  16. package/dist/flows.d.ts +38 -0
  17. package/dist/flows.js +141 -0
  18. package/dist/flows.js.map +1 -0
  19. package/dist/format.d.ts +3 -0
  20. package/dist/format.js +27 -0
  21. package/dist/format.js.map +1 -0
  22. package/dist/gaps.d.ts +21 -0
  23. package/dist/gaps.js +52 -0
  24. package/dist/gaps.js.map +1 -0
  25. package/dist/hook.d.ts +33 -0
  26. package/dist/hook.js +114 -0
  27. package/dist/hook.js.map +1 -0
  28. package/dist/hosting.d.ts +18 -0
  29. package/dist/hosting.js +51 -0
  30. package/dist/hosting.js.map +1 -0
  31. package/dist/index.d.ts +19 -0
  32. package/dist/index.js +21 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/issue.d.ts +17 -0
  35. package/dist/issue.js +65 -0
  36. package/dist/issue.js.map +1 -0
  37. package/dist/links.d.ts +15 -0
  38. package/dist/links.js +60 -0
  39. package/dist/links.js.map +1 -0
  40. package/dist/preview.d.ts +16 -0
  41. package/dist/preview.js +38 -0
  42. package/dist/preview.js.map +1 -0
  43. package/dist/project.d.ts +55 -0
  44. package/dist/project.js +145 -0
  45. package/dist/project.js.map +1 -0
  46. package/dist/registry.d.ts +67 -0
  47. package/dist/registry.js +227 -0
  48. package/dist/registry.js.map +1 -0
  49. package/dist/rules/css-values.d.ts +31 -0
  50. package/dist/rules/css-values.js +185 -0
  51. package/dist/rules/css-values.js.map +1 -0
  52. package/dist/rules/css.d.ts +4 -0
  53. package/dist/rules/css.js +39 -0
  54. package/dist/rules/css.js.map +1 -0
  55. package/dist/rules/tsx.d.ts +26 -0
  56. package/dist/rules/tsx.js +191 -0
  57. package/dist/rules/tsx.js.map +1 -0
  58. package/dist/save.d.ts +30 -0
  59. package/dist/save.js +164 -0
  60. package/dist/save.js.map +1 -0
  61. package/dist/scaffold.d.ts +42 -0
  62. package/dist/scaffold.js +232 -0
  63. package/dist/scaffold.js.map +1 -0
  64. package/dist/snapshots.d.ts +43 -0
  65. package/dist/snapshots.js +140 -0
  66. package/dist/snapshots.js.map +1 -0
  67. package/dist/status.d.ts +14 -0
  68. package/dist/status.js +38 -0
  69. package/dist/status.js.map +1 -0
  70. package/dist/validate.d.ts +18 -0
  71. package/dist/validate.js +72 -0
  72. package/dist/validate.js.map +1 -0
  73. package/dist/workspace.d.ts +18 -0
  74. package/dist/workspace.js +119 -0
  75. package/dist/workspace.js.map +1 -0
  76. package/package.json +52 -3
@@ -0,0 +1,232 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import ts from "typescript";
4
+ import { KitError } from "./errors.js";
5
+ import { exportFlows } from "./export-flows.js";
6
+ import { findCalls, getProperty, literalText, parseSource } from "./issue.js";
7
+ import { canvasLink, stateLink } from "./links.js";
8
+ import { LAYOUT, sourceFiles } from "./project.js";
9
+ import { ID_PATTERNS, humanize, readRegistry } from "./registry.js";
10
+ import { validate } from "./validate.js";
11
+ const q = (s) => JSON.stringify(s);
12
+ /** Indentation of the line that `pos` is on. */
13
+ function indentAt(text, pos) {
14
+ const start = text.lastIndexOf("\n", pos - 1) + 1;
15
+ return /^[ \t]*/.exec(text.slice(start))[0];
16
+ }
17
+ /**
18
+ * Adds `item` as the last element of an array or object literal, matching how the existing
19
+ * elements are laid out (one per line, or all on one line).
20
+ */
21
+ function appendToLiteral(sf, text, node, item) {
22
+ const elements = ts.isArrayLiteralExpression(node) ? node.elements : node.properties;
23
+ const start = node.getStart(sf);
24
+ const close = node.getEnd() - 1;
25
+ const last = elements[elements.length - 1];
26
+ if (!last) {
27
+ const indent = indentAt(text, start);
28
+ const open = text[start];
29
+ return `${text.slice(0, start)}${open}\n${indent} ${item(`${indent} `)},\n${indent}${text[close]}${text.slice(node.getEnd())}`;
30
+ }
31
+ const lastEnd = last.getEnd();
32
+ if (!text.slice(start, last.getStart(sf)).includes("\n")) {
33
+ return `${text.slice(0, lastEnd)}, ${item(indentAt(text, start))}${text.slice(lastEnd)}`;
34
+ }
35
+ const indent = indentAt(text, last.getStart(sf));
36
+ const comma = text.indexOf(",", lastEnd);
37
+ const at = elements.hasTrailingComma && comma >= 0 && comma < close ? comma + 1 : lastEnd;
38
+ return `${text.slice(0, at)}${elements.hasTrailingComma ? "" : ","}\n${indent}${item(indent)},${text.slice(at)}`;
39
+ }
40
+ const registryOf = (project) => readRegistry(project, sourceFiles(project));
41
+ function splitNode(node) {
42
+ const dot = node.lastIndexOf(".");
43
+ if (dot < 0)
44
+ throw new KitError(`"${node}" isn't a step. Steps are written as "{screen id}.{state}", like "checkout.payment.default".`);
45
+ return { screen: node.slice(0, dot), state: node.slice(dot + 1) };
46
+ }
47
+ function requireStep(reg, node) {
48
+ const { screen, state } = splitNode(node);
49
+ const s = reg.screens.find((x) => x.id === screen);
50
+ if (!s)
51
+ throw new KitError(`There's no screen with the id "${screen}".`);
52
+ if (!s.fixtures.some((f) => f.key === state)) {
53
+ throw new KitError(`The ${s.title} screen has no state called "${state}" (it has: ${s.fixtures.map((f) => `"${f.key}"`).join(", ")}).`);
54
+ }
55
+ }
56
+ function finish(project, files, node) {
57
+ exportFlows(project);
58
+ const r = validate(project, { files });
59
+ return { ...r, files, node, link: stateLink(project, node), canvas: canvasLink(project, node) };
60
+ }
61
+ function componentName(id) {
62
+ const last = id.slice(id.lastIndexOf(".") + 1);
63
+ return last.replace(/(^|[-_])([a-z0-9])/g, (_m, _s, c) => c.toUpperCase()) + "Screen";
64
+ }
65
+ function screenSource(id, route, title) {
66
+ const name = componentName(id);
67
+ return `import { defineScreen } from "@cogitato/runtime";
68
+
69
+ // Build this screen with components from the design kit (@/kit/...) and token variables only.
70
+ function ${name}() {
71
+ return (
72
+ <main>
73
+ <h1>${title.replace(/[{}<>]/g, "")}</h1>
74
+ </main>
75
+ );
76
+ }
77
+
78
+ export default defineScreen({
79
+ id: ${q(id)},
80
+ route: ${q(route)},
81
+ title: ${q(title)},
82
+ fixtures: {
83
+ default: { title: "Default", data: {} },
84
+ },
85
+ component: ${name},
86
+ });
87
+ `;
88
+ }
89
+ /** Creates src/screens/<id as folders>/screen.tsx with a default state, and optionally an arrow to it. */
90
+ export function createScreen(project, opts) {
91
+ const { id } = opts;
92
+ if (!ID_PATTERNS.screenId.test(id)) {
93
+ throw new KitError(`"${id}" isn't a valid screen id. Use lowercase words separated by dots, like "checkout.review".`);
94
+ }
95
+ const reg = registryOf(project);
96
+ if (reg.screens.some((s) => s.id === id))
97
+ throw new KitError(`There's already a screen called "${id}".`);
98
+ const route = opts.route ?? `/${id.split(".").join("/")}`;
99
+ if (route === "/")
100
+ throw new KitError(`The address "/" is the prototype's list of screens, so a screen can't use it. Pick another one, like "/home".`);
101
+ if (!route.startsWith("/"))
102
+ throw new KitError(`The address "${route}" needs to start with "/", like "/checkout/review".`);
103
+ const clash = reg.screens.find((s) => s.route === route);
104
+ if (clash)
105
+ throw new KitError(`The address "${route}" is already used by the ${clash.title} screen. Pick another one.`);
106
+ if (opts.from)
107
+ requireStep(reg, opts.from);
108
+ const file = `${LAYOUT.screens}/${id.split(".").join("/")}/${LAYOUT.screenFile}`;
109
+ if (existsSync(project.abs(file)))
110
+ throw new KitError(`${file} already exists. Pick another screen id.`);
111
+ const title = opts.title?.trim() || humanize(id.slice(id.lastIndexOf(".") + 1));
112
+ mkdirSync(dirname(project.abs(file)), { recursive: true });
113
+ writeFileSync(project.abs(file), screenSource(id, route, title));
114
+ const node = `${id}.default`;
115
+ const files = [file];
116
+ if (opts.from)
117
+ files.push(insertEdge(project, { from: opts.from, to: node, label: opts.label, flow: opts.flow }));
118
+ return finish(project, files, node);
119
+ }
120
+ function fixturesLiteral(sf) {
121
+ const arg = findCalls(sf, "defineScreen")[0]?.arguments[0];
122
+ if (!arg || !ts.isObjectLiteralExpression(arg))
123
+ return undefined;
124
+ const init = getProperty(arg, "fixtures")?.initializer;
125
+ return init && ts.isObjectLiteralExpression(init) ? init : undefined;
126
+ }
127
+ /** Adds a state (fixture) to an existing screen, and optionally an arrow to it. */
128
+ export function addState(project, opts) {
129
+ const { state } = opts;
130
+ if (!ID_PATTERNS.slug.test(state)) {
131
+ throw new KitError(`"${state}" isn't a valid state name. Use lowercase words joined by dashes, like "card-expired".`);
132
+ }
133
+ const reg = registryOf(project);
134
+ const screen = reg.screens.find((s) => s.id === opts.screen);
135
+ if (!screen)
136
+ throw new KitError(`There's no screen with the id "${opts.screen}".`);
137
+ if (screen.fixtures.some((f) => f.key === state))
138
+ throw new KitError(`The ${screen.title} screen already has a "${state}" state.`);
139
+ if (opts.from)
140
+ requireStep(reg, opts.from);
141
+ const text = readFileSync(project.abs(screen.file), "utf8");
142
+ const sf = parseSource(screen.file, text);
143
+ const fixtures = fixturesLiteral(sf);
144
+ if (!fixtures)
145
+ throw new KitError(`Couldn't find the list of states in ${screen.file}.`);
146
+ const title = opts.title?.trim() || humanize(state);
147
+ const key = /^[a-z_$][a-z0-9_$]*$/i.test(state) ? state : q(state);
148
+ writeFileSync(project.abs(screen.file), appendToLiteral(sf, text, fixtures, () => `${key}: { title: ${q(title)}, data: {} }`));
149
+ const node = `${screen.id}.${state}`;
150
+ const files = [screen.file];
151
+ if (opts.from)
152
+ files.push(insertEdge(project, { from: opts.from, to: node, label: opts.label, flow: opts.flow }));
153
+ return finish(project, files, node);
154
+ }
155
+ /** Adds an arrow between two existing states in src/flows.ts. */
156
+ export function addEdge(project, opts) {
157
+ const reg = registryOf(project);
158
+ requireStep(reg, opts.from);
159
+ requireStep(reg, opts.to);
160
+ return finish(project, [insertEdge(project, opts)], opts.to);
161
+ }
162
+ function edgeText(e) {
163
+ return `{ from: ${q(e.from)}, to: ${q(e.to)}${e.label ? `, label: ${q(e.label)}` : ""} }`;
164
+ }
165
+ /** Writes the arrow into src/flows.ts (creating the file or the flow if needed). Returns the file. */
166
+ function insertEdge(project, e) {
167
+ const file = LAYOUT.flowsTs;
168
+ const path = project.abs(file);
169
+ const flowId = (flows) => e.flow ??
170
+ flows.find((f) => f.edges.some((x) => x.from === e.from || x.to === e.from))?.id ??
171
+ flows[0]?.id ??
172
+ splitNode(e.from).screen.split(".")[0];
173
+ if (!existsSync(path)) {
174
+ const id = e.flow ?? splitNode(e.from).screen.split(".")[0];
175
+ writeFileSync(path, `import { defineFlows } from "@cogitato/runtime";
176
+
177
+ // How screens connect. Each step is "{screen id}.{state}".
178
+ export default defineFlows([
179
+ {
180
+ id: ${q(id)},
181
+ title: ${q(humanize(id))},
182
+ edges: [
183
+ ${edgeText(e)},
184
+ ],
185
+ },
186
+ ]);
187
+ `);
188
+ return file;
189
+ }
190
+ const text = readFileSync(path, "utf8");
191
+ const sf = parseSource(file, text);
192
+ const list = findCalls(sf, "defineFlows")[0]?.arguments[0];
193
+ if (!list || !ts.isArrayLiteralExpression(list))
194
+ throw new KitError(`Couldn't find the list of flows in ${file}.`);
195
+ const flows = list.elements.filter(ts.isObjectLiteralExpression).map((el) => {
196
+ const edges = getProperty(el, "edges")?.initializer;
197
+ const edgeList = edges && ts.isArrayLiteralExpression(edges) ? edges : undefined;
198
+ return {
199
+ el,
200
+ id: literalText(getProperty(el, "id")?.initializer) ?? "",
201
+ edgeList,
202
+ edges: [...(edgeList?.elements ?? [])].filter(ts.isObjectLiteralExpression).map((x) => ({
203
+ from: literalText(getProperty(x, "from")?.initializer) ?? "",
204
+ to: literalText(getProperty(x, "to")?.initializer) ?? "",
205
+ })),
206
+ };
207
+ });
208
+ const target = flowId(flows);
209
+ const flow = flows.find((f) => f.id === target);
210
+ if (flow?.edges.some((x) => x.from === e.from && x.to === e.to))
211
+ return file;
212
+ let next;
213
+ if (flow) {
214
+ if (!flow.edgeList)
215
+ throw new KitError(`Couldn't find the arrows (edges) of the "${target}" flow in ${file}.`);
216
+ next = appendToLiteral(sf, text, flow.edgeList, () => edgeText(e));
217
+ }
218
+ else {
219
+ next = appendToLiteral(sf, text, list, (indent) => [
220
+ "{",
221
+ `${indent} id: ${q(target)},`,
222
+ `${indent} title: ${q(humanize(target))},`,
223
+ `${indent} edges: [`,
224
+ `${indent} ${edgeText(e)},`,
225
+ `${indent} ],`,
226
+ `${indent}}`,
227
+ ].join("\n"));
228
+ }
229
+ writeFileSync(path, next);
230
+ return file;
231
+ }
232
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import { type SnapshotManifest } from "@cogitato/contract";
3
+ import type { Project } from "./project.js";
4
+ export declare const SNAPSHOT_VIEWPORT: {
5
+ readonly width: 1280;
6
+ readonly height: 800;
7
+ };
8
+ /** A port the operating system says is free right now, so parallel builds and dev servers never collide. */
9
+ export declare function freePort(): Promise<number>;
10
+ export interface SnapshotOptions {
11
+ /** Built prototype folder, project-relative. */
12
+ dist?: string;
13
+ /** Serve the build on this port. By default a free one is picked, so parallel builds don't collide. */
14
+ port?: number;
15
+ /** For tests: replace the browser and the preview server. */
16
+ deps?: {
17
+ launchBrowser?: (project: Project) => Promise<Browser | string>;
18
+ startPreview?: (project: Project, vite: string, dist: string, port: number) => Promise<ChildProcess>;
19
+ };
20
+ log?: (message: string) => void;
21
+ }
22
+ export interface SnapshotResult {
23
+ written: number;
24
+ /** Why no snapshots were taken, in plain words. */
25
+ skipped?: string;
26
+ manifest?: SnapshotManifest;
27
+ }
28
+ /** Image file for a state, relative to __kit-data/. */
29
+ export declare const snapshotFile: (node: string) => string;
30
+ export declare function snapshotManifest(nodes: string[], commit?: string): SnapshotManifest;
31
+ export interface Page {
32
+ goto(url: string, opts?: object): Promise<unknown>;
33
+ waitForSelector(selector: string, opts?: object): Promise<unknown>;
34
+ waitForLoadState(state: string, opts?: object): Promise<unknown>;
35
+ waitForTimeout(ms: number): Promise<void>;
36
+ screenshot(opts: object): Promise<unknown>;
37
+ }
38
+ export interface Browser {
39
+ newPage(opts: object): Promise<Page>;
40
+ close(): Promise<void>;
41
+ }
42
+ /** Takes a screenshot of every state in the built prototype and writes __kit-data/snapshots.json. */
43
+ export declare function takeSnapshots(project: Project, opts?: SnapshotOptions): Promise<SnapshotResult>;
@@ -0,0 +1,140 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { createServer } from "node:net";
4
+ import { createRequire } from "node:module";
5
+ import { join } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { SCHEMA_VERSIONS, validateFormat } from "@cogitato/contract";
8
+ import { isServerUp } from "./doctor.js";
9
+ import { readFlowsJson } from "./flows.js";
10
+ // Screenshots of every state in a built prototype, so the canvas can show zoomed-out frames
11
+ // instantly (07-canvas). Best effort: without Playwright or a browser it explains and skips, and
12
+ // the canvas shows placeholders instead. A deploy never fails because of snapshots (ADR 013).
13
+ export const SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
14
+ /** A port the operating system says is free right now, so parallel builds and dev servers never collide. */
15
+ export function freePort() {
16
+ return new Promise((resolve, reject) => {
17
+ const probe = createServer();
18
+ probe.once("error", reject);
19
+ probe.listen(0, () => {
20
+ const address = probe.address();
21
+ const port = typeof address === "object" && address ? address.port : 0;
22
+ probe.close(() => (port ? resolve(port) : reject(new Error("No free port could be found."))));
23
+ });
24
+ });
25
+ }
26
+ /** Image file for a state, relative to __kit-data/. */
27
+ export const snapshotFile = (node) => `snapshots/${node}.jpg`;
28
+ export function snapshotManifest(nodes, commit) {
29
+ return {
30
+ schemaVersion: SCHEMA_VERSIONS.snapshots,
31
+ ...(commit ? { commit } : {}),
32
+ viewport: { ...SNAPSHOT_VIEWPORT },
33
+ frames: Object.fromEntries(nodes.map((n) => [n, snapshotFile(n)])),
34
+ };
35
+ }
36
+ const states = (m) => m.screens.flatMap((s) => s.states.map((st) => ({ node: `${s.id}.${st.id}`, url: `${s.route}?fixture=${encodeURIComponent(st.fixture)}&kit-frame=1` })));
37
+ /**
38
+ * A package the prototype itself has installed. Only its own node_modules counts, not whatever
39
+ * Node could find elsewhere (NODE_PATH, parent folders), so snapshots use the prototype's versions.
40
+ */
41
+ function ownPackage(project, name) {
42
+ const pkg = project.abs(join("node_modules", name, "package.json"));
43
+ return existsSync(pkg) ? pkg : undefined;
44
+ }
45
+ async function launchBrowser(project) {
46
+ let playwright;
47
+ try {
48
+ const pkg = ownPackage(project, "playwright");
49
+ if (!pkg)
50
+ throw new Error("not installed");
51
+ const entry = createRequire(pkg).resolve("playwright");
52
+ const mod = (await import(pathToFileURL(entry).href));
53
+ // Playwright is CommonJS: depending on how Node reads it, its API is on the module or on `default`.
54
+ playwright = mod.chromium ? mod : mod.default;
55
+ }
56
+ catch {
57
+ return "Playwright isn't installed in this prototype, so no snapshots were taken. The canvas will show placeholders instead.";
58
+ }
59
+ try {
60
+ return await playwright.chromium.launch();
61
+ }
62
+ catch {
63
+ return "The snapshot browser couldn't start, so no snapshots were taken (run `npx playwright install chromium` to fix it). The canvas will show placeholders instead.";
64
+ }
65
+ }
66
+ /** The prototype's own Vite, which serves the build. Undefined when it isn't installed. */
67
+ function viteBin(project) {
68
+ const pkg = ownPackage(project, "vite");
69
+ return pkg ? join(pkg, "..", "bin", "vite.js") : undefined;
70
+ }
71
+ async function startPreview(project, vite, dist, port) {
72
+ const server = spawn(process.execPath, [vite, "preview", "--outDir", dist, "--port", String(port), "--strictPort"], {
73
+ cwd: project.root,
74
+ stdio: "ignore",
75
+ });
76
+ const deadline = Date.now() + 20_000;
77
+ while (!(await isServerUp(port))) {
78
+ if (server.exitCode !== null || Date.now() > deadline) {
79
+ server.kill();
80
+ throw new Error("The built prototype couldn't be served for snapshots.");
81
+ }
82
+ await new Promise((r) => setTimeout(r, 200));
83
+ }
84
+ return server;
85
+ }
86
+ /** Takes a screenshot of every state in the built prototype and writes __kit-data/snapshots.json. */
87
+ export async function takeSnapshots(project, opts = {}) {
88
+ const log = opts.log ?? (() => { });
89
+ const dist = opts.dist ?? "dist";
90
+ const out = project.abs(join(dist, "__kit-data"));
91
+ const flows = readFlowsJson(project);
92
+ if (!existsSync(project.abs(dist)))
93
+ return { written: 0, skipped: "There's no built prototype to take snapshots of yet. Build it first." };
94
+ if (!flows)
95
+ return { written: 0, skipped: "There's no flow map (design/flows.json) yet, so there's nothing to take snapshots of." };
96
+ const vite = opts.deps?.startPreview ? "" : viteBin(project);
97
+ if (vite === undefined)
98
+ return { written: 0, skipped: "The prototype's parts aren't installed, so no snapshots were taken. The canvas will show placeholders instead." };
99
+ const browser = await (opts.deps?.launchBrowser ?? launchBrowser)(project);
100
+ if (typeof browser === "string")
101
+ return { written: 0, skipped: browser };
102
+ let server;
103
+ const done = [];
104
+ let port = opts.port ?? 0;
105
+ try {
106
+ if (!opts.port)
107
+ port = await freePort();
108
+ server = await (opts.deps?.startPreview ?? startPreview)(project, vite, dist, port);
109
+ const page = await browser.newPage({ viewport: { ...SNAPSHOT_VIEWPORT } });
110
+ mkdirSync(join(out, "snapshots"), { recursive: true });
111
+ for (const { node, url } of states(flows)) {
112
+ try {
113
+ await page.goto(`http://localhost:${port}${url}`, { waitUntil: "load" });
114
+ await page.waitForSelector("html[data-kit-ready]", { timeout: 10_000 });
115
+ await page.waitForLoadState("networkidle", { timeout: 5_000 }).catch(() => { });
116
+ await page.waitForTimeout(150);
117
+ await page.screenshot({ path: join(out, snapshotFile(node)), type: "jpeg", quality: 70 });
118
+ done.push(node);
119
+ }
120
+ catch {
121
+ log(`Couldn't take a snapshot of ${node}; the canvas will show a placeholder for it.`);
122
+ }
123
+ }
124
+ }
125
+ catch (e) {
126
+ return { written: 0, skipped: `No snapshots were taken: ${e instanceof Error ? e.message : String(e)}` };
127
+ }
128
+ finally {
129
+ await browser.close().catch(() => { });
130
+ server?.kill();
131
+ }
132
+ const manifest = snapshotManifest(done, flows.commit);
133
+ const check = validateFormat("snapshots", manifest, "__kit-data/snapshots.json");
134
+ if (!check.ok)
135
+ return { written: 0, skipped: `The snapshot list came out wrong: ${check.issues[0].message}` };
136
+ writeFileSync(join(out, "snapshots.json"), JSON.stringify(manifest, null, 2) + "\n");
137
+ log(`Took ${done.length} snapshot${done.length === 1 ? "" : "s"} for the canvas.`);
138
+ return { written: done.length, manifest };
139
+ }
140
+ //# sourceMappingURL=snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../src/snapshots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Project } from "./project.js";
2
+ /** exploring, in-review, approved, handed-off: from the shared schema. */
3
+ export type FrameStatusValue = "exploring" | "in-review" | "approved" | "handed-off";
4
+ export declare const FRAME_STATUSES: FrameStatusValue[];
5
+ /**
6
+ * Sets a state's review status. "exploring" is the default, so it removes the entry.
7
+ * Returns the state's previous status.
8
+ */
9
+ export declare function setStatus(project: Project, node: string, status: string): {
10
+ node: string;
11
+ status: FrameStatusValue;
12
+ previous: FrameStatusValue;
13
+ };
14
+ export declare const statusLabel: (status: string) => string;
package/dist/status.js ADDED
@@ -0,0 +1,38 @@
1
+ import { readSchema } from "@cogitato/contract";
2
+ import { KitError } from "./errors.js";
3
+ import { exportFlows } from "./export-flows.js";
4
+ import { readFlowsJson, writeManifest } from "./flows.js";
5
+ // Review status of each frame on the canvas, kept in flows.json's `status` (ADR 014).
6
+ const common = readSchema("common.schema.json");
7
+ export const FRAME_STATUSES = common.$defs.frameStatus.enum;
8
+ const LABELS = { exploring: "Exploring", "in-review": "In review", approved: "Approved", "handed-off": "Handed off" };
9
+ /**
10
+ * Sets a state's review status. "exploring" is the default, so it removes the entry.
11
+ * Returns the state's previous status.
12
+ */
13
+ export function setStatus(project, node, status) {
14
+ if (!FRAME_STATUSES.includes(status)) {
15
+ throw new KitError(`"${status}" isn't a review status. Use one of: ${FRAME_STATUSES.map((s) => `${s} (${LABELS[s] ?? s})`).join(", ")}.`);
16
+ }
17
+ // Start from a fresh export so the status lands on what the code has right now.
18
+ const exported = exportFlows(project);
19
+ if (exported.issues.some((i) => i.severity === "error")) {
20
+ throw new KitError("The flow map can't be updated until the prototype's problems are fixed. Check the prototype first.");
21
+ }
22
+ const manifest = readFlowsJson(project) ?? exported.manifest;
23
+ const known = manifest.screens.flatMap((s) => s.states.map((st) => `${s.id}.${st.id}`));
24
+ if (!known.includes(node))
25
+ throw new KitError(`There's no screen state called "${node}". States look like "checkout.payment.card-declined".`);
26
+ const current = (manifest.status ?? {});
27
+ const previous = current[node] ?? "exploring";
28
+ const { [node]: _old, ...others } = current;
29
+ const statuses = status === "exploring" ? others : { ...others, [node]: status };
30
+ const { status: _s, ...rest } = manifest;
31
+ const next = (Object.keys(statuses).length ? { ...rest, status: statuses } : rest);
32
+ const r = writeManifest(project, { manifest: next, issues: [] }, false);
33
+ if (r.issues.some((i) => i.severity === "error"))
34
+ throw new KitError(`The status couldn't be saved: ${r.issues[0].message}`);
35
+ return { node, status: status, previous };
36
+ }
37
+ export const statusLabel = (status) => LABELS[status] ?? status;
38
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG1D,sFAAsF;AAEtF,MAAM,MAAM,GAAG,UAAU,CAAC,oBAAoB,CAAmD,CAAC;AAGlG,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAA0B,CAAC;AAElF,MAAM,MAAM,GAA2B,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AAE9I;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,IAAY,EAAE,MAAc;IACtE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,MAA0B,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,QAAQ,CAAC,IAAI,MAAM,wCAAwC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5I,CAAC;IACD,gFAAgF;IAChF,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,QAAQ,CAAC,oGAAoG,CAAC,CAAC;IAC3H,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAC7D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACxF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,QAAQ,CAAC,mCAAmC,IAAI,uDAAuD,CAAC,CAAC;IAE9I,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAqC,CAAC;IAC5E,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC;IAC9C,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAC5C,MAAM,QAAQ,GAAG,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,MAA0B,EAAE,CAAC;IACrG,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IACzC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAiB,CAAC;IACnG,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;QAAE,MAAM,IAAI,QAAQ,CAAC,iCAAiC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9H,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAA0B,EAAE,QAAQ,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type ValidationResult } from "@cogitato/contract";
2
+ import type { Issue } from "./issue.js";
3
+ import { type Project } from "./project.js";
4
+ export interface ValidateOptions {
5
+ /** Only check the design contract (the workspace's design/contract and this prototype's design/ files). */
6
+ contractOnly?: boolean;
7
+ /** Don't check the shared contract (validateWorkspace checks it once). */
8
+ skipContract?: boolean;
9
+ /**
10
+ * Only check these files (project-relative or absolute). The fast path for edit hooks.
11
+ * Screen registrations and flows are still read from the whole project, since ids and flow
12
+ * steps depend on every screen.
13
+ */
14
+ files?: string[];
15
+ }
16
+ export declare function sortIssues(issues: Issue[]): Issue[];
17
+ /** Validates a prototype: its design contract, its code against the contract, and its flows. */
18
+ export declare function validate(project: Project, opts?: ValidateOptions): ValidationResult;
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { validateProject } from "@cogitato/contract";
3
+ import { buildManifest, staleIssue } from "./flows.js";
4
+ import { LAYOUT, WORKSPACE_LAYOUT, isScreenFile, readContract, sourceFiles } from "./project.js";
5
+ import { parseProjectFile, readRegistry } from "./registry.js";
6
+ import { checkCssFile } from "./rules/css.js";
7
+ import { checkTsxFile, duplicateKitIds } from "./rules/tsx.js";
8
+ const ownerOf = (screens, file) => screens
9
+ .filter((s) => file.startsWith(`${s.dir}/`))
10
+ .sort((a, b) => b.dir.length - a.dir.length)[0];
11
+ export function sortIssues(issues) {
12
+ return [...issues].sort((a, b) => a.file.localeCompare(b.file) || (a.line ?? 0) - (b.line ?? 0) || (a.column ?? 0) - (b.column ?? 0));
13
+ }
14
+ const finish = (issues) => ({
15
+ ok: !issues.some((i) => i.severity === "error"),
16
+ issues: sortIssues(issues),
17
+ });
18
+ /** Validates a prototype: its design contract, its code against the contract, and its flows. */
19
+ export function validate(project, opts = {}) {
20
+ const issues = [];
21
+ const targets = opts.files?.map((f) => project.rel(f));
22
+ const touches = (pred) => !targets || targets.some(pred);
23
+ const sharedPrefix = `../../${WORKSPACE_LAYOUT.contract}/`;
24
+ if (touches((f) => f.startsWith(`${LAYOUT.design}/`) || f.startsWith(sharedPrefix))) {
25
+ const contractDir = project.workspace.abs(WORKSPACE_LAYOUT.contract);
26
+ const r = validateProject(project.abs(LAYOUT.design), contractDir);
27
+ for (const i of r.issues) {
28
+ const shared = i.file.startsWith("contract/");
29
+ if (shared && opts.skipContract)
30
+ continue;
31
+ // Contract issues are named from the workspace root, prototype issues from the prototype.
32
+ issues.push({ ...i, file: shared ? `${WORKSPACE_LAYOUT.contract}/${i.file.slice("contract/".length)}` : `${LAYOUT.design}/${i.file}` });
33
+ }
34
+ }
35
+ if (opts.contractOnly)
36
+ return finish(issues);
37
+ const contract = readContract(project);
38
+ const all = sourceFiles(project);
39
+ const registry = readRegistry(project, all);
40
+ const inScope = (file) => !targets || targets.includes(file);
41
+ issues.push(...registry.issues.filter((i) => inScope(i.file)));
42
+ const kitIds = [];
43
+ for (const file of all) {
44
+ if (file.endsWith(".css")) {
45
+ if (inScope(file))
46
+ issues.push(...checkCssFile(file, readFileSync(project.abs(file), "utf8"), contract));
47
+ continue;
48
+ }
49
+ // Every tsx file is read for its ids so duplicates across a screen's files are caught,
50
+ // but only files in scope report their own problems.
51
+ const isTsx = file.endsWith(".tsx");
52
+ if (!inScope(file) && !isTsx)
53
+ continue;
54
+ const r = checkTsxFile(parseProjectFile(project, file), { contract, owner: ownerOf(registry.screens, file) });
55
+ kitIds.push(...r.kitIds);
56
+ if (inScope(file))
57
+ issues.push(...r.issues);
58
+ }
59
+ issues.push(...duplicateKitIds(kitIds).filter((i) => inScope(i.file)));
60
+ // Flow steps depend on every screen, so check them whenever a screen or flows.ts is in scope.
61
+ if (registry.complete && touches((f) => f === LAYOUT.flowsTs || isScreenFile(f))) {
62
+ const built = buildManifest(registry);
63
+ issues.push(...built.issues);
64
+ if (!registry.issues.some((i) => i.severity === "error") && !built.issues.length) {
65
+ const stale = staleIssue(project, built.manifest);
66
+ if (stale)
67
+ issues.push(stale);
68
+ }
69
+ }
70
+ return finish(issues);
71
+ }
72
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type ValidationResult } from "@cogitato/contract";
2
+ import { type Issue } from "./issue.js";
3
+ import { type Workspace } from "./project.js";
4
+ /** Checks the shared kit's CSS and TSX against the contract. Issue files are workspace-relative. */
5
+ export declare function validateKit(ws: Workspace): Issue[];
6
+ /** Checks the shared contract and kit once, then every prototype. Issue files are workspace-relative. */
7
+ export declare function validateWorkspace(ws: Workspace, opts?: {
8
+ contractOnly?: boolean;
9
+ }): ValidationResult;
10
+ /** What to tell a designer when validateWorkspace found nothing, so an empty workspace doesn't read as checked prototypes. */
11
+ export declare function workspaceSuccess(ws: Workspace, opts?: {
12
+ contractOnly?: boolean;
13
+ }): string;
14
+ /** Starts prototypes/<name>/ from an existing prototype's setup, with one empty Home screen. */
15
+ export declare function createPrototype(ws: Workspace, name: string): {
16
+ name: string;
17
+ files: string[];
18
+ };