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