@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/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +174 -2
- package/dist/chunk-PQGMDEUF.js +1950 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +256 -0
- package/dist/index.d.ts +577 -0
- package/dist/index.js +70 -0
- package/package.json +73 -3
- package/schemas/rule.schema.json +31 -0
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
|
+
}
|