design-constraint-validator 2.1.0 → 2.2.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.
- package/README.md +17 -6
- package/cli/commands/build.d.ts.map +1 -1
- package/cli/commands/build.js +32 -24
- package/cli/commands/build.ts +26 -17
- package/cli/commands/graph.d.ts.map +1 -1
- package/cli/commands/graph.js +33 -16
- package/cli/commands/graph.ts +28 -15
- package/cli/commands/patch-apply.d.ts.map +1 -1
- package/cli/commands/patch-apply.js +4 -1
- package/cli/commands/patch-apply.ts +4 -1
- package/cli/commands/set.d.ts.map +1 -1
- package/cli/commands/set.js +18 -19
- package/cli/commands/set.ts +19 -19
- package/cli/commands/utils.d.ts +1 -0
- package/cli/commands/utils.d.ts.map +1 -1
- package/cli/commands/utils.js +20 -1
- package/cli/commands/utils.ts +23 -1
- package/cli/commands/validate.d.ts.map +1 -1
- package/cli/commands/validate.js +5 -17
- package/cli/commands/validate.ts +10 -18
- package/cli/commands/why.d.ts.map +1 -1
- package/cli/commands/why.js +22 -10
- package/cli/commands/why.ts +20 -9
- package/cli/config-schema.d.ts +144 -178
- package/cli/config-schema.d.ts.map +1 -1
- package/cli/config-schema.js +25 -5
- package/cli/config-schema.ts +27 -5
- package/cli/constraint-registry.d.ts.map +1 -1
- package/cli/constraint-registry.js +53 -15
- package/cli/constraint-registry.ts +53 -18
- package/cli/cross-axis-loader.d.ts +62 -0
- package/cli/cross-axis-loader.d.ts.map +1 -1
- package/cli/cross-axis-loader.js +186 -31
- package/cli/cross-axis-loader.ts +199 -24
- package/cli/dcv.js +23 -1
- package/cli/dcv.ts +23 -1
- package/cli/types.d.ts +19 -9
- package/cli/types.d.ts.map +1 -1
- package/cli/types.ts +23 -10
- package/cli/validate-api.d.ts.map +1 -1
- package/cli/validate-api.js +6 -1
- package/cli/validate-api.ts +6 -1
- package/core/constraints/cross-axis.d.ts.map +1 -1
- package/core/constraints/cross-axis.js +37 -9
- package/core/constraints/cross-axis.ts +37 -9
- package/core/constraints/monotonic.d.ts.map +1 -1
- package/core/constraints/monotonic.js +32 -8
- package/core/constraints/monotonic.ts +29 -8
- package/core/constraints/threshold.d.ts.map +1 -1
- package/core/constraints/threshold.js +24 -4
- package/core/constraints/threshold.ts +23 -4
- package/core/constraints/wcag.js +1 -1
- package/core/constraints/wcag.ts +1 -1
- package/core/flatten.d.ts.map +1 -1
- package/core/flatten.js +8 -0
- package/core/flatten.ts +9 -0
- package/core/poset.d.ts +6 -1
- package/core/poset.d.ts.map +1 -1
- package/core/poset.js +7 -2
- package/core/poset.ts +7 -2
- package/mcp/contracts.d.ts +1456 -13
- package/mcp/contracts.d.ts.map +1 -1
- package/mcp/contracts.js +45 -1
- package/mcp/contracts.ts +55 -1
- package/mcp/index.d.ts +6 -4
- package/mcp/index.d.ts.map +1 -1
- package/mcp/index.js +6 -3
- package/mcp/index.ts +28 -1
- package/mcp/insights.d.ts +94 -0
- package/mcp/insights.d.ts.map +1 -0
- package/mcp/insights.js +445 -0
- package/mcp/insights.ts +541 -0
- package/mcp/tools.d.ts +14 -3
- package/mcp/tools.d.ts.map +1 -1
- package/mcp/tools.js +133 -6
- package/mcp/tools.ts +188 -11
- package/package.json +2 -7
- package/server.json +3 -3
package/cli/cross-axis-loader.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
|
+
import { z } from 'zod';
|
|
12
13
|
import type { CrossAxisRule } from '../core/constraints/cross-axis.js';
|
|
14
|
+
import type { ConstraintIssue } from '../core/engine.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Raw rule format as stored in JSON files.
|
|
@@ -71,27 +73,132 @@ export type LoadCrossAxisOptions = {
|
|
|
71
73
|
* @param path Path to cross-axis rules JSON file
|
|
72
74
|
* @returns Parsed rules or undefined if file missing/invalid
|
|
73
75
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Outcome of reading a cross-axis rules file, distinguishing the three cases a
|
|
78
|
+
* validator must not collapse (TASK-037): a *missing* file (no rules, fine) vs.
|
|
79
|
+
* a *present-but-unusable* file (bad JSON / wrong shape — must be surfaced, never
|
|
80
|
+
* silently treated as "no rules → green") vs. a successfully read rule array.
|
|
81
|
+
*/
|
|
82
|
+
export type RawCrossAxisFileResult =
|
|
83
|
+
| { status: 'missing' }
|
|
84
|
+
| { status: 'invalid'; reason: string }
|
|
85
|
+
| { status: 'ok'; rules: RawCrossAxisRule[] };
|
|
78
86
|
|
|
87
|
+
export function readCrossAxisRulesFile(path: string): RawCrossAxisFileResult {
|
|
88
|
+
if (!existsSync(path)) return { status: 'missing' };
|
|
89
|
+
let data: unknown;
|
|
79
90
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
data = JSON.parse(readFileSync(path, 'utf8'));
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
return { status: 'invalid', reason: `invalid JSON (${e?.message ?? e})` };
|
|
94
|
+
}
|
|
95
|
+
const rules = (data as { rules?: unknown } | null)?.rules;
|
|
96
|
+
if (!Array.isArray(rules)) {
|
|
97
|
+
return { status: 'invalid', reason: 'expected a top-level "rules" array' };
|
|
98
|
+
}
|
|
99
|
+
return { status: 'ok', rules: rules as RawCrossAxisRule[] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Back-compat shim: returns the rule array, or `undefined` for both missing and
|
|
104
|
+
* present-but-unusable files. Prefer {@link readCrossAxisRulesFile} when the
|
|
105
|
+
* caller needs to surface the unusable case.
|
|
106
|
+
*/
|
|
107
|
+
export function loadCrossAxisRulesFromFile(path: string): RawCrossAxisRule[] | undefined {
|
|
108
|
+
const res = readCrossAxisRulesFile(path);
|
|
109
|
+
return res.status === 'ok' ? res.rules : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Rule shape validation (TASK-037)
|
|
114
|
+
//
|
|
115
|
+
// A cross-axis rule that fails to compile must be SKIPPED WITH A REASON, never
|
|
116
|
+
// compiled into an always-true predicate or a NaN comparison. Examples the old
|
|
117
|
+
// code admitted silently: a `when` missing `op`/`value` (always-true), a
|
|
118
|
+
// `require` with no RHS (compared against 0), a non-numeric `fallback` (NaN).
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
const opEnum = z.enum(['<=', '>=', '<', '>', '==', '!=']);
|
|
122
|
+
const sizeConst = z.union([z.number(), z.string()]);
|
|
123
|
+
|
|
124
|
+
const RawCrossAxisRuleSchema = z
|
|
125
|
+
.object({
|
|
126
|
+
id: z.string(),
|
|
127
|
+
level: z.enum(['error', 'warn']).optional(),
|
|
128
|
+
where: z.string().optional(),
|
|
129
|
+
bp: z.string().optional(),
|
|
130
|
+
when: z.object({ id: z.string(), op: opEnum, value: z.number().finite() }).strict().optional(),
|
|
131
|
+
require: z
|
|
132
|
+
.object({ id: z.string(), op: opEnum, ref: z.string().optional(), fallback: sizeConst.optional() })
|
|
133
|
+
.strict()
|
|
134
|
+
.optional(),
|
|
135
|
+
compare: z.object({ a: z.string(), op: opEnum, b: z.string(), delta: sizeConst.optional() }).strict().optional(),
|
|
136
|
+
})
|
|
137
|
+
.strict();
|
|
138
|
+
|
|
139
|
+
export type RuleValidation =
|
|
140
|
+
| { ok: true; rule: RawCrossAxisRule }
|
|
141
|
+
| { ok: false; id?: string; reason: string };
|
|
142
|
+
|
|
143
|
+
export function validateRawRule(raw: unknown): RuleValidation {
|
|
144
|
+
const parsed = RawCrossAxisRuleSchema.safeParse(raw);
|
|
145
|
+
if (!parsed.success) {
|
|
146
|
+
const id =
|
|
147
|
+
raw && typeof raw === 'object' && 'id' in raw && typeof (raw as any).id === 'string'
|
|
148
|
+
? (raw as any).id
|
|
149
|
+
: undefined;
|
|
150
|
+
const reason = parsed.error.issues.map((e) => `${e.path.join('.') || '<root>'}: ${e.message}`).join('; ');
|
|
151
|
+
return { ok: false, id, reason };
|
|
152
|
+
}
|
|
153
|
+
const rule = parsed.data as RawCrossAxisRule;
|
|
154
|
+
const hasWhenReq = !!(rule.when && rule.require);
|
|
155
|
+
const hasCompare = !!rule.compare;
|
|
156
|
+
if (!hasWhenReq && !hasCompare) {
|
|
157
|
+
return { ok: false, id: rule.id, reason: 'must define when+require or compare' };
|
|
158
|
+
}
|
|
159
|
+
if (hasWhenReq && hasCompare) {
|
|
160
|
+
return { ok: false, id: rule.id, reason: 'must define exactly one of when+require or compare' };
|
|
161
|
+
}
|
|
162
|
+
// Size constants must parse, or the compiled predicate degrades to NaN/0.
|
|
163
|
+
if (rule.require?.fallback !== undefined && px(rule.require.fallback) === null) {
|
|
164
|
+
return { ok: false, id: rule.id, reason: `require.fallback is not a parseable size: ${JSON.stringify(rule.require.fallback)}` };
|
|
165
|
+
}
|
|
166
|
+
if (rule.compare?.delta !== undefined && px(rule.compare.delta) === null) {
|
|
167
|
+
return { ok: false, id: rule.id, reason: `compare.delta is not a parseable size: ${JSON.stringify(rule.compare.delta)}` };
|
|
85
168
|
}
|
|
169
|
+
return { ok: true, rule };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Whether a rule is active for a given breakpoint scope. Shared by rule
|
|
174
|
+
* compilation and coverage enumeration so the two never drift (TASK-037): a
|
|
175
|
+
* rule that did not run must not be able to make coverage look "matched".
|
|
176
|
+
*/
|
|
177
|
+
export function ruleMatchesBp(r: { bp?: string }, bp?: string): boolean {
|
|
178
|
+
if (r?.bp && bp && r.bp !== bp) return false; // targets a different bp
|
|
179
|
+
if (r?.bp && !bp) return false; // bp-specific rule in a global run
|
|
180
|
+
return true;
|
|
86
181
|
}
|
|
87
182
|
|
|
88
183
|
// ============================================================================
|
|
89
184
|
// Rule Parsing and Validation
|
|
90
185
|
// ============================================================================
|
|
91
186
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
187
|
+
// Parse a cross-axis size CONSTANT (rule `fallback`/`delta`), not a token value.
|
|
188
|
+
// Mirrors the hardened finite-size policy (TASK-037): real numbers only, `rem`/
|
|
189
|
+
// `em` 16px-relative, non-finite rejected — but allows a leading `-` because a
|
|
190
|
+
// `compare.delta` is a signed offset (e.g. "-560px"). Returns null on garbage so
|
|
191
|
+
// callers reject the rule instead of silently degrading to NaN/0.
|
|
192
|
+
const px = (v: string | number): number | null => {
|
|
193
|
+
if (typeof v === 'number') return Number.isFinite(v) ? v : null;
|
|
194
|
+
if (typeof v !== 'string') return null;
|
|
195
|
+
const m = v.trim().match(/^(-?\d*\.?\d+)\s*(px|rem|em)?$/i);
|
|
196
|
+
if (!m) return null;
|
|
197
|
+
const n = parseFloat(m[1]);
|
|
198
|
+
if (!Number.isFinite(n)) return null;
|
|
199
|
+
const unit = (m[2] || 'px').toLowerCase();
|
|
200
|
+
return unit === 'rem' || unit === 'em' ? n * 16 : n;
|
|
201
|
+
};
|
|
95
202
|
|
|
96
203
|
const cmp = (a: number, b: number, op: '<=' | '>=' | '<' | '>' | '==' | '!=') =>
|
|
97
204
|
op === '>=' ? a >= b : op === '>' ? a > b : op === '<=' ? a <= b : op === '<' ? a < b : op === '==' ? a === b : a !== b;
|
|
@@ -105,7 +212,10 @@ function valueOrRef(ctx: any, ref?: string, fallback?: string | number) {
|
|
|
105
212
|
const v = ctx.getPx(ref);
|
|
106
213
|
if (v != null) return v;
|
|
107
214
|
}
|
|
108
|
-
|
|
215
|
+
if (typeof fallback === 'number') return fallback;
|
|
216
|
+
// `?? 0` is defensive only: validateRawRule rejects rules whose fallback does
|
|
217
|
+
// not parse, so a compiled rule never reaches here with garbage.
|
|
218
|
+
return px(fallback ?? 0) ?? 0;
|
|
109
219
|
}
|
|
110
220
|
|
|
111
221
|
function makeOp(op: '<=' | '>=' | '<' | '>' | '==' | '!=', rhs: number) {
|
|
@@ -169,15 +279,18 @@ export function parseCrossAxisRules(rawRules: RawCrossAxisRule[], opts: LoadCros
|
|
|
169
279
|
return true;
|
|
170
280
|
};
|
|
171
281
|
|
|
172
|
-
for (const
|
|
173
|
-
// Filter by breakpoint
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
282
|
+
for (const raw of rawRules) {
|
|
283
|
+
// Filter by breakpoint (shared with coverage enumeration so they never drift).
|
|
284
|
+
if (!ruleMatchesBp(raw as RawCrossAxisRule, bp)) continue;
|
|
285
|
+
|
|
286
|
+
// Validate shape BEFORE compiling: an invalid rule is skipped with a reason,
|
|
287
|
+
// never compiled into an always-true or NaN predicate (TASK-037).
|
|
288
|
+
const valid = validateRawRule(raw);
|
|
289
|
+
if (!valid.ok) {
|
|
290
|
+
skipped.push({ id: valid.id, reason: valid.reason });
|
|
179
291
|
continue;
|
|
180
292
|
}
|
|
293
|
+
const r = valid.rule;
|
|
181
294
|
|
|
182
295
|
try {
|
|
183
296
|
if (r.when && r.require) {
|
|
@@ -217,20 +330,18 @@ export function parseCrossAxisRules(rawRules: RawCrossAxisRule[], opts: LoadCros
|
|
|
217
330
|
test: (_: number, ctx: any) => {
|
|
218
331
|
const a = ctx.getPx(r.compare!.a) ?? NaN;
|
|
219
332
|
const b = ctx.getPx(r.compare!.b) ?? NaN;
|
|
220
|
-
const delta = px(r.compare!.delta ?? 0);
|
|
333
|
+
const delta = px(r.compare!.delta ?? 0) ?? 0;
|
|
221
334
|
if (Number.isNaN(a) || Number.isNaN(b)) return true; // skip check if missing
|
|
222
335
|
return cmp(a, b + delta, r.compare!.op);
|
|
223
336
|
},
|
|
224
337
|
msg: (_: number, ctx: any) => {
|
|
225
338
|
const a = ctx.getPx(r.compare!.a);
|
|
226
339
|
const b = ctx.getPx(r.compare!.b);
|
|
227
|
-
const delta = px(r.compare!.delta ?? 0);
|
|
340
|
+
const delta = px(r.compare!.delta ?? 0) ?? 0;
|
|
228
341
|
return `${r.compare!.a} ${prettyFail(r.compare!.op)} ${fmt((b ?? 0) + delta)} (was ${fmt(a ?? NaN)})`;
|
|
229
342
|
},
|
|
230
343
|
},
|
|
231
344
|
});
|
|
232
|
-
} else {
|
|
233
|
-
skipped.push({ id: r.id, reason: 'neither when+require nor compare present' });
|
|
234
345
|
}
|
|
235
346
|
} catch (e: any) {
|
|
236
347
|
skipped.push({ id: r.id, reason: `exception: ${e?.message ?? e}` });
|
|
@@ -287,3 +398,67 @@ export function loadCrossAxisRules(path: string, opts: LoadCrossAxisOptions = {}
|
|
|
287
398
|
const result = parseCrossAxisRules(rawRules, opts);
|
|
288
399
|
return result.rules;
|
|
289
400
|
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Load + parse cross-axis rules AND return notices to surface (TASK-037).
|
|
404
|
+
*
|
|
405
|
+
* Unlike {@link loadCrossAxisRules}, a present-but-unusable file or a skipped
|
|
406
|
+
* (invalid) rule produces a `warn`-level {@link ConstraintIssue} so it appears
|
|
407
|
+
* in the validation result instead of vanishing into a silent "no rules → green".
|
|
408
|
+
*/
|
|
409
|
+
export function loadCrossAxisRulesDetailed(
|
|
410
|
+
path: string,
|
|
411
|
+
opts: LoadCrossAxisOptions = {},
|
|
412
|
+
): { rules: CrossAxisRule[]; notices: ConstraintIssue[] } {
|
|
413
|
+
const file = readCrossAxisRulesFile(path);
|
|
414
|
+
if (file.status === 'missing') return { rules: [], notices: [] };
|
|
415
|
+
if (file.status === 'invalid') {
|
|
416
|
+
return {
|
|
417
|
+
rules: [],
|
|
418
|
+
notices: [
|
|
419
|
+
{
|
|
420
|
+
id: 'cross-axis',
|
|
421
|
+
rule: 'cross-axis',
|
|
422
|
+
level: 'warn',
|
|
423
|
+
where: path,
|
|
424
|
+
message: `Cross-axis rules file present but unusable: ${file.reason}`,
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const result = parseCrossAxisRules(file.rules, opts);
|
|
430
|
+
const notices: ConstraintIssue[] = result.skipped.map((s) => ({
|
|
431
|
+
id: `cross-axis:${s.id ?? '(no id)'}`,
|
|
432
|
+
rule: 'cross-axis',
|
|
433
|
+
level: 'warn' as const,
|
|
434
|
+
message: `Cross-axis rule ${s.id ? `"${s.id}" ` : ''}skipped: ${s.reason}`,
|
|
435
|
+
}));
|
|
436
|
+
return { rules: result.rules, notices };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Token ids a cross-axis file contributes to constraint coverage for a given
|
|
441
|
+
* breakpoint scope (TASK-037). Mirrors compilation exactly: the SAME bp filter
|
|
442
|
+
* and the SAME shape validation, so a rule that did not run cannot make coverage
|
|
443
|
+
* look "matched" (which would suppress the "nothing was checked" note).
|
|
444
|
+
*
|
|
445
|
+
* `coverageKnown` is false only when the file is present-but-unusable.
|
|
446
|
+
*/
|
|
447
|
+
export function referencedIdsForFile(path: string, bp?: string): { ids: string[]; coverageKnown: boolean } {
|
|
448
|
+
const file = readCrossAxisRulesFile(path);
|
|
449
|
+
if (file.status === 'missing') return { ids: [], coverageKnown: true };
|
|
450
|
+
if (file.status === 'invalid') return { ids: [], coverageKnown: false };
|
|
451
|
+
const ids: string[] = [];
|
|
452
|
+
for (const raw of file.rules) {
|
|
453
|
+
if (!ruleMatchesBp(raw as RawCrossAxisRule, bp)) continue;
|
|
454
|
+
const v = validateRawRule(raw);
|
|
455
|
+
if (!v.ok) continue; // invalid rule did not compile → contributes no coverage
|
|
456
|
+
const r = v.rule;
|
|
457
|
+
if (r.when?.id) ids.push(r.when.id);
|
|
458
|
+
if (r.require?.id) ids.push(r.require.id);
|
|
459
|
+
if (r.require?.ref) ids.push(r.require.ref);
|
|
460
|
+
if (r.compare?.a) ids.push(r.compare.a);
|
|
461
|
+
if (r.compare?.b) ids.push(r.compare.b);
|
|
462
|
+
}
|
|
463
|
+
return { ids, coverageKnown: true };
|
|
464
|
+
}
|
package/cli/dcv.js
CHANGED
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
import yargs from 'yargs/yargs';
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
5
|
import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
|
|
6
|
+
import { getVersionInfo } from './version-banner.js';
|
|
6
7
|
const cli = yargs(hideBin(process.argv))
|
|
7
8
|
.scriptName('dcv')
|
|
9
|
+
// camel-case-expansion is intentionally OFF, so the CLI delivers flags only
|
|
10
|
+
// under their kebab key (e.g. argv['dry-run'], never argv.dryRun). Commands
|
|
11
|
+
// read BOTH forms — `options['dry-run'] ?? options.dryRun` — so the same handler
|
|
12
|
+
// works whether invoked by the CLI (kebab) or programmatically/tests (camelCase).
|
|
13
|
+
// Reading only one form silently no-ops for the other caller (TASK-024).
|
|
8
14
|
.parserConfiguration({ 'camel-case-expansion': false })
|
|
9
15
|
.option('quiet', { type: 'boolean' })
|
|
10
16
|
.option('config', { type: 'string', describe: 'Path to JSON config file' });
|
|
11
|
-
|
|
17
|
+
// `[expressions..]` is OPTIONAL: batch mode (--json / `-` stdin) and unset-only
|
|
18
|
+
// mode (--unset) take no positional, and a mandatory positional made yargs abort
|
|
19
|
+
// before the handler (TASK-032).
|
|
20
|
+
cli.command('set [expressions..]', 'Set token values', y => y
|
|
12
21
|
.positional('expressions', { type: 'string', array: true })
|
|
13
22
|
.option('dry-run', { type: 'boolean', default: false })
|
|
14
23
|
.option('write', { type: 'boolean' })
|
|
@@ -17,6 +26,7 @@ cli.command('set <expressions..>', 'Set token values', y => y
|
|
|
17
26
|
.option('format', { type: 'string', choices: ['json', 'css', 'js'], default: 'json' })
|
|
18
27
|
.option('output', { type: 'string' })
|
|
19
28
|
.option('theme', { type: 'string' })
|
|
29
|
+
.option('debug-set', { type: 'boolean', hidden: true }) // hidden debug aid (also DCV_DEBUG_SET=1)
|
|
20
30
|
.option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => setCommand(a));
|
|
21
31
|
cli.command('build', 'Build token outputs', y => y
|
|
22
32
|
.option('format', { type: 'string', choices: ['css', 'json', 'js'], default: 'css' })
|
|
@@ -38,6 +48,7 @@ cli.command('validate [tokens-path]', 'Validate constraints', y => y
|
|
|
38
48
|
.option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
|
|
39
49
|
.option('breakpoint', { type: 'string' })
|
|
40
50
|
.option('all-breakpoints', { type: 'boolean' })
|
|
51
|
+
.option('cross-axis-debug', { type: 'boolean', hidden: true }) // hidden debug aid
|
|
41
52
|
.option('perf', { type: 'boolean', describe: 'Print timing info' })
|
|
42
53
|
.option('budget-total-ms', { type: 'number', describe: 'Fail if total validation exceeds this (ms)' })
|
|
43
54
|
.option('budget-per-bp-ms', { type: 'number', describe: 'Fail if any single breakpoint exceeds this (ms)' }), a => validateCommand(a));
|
|
@@ -45,6 +56,10 @@ cli.command('graph', 'Generate dependency / constraint graph', y => y
|
|
|
45
56
|
.option('format', { type: 'string', choices: ['json', 'mermaid', 'dot', 'svg', 'png'], default: 'json' })
|
|
46
57
|
.option('bundle', { type: 'boolean', describe: 'When used with --hasse export mermaid+dot (+image if svg/png requested)' })
|
|
47
58
|
.option('hasse', { type: 'string' })
|
|
59
|
+
.option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files (default: themes). Used with --hasse.' })
|
|
60
|
+
.option('filter', { type: 'string', describe: 'Filter dependency-graph edges by regex (matches either endpoint)' })
|
|
61
|
+
.option('breakpoint', { type: 'string', choices: ['sm', 'md', 'lg'] })
|
|
62
|
+
.option('all-breakpoints', { type: 'boolean' })
|
|
48
63
|
.option('filter-prefix', { type: 'string' })
|
|
49
64
|
.option('exclude-prefix', { type: 'string' })
|
|
50
65
|
.option('only-violations', { type: 'boolean' })
|
|
@@ -52,12 +67,15 @@ cli.command('graph', 'Generate dependency / constraint graph', y => y
|
|
|
52
67
|
.option('label-violations', { type: 'boolean' })
|
|
53
68
|
.option('label-truncate', { type: 'number', default: 0 })
|
|
54
69
|
.option('min-severity', { type: 'string', choices: ['warn', 'error'], default: 'warn' })
|
|
70
|
+
.option('violation-color', { type: 'string', describe: 'Hex color for highlighted violations (default: #ff2d55)' })
|
|
71
|
+
.option('image-from', { type: 'string', choices: ['mermaid', 'dot'], describe: 'Source format for svg/png export (default: mermaid)' })
|
|
55
72
|
.option('focus', { type: 'string' })
|
|
56
73
|
.option('radius', { type: 'number', default: 1 })
|
|
57
74
|
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }), a => graphCommand(a));
|
|
58
75
|
cli.command('why <tokenId>', 'Explain token provenance', y => y
|
|
59
76
|
.positional('tokenId', { type: 'string', demandOption: true })
|
|
60
77
|
.option('format', { type: 'string', choices: ['json', 'table'], default: 'json' })
|
|
78
|
+
.option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files for the constraint summary (default: themes)' })
|
|
61
79
|
.option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => whyCommand(a));
|
|
62
80
|
cli.command('patch', 'Export patch (diff) from overrides', y => y
|
|
63
81
|
.option('overrides', { type: 'string', describe: 'Path or inline JSON of flat overrides' })
|
|
@@ -69,6 +87,10 @@ cli.command('patch:apply <patch>', 'Apply patch document to tokens', y => y
|
|
|
69
87
|
.option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
|
|
70
88
|
.option('output', { type: 'string', describe: 'Write updated tokens to this file' })
|
|
71
89
|
.option('dry-run', { type: 'boolean', default: false }), a => patchApplyCommand(a));
|
|
90
|
+
// Wire `--version` to the real package version (same source as the banner).
|
|
91
|
+
// Without this, yargs can't locate package.json from the installed bin and
|
|
92
|
+
// prints "unknown" (TASK-021 release smoke-test finding).
|
|
93
|
+
cli.version(getVersionInfo().version);
|
|
72
94
|
cli.help().alias('h', 'help').strict().wrap(cli.terminalWidth());
|
|
73
95
|
cli.parse();
|
|
74
96
|
// EOF
|
package/cli/dcv.ts
CHANGED
|
@@ -4,14 +4,23 @@ import yargs from 'yargs/yargs';
|
|
|
4
4
|
import { hideBin } from 'yargs/helpers';
|
|
5
5
|
import { type SetOptions, type BuildOptions, type ValidateOptions, type GraphOptions, type WhyOptions, type PatchOptions, type PatchApplyOptions } from './types.js';
|
|
6
6
|
import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
|
|
7
|
+
import { getVersionInfo } from './version-banner.js';
|
|
7
8
|
|
|
8
9
|
const cli = yargs(hideBin(process.argv))
|
|
9
10
|
.scriptName('dcv')
|
|
11
|
+
// camel-case-expansion is intentionally OFF, so the CLI delivers flags only
|
|
12
|
+
// under their kebab key (e.g. argv['dry-run'], never argv.dryRun). Commands
|
|
13
|
+
// read BOTH forms — `options['dry-run'] ?? options.dryRun` — so the same handler
|
|
14
|
+
// works whether invoked by the CLI (kebab) or programmatically/tests (camelCase).
|
|
15
|
+
// Reading only one form silently no-ops for the other caller (TASK-024).
|
|
10
16
|
.parserConfiguration({ 'camel-case-expansion': false })
|
|
11
17
|
.option('quiet', { type: 'boolean' })
|
|
12
18
|
.option('config', { type: 'string', describe: 'Path to JSON config file' });
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
// `[expressions..]` is OPTIONAL: batch mode (--json / `-` stdin) and unset-only
|
|
21
|
+
// mode (--unset) take no positional, and a mandatory positional made yargs abort
|
|
22
|
+
// before the handler (TASK-032).
|
|
23
|
+
cli.command<SetOptions>('set [expressions..]', 'Set token values', y => y
|
|
15
24
|
.positional('expressions', { type: 'string', array: true })
|
|
16
25
|
.option('dry-run', { type: 'boolean', default: false })
|
|
17
26
|
.option('write', { type: 'boolean' })
|
|
@@ -20,6 +29,7 @@ cli.command<SetOptions>('set <expressions..>', 'Set token values', y => y
|
|
|
20
29
|
.option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
|
|
21
30
|
.option('output', { type: 'string' })
|
|
22
31
|
.option('theme', { type: 'string' })
|
|
32
|
+
.option('debug-set', { type: 'boolean', hidden: true }) // hidden debug aid (also DCV_DEBUG_SET=1)
|
|
23
33
|
.option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
|
|
24
34
|
a => setCommand(a)
|
|
25
35
|
);
|
|
@@ -47,6 +57,7 @@ cli.command<ValidateOptions>('validate [tokens-path]', 'Validate constraints', y
|
|
|
47
57
|
.option('theme', { type: 'string', describe: 'Apply named theme tokens before validation' })
|
|
48
58
|
.option('breakpoint', { type: 'string' })
|
|
49
59
|
.option('all-breakpoints', { type: 'boolean' })
|
|
60
|
+
.option('cross-axis-debug', { type: 'boolean', hidden: true }) // hidden debug aid
|
|
50
61
|
.option('perf', { type: 'boolean', describe: 'Print timing info' })
|
|
51
62
|
.option('budget-total-ms', { type: 'number', describe: 'Fail if total validation exceeds this (ms)' })
|
|
52
63
|
.option('budget-per-bp-ms', { type: 'number', describe: 'Fail if any single breakpoint exceeds this (ms)' }),
|
|
@@ -57,6 +68,10 @@ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y =
|
|
|
57
68
|
.option('format', { type: 'string', choices: ['json','mermaid','dot','svg','png'], default: 'json' })
|
|
58
69
|
.option('bundle', { type: 'boolean', describe: 'When used with --hasse export mermaid+dot (+image if svg/png requested)' })
|
|
59
70
|
.option('hasse', { type: 'string' })
|
|
71
|
+
.option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files (default: themes). Used with --hasse.' })
|
|
72
|
+
.option('filter', { type: 'string', describe: 'Filter dependency-graph edges by regex (matches either endpoint)' })
|
|
73
|
+
.option('breakpoint', { type: 'string', choices: ['sm','md','lg'] })
|
|
74
|
+
.option('all-breakpoints', { type: 'boolean' })
|
|
60
75
|
.option('filter-prefix', { type: 'string' })
|
|
61
76
|
.option('exclude-prefix', { type: 'string' })
|
|
62
77
|
.option('only-violations', { type: 'boolean' })
|
|
@@ -64,6 +79,8 @@ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y =
|
|
|
64
79
|
.option('label-violations', { type: 'boolean' })
|
|
65
80
|
.option('label-truncate', { type: 'number', default: 0 })
|
|
66
81
|
.option('min-severity', { type: 'string', choices: ['warn','error'], default: 'warn' })
|
|
82
|
+
.option('violation-color', { type: 'string', describe: 'Hex color for highlighted violations (default: #ff2d55)' })
|
|
83
|
+
.option('image-from', { type: 'string', choices: ['mermaid','dot'], describe: 'Source format for svg/png export (default: mermaid)' })
|
|
67
84
|
.option('focus', { type: 'string' })
|
|
68
85
|
.option('radius', { type: 'number', default: 1 })
|
|
69
86
|
.option('tokens', { type: 'string', describe: 'Path to a tokens file (defaults to tokens/tokens.example.json)' }),
|
|
@@ -73,6 +90,7 @@ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y =
|
|
|
73
90
|
cli.command<WhyOptions>('why <tokenId>', 'Explain token provenance', y => y
|
|
74
91
|
.positional('tokenId', { type: 'string', demandOption: true })
|
|
75
92
|
.option('format', { type: 'string', choices: ['json','table'], default: 'json' })
|
|
93
|
+
.option('constraints-dir', { type: 'string', describe: 'Directory holding order / cross-axis constraint files for the constraint summary (default: themes)' })
|
|
76
94
|
.option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
|
|
77
95
|
a => whyCommand(a)
|
|
78
96
|
);
|
|
@@ -93,6 +111,10 @@ cli.command<PatchApplyOptions>('patch:apply <patch>', 'Apply patch document to t
|
|
|
93
111
|
a => patchApplyCommand(a)
|
|
94
112
|
);
|
|
95
113
|
|
|
114
|
+
// Wire `--version` to the real package version (same source as the banner).
|
|
115
|
+
// Without this, yargs can't locate package.json from the installed bin and
|
|
116
|
+
// prints "unknown" (TASK-021 release smoke-test finding).
|
|
117
|
+
cli.version(getVersionInfo().version);
|
|
96
118
|
cli.help().alias('h','help').strict().wrap(cli.terminalWidth());
|
|
97
119
|
cli.parse();
|
|
98
120
|
|
package/cli/types.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface SetOptions extends GlobalOptions {
|
|
|
32
32
|
write?: boolean;
|
|
33
33
|
json?: string;
|
|
34
34
|
unset?: string[];
|
|
35
|
+
dryRun?: boolean;
|
|
36
|
+
'dry-run'?: boolean;
|
|
35
37
|
}
|
|
36
38
|
export interface BuildOptions extends GlobalOptions {
|
|
37
39
|
output?: string;
|
|
@@ -40,7 +42,9 @@ export interface BuildOptions extends GlobalOptions {
|
|
|
40
42
|
theme?: string;
|
|
41
43
|
mapper?: string;
|
|
42
44
|
dryRun?: boolean;
|
|
45
|
+
'dry-run'?: boolean;
|
|
43
46
|
allFormats?: boolean;
|
|
47
|
+
'all-formats'?: boolean;
|
|
44
48
|
}
|
|
45
49
|
export interface ValidateOptions extends GlobalOptions {
|
|
46
50
|
strict?: boolean;
|
|
@@ -49,33 +53,38 @@ export interface ValidateOptions extends GlobalOptions {
|
|
|
49
53
|
'constraints-dir'?: string;
|
|
50
54
|
perf?: boolean;
|
|
51
55
|
budgetTotalMs?: number;
|
|
56
|
+
'budget-total-ms'?: number;
|
|
52
57
|
budgetPerBpMs?: number;
|
|
58
|
+
'budget-per-bp-ms'?: number;
|
|
53
59
|
format?: 'text' | 'json';
|
|
54
60
|
output?: string;
|
|
55
61
|
receipt?: string;
|
|
56
62
|
failOn?: 'off' | 'warn' | 'error';
|
|
63
|
+
'fail-on'?: 'off' | 'warn' | 'error';
|
|
57
64
|
summary?: 'none' | 'table' | 'json';
|
|
58
65
|
}
|
|
59
66
|
export interface GraphOptions extends GlobalOptions {
|
|
60
67
|
output?: string;
|
|
68
|
+
'constraints-dir'?: string;
|
|
61
69
|
format?: 'dot' | 'mermaid' | 'json' | 'svg' | 'png';
|
|
62
|
-
imageFrom?: 'mermaid' | 'dot';
|
|
63
70
|
filter?: string;
|
|
64
71
|
hasse?: string;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
'image-from'?: 'mermaid' | 'dot';
|
|
73
|
+
'filter-prefix'?: string;
|
|
74
|
+
'exclude-prefix'?: string;
|
|
75
|
+
'only-violations'?: boolean;
|
|
76
|
+
'highlight-violations'?: boolean;
|
|
77
|
+
'violation-color'?: string;
|
|
78
|
+
'label-violations'?: boolean;
|
|
79
|
+
'label-truncate'?: number;
|
|
80
|
+
'min-severity'?: 'warn' | 'error';
|
|
73
81
|
focus?: string;
|
|
74
82
|
radius?: number;
|
|
75
83
|
tokens?: string;
|
|
76
84
|
}
|
|
77
85
|
export interface WhyOptions extends GlobalOptions {
|
|
78
86
|
tokenId: string;
|
|
87
|
+
'constraints-dir'?: string;
|
|
79
88
|
format?: 'json' | 'table';
|
|
80
89
|
}
|
|
81
90
|
export interface PatchOptions extends GlobalOptions {
|
|
@@ -89,6 +98,7 @@ export interface PatchApplyOptions extends GlobalOptions {
|
|
|
89
98
|
output?: string;
|
|
90
99
|
tokens?: string;
|
|
91
100
|
dryRun?: boolean;
|
|
101
|
+
'dry-run'?: boolean;
|
|
92
102
|
}
|
|
93
103
|
export type { Breakpoint };
|
|
94
104
|
//# sourceMappingURL=types.d.ts.map
|
package/cli/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,cAAc;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE;AAChH,MAAM,MAAM,SAAS,GAAG,eAAe,CAAC;AACxC,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACrD,MAAM,WAAW,aAAa;IAAG,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE;AAClE,MAAM,MAAM,aAAa,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,aAAa,CAAA;CAAE,GAAG,aAAa,CAAC;AAE3F,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AACD,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
|
package/cli/types.ts
CHANGED
|
@@ -25,6 +25,10 @@ export interface SetOptions extends GlobalOptions {
|
|
|
25
25
|
write?: boolean;
|
|
26
26
|
json?: string;
|
|
27
27
|
unset?: string[];
|
|
28
|
+
// CLI delivers the kebab key (camel-case-expansion off); programmatic callers
|
|
29
|
+
// pass camelCase. Commands read both (TASK-024).
|
|
30
|
+
dryRun?: boolean;
|
|
31
|
+
'dry-run'?: boolean;
|
|
28
32
|
}
|
|
29
33
|
export interface BuildOptions extends GlobalOptions {
|
|
30
34
|
output?: string;
|
|
@@ -33,7 +37,9 @@ export interface BuildOptions extends GlobalOptions {
|
|
|
33
37
|
theme?: string;
|
|
34
38
|
mapper?: string;
|
|
35
39
|
dryRun?: boolean;
|
|
40
|
+
'dry-run'?: boolean;
|
|
36
41
|
allFormats?: boolean;
|
|
42
|
+
'all-formats'?: boolean;
|
|
37
43
|
}
|
|
38
44
|
export interface ValidateOptions extends GlobalOptions {
|
|
39
45
|
strict?: boolean;
|
|
@@ -42,33 +48,39 @@ export interface ValidateOptions extends GlobalOptions {
|
|
|
42
48
|
'constraints-dir'?: string;
|
|
43
49
|
perf?: boolean;
|
|
44
50
|
budgetTotalMs?: number;
|
|
51
|
+
'budget-total-ms'?: number;
|
|
45
52
|
budgetPerBpMs?: number;
|
|
53
|
+
'budget-per-bp-ms'?: number;
|
|
46
54
|
format?: 'text' | 'json';
|
|
47
55
|
output?: string;
|
|
48
56
|
receipt?: string;
|
|
49
57
|
failOn?: 'off' | 'warn' | 'error';
|
|
58
|
+
'fail-on'?: 'off' | 'warn' | 'error';
|
|
50
59
|
summary?: 'none' | 'table' | 'json';
|
|
51
60
|
}
|
|
52
61
|
export interface GraphOptions extends GlobalOptions {
|
|
53
62
|
output?: string;
|
|
63
|
+
'constraints-dir'?: string;
|
|
54
64
|
format?: 'dot' | 'mermaid' | 'json' | 'svg' | 'png';
|
|
55
|
-
imageFrom?: 'mermaid' | 'dot';
|
|
56
65
|
filter?: string;
|
|
57
66
|
hasse?: string;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
// Kebab keys: the CLI parser runs with camel-case-expansion off.
|
|
68
|
+
'image-from'?: 'mermaid' | 'dot';
|
|
69
|
+
'filter-prefix'?: string;
|
|
70
|
+
'exclude-prefix'?: string;
|
|
71
|
+
'only-violations'?: boolean;
|
|
72
|
+
'highlight-violations'?: boolean;
|
|
73
|
+
'violation-color'?: string;
|
|
74
|
+
'label-violations'?: boolean;
|
|
75
|
+
'label-truncate'?: number;
|
|
76
|
+
'min-severity'?: 'warn' | 'error';
|
|
66
77
|
focus?: string;
|
|
67
78
|
radius?: number;
|
|
68
79
|
tokens?: string;
|
|
69
80
|
}
|
|
70
81
|
export interface WhyOptions extends GlobalOptions {
|
|
71
82
|
tokenId: string;
|
|
83
|
+
'constraints-dir'?: string;
|
|
72
84
|
format?: 'json' | 'table';
|
|
73
85
|
}
|
|
74
86
|
export interface PatchOptions extends GlobalOptions {
|
|
@@ -81,6 +93,7 @@ export interface PatchApplyOptions extends GlobalOptions {
|
|
|
81
93
|
patch: string; // path or inline patch JSON
|
|
82
94
|
output?: string; // where to write updated tokens (if omitted, prints result)
|
|
83
95
|
tokens?: string; // source tokens file (baseline)
|
|
84
|
-
dryRun?: boolean; // if true do not write
|
|
96
|
+
dryRun?: boolean; // if true do not write (programmatic callers)
|
|
97
|
+
'dry-run'?: boolean; // CLI key (camel-case-expansion off)
|
|
85
98
|
}
|
|
86
99
|
export type { Breakpoint };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-api.d.ts","sourceRoot":"","sources":["validate-api.ts"],"names":[],"mappings":"AAYA,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AAEnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-api.d.ts","sourceRoot":"","sources":["validate-api.ts"],"names":[],"mappings":"AAYA,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AAEnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIzD,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC5B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kHAAkH;IAClH,WAAW,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IACvC,wHAAwH;IACxH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qFAAqF;IACrF,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA6BD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,GAAE,aAAkB,GAAG,cAAc,CAkDlE"}
|
package/cli/validate-api.js
CHANGED
|
@@ -13,6 +13,7 @@ import fs from 'node:fs';
|
|
|
13
13
|
import { flattenTokens } from '../core/flatten.js';
|
|
14
14
|
import { Engine } from '../core/engine.js';
|
|
15
15
|
import { loadConfig } from './config.js';
|
|
16
|
+
import { validateConfig } from './config-schema.js';
|
|
16
17
|
import { setupConstraints, collectReferencedIds } from './constraint-registry.js';
|
|
17
18
|
import { formatViolation } from './json-output.js';
|
|
18
19
|
function readTokensFile(p) {
|
|
@@ -29,7 +30,11 @@ function readTokensFile(p) {
|
|
|
29
30
|
}
|
|
30
31
|
function resolveConfig(input) {
|
|
31
32
|
if (input.constraints !== undefined) {
|
|
32
|
-
|
|
33
|
+
const { value, errors } = validateConfig({ constraints: input.constraints });
|
|
34
|
+
if (errors) {
|
|
35
|
+
throw new Error(`Inline constraints validation failed:\n - ${errors.join('\n - ')}`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
33
38
|
}
|
|
34
39
|
const res = loadConfig(input.configPath);
|
|
35
40
|
if (!res.ok) {
|