@cogitato/kit-cli 0.0.0-stage → 0.1.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 (70) 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 +204 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/doctor.d.ts +19 -0
  8. package/dist/doctor.js +72 -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 +78 -0
  27. package/dist/hook.js.map +1 -0
  28. package/dist/index.d.ts +17 -0
  29. package/dist/index.js +19 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/issue.d.ts +17 -0
  32. package/dist/issue.js +65 -0
  33. package/dist/issue.js.map +1 -0
  34. package/dist/links.d.ts +16 -0
  35. package/dist/links.js +61 -0
  36. package/dist/links.js.map +1 -0
  37. package/dist/preview.d.ts +16 -0
  38. package/dist/preview.js +38 -0
  39. package/dist/preview.js.map +1 -0
  40. package/dist/project.d.ts +35 -0
  41. package/dist/project.js +88 -0
  42. package/dist/project.js.map +1 -0
  43. package/dist/registry.d.ts +67 -0
  44. package/dist/registry.js +224 -0
  45. package/dist/registry.js.map +1 -0
  46. package/dist/rules/css-values.d.ts +31 -0
  47. package/dist/rules/css-values.js +185 -0
  48. package/dist/rules/css-values.js.map +1 -0
  49. package/dist/rules/css.d.ts +4 -0
  50. package/dist/rules/css.js +39 -0
  51. package/dist/rules/css.js.map +1 -0
  52. package/dist/rules/tsx.d.ts +24 -0
  53. package/dist/rules/tsx.js +190 -0
  54. package/dist/rules/tsx.js.map +1 -0
  55. package/dist/save.d.ts +27 -0
  56. package/dist/save.js +125 -0
  57. package/dist/save.js.map +1 -0
  58. package/dist/scaffold.d.ts +42 -0
  59. package/dist/scaffold.js +230 -0
  60. package/dist/scaffold.js.map +1 -0
  61. package/dist/snapshots.d.ts +23 -0
  62. package/dist/snapshots.js +126 -0
  63. package/dist/snapshots.js.map +1 -0
  64. package/dist/status.d.ts +14 -0
  65. package/dist/status.js +38 -0
  66. package/dist/status.js.map +1 -0
  67. package/dist/validate.d.ts +14 -0
  68. package/dist/validate.js +64 -0
  69. package/dist/validate.js.map +1 -0
  70. package/package.json +48 -3
