@designtools/manifest 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.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CHANGE_CLASSES,
4
+ VERSION,
5
+ agentsFile,
6
+ agentsSection,
7
+ buildManifest,
8
+ checkProse,
9
+ defaultProseFiles,
10
+ diffManifests,
11
+ formatReport,
12
+ loadConfig,
13
+ outDir,
14
+ provenance,
15
+ readIfExists,
16
+ withAgentsSection
17
+ } from "./chunk-EFGVJ5ZQ.js";
18
+
19
+ // src/cli.ts
20
+ import { execFileSync } from "child_process";
21
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
22
+ import { dirname, resolve } from "path";
23
+ import { parseArgs } from "util";
24
+ var HELP = `designtools-manifest ${VERSION}
25
+
26
+ Reads a React design system (components, variants, data-slots, examples, tokens,
27
+ rules, patterns, taxonomy and brand assets) into committed JSON that docs,
28
+ checks and agents read from.
29
+
30
+ Usage
31
+ designtools-manifest build Write <system>/manifest/*.json
32
+ designtools-manifest check Exit 1 if the committed files are out of date
33
+ designtools-manifest diff [base] Classify every change against a git ref (default: main)
34
+ designtools-manifest agents Write the pointer section of CLAUDE.md (or AGENTS.md)
35
+ designtools-manifest agents --check Exit 1 if that section is out of date
36
+ designtools-manifest prose-check [file\u2026] Warn about names the manifest does not have, wrong forms,
37
+ and restated lists. Never fails
38
+ designtools-manifest provenance Print "Generated from <name>@<version> at <commit>"
39
+
40
+ Options
41
+ --system <dir> The design system folder, e.g. src/ds
42
+ --tokens <file> A token stylesheet; repeat for each, in import order
43
+ --tsconfig <file> tsconfig.json for resolving imports (found from the root when absent)
44
+ --out <dir> Where the manifest goes (default: <system>/manifest)
45
+ --root <dir> Project root (default: the current directory)
46
+ --file <file> agents: the file to write (default: CLAUDE.md if it exists, else AGENTS.md)
47
+ --check agents: compare instead of writing
48
+ --json diff, prose-check, provenance: print JSON
49
+ --fail-on <class> diff: exit 1 if any change of this class is found (breaking, additive, visual, docs)
50
+ --quiet Do not print warnings
51
+ -h, --help Show this help
52
+ -v, --version Show the version
53
+
54
+ Settings live in designtools.json at the project root, so a project records them once:
55
+ { "manifest": { "system": "src/ds", "tokens": ["app/styles/tokens.css", "app/styles/scale.css"], "docs": "/design/system" } }
56
+ Rules default to <system>/rules, the taxonomy to <system>/taxonomy.json, brand assets to <system>/brand.
57
+ `;
58
+ function fail(message) {
59
+ process.stderr.write(`designtools-manifest: ${message}
60
+ `);
61
+ process.exit(2);
62
+ }
63
+ var args;
64
+ function parse() {
65
+ return parseArgs({
66
+ allowPositionals: true,
67
+ options: {
68
+ system: { type: "string" },
69
+ tokens: { type: "string", multiple: true },
70
+ tsconfig: { type: "string" },
71
+ out: { type: "string" },
72
+ root: { type: "string" },
73
+ file: { type: "string" },
74
+ check: { type: "boolean" },
75
+ json: { type: "boolean" },
76
+ "fail-on": { type: "string" },
77
+ quiet: { type: "boolean" },
78
+ help: { type: "boolean", short: "h" },
79
+ version: { type: "boolean", short: "v" }
80
+ }
81
+ });
82
+ }
83
+ try {
84
+ args = parse();
85
+ } catch (e) {
86
+ fail(`${e.message}
87
+
88
+ ${HELP}`);
89
+ }
90
+ var { values, positionals } = args;
91
+ if (values.help) {
92
+ process.stdout.write(HELP);
93
+ process.exit(0);
94
+ }
95
+ if (values.version) {
96
+ process.stdout.write(`${VERSION}
97
+ `);
98
+ process.exit(0);
99
+ }
100
+ var COMMANDS = ["build", "check", "diff", "agents", "prose-check", "provenance"];
101
+ var [command, ...rest] = positionals;
102
+ if (!command || !COMMANDS.includes(command)) {
103
+ fail(command ? `unknown command "${command}"
104
+
105
+ ${HELP}` : `a command is required
106
+
107
+ ${HELP}`);
108
+ }
109
+ if (command === "provenance") {
110
+ const p = provenance(resolve(values.root ?? "."));
111
+ process.stdout.write(values.json ? JSON.stringify(p, null, 2) + "\n" : `${p.line}
112
+ `);
113
+ process.exit(0);
114
+ }
115
+ var config;
116
+ var built;
117
+ try {
118
+ config = loadConfig({
119
+ root: values.root,
120
+ system: values.system,
121
+ tokens: values.tokens,
122
+ tsconfig: values.tsconfig,
123
+ out: values.out
124
+ });
125
+ built = buildManifest(config);
126
+ } catch (e) {
127
+ fail(e.message);
128
+ }
129
+ if (!values.quiet && command !== "prose-check") {
130
+ for (const w of built.warnings) {
131
+ process.stderr.write(`warning: ${w.file}${w.line ? `:${w.line}` : ""} ${w.message}
132
+ `);
133
+ }
134
+ }
135
+ var lf = (text) => text.replace(/\r\n/g, "\n");
136
+ switch (command) {
137
+ case "build": {
138
+ for (const [path, content] of Object.entries(built.files)) {
139
+ const abs = resolve(config.root, path);
140
+ mkdirSync(dirname(abs), { recursive: true });
141
+ writeFileSync(abs, content);
142
+ }
143
+ const count = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
144
+ process.stderr.write(
145
+ `wrote ${outDir(config)}: ${[
146
+ count(built.components.components.length, "component"),
147
+ count(built.tokens.tokens.length, "token"),
148
+ count(built.rules.rules.length, "rule"),
149
+ count(built.patterns.patterns.length, "pattern"),
150
+ count(built.taxonomy.terms.length, "term"),
151
+ count(built.assets.assets.length, "asset")
152
+ ].join(", ")}
153
+ `
154
+ );
155
+ process.exit(0);
156
+ }
157
+ case "check": {
158
+ let ok = true;
159
+ for (const [path, content] of Object.entries(built.files)) {
160
+ let existing;
161
+ try {
162
+ existing = readFileSync(resolve(config.root, path), "utf8");
163
+ } catch {
164
+ process.stderr.write(`check: ${path} does not exist. Run designtools-manifest build and commit it.
165
+ `);
166
+ ok = false;
167
+ continue;
168
+ }
169
+ if (lf(existing) === content) {
170
+ process.stderr.write(`check: ${path} up to date
171
+ `);
172
+ continue;
173
+ }
174
+ ok = false;
175
+ const generator = /"generator":\s*"([^"]+)"/.exec(existing)?.[1];
176
+ if (generator && !generator.endsWith(`@${VERSION}`)) {
177
+ process.stderr.write(
178
+ `check: ${path} differs: written by ${generator}, running ${VERSION}. Expected after an upgrade; run designtools-manifest build.
179
+ `
180
+ );
181
+ } else {
182
+ process.stderr.write(`check: ${path} is out of date with the source. Run designtools-manifest build and commit it.
183
+ `);
184
+ }
185
+ }
186
+ process.exit(ok ? 0 : 1);
187
+ }
188
+ case "agents": {
189
+ const file = agentsFile(config.root, values.file);
190
+ const path = resolve(config.root, file);
191
+ const existing = readIfExists(path);
192
+ const next = withAgentsSection(existing, agentsSection(config));
193
+ if (values.check) {
194
+ const ok = existing !== void 0 && lf(existing) === next;
195
+ process.stderr.write(ok ? `agents: ${file} up to date
196
+ ` : `agents: ${file} is out of date. Run designtools-manifest agents.
197
+ `);
198
+ process.exit(ok ? 0 : 1);
199
+ }
200
+ writeFileSync(path, next);
201
+ process.stderr.write(`${existing === void 0 ? "wrote" : "updated"} the design system section of ${file}
202
+ `);
203
+ process.exit(0);
204
+ }
205
+ case "prose-check": {
206
+ const files = rest.length ? rest : defaultProseFiles(config.root, config.system);
207
+ const findings = checkProse(config.root, files, {
208
+ components: built.components.components,
209
+ patterns: built.patterns.patterns,
210
+ tokens: built.tokens.tokens,
211
+ terms: built.taxonomy.terms
212
+ });
213
+ if (values.json) process.stdout.write(JSON.stringify(findings, null, 2) + "\n");
214
+ else {
215
+ for (const f of findings) process.stderr.write(`warning: ${f.file}:${f.line} ${f.message}
216
+ `);
217
+ process.stderr.write(`prose-check: ${findings.length} ${findings.length === 1 ? "warning" : "warnings"} in ${files.length} ${files.length === 1 ? "file" : "files"}
218
+ `);
219
+ }
220
+ process.exit(0);
221
+ }
222
+ }
223
+ var failOn = values["fail-on"];
224
+ if (failOn && !CHANGE_CLASSES.includes(failOn)) fail(`--fail-on must be one of ${CHANGE_CLASSES.join(", ")}`);
225
+ var base = rest[0] ?? "main";
226
+ var out = outDir(config);
227
+ var before = {
228
+ components: readAtRef(base, `${out}/components.json`),
229
+ tokens: readAtRef(base, `${out}/tokens.json`),
230
+ rules: readAtRef(base, `${out}/rules.json`),
231
+ patterns: readAtRef(base, `${out}/patterns.json`),
232
+ taxonomy: readAtRef(base, `${out}/taxonomy.json`),
233
+ assets: readAtRef(base, `${out}/assets.json`)
234
+ };
235
+ var changes = diffManifests(before, built);
236
+ process.stdout.write(values.json ? JSON.stringify(changes, null, 2) + "\n" : formatReport(changes, base));
237
+ process.exit(failOn && changes.some((c) => c.class === failOn) ? 1 : 0);
238
+ function readAtRef(ref, path) {
239
+ try {
240
+ const text = execFileSync("git", ["show", `${ref}:./${path}`], {
241
+ cwd: config.root,
242
+ encoding: "utf8",
243
+ // git's messages are translated; read them in English to tell "not there yet" from a real failure
244
+ env: { ...process.env, LC_ALL: "C" },
245
+ stdio: ["ignore", "pipe", "pipe"],
246
+ maxBuffer: 64 * 1024 * 1024
247
+ });
248
+ return JSON.parse(text);
249
+ } catch (e) {
250
+ const stderr = String(e.stderr ?? "");
251
+ if (/does not exist in|exists on disk, but not in/.test(stderr)) return void 0;
252
+ fail(`could not read ${path} at ${ref}: ${stderr.trim() || e.message}`);
253
+ }
254
+ }