design-constraint-validator 1.0.0 → 2.0.1

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 (121) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +229 -659
  3. package/adapters/README.md +46 -46
  4. package/adapters/css.ts +116 -116
  5. package/adapters/decisionthemes.d.ts +44 -0
  6. package/adapters/decisionthemes.d.ts.map +1 -0
  7. package/adapters/decisionthemes.js +35 -0
  8. package/adapters/decisionthemes.ts +59 -0
  9. package/adapters/js.ts +14 -14
  10. package/adapters/json.ts +45 -45
  11. package/cli/build-css.ts +32 -32
  12. package/cli/commands/build.ts +65 -65
  13. package/cli/commands/graph.d.ts.map +1 -1
  14. package/cli/commands/graph.js +26 -10
  15. package/cli/commands/graph.ts +180 -137
  16. package/cli/commands/index.ts +7 -7
  17. package/cli/commands/patch-apply.ts +80 -80
  18. package/cli/commands/patch.ts +22 -22
  19. package/cli/commands/set.d.ts.map +1 -1
  20. package/cli/commands/set.js +12 -4
  21. package/cli/commands/set.ts +239 -225
  22. package/cli/commands/utils.ts +50 -50
  23. package/cli/commands/validate.d.ts.map +1 -1
  24. package/cli/commands/validate.js +89 -33
  25. package/cli/commands/validate.ts +180 -115
  26. package/cli/commands/why.d.ts.map +1 -1
  27. package/cli/commands/why.js +86 -20
  28. package/cli/commands/why.ts +158 -46
  29. package/cli/config-schema.ts +27 -27
  30. package/cli/config.ts +35 -35
  31. package/cli/constraint-registry.d.ts +101 -0
  32. package/cli/constraint-registry.d.ts.map +1 -0
  33. package/cli/constraint-registry.js +225 -0
  34. package/cli/constraint-registry.ts +304 -0
  35. package/cli/constraints-loader.d.ts.map +1 -0
  36. package/cli/cross-axis-loader.d.ts +91 -0
  37. package/cli/cross-axis-loader.d.ts.map +1 -0
  38. package/cli/cross-axis-loader.js +222 -0
  39. package/cli/cross-axis-loader.ts +289 -0
  40. package/cli/dcv.js +4 -0
  41. package/cli/dcv.ts +111 -107
  42. package/cli/engine-helpers.d.ts.map +1 -1
  43. package/cli/graph-poset.ts +74 -74
  44. package/cli/json-output.d.ts +69 -0
  45. package/cli/json-output.d.ts.map +1 -0
  46. package/cli/json-output.js +109 -0
  47. package/cli/json-output.ts +184 -0
  48. package/cli/result.ts +27 -27
  49. package/cli/run.ts +54 -54
  50. package/cli/smoke-test.ts +40 -40
  51. package/cli/types.d.ts +6 -0
  52. package/cli/types.d.ts.map +1 -1
  53. package/cli/types.ts +84 -78
  54. package/cli/version-banner.d.ts +20 -0
  55. package/cli/version-banner.d.ts.map +1 -0
  56. package/cli/version-banner.js +49 -0
  57. package/cli/version-banner.ts +61 -0
  58. package/core/breakpoints.ts +50 -50
  59. package/core/cli-format.ts +31 -31
  60. package/core/color.ts +148 -148
  61. package/core/constraints/cross-axis.ts +114 -114
  62. package/core/constraints/monotonic-lightness.ts +38 -38
  63. package/core/constraints/monotonic.ts +74 -74
  64. package/core/constraints/threshold.ts +43 -43
  65. package/core/constraints/wcag.ts +70 -70
  66. package/core/cross-axis-config.d.ts.map +1 -1
  67. package/core/engine.d.ts +95 -0
  68. package/core/engine.d.ts.map +1 -1
  69. package/core/engine.js +22 -0
  70. package/core/engine.ts +167 -65
  71. package/core/flatten.ts +116 -116
  72. package/core/image-export.ts +48 -48
  73. package/core/index.d.ts +9 -30
  74. package/core/index.d.ts.map +1 -1
  75. package/core/index.js +7 -54
  76. package/core/index.ts +10 -72
  77. package/core/patch.ts +134 -134
  78. package/core/poset.ts +311 -311
  79. package/core/why.ts +63 -63
  80. package/package.json +96 -90
  81. package/themes/color.lg.order.json +15 -15
  82. package/themes/color.md.order.json +15 -15
  83. package/themes/color.order.json +15 -15
  84. package/themes/color.sm.order.json +15 -15
  85. package/themes/cross-axis.rules.json +35 -35
  86. package/themes/cross-axis.sm.rules.json +12 -12
  87. package/themes/layout.lg.order.json +18 -18
  88. package/themes/layout.md.order.json +18 -18
  89. package/themes/layout.order.json +18 -18
  90. package/themes/layout.sm.order.json +18 -18
  91. package/themes/spacing.order.json +14 -14
  92. package/themes/typography.lg.order.json +15 -15
  93. package/themes/typography.md.order.json +15 -15
  94. package/themes/typography.order.json +15 -15
  95. package/themes/typography.sm.order.json +15 -15
  96. package/cli/engine-helpers.d.ts +0 -8
  97. package/cli/engine-helpers.js +0 -70
  98. package/cli/engine-helpers.ts +0 -61
  99. package/core/cross-axis-config.d.ts +0 -5
  100. package/core/cross-axis-config.js +0 -144
  101. package/core/cross-axis-config.ts +0 -152
  102. package/dist/test-overrides-removal.json +0 -4
  103. package/dist/tmp.patch.json +0 -35
  104. package/tokens/overrides/base.json +0 -22
  105. package/tokens/overrides/lg.json +0 -20
  106. package/tokens/overrides/md.json +0 -16
  107. package/tokens/overrides/sm.json +0 -16
  108. package/tokens/overrides/viol.color.json +0 -6
  109. package/tokens/overrides/viol.typography.json +0 -6
  110. package/tokens/tokens.demo-violations.json +0 -116
  111. package/tokens/tokens.example.json +0 -128
  112. package/tokens/tokens.json +0 -67
  113. package/tokens/tokens.multi-violations.json +0 -21
  114. package/tokens/tokens.schema.d.ts +0 -2298
  115. package/tokens/tokens.schema.d.ts.map +0 -1
  116. package/tokens/tokens.schema.js +0 -148
  117. package/tokens/tokens.schema.ts +0 -196
  118. package/tokens/tokens.test.json +0 -38
  119. package/tokens/tokens.touch-violation.json +0 -8
  120. package/tokens/typography.classes.css +0 -11
  121. package/tokens/typography.css +0 -20
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Filesystem loader for cross-axis constraint rules.
3
+ *
4
+ * Phase 3B (Filesystem Separation): This module handles reading cross-axis rules
5
+ * from JSON files and parsing them into in-memory data structures.
6
+ *
7
+ * Core modules (core/constraints/cross-axis.ts) accept pre-parsed rules,
8
+ * while CLI modules use this loader to read from filesystem.
9
+ */
10
+
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import type { CrossAxisRule } from '../core/constraints/cross-axis.js';
13
+
14
+ /**
15
+ * Raw rule format as stored in JSON files.
16
+ */
17
+ export type RawCrossAxisRule = {
18
+ id: string;
19
+ level?: 'error' | 'warn';
20
+ where?: string;
21
+ bp?: string;
22
+ when?: {
23
+ id: string;
24
+ op: '<=' | '>=' | '<' | '>' | '==' | '!=';
25
+ value: number;
26
+ };
27
+ require?: {
28
+ id: string;
29
+ op: '<=' | '>=' | '<' | '>' | '==' | '!=';
30
+ ref?: string;
31
+ fallback?: string | number;
32
+ };
33
+ compare?: {
34
+ a: string;
35
+ op: '<=' | '>=' | '<' | '>' | '==' | '!=';
36
+ b: string;
37
+ delta?: string | number;
38
+ };
39
+ };
40
+
41
+ /**
42
+ * Result of loading and parsing cross-axis rules.
43
+ */
44
+ export type LoadCrossAxisResult = {
45
+ rules: CrossAxisRule[];
46
+ unknownIds: Set<string>;
47
+ skipped: Array<{ id?: string; reason: string }>;
48
+ };
49
+
50
+ /**
51
+ * Options for loading cross-axis rules.
52
+ */
53
+ export type LoadCrossAxisOptions = {
54
+ /** Breakpoint to filter rules for */
55
+ bp?: string;
56
+ /** Set of known token IDs for validation */
57
+ knownIds?: Set<string>;
58
+ /** Enable debug logging */
59
+ debug?: boolean;
60
+ };
61
+
62
+ // ============================================================================
63
+ // Filesystem Loading
64
+ // ============================================================================
65
+
66
+ /**
67
+ * Load raw cross-axis rules from a JSON file.
68
+ *
69
+ * Returns undefined if file doesn't exist or can't be parsed.
70
+ *
71
+ * @param path Path to cross-axis rules JSON file
72
+ * @returns Parsed rules or undefined if file missing/invalid
73
+ */
74
+ export function loadCrossAxisRulesFromFile(path: string): RawCrossAxisRule[] | undefined {
75
+ if (!existsSync(path)) {
76
+ return undefined;
77
+ }
78
+
79
+ try {
80
+ const data = JSON.parse(readFileSync(path, 'utf8')) as { rules: RawCrossAxisRule[] };
81
+ return data.rules || [];
82
+ } catch {
83
+ // Return undefined on parse errors (consistent with silent failure behavior)
84
+ return undefined;
85
+ }
86
+ }
87
+
88
+ // ============================================================================
89
+ // Rule Parsing and Validation
90
+ // ============================================================================
91
+
92
+ // Helper functions (copied from core/cross-axis-config.ts)
93
+ const px = (v: string | number) =>
94
+ typeof v === 'number' ? v : parseFloat(String(v)) * (String(v).trim().endsWith('rem') ? 16 : 1);
95
+
96
+ const cmp = (a: number, b: number, op: '<=' | '>=' | '<' | '>' | '==' | '!=') =>
97
+ op === '>=' ? a >= b : op === '>' ? a > b : op === '<=' ? a <= b : op === '<' ? a < b : op === '==' ? a === b : a !== b;
98
+
99
+ const prettyFail = (op: string) => ({ '>=': '<', '>': '≤', '<=': '>', '<': '≥', '==': '≠', '!=': '=' } as any)[op] || '≠';
100
+
101
+ const fmt = (v: number | string) => (Number.isFinite(Number(v)) ? `${Number(v)}px` : String(v));
102
+
103
+ function valueOrRef(ctx: any, ref?: string, fallback?: string | number) {
104
+ if (ref) {
105
+ const v = ctx.getPx(ref);
106
+ if (v != null) return v;
107
+ }
108
+ return typeof fallback === 'number' ? fallback : px(fallback ?? 0);
109
+ }
110
+
111
+ function makeOp(op: '<=' | '>=' | '<' | '>' | '==' | '!=', rhs: number) {
112
+ return (v: number) => cmp(v, rhs, op);
113
+ }
114
+
115
+ // Lightweight Levenshtein distance for suggestions
116
+ function levenshtein(a: string, b: string) {
117
+ const dp = Array(b.length + 1)
118
+ .fill(0)
119
+ .map((_, j) => j);
120
+ for (let i = 1; i <= a.length; i++) {
121
+ let prev = i - 1,
122
+ cur = i;
123
+ for (let j = 1; j <= b.length; j++) {
124
+ const tmp = cur;
125
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
126
+ cur = Math.min(dp[j] + 1, cur + 1, prev + cost);
127
+ dp[j] = tmp;
128
+ prev = tmp;
129
+ }
130
+ dp[b.length] = cur;
131
+ }
132
+ return dp[b.length];
133
+ }
134
+
135
+ function suggest(id: string, known: Set<string>, k = 3) {
136
+ return [...known]
137
+ .map((c) => ({ id: c, d: levenshtein(id, c) }))
138
+ .sort((a, b) => a.d - b.d)
139
+ .slice(0, k);
140
+ }
141
+
142
+ /**
143
+ * Parse raw cross-axis rules into executable constraint rules.
144
+ *
145
+ * This function performs:
146
+ * - Breakpoint filtering (only include rules matching the target breakpoint)
147
+ * - Token ID validation (track unknown IDs)
148
+ * - Rule compilation (convert JSON predicates into executable functions)
149
+ *
150
+ * @param rawRules Raw rules from JSON file
151
+ * @param opts Parsing options (breakpoint, knownIds, debug)
152
+ * @returns Parsed rules with validation info
153
+ */
154
+ export function parseCrossAxisRules(rawRules: RawCrossAxisRule[], opts: LoadCrossAxisOptions = {}): LoadCrossAxisResult {
155
+ const { bp, knownIds = new Set(), debug = false } = opts;
156
+ const rules: CrossAxisRule[] = [];
157
+ const unknownIds = new Set<string>();
158
+ const skipped: Array<{ id?: string; reason: string }> = [];
159
+
160
+ const log = (...args: any[]) => {
161
+ if (debug) console.log('[cross-axis]', ...args);
162
+ };
163
+
164
+ const needId = (id?: string) => {
165
+ if (!id) return false;
166
+ if (!knownIds.has(id)) {
167
+ unknownIds.add(id);
168
+ }
169
+ return true;
170
+ };
171
+
172
+ for (const r of rawRules) {
173
+ // Filter by breakpoint
174
+ if (r.bp && bp && r.bp !== bp) {
175
+ continue;
176
+ }
177
+ if (r.bp && !bp) {
178
+ // Rule targets specific breakpoint; skip in global run
179
+ continue;
180
+ }
181
+
182
+ try {
183
+ if (r.when && r.require) {
184
+ // Validate IDs
185
+ needId(r.when.id);
186
+ needId(r.require.id);
187
+ if (r.require.ref) needId(r.require.ref);
188
+
189
+ rules.push({
190
+ id: r.id,
191
+ level: r.level,
192
+ where: r.where,
193
+ when: { id: r.when.id, test: makeOp(r.when.op, r.when.value) },
194
+ require: {
195
+ id: r.require.id,
196
+ test: (v: number, ctx: any) => {
197
+ const rhs = valueOrRef(ctx, r.require!.ref, r.require!.fallback);
198
+ return cmp(v, rhs, r.require!.op);
199
+ },
200
+ msg: (v: number, ctx: any) => {
201
+ const rhs = valueOrRef(ctx, r.require!.ref, r.require!.fallback);
202
+ return `${r.require!.id} ${prettyFail(r.require!.op)} ${fmt(rhs)} (was ${fmt(v)})`;
203
+ },
204
+ },
205
+ });
206
+ } else if (r.compare) {
207
+ needId(r.compare.a);
208
+ needId(r.compare.b);
209
+
210
+ rules.push({
211
+ id: r.id,
212
+ level: r.level,
213
+ where: r.where,
214
+ when: { id: r.compare.a, test: () => true },
215
+ require: {
216
+ id: r.compare.a,
217
+ test: (_: number, ctx: any) => {
218
+ const a = ctx.getPx(r.compare!.a) ?? NaN;
219
+ const b = ctx.getPx(r.compare!.b) ?? NaN;
220
+ const delta = px(r.compare!.delta ?? 0);
221
+ if (Number.isNaN(a) || Number.isNaN(b)) return true; // skip check if missing
222
+ return cmp(a, b + delta, r.compare!.op);
223
+ },
224
+ msg: (_: number, ctx: any) => {
225
+ const a = ctx.getPx(r.compare!.a);
226
+ const b = ctx.getPx(r.compare!.b);
227
+ const delta = px(r.compare!.delta ?? 0);
228
+ return `${r.compare!.a} ${prettyFail(r.compare!.op)} ${fmt((b ?? 0) + delta)} (was ${fmt(a ?? NaN)})`;
229
+ },
230
+ },
231
+ });
232
+ } else {
233
+ skipped.push({ id: r.id, reason: 'neither when+require nor compare present' });
234
+ }
235
+ } catch (e: any) {
236
+ skipped.push({ id: r.id, reason: `exception: ${e?.message ?? e}` });
237
+ }
238
+ }
239
+
240
+ // Debug logging
241
+ if (debug) {
242
+ log(`parsed ${rules.length} rule(s)${bp ? ` [bp=${bp}]` : ''}`);
243
+ if (unknownIds.size) {
244
+ log(`unknown ids referenced:`, [...unknownIds].join(', '));
245
+ for (const u of unknownIds) {
246
+ const s = suggest(u, knownIds, 3);
247
+ if (s.length) log(` did you mean: ${s.map((x) => `${x.id} (d=${x.d})`).join(', ')}`);
248
+ }
249
+ }
250
+ if (skipped.length) {
251
+ for (const s of skipped) log(`skipped rule ${s.id ?? '(no id)'} — ${s.reason}`);
252
+ }
253
+
254
+ // Extra hint for common anchor pitfall
255
+ for (const r of rawRules) {
256
+ if (r.require?.ref && !knownIds.has(r.require.ref)) {
257
+ log(`anchor missing: ${r.require.ref} → will use fallback=${JSON.stringify(r.require.fallback)} when evaluating`);
258
+ }
259
+ }
260
+ }
261
+
262
+ return { rules, unknownIds, skipped };
263
+ }
264
+
265
+ /**
266
+ * Load and parse cross-axis rules from a JSON file.
267
+ *
268
+ * This is the main entry point for CLI code that needs to load cross-axis rules.
269
+ *
270
+ * @param path Path to cross-axis rules JSON file
271
+ * @param opts Parsing options
272
+ * @returns Parsed rules (empty array if file doesn't exist)
273
+ */
274
+ export function loadCrossAxisRules(path: string, opts: LoadCrossAxisOptions = {}): CrossAxisRule[] {
275
+ const { debug = false } = opts;
276
+ const log = (...args: any[]) => {
277
+ if (debug) console.log('[cross-axis]', ...args);
278
+ };
279
+
280
+ const rawRules = loadCrossAxisRulesFromFile(path);
281
+
282
+ if (!rawRules) {
283
+ log(`no rules file at ${path} (bp=${opts.bp ?? 'global'})`);
284
+ return [];
285
+ }
286
+
287
+ const result = parseCrossAxisRules(rawRules, opts);
288
+ return result.rules;
289
+ }
package/cli/dcv.js CHANGED
@@ -47,7 +47,11 @@ cli.command('build', 'Build token outputs', y => y
47
47
  cli.command('validate', 'Validate constraints', y => y
48
48
  .option('fail-on', { type: 'string', choices: ['off', 'warn', 'error'], default: 'error' })
49
49
  .option('summary', { type: 'string', choices: ['none', 'table', 'json'], default: 'none' })
50
+ .option('format', { type: 'string', choices: ['text', 'json'], default: 'text', describe: 'Output format' })
51
+ .option('output', { type: 'string', describe: 'Write JSON output to file' })
52
+ .option('receipt', { type: 'string', describe: 'Generate validation receipt with audit trail' })
50
53
  .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
54
+ .option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
51
55
  .option('breakpoint', { type: 'string' })
52
56
  .option('all-breakpoints', { type: 'boolean' })
53
57
  .option('perf', { type: 'boolean', describe: 'Print timing info' })
package/cli/dcv.ts CHANGED
@@ -1,107 +1,111 @@
1
- #!/usr/bin/env node
2
- // Clean minimal DCV CLI entrypoint
3
- import yargs from 'yargs/yargs';
4
- import { hideBin } from 'yargs/helpers';
5
- import { spawnSync } from 'node:child_process';
6
- import { createRequire } from 'module';
7
- import path from 'node:path';
8
- import { type SetOptions, type BuildOptions, type ValidateOptions, type GraphOptions, type WhyOptions, type PatchOptions, type PatchApplyOptions } from './types.js';
9
- import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
10
-
11
- // Early intercept: experimental graph diff helper
12
- (() => {
13
- const args = process.argv.slice(2);
14
- if (args[0] === 'graph' && args[1] === 'diff') {
15
- const pass = args.slice(2);
16
- const require = createRequire(import.meta.url);
17
- const tsx = (() => { try { return require.resolve('tsx/dist/cli.mjs'); } catch { return path.resolve('node_modules/tsx/dist/cli.mjs'); }})();
18
- const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
19
- process.exit(r.status ?? 0);
20
- }
21
- })();
22
-
23
- const cli = yargs(hideBin(process.argv))
24
- .scriptName('dcv')
25
- .parserConfiguration({ 'camel-case-expansion': false })
26
- .option('quiet', { type: 'boolean' });
27
-
28
- cli.command<SetOptions>('set <expressions..>', 'Set token values', y => y
29
- .positional('expressions', { type: 'string', array: true })
30
- .option('dry-run', { type: 'boolean', default: false })
31
- .option('write', { type: 'boolean' })
32
- .option('json', { type: 'string' })
33
- .option('unset', { type: 'array', string: true })
34
- .option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
35
- .option('output', { type: 'string' })
36
- .option('theme', { type: 'string' })
37
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
38
- a => setCommand(a)
39
- );
40
-
41
- cli.command<BuildOptions>('build', 'Build token outputs', y => y
42
- .option('format', { type: 'string', choices: ['css','json','js'], default: 'css' })
43
- .option('all-formats', { type: 'boolean', default: false })
44
- .option('output', { type: 'string' })
45
- .option('mapper', { type: 'string' })
46
- .option('theme', { type: 'string' })
47
- .option('dry-run', { type: 'boolean', default: false })
48
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
49
- a => buildCommand(a)
50
- );
51
-
52
- cli.command<ValidateOptions>('validate', 'Validate constraints', y => y
53
- .option('fail-on', { type: 'string', choices: ['off','warn','error'], default: 'error' })
54
- .option('summary', { type: 'string', choices: ['none','table','json'], default: 'none' })
55
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
56
- .option('breakpoint', { type: 'string' })
57
- .option('all-breakpoints', { type: 'boolean' })
58
- .option('perf', { type: 'boolean', describe: 'Print timing info' })
59
- .option('budget-total-ms', { type: 'number', describe: 'Fail if total validation exceeds this (ms)' })
60
- .option('budget-per-bp-ms', { type: 'number', describe: 'Fail if any single breakpoint exceeds this (ms)' }),
61
- a => validateCommand(a)
62
- );
63
-
64
- cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y => y
65
- .option('format', { type: 'string', choices: ['json','mermaid','dot','svg','png'], default: 'json' })
66
- .option('bundle', { type: 'boolean', describe: 'When used with --hasse export mermaid+dot (+image if svg/png requested)' })
67
- .option('hasse', { type: 'string' })
68
- .option('filter-prefix', { type: 'string' })
69
- .option('exclude-prefix', { type: 'string' })
70
- .option('only-violations', { type: 'boolean' })
71
- .option('highlight-violations', { type: 'boolean' })
72
- .option('label-violations', { type: 'boolean' })
73
- .option('label-truncate', { type: 'number', default: 0 })
74
- .option('min-severity', { type: 'string', choices: ['warn','error'], default: 'warn' })
75
- .option('focus', { type: 'string' })
76
- .option('radius', { type: 'number', default: 1 })
77
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
78
- a => graphCommand(a)
79
- );
80
-
81
- cli.command<WhyOptions>('why <tokenId>', 'Explain token provenance', y => y
82
- .positional('tokenId', { type: 'string', demandOption: true })
83
- .option('format', { type: 'string', choices: ['json','table'], default: 'json' })
84
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
85
- a => whyCommand(a)
86
- );
87
-
88
- cli.command<PatchOptions>('patch', 'Export patch (diff) from overrides', y => y
89
- .option('overrides', { type: 'string', describe: 'Path or inline JSON of flat overrides' })
90
- .option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
91
- .option('output', { type: 'string' })
92
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
93
- a => patchCommand(a)
94
- );
95
-
96
- cli.command<PatchApplyOptions>('patch:apply <patch>', 'Apply patch document to tokens', y => y
97
- .positional('patch', { type: 'string', demandOption: true })
98
- .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
99
- .option('output', { type: 'string', describe: 'Write updated tokens to this file' })
100
- .option('dry-run', { type: 'boolean', default: false }),
101
- a => patchApplyCommand(a)
102
- );
103
-
104
- cli.help().alias('h','help').strict().wrap(cli.terminalWidth());
105
- cli.parse();
106
-
107
- // EOF
1
+ #!/usr/bin/env node
2
+ // Clean minimal DCV CLI entrypoint
3
+ import yargs from 'yargs/yargs';
4
+ import { hideBin } from 'yargs/helpers';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { createRequire } from 'module';
7
+ import path from 'node:path';
8
+ import { type SetOptions, type BuildOptions, type ValidateOptions, type GraphOptions, type WhyOptions, type PatchOptions, type PatchApplyOptions } from './types.js';
9
+ import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
10
+
11
+ // Early intercept: experimental graph diff helper
12
+ (() => {
13
+ const args = process.argv.slice(2);
14
+ if (args[0] === 'graph' && args[1] === 'diff') {
15
+ const pass = args.slice(2);
16
+ const require = createRequire(import.meta.url);
17
+ const tsx = (() => { try { return require.resolve('tsx/dist/cli.mjs'); } catch { return path.resolve('node_modules/tsx/dist/cli.mjs'); }})();
18
+ const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
19
+ process.exit(r.status ?? 0);
20
+ }
21
+ })();
22
+
23
+ const cli = yargs(hideBin(process.argv))
24
+ .scriptName('dcv')
25
+ .parserConfiguration({ 'camel-case-expansion': false })
26
+ .option('quiet', { type: 'boolean' });
27
+
28
+ cli.command<SetOptions>('set <expressions..>', 'Set token values', y => y
29
+ .positional('expressions', { type: 'string', array: true })
30
+ .option('dry-run', { type: 'boolean', default: false })
31
+ .option('write', { type: 'boolean' })
32
+ .option('json', { type: 'string' })
33
+ .option('unset', { type: 'array', string: true })
34
+ .option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
35
+ .option('output', { type: 'string' })
36
+ .option('theme', { type: 'string' })
37
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
38
+ a => setCommand(a)
39
+ );
40
+
41
+ cli.command<BuildOptions>('build', 'Build token outputs', y => y
42
+ .option('format', { type: 'string', choices: ['css','json','js'], default: 'css' })
43
+ .option('all-formats', { type: 'boolean', default: false })
44
+ .option('output', { type: 'string' })
45
+ .option('mapper', { type: 'string' })
46
+ .option('theme', { type: 'string' })
47
+ .option('dry-run', { type: 'boolean', default: false })
48
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
49
+ a => buildCommand(a)
50
+ );
51
+
52
+ cli.command<ValidateOptions>('validate', 'Validate constraints', y => y
53
+ .option('fail-on', { type: 'string', choices: ['off','warn','error'], default: 'error' })
54
+ .option('summary', { type: 'string', choices: ['none','table','json'], default: 'none' })
55
+ .option('format', { type: 'string', choices: ['text','json'], default: 'text', describe: 'Output format' })
56
+ .option('output', { type: 'string', describe: 'Write JSON output to file' })
57
+ .option('receipt', { type: 'string', describe: 'Generate validation receipt with audit trail' })
58
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
59
+ .option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
60
+ .option('breakpoint', { type: 'string' })
61
+ .option('all-breakpoints', { type: 'boolean' })
62
+ .option('perf', { type: 'boolean', describe: 'Print timing info' })
63
+ .option('budget-total-ms', { type: 'number', describe: 'Fail if total validation exceeds this (ms)' })
64
+ .option('budget-per-bp-ms', { type: 'number', describe: 'Fail if any single breakpoint exceeds this (ms)' }),
65
+ a => validateCommand(a)
66
+ );
67
+
68
+ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y => y
69
+ .option('format', { type: 'string', choices: ['json','mermaid','dot','svg','png'], default: 'json' })
70
+ .option('bundle', { type: 'boolean', describe: 'When used with --hasse export mermaid+dot (+image if svg/png requested)' })
71
+ .option('hasse', { type: 'string' })
72
+ .option('filter-prefix', { type: 'string' })
73
+ .option('exclude-prefix', { type: 'string' })
74
+ .option('only-violations', { type: 'boolean' })
75
+ .option('highlight-violations', { type: 'boolean' })
76
+ .option('label-violations', { type: 'boolean' })
77
+ .option('label-truncate', { type: 'number', default: 0 })
78
+ .option('min-severity', { type: 'string', choices: ['warn','error'], default: 'warn' })
79
+ .option('focus', { type: 'string' })
80
+ .option('radius', { type: 'number', default: 1 })
81
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
82
+ a => graphCommand(a)
83
+ );
84
+
85
+ cli.command<WhyOptions>('why <tokenId>', 'Explain token provenance', y => y
86
+ .positional('tokenId', { type: 'string', demandOption: true })
87
+ .option('format', { type: 'string', choices: ['json','table'], default: 'json' })
88
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
89
+ a => whyCommand(a)
90
+ );
91
+
92
+ cli.command<PatchOptions>('patch', 'Export patch (diff) from overrides', y => y
93
+ .option('overrides', { type: 'string', describe: 'Path or inline JSON of flat overrides' })
94
+ .option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
95
+ .option('output', { type: 'string' })
96
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
97
+ a => patchCommand(a)
98
+ );
99
+
100
+ cli.command<PatchApplyOptions>('patch:apply <patch>', 'Apply patch document to tokens', y => y
101
+ .positional('patch', { type: 'string', demandOption: true })
102
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
103
+ .option('output', { type: 'string', describe: 'Write updated tokens to this file' })
104
+ .option('dry-run', { type: 'boolean', default: false }),
105
+ a => patchApplyCommand(a)
106
+ );
107
+
108
+ cli.help().alias('h','help').strict().wrap(cli.terminalWidth());
109
+ cli.parse();
110
+
111
+ // EOF
@@ -1 +1 @@
1
- {"version":3,"file":"engine-helpers.d.ts","sourceRoot":"","sources":["engine-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI3C,OAAO,EAA8B,wBAAwB,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,wBAAgB,YAAY,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,GAAE,SAAc,GAAG,MAAM,CA8BlF;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,CAkBnH;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
1
+ {"version":3,"file":"engine-helpers.d.ts","sourceRoot":"","sources":["engine-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI3C,OAAO,EAA8B,wBAAwB,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgE5C;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,GAAE,SAAc,GAAG,MAAM,CAQlF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,CAgBnH;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAC"}