@@ -0,0 +1,185 @@
1
+ import { tokenCssVar } from "@cogitato/contract";
2
+ // Which CSS properties must use tokens, and which token group sizes come from.
3
+ const COLOR_PROP = /^(color|background(-color|-image)?|border(-(top|right|bottom|left|block|inline)(-(start|end))?)?(-color)?|outline(-color)?|fill|stroke|box-shadow|text-shadow|text-decoration(-color)?|caret-color|accent-color|column-rule(-color)?)$/;
4
+ const SIZE_GROUPS = [
5
+ { prop: /^(padding|margin)(-(top|right|bottom|left|block|inline)(-(start|end))?)?$|^(row-|column-)?gap$/, group: "space", scale: "spacing scale" },
6
+ { prop: /^border(-(top|bottom)-(left|right)|-(start|end)-(start|end))?-radius$/, group: "radius", scale: "corner radius scale" },
7
+ { prop: /^font-size$/, group: "font.size", scale: "font size scale" },
8
+ ];
9
+ // Named colors designers are likely to type, with values so the nearest token can be suggested.
10
+ const NAMED_COLORS = {
11
+ black: "#000000", white: "#ffffff", red: "#ff0000", green: "#008000", blue: "#0000ff",
12
+ yellow: "#ffff00", orange: "#ffa500", purple: "#800080", pink: "#ffc0cb", gray: "#808080",
13
+ grey: "#808080", silver: "#c0c0c0", navy: "#000080", teal: "#008080", maroon: "#800000",
14
+ olive: "#808000", lime: "#00ff00", aqua: "#00ffff", cyan: "#00ffff", magenta: "#ff00ff",
15
+ fuchsia: "#ff00ff", brown: "#a52a2a", gold: "#ffd700", indigo: "#4b0082", violet: "#ee82ee",
16
+ coral: "#ff7f50", salmon: "#fa8072", tomato: "#ff6347", crimson: "#dc143c", beige: "#f5f5dc",
17
+ ivory: "#fffff0", khaki: "#f0e68c", lavender: "#e6e6fa", tan: "#d2b48c", turquoise: "#40e0d0",
18
+ darkgray: "#a9a9a9", darkgrey: "#a9a9a9", lightgray: "#d3d3d3", lightgrey: "#d3d3d3",
19
+ whitesmoke: "#f5f5f5", gainsboro: "#dcdcdc", slategray: "#708090", dimgray: "#696969",
20
+ };
21
+ const HEX = /#([0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/g;
22
+ const COLOR_FN = /\b(rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)/gi;
23
+ const NAMED = new RegExp(`(?<![\\w-])(${Object.keys(NAMED_COLORS).join("|")})(?![\\w-])`, "gi");
24
+ const LENGTH = /(?<![\w.-])(-?\d*\.?\d+)(px|rem|em)\b/g;
25
+ const VAR = /var\(\s*(--[A-Za-z0-9_-]+)/g;
26
+ /** Stands in for a `${...}` substitution when a template literal's text is checked. */
27
+ export const SUBSTITUTION = "\u0001";
28
+ const toKebab = (prop) => prop.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
29
+ function parseColor(literal) {
30
+ const named = NAMED_COLORS[literal.toLowerCase()];
31
+ const value = named ?? literal;
32
+ const hex = /^#([0-9a-f]{3,8})$/i.exec(value)?.[1];
33
+ if (hex) {
34
+ const full = hex.length <= 4 ? [...hex.slice(0, 3)].map((c) => c + c).join("") : hex.slice(0, 6);
35
+ return [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16));
36
+ }
37
+ const rgb = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/i.exec(value);
38
+ if (rgb)
39
+ return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
40
+ return undefined;
41
+ }
42
+ const distance = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
43
+ // Semantic token group that fits a property best, used to break ties between equal colors.
44
+ function colorContext(prop) {
45
+ if (prop === "color" || prop.startsWith("text-") || prop === "caret-color")
46
+ return "color.text.";
47
+ if (prop.startsWith("background"))
48
+ return "color.surface.";
49
+ if (prop.startsWith("border") || prop.startsWith("outline") || prop.startsWith("column-rule"))
50
+ return "color.border.";
51
+ return "";
52
+ }
53
+ function nearestColor(contract, rgb, prop) {
54
+ const context = colorContext(prop);
55
+ const scored = contract.tokens
56
+ .filter((t) => t.type === "color" && typeof t.resolved === "string")
57
+ .map((t) => ({ t, rgb: parseColor(t.resolved) }))
58
+ .filter((x) => x.rgb !== undefined)
59
+ .map(({ t, rgb: c }) => ({
60
+ t,
61
+ key: [distance(rgb, c), context && t.path.startsWith(context) ? 0 : 1, t.aliasOf ? 1 : 0],
62
+ }))
63
+ .sort((a, b) => a.key[0] - b.key[0] || a.key[1] - b.key[1] || a.key[2] - b.key[2] || a.t.path.localeCompare(b.t.path));
64
+ const best = scored[0];
65
+ return best ? { token: best.t, exact: best.key[0] === 0 } : undefined;
66
+ }
67
+ function toPx(value) {
68
+ if (typeof value === "string") {
69
+ const m = /^(-?\d*\.?\d+)(px|rem)$/.exec(value);
70
+ return m ? Number(m[1]) * (m[2] === "rem" ? 16 : 1) : undefined;
71
+ }
72
+ if (value && typeof value === "object" && "value" in value && "unit" in value) {
73
+ const v = value;
74
+ return v.unit === "rem" ? v.value * 16 : v.unit === "px" ? v.value : undefined;
75
+ }
76
+ return undefined;
77
+ }
78
+ function nearestSize(contract, px, group) {
79
+ const best = contract.tokens
80
+ .filter((t) => t.type === "dimension" && t.path.startsWith(`${group}.`))
81
+ .map((t) => ({ t, px: toPx(t.resolved) }))
82
+ .filter((x) => x.px !== undefined)
83
+ .sort((a, b) => Math.abs(a.px - px) - Math.abs(b.px - px) || (a.t.aliasOf ? 1 : 0) - (b.t.aliasOf ? 1 : 0))[0];
84
+ return best ? { token: best.t, px: best.px, exact: best.px === px } : undefined;
85
+ }
86
+ function levenshtein(a, b) {
87
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
88
+ for (let i = 1; i <= a.length; i++) {
89
+ let prev = row[0];
90
+ row[0] = i;
91
+ for (let j = 1; j <= b.length; j++) {
92
+ const tmp = row[j];
93
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
94
+ prev = tmp;
95
+ }
96
+ }
97
+ return row[b.length];
98
+ }
99
+ const label = (t) => (t.description ? `${t.path} (${t.description})` : t.path);
100
+ /**
101
+ * Checks one CSS declaration (or inline style property). `prop` may be camelCase.
102
+ * Token variables, 0, auto, inherit, transparent, currentColor and percentages are fine.
103
+ */
104
+ export function checkCssValue(contract, rawProp, value) {
105
+ const prop = toKebab(rawProp.trim()).toLowerCase();
106
+ if (prop.startsWith("--"))
107
+ return [];
108
+ const findings = [];
109
+ for (const m of value.matchAll(VAR)) {
110
+ const name = m[1];
111
+ // A name finished by a substitution (`var(--kit-space-${gap})`) is only known at runtime.
112
+ if (value[m.index + m[0].length] === SUBSTITUTION)
113
+ continue;
114
+ if (!name.startsWith("--kit-") || contract.cssVars.has(name))
115
+ continue;
116
+ // Ties go to names of the same length: --kit-color-brand-650 is nearer 600 than 50.
117
+ const closest = [...contract.cssVars].sort((a, b) => levenshtein(name, a) - levenshtein(name, b) ||
118
+ Math.abs(a.length - name.length) - Math.abs(b.length - name.length))[0];
119
+ findings.push({
120
+ kind: "unknown-token",
121
+ literal: name,
122
+ message: `var(${name}) isn't a token in the design system. Check the spelling${closest ? `; did you mean var(${closest})?` : ", or pick an existing token."}`,
123
+ ...(closest ? { suggestion: `var(${closest})` } : {}),
124
+ });
125
+ }
126
+ // Token references are fine; check only what's left once they're removed.
127
+ let rest = value.replaceAll(SUBSTITUTION, " ");
128
+ while (/var\([^()]*\)/.test(rest))
129
+ rest = rest.replace(/var\([^()]*\)/g, " ");
130
+ if (COLOR_PROP.test(prop)) {
131
+ const literals = [...rest.matchAll(HEX), ...rest.matchAll(COLOR_FN), ...rest.matchAll(NAMED)].map((m) => m[0]);
132
+ for (const literal of literals) {
133
+ const rgb = parseColor(literal);
134
+ const hit = rgb && nearestColor(contract, rgb, prop);
135
+ const suggestion = hit ? `var(${tokenCssVar(hit.token.path)})` : undefined;
136
+ findings.push({
137
+ kind: "color",
138
+ literal,
139
+ message: !hit
140
+ ? `This color (${literal}) isn't from the palette. Use a color token instead.`
141
+ : hit.exact
142
+ ? `${literal} is ${label(hit.token)} in the palette. Use the token instead of typing the color, so it stays in sync with the design system.`
143
+ : `This color (${literal}) isn't in the palette. Use ${label(hit.token)} instead; it's the closest match.`,
144
+ ...(suggestion ? { suggestion } : {}),
145
+ });
146
+ }
147
+ }
148
+ const size = SIZE_GROUPS.find((g) => g.prop.test(prop));
149
+ if (size) {
150
+ for (const m of rest.matchAll(LENGTH)) {
151
+ const amount = Number(m[1]);
152
+ if (amount === 0)
153
+ continue;
154
+ const px = amount * (m[2] === "px" ? 1 : 16);
155
+ const hit = nearestSize(contract, px, size.group);
156
+ const suggestion = hit ? `var(${tokenCssVar(hit.token.path)})` : undefined;
157
+ findings.push({
158
+ kind: "size",
159
+ literal: m[0],
160
+ message: !hit
161
+ ? `${m[0]} isn't on the ${size.scale}. Use a ${size.group} token instead.`
162
+ : hit.exact
163
+ ? `${m[0]} is ${hit.token.path} on the ${size.scale}. Use the token instead of typing the size.`
164
+ : `${m[0]} isn't on the ${size.scale}. Use ${hit.token.path} (${hit.px}px) instead; it's the closest size.`,
165
+ ...(suggestion ? { suggestion } : {}),
166
+ });
167
+ }
168
+ }
169
+ return findings;
170
+ }
171
+ /** A bare number in a React inline style means pixels. */
172
+ export function checkCssNumber(contract, rawProp, n) {
173
+ if (n === 0)
174
+ return [];
175
+ const prop = toKebab(rawProp).toLowerCase();
176
+ if (!SIZE_GROUPS.some((g) => g.prop.test(prop)))
177
+ return [];
178
+ return checkCssValue(contract, prop, `${n}px`);
179
+ }
180
+ export const FINDING_CODES = {
181
+ color: "code-hardcoded-color",
182
+ size: "code-hardcoded-size",
183
+ "unknown-token": "code-unknown-token",
184
+ };
185
+ //# sourceMappingURL=css-values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-values.js","sourceRoot":"","sources":["../../src/rules/css-values.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Issue } from "../issue.js";
2
+ import type { ContractView } from "../project.js";
3
+ /** Token rule for a .css / .module.css file. */
4
+ export declare function checkCssFile(file: string, text: string, contract: ContractView): Issue[];
@@ -0,0 +1,39 @@
1
+ import postcss from "postcss";
2
+ import { FINDING_CODES, checkCssValue } from "./css-values.js";
3
+ /** Token rule for a .css / .module.css file. */
4
+ export function checkCssFile(file, text, contract) {
5
+ let root;
6
+ try {
7
+ root = postcss.parse(text, { from: file });
8
+ }
9
+ catch (e) {
10
+ const err = e;
11
+ return [
12
+ {
13
+ code: "code-css-unreadable",
14
+ severity: "error",
15
+ file,
16
+ path: "",
17
+ ...(err.line ? { line: err.line } : {}),
18
+ ...(err.column ? { column: err.column } : {}),
19
+ message: `This stylesheet has a typo Cogitato can't read past (${err.reason ?? "invalid CSS"}).`,
20
+ },
21
+ ];
22
+ }
23
+ const issues = [];
24
+ root.walkDecls((decl) => {
25
+ for (const f of checkCssValue(contract, decl.prop, decl.value)) {
26
+ issues.push({
27
+ code: FINDING_CODES[f.kind],
28
+ severity: "error",
29
+ file,
30
+ path: "",
31
+ ...(decl.source?.start ? { line: decl.source.start.line, column: decl.source.start.column } : {}),
32
+ message: f.message,
33
+ ...(f.suggestion ? { suggestion: f.suggestion } : {}),
34
+ });
35
+ }
36
+ });
37
+ return issues;
38
+ }
39
+ //# sourceMappingURL=css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.js","sourceRoot":"","sources":["../../src/rules/css.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAG9B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE/D,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY,EAAE,QAAsB;IAC7E,IAAI,IAAkB,CAAC;IACvB,IAAI,CAAC;QACH,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAwD,CAAC;QACrE,OAAO;YACL;gBACE,IAAI,EAAE,qBAAqB;gBAC3B,QAAQ,EAAE,OAAO;gBACjB,IAAI;gBACJ,IAAI,EAAE,EAAE;gBACR,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7C,OAAO,EAAE,wDAAwD,GAAG,CAAC,MAAM,IAAI,aAAa,IAAI;aACjG;SACF,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;QACtB,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/D,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC3B,QAAQ,EAAE,OAAO;gBACjB,IAAI;gBACJ,IAAI,EAAE,EAAE;gBACR,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjG,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import ts from "typescript";
2
+ import { type Issue } from "../issue.js";
3
+ import { type ContractView } from "../project.js";
4
+ import { type ScreenEntry } from "../registry.js";
5
+ /** A literal data-kit-id found in code, for duplicate detection across a screen's files. */
6
+ export interface KitIdUse {
7
+ id: string;
8
+ owner?: ScreenEntry;
9
+ file: string;
10
+ line: number;
11
+ column: number;
12
+ }
13
+ export interface TsxContext {
14
+ contract: ContractView;
15
+ /** The screen whose folder contains this file, if any. */
16
+ owner?: ScreenEntry;
17
+ }
18
+ /** Token, component and data-kit-id rules for one .ts/.tsx file. */
19
+ export declare function checkTsxFile(sf: ts.SourceFile, ctx: TsxContext): {
20
+ issues: Issue[];
21
+ kitIds: KitIdUse[];
22
+ };
23
+ /** Duplicate data-kit-ids within one screen. Reported where the later use is. */
24
+ export declare function duplicateKitIds(uses: KitIdUse[]): Issue[];
@@ -0,0 +1,190 @@
1
+ import { posix } from "node:path";
2
+ import ts from "typescript";
3
+ import { issueAt, literalText, propertyName } from "../issue.js";
4
+ import { LAYOUT, isKitFile } from "../project.js";
5
+ import { ID_PATTERNS } from "../registry.js";
6
+ import { FINDING_CODES, SUBSTITUTION, checkCssNumber, checkCssValue } from "./css-values.js";
7
+ const NATIVE_INTERACTIVE = new Set(["button", "a", "input", "select", "textarea"]);
8
+ /** Kit component name for an import specifier, or undefined if it doesn't point into src/kit. */
9
+ function kitImportName(file, specifier) {
10
+ let rest;
11
+ if (specifier.startsWith("@/kit/"))
12
+ rest = specifier.slice("@/kit/".length);
13
+ else if (specifier.startsWith(".")) {
14
+ const target = posix.normalize(posix.join(posix.dirname(file), specifier));
15
+ if (target.startsWith(`${LAYOUT.kit}/`))
16
+ rest = target.slice(LAYOUT.kit.length + 1);
17
+ }
18
+ const name = rest?.split("/")[0]?.replace(/\.(tsx?|jsx?)$/, "");
19
+ // Lowercase files and stylesheets in the kit folder are helpers, not components.
20
+ return name && /^[A-Z][A-Za-z0-9]*$/.test(name) ? name : undefined;
21
+ }
22
+ const slugify = (s) => s
23
+ .toLowerCase()
24
+ .replace(/[^a-z0-9]+/g, "-")
25
+ .replace(/^-+|-+$/g, "")
26
+ .slice(0, 30)
27
+ .replace(/-+$/, "");
28
+ // A readable element name for a suggested id: from its label, aria-label, name or text.
29
+ function suggestElementName(el, sf) {
30
+ for (const attr of el.attributes.properties) {
31
+ if (!ts.isJsxAttribute(attr) || !attr.initializer)
32
+ continue;
33
+ const name = attr.name.getText(sf);
34
+ if (!["label", "aria-label", "name", "placeholder", "title"].includes(name))
35
+ continue;
36
+ const text = ts.isStringLiteral(attr.initializer) ? attr.initializer.text : undefined;
37
+ if (text && slugify(text))
38
+ return slugify(text);
39
+ }
40
+ const parent = el.parent;
41
+ if (ts.isJsxElement(parent)) {
42
+ const text = parent.children
43
+ .filter(ts.isJsxText)
44
+ .map((c) => c.text)
45
+ .join(" ")
46
+ .trim();
47
+ if (text && slugify(text))
48
+ return slugify(text);
49
+ }
50
+ return slugify(el.tagName.getText(sf)) || "element";
51
+ }
52
+ /** Token, component and data-kit-id rules for one .ts/.tsx file. */
53
+ export function checkTsxFile(sf, ctx) {
54
+ const issues = [];
55
+ const kitIds = [];
56
+ const file = sf.fileName;
57
+ const inKit = isKitFile(file);
58
+ const { contract, owner } = ctx;
59
+ // Local identifier -> kit component name, from this file's imports.
60
+ const kitLocals = new Map();
61
+ const styleFindings = (prop, node) => {
62
+ const findings = ts.isNumericLiteral(node)
63
+ ? checkCssNumber(contract, prop, Number(node.text))
64
+ : ts.isTemplateExpression(node)
65
+ ? checkCssValue(contract, prop, [node.head.text, ...node.templateSpans.map((s) => s.literal.text)].join(SUBSTITUTION))
66
+ : literalText(node) !== undefined
67
+ ? checkCssValue(contract, prop, literalText(node))
68
+ : [];
69
+ for (const f of findings) {
70
+ issues.push(issueAt(FINDING_CODES[f.kind], sf, node, f.message, f.suggestion ? { suggestion: f.suggestion } : {}));
71
+ }
72
+ };
73
+ const checkKitId = (el, what) => {
74
+ let idAttr;
75
+ let hasSpread = false;
76
+ for (const a of el.attributes.properties) {
77
+ if (ts.isJsxSpreadAttribute(a))
78
+ hasSpread = true;
79
+ else if (a.name.getText(sf) === "data-kit-id")
80
+ idAttr = a;
81
+ }
82
+ const prefix = owner?.id;
83
+ const example = `${prefix ?? "screen.id"}.${suggestElementName(el, sf)}`;
84
+ if (!idAttr) {
85
+ // A spread may be passing the id through; that's the kit-wrapper pattern.
86
+ if (hasSpread)
87
+ return;
88
+ issues.push(issueAt("code-missing-kit-id", sf, el, `This ${what} needs a data-kit-id so comments can be pinned to it. Add data-kit-id="${example}".`, {
89
+ suggestion: example,
90
+ }));
91
+ return;
92
+ }
93
+ const init = idAttr.initializer;
94
+ const expr = init && ts.isJsxExpression(init) ? init.expression : init;
95
+ const id = literalText(expr);
96
+ if (id === undefined) {
97
+ if (expr && ts.isTemplateExpression(expr) && prefix && !expr.head.text.startsWith(`${prefix}.`)) {
98
+ issues.push(issueAt("code-invalid-kit-id", sf, expr, `This element id doesn't start with "${prefix}.", the ${owner.title} screen's id. Element ids start with their screen's id.`));
99
+ }
100
+ else if (!expr || !ts.isTemplateExpression(expr)) {
101
+ issues.push(issueAt("code-kit-id-unchecked", sf, idAttr, `This data-kit-id is computed, so Cogitato can't check it. Prefer plain text, like data-kit-id="${example}".`, {
102
+ severity: "warning",
103
+ }));
104
+ }
105
+ return;
106
+ }
107
+ if (!ID_PATTERNS.kitId.test(id)) {
108
+ issues.push(issueAt("code-invalid-kit-id", sf, idAttr, `"${id}" isn't a valid element id. Element ids are the screen id plus an element name, lowercase and dot-separated, like "${example}".`, {
109
+ suggestion: example,
110
+ }));
111
+ return;
112
+ }
113
+ if (prefix && !id.startsWith(`${prefix}.`)) {
114
+ const fixed = `${prefix}.${id.slice(id.lastIndexOf(".") + 1)}`;
115
+ issues.push(issueAt("code-invalid-kit-id", sf, idAttr, `"${id}" doesn't start with this screen's id. Elements on the ${owner.title} screen need ids starting with "${prefix}.", like "${fixed}".`, {
116
+ suggestion: fixed,
117
+ }));
118
+ return;
119
+ }
120
+ const { line, character } = sf.getLineAndCharacterOfPosition(idAttr.getStart(sf));
121
+ kitIds.push({ id, owner, file, line: line + 1, column: character + 1 });
122
+ };
123
+ const visit = (node) => {
124
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
125
+ const name = kitImportName(file, node.moduleSpecifier.text);
126
+ if (name && !inKit) {
127
+ if (!contract.manifests.has(name)) {
128
+ issues.push(issueAt("code-unknown-component", sf, node, `${name} isn't in the design system yet, so it can't be used here. Use an existing component from the kit, or record it as a gap so the team can add it.`));
129
+ }
130
+ const clause = node.importClause;
131
+ if (clause?.name)
132
+ kitLocals.set(clause.name.text, name);
133
+ if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
134
+ for (const el of clause.namedBindings.elements) {
135
+ kitLocals.set(el.name.text, (el.propertyName ?? el.name).text);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ if ((ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && !inKit) {
141
+ const tag = node.tagName.getText(sf);
142
+ const handlers = node.attributes.properties.some((a) => ts.isJsxAttribute(a) && /^on[A-Z]/.test(a.name.getText(sf)));
143
+ const kitName = kitLocals.get(tag);
144
+ const manifest = kitName ? contract.manifests.get(kitName) : undefined;
145
+ const kitInteractive = manifest ? Object.values(manifest.props).some((p) => p?.type === "event") : false;
146
+ if (NATIVE_INTERACTIVE.has(tag) || handlers || kitInteractive) {
147
+ checkKitId(node, kitName ? `${kitName}` : /^[a-z]/.test(tag) ? `<${tag}>` : `${tag}`);
148
+ }
149
+ }
150
+ if (ts.isJsxAttribute(node) && node.name.getText(sf) === "style" && node.initializer && ts.isJsxExpression(node.initializer)) {
151
+ const obj = node.initializer.expression;
152
+ if (obj && ts.isObjectLiteralExpression(obj)) {
153
+ for (const p of obj.properties) {
154
+ if (!ts.isPropertyAssignment(p))
155
+ continue;
156
+ const prop = propertyName(p.name);
157
+ if (prop)
158
+ styleFindings(prop, p.initializer);
159
+ }
160
+ }
161
+ }
162
+ ts.forEachChild(node, visit);
163
+ };
164
+ visit(sf);
165
+ return { issues, kitIds };
166
+ }
167
+ /** Duplicate data-kit-ids within one screen. Reported where the later use is. */
168
+ export function duplicateKitIds(uses) {
169
+ const first = new Map();
170
+ const issues = [];
171
+ for (const u of uses) {
172
+ const key = `${u.owner?.id ?? ""}|${u.id}`;
173
+ const prev = first.get(key);
174
+ if (!prev) {
175
+ first.set(key, u);
176
+ continue;
177
+ }
178
+ issues.push({
179
+ code: "code-duplicate-kit-id",
180
+ severity: "error",
181
+ file: u.file,
182
+ path: "",
183
+ line: u.line,
184
+ column: u.column,
185
+ message: `"${u.id}" is used twice${u.owner ? ` on the ${u.owner.title} screen` : ""} (also at ${prev.file}, line ${prev.line}). Each element needs its own id so comments land on the right one.`,
186
+ });
187
+ }
188
+ return issues;
189
+ }
190
+ //# sourceMappingURL=tsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsx.js","sourceRoot":"","sources":["../../src/rules/tsx.ts"],"names":[],"mappings":"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"}
package/dist/save.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { FlowManifest, Issue } from "@cogitato/contract";
2
+ import { type Project } from "./project.js";
3
+ export interface SaveOptions {
4
+ /** Replaces the generated first line of the commit message. */
5
+ message?: string;
6
+ /** For tests. */
7
+ now?: Date;
8
+ }
9
+ export interface SaveResult {
10
+ saved: boolean;
11
+ branch?: string;
12
+ /** Short commit id. */
13
+ commit?: string;
14
+ /** The commit message's first line. */
15
+ summary?: string;
16
+ pushed: boolean;
17
+ /** The branch's hosted canvas, when the prototype has hosting set up and the push worked. */
18
+ preview?: string;
19
+ /** One or two plain sentences for the designer. */
20
+ message: string;
21
+ /** Validation problems at the time of saving. They don't stop the save. */
22
+ issues: Issue[];
23
+ }
24
+ /** What changed between two flows.json versions, as short phrases: "add screen checkout.review". */
25
+ export declare function describeChanges(before: FlowManifest | undefined, after: FlowManifest): string[];
26
+ /** Saves a version of the prototype: commit on a design branch, then push if there's a remote. */
27
+ export declare function saveVersion(project: Project, opts?: SaveOptions): SaveResult;
package/dist/save.js ADDED
@@ -0,0 +1,125 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { KitError } from "./errors.js";
3
+ import { exportFlows } from "./export-flows.js";
4
+ import { sameManifest } from "./flows.js";
5
+ import { previewLink } from "./links.js";
6
+ import { LAYOUT } from "./project.js";
7
+ import { statusLabel } from "./status.js";
8
+ import { validate } from "./validate.js";
9
+ function git(project, args, raw = false) {
10
+ const r = spawnSync("git", args, { cwd: project.root, encoding: "utf8" });
11
+ return { ok: r.status === 0, out: raw ? (r.stdout ?? "") : (r.stdout ?? "").trim(), err: (r.stderr ?? "").trim() };
12
+ }
13
+ const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "designer";
14
+ /** What changed between two flows.json versions, as short phrases: "add screen checkout.review". */
15
+ export function describeChanges(before, after) {
16
+ const states = (m) => new Map((m?.screens ?? []).flatMap((s) => s.states.map((st) => [`${s.id}.${st.id}`, s.id])));
17
+ const screens = (m) => new Set((m?.screens ?? []).map((s) => s.id));
18
+ const [s0, s1, st0, st1] = [screens(before), screens(after), states(before), states(after)];
19
+ const out = [];
20
+ for (const id of s1)
21
+ if (!s0.has(id))
22
+ out.push(`add screen ${id}`);
23
+ for (const [node, screen] of st1)
24
+ if (!st0.has(node) && s0.has(screen))
25
+ out.push(`add state ${node}`);
26
+ for (const id of s0)
27
+ if (!s1.has(id))
28
+ out.push(`remove screen ${id}`);
29
+ for (const [node, screen] of st0)
30
+ if (!st1.has(node) && s1.has(screen))
31
+ out.push(`remove state ${node}`);
32
+ for (const [node, status] of Object.entries(after.status ?? {})) {
33
+ if (st1.has(node) && before?.status?.[node] !== status)
34
+ out.push(`mark ${node} ${statusLabel(status ?? "exploring").toLowerCase()}`);
35
+ }
36
+ const edges = (m) => new Set((m?.flows ?? []).flatMap((f) => f.edges.map((e) => `${e.from}>${e.to}`)));
37
+ const [e0, e1] = [edges(before), edges(after)];
38
+ const newEdges = [...e1].filter((e) => !e0.has(e)).length;
39
+ if (newEdges && !out.length)
40
+ out.push(`connect ${newEdges} step${newEdges === 1 ? "" : "s"} in the flow`);
41
+ return out;
42
+ }
43
+ function summarize(changes, files) {
44
+ if (changes.length) {
45
+ const line = changes.join("; ");
46
+ const text = line.length <= 68 ? line : `${changes[0]} and ${changes.length - 1} more change${changes.length === 2 ? "" : "s"}`;
47
+ return text.charAt(0).toUpperCase() + text.slice(1);
48
+ }
49
+ const screens = [...new Set(files.map((f) => /^src\/screens\/(.+)\/[^/]+$/.exec(f)?.[1]).filter(Boolean))];
50
+ if (screens.length === 1)
51
+ return `Update ${screens[0].split("/").join(".")}`;
52
+ if (screens.length > 1)
53
+ return `Update ${screens.length} screens`;
54
+ if (files.some((f) => f.startsWith(`${LAYOUT.contract}/`)))
55
+ return "Update the design contract";
56
+ return "Update the prototype";
57
+ }
58
+ /** Saves a version of the prototype: commit on a design branch, then push if there's a remote. */
59
+ export function saveVersion(project, opts = {}) {
60
+ if (!git(project, ["rev-parse", "--is-inside-work-tree"]).ok) {
61
+ throw new KitError("This prototype doesn't keep a history yet, so versions can't be saved. Ask a developer to set it up with git.");
62
+ }
63
+ const exported = exportFlows(project);
64
+ const before = git(project, ["show", `HEAD:${LAYOUT.flowsJson}`]);
65
+ let previous;
66
+ try {
67
+ previous = before.ok ? JSON.parse(before.out) : undefined;
68
+ }
69
+ catch {
70
+ previous = undefined;
71
+ }
72
+ const status = git(project, ["status", "--porcelain", "--untracked-files=all"], true);
73
+ const files = status.out
74
+ .split("\n")
75
+ .filter(Boolean)
76
+ .map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop())
77
+ // A flows.json whose only change is the commit it was exported at isn't worth a version.
78
+ .filter((f) => !(f === LAYOUT.flowsJson && previous && sameManifest(previous, exported.manifest, { status: true })));
79
+ const issues = validate(project).issues.filter((i) => i.severity === "error");
80
+ if (!files.length)
81
+ return { saved: false, pushed: false, message: "Nothing new to save: everything is already saved.", issues };
82
+ const user = git(project, ["config", "user.name"]).out;
83
+ if (!user || !git(project, ["config", "user.email"]).out) {
84
+ throw new KitError("Saving needs your name and email set up once on this computer. Ask Claude to set them (git config user.name and user.email).");
85
+ }
86
+ let branch = git(project, ["rev-parse", "--abbrev-ref", "HEAD"]).out;
87
+ if (branch === "main" || branch === "master" || branch === "HEAD") {
88
+ const d = (opts.now ?? new Date()).toISOString().slice(0, 10).replace(/-/g, "");
89
+ const base = `design/${slug(user)}-${d}`;
90
+ branch = base;
91
+ for (let n = 2; git(project, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]).ok; n++)
92
+ branch = `${base}-${n}`;
93
+ const sw = git(project, ["switch", "-c", branch]);
94
+ if (!sw.ok)
95
+ throw new KitError(`Couldn't start a design branch for your work (${sw.err}).`);
96
+ }
97
+ const changes = describeChanges(previous, exported.manifest);
98
+ const summary = opts.message?.trim() || summarize(changes, files);
99
+ const body = [
100
+ ...(changes.length > 1 ? changes.map((c) => `- ${c}`) : []),
101
+ ...(issues.length ? [`Saved with ${issues.length} validation problem${issues.length === 1 ? "" : "s"} still open.`] : []),
102
+ ].join("\n");
103
+ if (!git(project, ["add", "-A"]).ok)
104
+ throw new KitError("Couldn't collect your changes to save them.");
105
+ const commit = git(project, ["commit", "-q", "-m", summary, ...(body ? ["-m", body] : [])]);
106
+ if (!commit.ok)
107
+ throw new KitError(`Couldn't save the version (${commit.err || commit.out}).`);
108
+ const sha = git(project, ["rev-parse", "--short", "HEAD"]).out;
109
+ const remote = git(project, ["remote"]).out.split("\n").filter(Boolean)[0];
110
+ let pushed = false;
111
+ let where = "Saved on this computer (there's no shared copy to send it to yet).";
112
+ if (remote) {
113
+ const push = git(project, ["push", "-u", remote, branch]);
114
+ pushed = push.ok;
115
+ where = push.ok
116
+ ? `Saved and shared on the "${branch}" branch.`
117
+ : `Saved on this computer, but sharing it failed (${push.err.split("\n")[0]}). Try saving again later.`;
118
+ }
119
+ const preview = pushed ? previewLink(project, branch) : undefined;
120
+ if (preview)
121
+ where += ` Your preview will be at ${preview} in a minute or two.`;
122
+ const problems = issues.length ? ` Heads up: ${issues.length} validation problem${issues.length === 1 ? " is" : "s are"} still open.` : "";
123
+ return { saved: true, branch, commit: sha, summary, pushed, ...(preview ? { preview } : {}), message: `${where} Version: "${summary}".${problems}`, issues };
124
+ }
125
+ //# sourceMappingURL=save.js.map