design-constraint-validator 1.0.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/LICENSE +21 -0
- package/README.md +659 -0
- package/adapters/README.md +46 -0
- package/adapters/css.d.ts +44 -0
- package/adapters/css.d.ts.map +1 -0
- package/adapters/css.js +97 -0
- package/adapters/css.ts +116 -0
- package/adapters/js.d.ts +3 -0
- package/adapters/js.d.ts.map +1 -0
- package/adapters/js.js +15 -0
- package/adapters/js.ts +14 -0
- package/adapters/json.d.ts +18 -0
- package/adapters/json.d.ts.map +1 -0
- package/adapters/json.js +35 -0
- package/adapters/json.ts +45 -0
- package/cli/build-css.d.ts +2 -0
- package/cli/build-css.d.ts.map +1 -0
- package/cli/build-css.js +23 -0
- package/cli/build-css.ts +32 -0
- package/cli/commands/build.d.ts +5 -0
- package/cli/commands/build.d.ts.map +1 -0
- package/cli/commands/build.js +89 -0
- package/cli/commands/build.ts +65 -0
- package/cli/commands/graph.d.ts +3 -0
- package/cli/commands/graph.d.ts.map +1 -0
- package/cli/commands/graph.js +219 -0
- package/cli/commands/graph.ts +137 -0
- package/cli/commands/index.d.ts +8 -0
- package/cli/commands/index.d.ts.map +1 -0
- package/cli/commands/index.js +7 -0
- package/cli/commands/index.ts +7 -0
- package/cli/commands/patch-apply.d.ts +3 -0
- package/cli/commands/patch-apply.d.ts.map +1 -0
- package/cli/commands/patch-apply.js +75 -0
- package/cli/commands/patch-apply.ts +80 -0
- package/cli/commands/patch.d.ts +3 -0
- package/cli/commands/patch.d.ts.map +1 -0
- package/cli/commands/patch.js +21 -0
- package/cli/commands/patch.ts +22 -0
- package/cli/commands/set.d.ts +3 -0
- package/cli/commands/set.d.ts.map +1 -0
- package/cli/commands/set.js +286 -0
- package/cli/commands/set.ts +225 -0
- package/cli/commands/utils.d.ts +4 -0
- package/cli/commands/utils.d.ts.map +1 -0
- package/cli/commands/utils.js +51 -0
- package/cli/commands/utils.ts +50 -0
- package/cli/commands/validate.d.ts +3 -0
- package/cli/commands/validate.d.ts.map +1 -0
- package/cli/commands/validate.js +131 -0
- package/cli/commands/validate.ts +115 -0
- package/cli/commands/why.d.ts +3 -0
- package/cli/commands/why.d.ts.map +1 -0
- package/cli/commands/why.js +64 -0
- package/cli/commands/why.ts +46 -0
- package/cli/config-schema.d.ts +238 -0
- package/cli/config-schema.d.ts.map +1 -0
- package/cli/config-schema.js +21 -0
- package/cli/config-schema.ts +27 -0
- package/cli/config.d.ts +4 -0
- package/cli/config.d.ts.map +1 -0
- package/cli/config.js +37 -0
- package/cli/config.ts +35 -0
- package/cli/dcv.d.ts +3 -0
- package/cli/dcv.d.ts.map +1 -0
- package/cli/dcv.js +86 -0
- package/cli/dcv.ts +107 -0
- package/cli/engine-helpers.d.ts +8 -0
- package/cli/engine-helpers.d.ts.map +1 -0
- package/cli/engine-helpers.js +70 -0
- package/cli/engine-helpers.ts +61 -0
- package/cli/graph-poset.d.ts +9 -0
- package/cli/graph-poset.d.ts.map +1 -0
- package/cli/graph-poset.js +58 -0
- package/cli/graph-poset.ts +74 -0
- package/cli/index.d.ts +3 -0
- package/cli/index.d.ts.map +1 -0
- package/cli/index.js +2 -0
- package/cli/index.ts +2 -0
- package/cli/result.d.ts +17 -0
- package/cli/result.d.ts.map +1 -0
- package/cli/result.js +29 -0
- package/cli/result.ts +27 -0
- package/cli/run.d.ts +3 -0
- package/cli/run.d.ts.map +1 -0
- package/cli/run.js +47 -0
- package/cli/run.ts +54 -0
- package/cli/smoke-test.d.ts +2 -0
- package/cli/smoke-test.d.ts.map +1 -0
- package/cli/smoke-test.js +33 -0
- package/cli/smoke-test.ts +40 -0
- package/cli/types.d.ts +86 -0
- package/cli/types.d.ts.map +1 -0
- package/cli/types.js +1 -0
- package/cli/types.ts +78 -0
- package/core/breakpoints.d.ts +12 -0
- package/core/breakpoints.d.ts.map +1 -0
- package/core/breakpoints.js +48 -0
- package/core/breakpoints.ts +50 -0
- package/core/cli-format.d.ts +8 -0
- package/core/cli-format.d.ts.map +1 -0
- package/core/cli-format.js +29 -0
- package/core/cli-format.ts +31 -0
- package/core/color.d.ts +14 -0
- package/core/color.d.ts.map +1 -0
- package/core/color.js +136 -0
- package/core/color.ts +148 -0
- package/core/constraints/cross-axis.d.ts +33 -0
- package/core/constraints/cross-axis.d.ts.map +1 -0
- package/core/constraints/cross-axis.js +93 -0
- package/core/constraints/cross-axis.ts +114 -0
- package/core/constraints/monotonic-lightness.d.ts +5 -0
- package/core/constraints/monotonic-lightness.d.ts.map +1 -0
- package/core/constraints/monotonic-lightness.js +37 -0
- package/core/constraints/monotonic-lightness.ts +38 -0
- package/core/constraints/monotonic.d.ts +7 -0
- package/core/constraints/monotonic.d.ts.map +1 -0
- package/core/constraints/monotonic.js +65 -0
- package/core/constraints/monotonic.ts +74 -0
- package/core/constraints/threshold.d.ts +10 -0
- package/core/constraints/threshold.d.ts.map +1 -0
- package/core/constraints/threshold.js +36 -0
- package/core/constraints/threshold.ts +43 -0
- package/core/constraints/wcag.d.ts +11 -0
- package/core/constraints/wcag.d.ts.map +1 -0
- package/core/constraints/wcag.js +53 -0
- package/core/constraints/wcag.ts +70 -0
- package/core/cross-axis-config.d.ts +5 -0
- package/core/cross-axis-config.d.ts.map +1 -0
- package/core/cross-axis-config.js +144 -0
- package/core/cross-axis-config.ts +152 -0
- package/core/engine.d.ts +32 -0
- package/core/engine.d.ts.map +1 -0
- package/core/engine.js +46 -0
- package/core/engine.ts +65 -0
- package/core/flatten.d.ts +20 -0
- package/core/flatten.d.ts.map +1 -0
- package/core/flatten.js +80 -0
- package/core/flatten.ts +116 -0
- package/core/image-export.d.ts +10 -0
- package/core/image-export.d.ts.map +1 -0
- package/core/image-export.js +43 -0
- package/core/image-export.ts +48 -0
- package/core/index.d.ts +31 -0
- package/core/index.d.ts.map +1 -0
- package/core/index.js +54 -0
- package/core/index.ts +72 -0
- package/core/patch.d.ts +28 -0
- package/core/patch.d.ts.map +1 -0
- package/core/patch.js +110 -0
- package/core/patch.ts +134 -0
- package/core/poset.d.ts +41 -0
- package/core/poset.d.ts.map +1 -0
- package/core/poset.js +275 -0
- package/core/poset.ts +311 -0
- package/core/why.d.ts +17 -0
- package/core/why.d.ts.map +1 -0
- package/core/why.js +45 -0
- package/core/why.ts +63 -0
- package/dist/test-overrides-removal.json +4 -0
- package/dist/tmp.patch.json +35 -0
- package/package.json +90 -0
- package/themes/color.lg.order.json +15 -0
- package/themes/color.md.order.json +15 -0
- package/themes/color.order.json +15 -0
- package/themes/color.sm.order.json +15 -0
- package/themes/cross-axis.rules.json +36 -0
- package/themes/cross-axis.sm.rules.json +12 -0
- package/themes/layout.lg.order.json +18 -0
- package/themes/layout.md.order.json +18 -0
- package/themes/layout.order.json +18 -0
- package/themes/layout.sm.order.json +18 -0
- package/themes/spacing.order.json +14 -0
- package/themes/typography.lg.order.json +15 -0
- package/themes/typography.md.order.json +15 -0
- package/themes/typography.order.json +15 -0
- package/themes/typography.sm.order.json +15 -0
- package/tokens/overrides/base.json +22 -0
- package/tokens/overrides/lg.json +20 -0
- package/tokens/overrides/md.json +16 -0
- package/tokens/overrides/sm.json +16 -0
- package/tokens/overrides/viol.color.json +6 -0
- package/tokens/overrides/viol.typography.json +6 -0
- package/tokens/tokens.demo-violations.json +116 -0
- package/tokens/tokens.example.json +128 -0
- package/tokens/tokens.json +67 -0
- package/tokens/tokens.multi-violations.json +21 -0
- package/tokens/tokens.schema.d.ts +2298 -0
- package/tokens/tokens.schema.d.ts.map +1 -0
- package/tokens/tokens.schema.js +148 -0
- package/tokens/tokens.schema.ts +196 -0
- package/tokens/tokens.test.json +38 -0
- package/tokens/tokens.touch-violation.json +8 -0
- package/tokens/typography.classes.css +11 -0
- package/tokens/typography.css +20 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["validate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0G9E"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { flattenTokens } from '../../core/flatten.js';
|
|
2
|
+
import { createValidationEngine } from '../engine-helpers.js';
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
import { parseBreakpoints, loadTokensWithBreakpoint } from '../../core/breakpoints.js';
|
|
5
|
+
import { loadCrossAxisPlugin } from '../../core/cross-axis-config.js';
|
|
6
|
+
export async function validateCommand(_options) {
|
|
7
|
+
try {
|
|
8
|
+
const bps = parseBreakpoints(process.argv);
|
|
9
|
+
const crossAxisDebug = process.argv.includes('--cross-axis-debug');
|
|
10
|
+
const plan = bps.length ? bps : [undefined];
|
|
11
|
+
let anyErrors = false;
|
|
12
|
+
let totalErrors = 0;
|
|
13
|
+
let totalWarnings = 0;
|
|
14
|
+
const argv = process.argv.slice(2);
|
|
15
|
+
const failOnIdx = argv.indexOf('--fail-on');
|
|
16
|
+
const failOn = failOnIdx >= 0 ? argv[failOnIdx + 1] : 'error';
|
|
17
|
+
const sumIdx = argv.indexOf('--summary');
|
|
18
|
+
const summaryFmt = sumIdx >= 0 ? argv[sumIdx + 1] : 'none';
|
|
19
|
+
const rows = [];
|
|
20
|
+
function pushRow(bpLabel, stats) { rows.push({ bp: bpLabel, ...stats }); }
|
|
21
|
+
function printSummaryTable(rs) {
|
|
22
|
+
if (!rs.length)
|
|
23
|
+
return;
|
|
24
|
+
const showTotalLine = !rs.some(r => r.bp === 'TOTAL');
|
|
25
|
+
const cols = ['scope', 'rules', 'warnings', 'errors'];
|
|
26
|
+
const data = rs.map(r => ({ scope: r.bp, rules: String(r.rules), warnings: String(r.warnings), errors: String(r.errors) }));
|
|
27
|
+
const widths = cols.map(c => Math.max(c.length, ...data.map(d => d[c].length)));
|
|
28
|
+
const line = (vals) => vals.map((v, i) => v.padEnd(widths[i])).join(' ');
|
|
29
|
+
console.log(line(cols));
|
|
30
|
+
console.log(line(widths.map(w => '-'.repeat(w))));
|
|
31
|
+
for (const d of data)
|
|
32
|
+
console.log(line(cols.map(c => d[c])));
|
|
33
|
+
if (showTotalLine && rs.length > 1) {
|
|
34
|
+
const tot = rs.reduce((a, b) => ({ rules: a.rules + b.rules, warnings: a.warnings + b.warnings, errors: a.errors + b.errors }), { rules: 0, warnings: 0, errors: 0 });
|
|
35
|
+
console.log(line(['TOTAL', String(tot.rules), String(tot.warnings), String(tot.errors)]));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const cfgRes = loadConfig(_options.config);
|
|
39
|
+
if (!cfgRes.ok) {
|
|
40
|
+
console.error(cfgRes.error);
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
const config = cfgRes.value;
|
|
44
|
+
const perBpTimings = [];
|
|
45
|
+
const tStartTotal = globalThis.performance.now();
|
|
46
|
+
for (const bp of plan) {
|
|
47
|
+
const tStart = globalThis.performance.now();
|
|
48
|
+
const tokens = loadTokensWithBreakpoint(bp);
|
|
49
|
+
const engine = createValidationEngine(tokens, bp, config);
|
|
50
|
+
const initIds = Object.keys(flattenTokens(tokens).flat).reduce((a, k) => { a[k] = true; return a; }, {});
|
|
51
|
+
const knownIds = new Set(Object.keys(initIds));
|
|
52
|
+
// Load global + bp-specific cross-axis rules (bp file optional)
|
|
53
|
+
engine.use(loadCrossAxisPlugin('themes/cross-axis.rules.json', bp, { debug: crossAxisDebug, knownIds }));
|
|
54
|
+
if (bp) {
|
|
55
|
+
const bpRulesPath = `themes/cross-axis.${bp}.rules.json`;
|
|
56
|
+
engine.use(loadCrossAxisPlugin(bpRulesPath, bp, { debug: crossAxisDebug, knownIds }));
|
|
57
|
+
}
|
|
58
|
+
const { ThresholdPlugin } = await import('../../core/constraints/threshold.js');
|
|
59
|
+
engine.use(ThresholdPlugin([{ id: 'control.size.min', op: '>=', valuePx: 44, where: 'Touch target (WCAG / Apple HIG)' }]));
|
|
60
|
+
const allIds = new Set(Object.keys(initIds));
|
|
61
|
+
const issues = engine.evaluate(allIds);
|
|
62
|
+
const errs = issues.filter((i) => i.level === 'error');
|
|
63
|
+
const warns = issues.filter((i) => i.level !== 'error');
|
|
64
|
+
if (errs.length)
|
|
65
|
+
anyErrors = true;
|
|
66
|
+
totalErrors += errs.length;
|
|
67
|
+
totalWarnings += warns.length;
|
|
68
|
+
const rulesEvaluated = errs.length + warns.length;
|
|
69
|
+
pushRow(bp ?? 'global', { rules: rulesEvaluated, warnings: warns.length, errors: errs.length });
|
|
70
|
+
const dur = globalThis.performance.now() - tStart;
|
|
71
|
+
perBpTimings.push({ bp: bp ?? 'global', ms: dur });
|
|
72
|
+
console.log(`validate${bp ? ` [bp=${bp}]` : ''}: ${errs.length} error(s), ${warns.length} warning(s)${_options.perf ? ` (${dur.toFixed(2)}ms)` : ''}`);
|
|
73
|
+
for (const it of issues) {
|
|
74
|
+
const tag = it.level === 'error' ? 'ERROR' : 'WARN ';
|
|
75
|
+
console.log(`${tag} ${it.rule} ${it.id}${it.where ? ' @ ' + it.where : ''}${bp ? ` [${bp}]` : ''} — ${it.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const totalMs = globalThis.performance.now() - tStartTotal;
|
|
79
|
+
// Append aggregate total row if multiple scopes and not already added
|
|
80
|
+
if (rows.length > 1) {
|
|
81
|
+
const agg = rows.reduce((a, b) => ({ rules: a.rules + b.rules, warnings: a.warnings + b.warnings, errors: a.errors + b.errors }), { rules: 0, warnings: 0, errors: 0 });
|
|
82
|
+
rows.push({ bp: 'TOTAL', ...agg });
|
|
83
|
+
}
|
|
84
|
+
if (_options.perf) {
|
|
85
|
+
console.log('[perf] per-breakpoint timings:');
|
|
86
|
+
for (const t of perBpTimings)
|
|
87
|
+
console.log(` ${t.bp}: ${t.ms.toFixed(2)}ms`);
|
|
88
|
+
console.log(`[perf] total: ${totalMs.toFixed(2)}ms`);
|
|
89
|
+
}
|
|
90
|
+
if (summaryFmt === 'json') {
|
|
91
|
+
// Provide machine-readable aggregate separate from rows if TOTAL present
|
|
92
|
+
const totalRow = rows.find(r => r.bp === 'TOTAL');
|
|
93
|
+
const json = totalRow ? { rows, total: { rules: totalRow.rules, warnings: totalRow.warnings, errors: totalRow.errors } } : { rows };
|
|
94
|
+
console.log(JSON.stringify(json, null, 2));
|
|
95
|
+
}
|
|
96
|
+
else if (summaryFmt === 'table') {
|
|
97
|
+
printSummaryTable(rows);
|
|
98
|
+
}
|
|
99
|
+
let code = anyErrors ? 1 : 0;
|
|
100
|
+
// Budget checks (do not override fail-on semantics unless budgets add failures)
|
|
101
|
+
const budgetTotal = _options['budget-total-ms'] ?? _options.budgetTotalMs;
|
|
102
|
+
const budgetPerBp = _options['budget-per-bp-ms'] ?? _options.budgetPerBpMs;
|
|
103
|
+
let budgetFailed = false;
|
|
104
|
+
if (budgetTotal != null && totalMs > budgetTotal) {
|
|
105
|
+
console.error(`[perf] total time ${totalMs.toFixed(2)}ms exceeded budget ${budgetTotal}ms`);
|
|
106
|
+
budgetFailed = true;
|
|
107
|
+
}
|
|
108
|
+
if (budgetPerBp != null) {
|
|
109
|
+
for (const t of perBpTimings) {
|
|
110
|
+
if (t.ms > budgetPerBp) {
|
|
111
|
+
console.error(`[perf] ${t.bp} time ${t.ms.toFixed(2)}ms exceeded per-breakpoint budget ${budgetPerBp}ms`);
|
|
112
|
+
budgetFailed = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (failOn === 'off')
|
|
117
|
+
code = 0;
|
|
118
|
+
else if (failOn === 'warn')
|
|
119
|
+
code = (totalErrors + totalWarnings) > 0 ? 1 : 0;
|
|
120
|
+
else
|
|
121
|
+
code = totalErrors > 0 ? 1 : 0;
|
|
122
|
+
if (budgetFailed)
|
|
123
|
+
code = Math.max(code, 1);
|
|
124
|
+
process.exit(code);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
128
|
+
console.error('validate: failed:', msg);
|
|
129
|
+
process.exit(2);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { flattenTokens } from '../../core/flatten.js';
|
|
2
|
+
import { createValidationEngine } from '../engine-helpers.js';
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
import { parseBreakpoints, loadTokensWithBreakpoint, type Breakpoint } from '../../core/breakpoints.js';
|
|
5
|
+
import { loadCrossAxisPlugin } from '../../core/cross-axis-config.js';
|
|
6
|
+
import type { ConstraintIssue } from '../../core/engine.js';
|
|
7
|
+
import type { ValidateOptions } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export async function validateCommand(_options: ValidateOptions): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
const bps = parseBreakpoints(process.argv);
|
|
12
|
+
const crossAxisDebug = process.argv.includes('--cross-axis-debug');
|
|
13
|
+
const plan: (Breakpoint | undefined)[] = bps.length ? bps : [undefined];
|
|
14
|
+
let anyErrors = false; let totalErrors = 0; let totalWarnings = 0;
|
|
15
|
+
const argv = process.argv.slice(2);
|
|
16
|
+
const failOnIdx = argv.indexOf('--fail-on');
|
|
17
|
+
type FailOn = 'off' | 'warn' | 'error';
|
|
18
|
+
const failOn: FailOn = failOnIdx >= 0 ? (argv[failOnIdx + 1] as FailOn) : 'error';
|
|
19
|
+
const sumIdx = argv.indexOf('--summary');
|
|
20
|
+
type SummaryFmt = 'table' | 'json' | 'none';
|
|
21
|
+
const summaryFmt: SummaryFmt = sumIdx >= 0 ? (argv[sumIdx + 1] as SummaryFmt) : 'none';
|
|
22
|
+
type VRow = { bp: string; rules: number; warnings: number; errors: number };
|
|
23
|
+
const rows: VRow[] = [];
|
|
24
|
+
function pushRow(bpLabel: string, stats: { rules: number; warnings: number; errors: number }) { rows.push({ bp: bpLabel, ...stats }); }
|
|
25
|
+
function printSummaryTable(rs: VRow[]) {
|
|
26
|
+
if (!rs.length) return;
|
|
27
|
+
const showTotalLine = !rs.some(r => r.bp === 'TOTAL');
|
|
28
|
+
const cols = ['scope','rules','warnings','errors'] as const;
|
|
29
|
+
const data = rs.map(r => ({ scope: r.bp, rules: String(r.rules), warnings: String(r.warnings), errors: String(r.errors) }));
|
|
30
|
+
const widths = cols.map(c => Math.max(c.length, ...data.map(d => d[c].length)));
|
|
31
|
+
const line = (vals: string[]) => vals.map((v,i)=>v.padEnd(widths[i])).join(' ');
|
|
32
|
+
console.log(line(cols as unknown as string[]));
|
|
33
|
+
console.log(line(widths.map(w => '-'.repeat(w))));
|
|
34
|
+
for (const d of data) console.log(line(cols.map(c => d[c])));
|
|
35
|
+
if (showTotalLine && rs.length > 1) {
|
|
36
|
+
const tot = rs.reduce((a,b)=>({ rules:a.rules+b.rules, warnings:a.warnings+b.warnings, errors:a.errors+b.errors }), { rules:0,warnings:0,errors:0 });
|
|
37
|
+
console.log(line(['TOTAL', String(tot.rules), String(tot.warnings), String(tot.errors)]));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const cfgRes = loadConfig(_options.config);
|
|
41
|
+
if (!cfgRes.ok) { console.error(cfgRes.error); process.exit(2); }
|
|
42
|
+
const config = cfgRes.value;
|
|
43
|
+
const perBpTimings: Array<{ bp: string; ms: number }> = [];
|
|
44
|
+
const tStartTotal = globalThis.performance.now();
|
|
45
|
+
for (const bp of plan) {
|
|
46
|
+
const tStart = globalThis.performance.now();
|
|
47
|
+
const tokens = loadTokensWithBreakpoint(bp);
|
|
48
|
+
const engine = createValidationEngine(tokens, bp, config);
|
|
49
|
+
const initIds = Object.keys(flattenTokens(tokens).flat).reduce<Record<string, true>>((a,k)=>{a[k]=true;return a;},{});
|
|
50
|
+
const knownIds = new Set(Object.keys(initIds));
|
|
51
|
+
// Load global + bp-specific cross-axis rules (bp file optional)
|
|
52
|
+
engine.use(loadCrossAxisPlugin('themes/cross-axis.rules.json', bp, { debug: crossAxisDebug, knownIds }));
|
|
53
|
+
if (bp) {
|
|
54
|
+
const bpRulesPath = `themes/cross-axis.${bp}.rules.json`;
|
|
55
|
+
engine.use(loadCrossAxisPlugin(bpRulesPath, bp, { debug: crossAxisDebug, knownIds }));
|
|
56
|
+
}
|
|
57
|
+
const { ThresholdPlugin } = await import('../../core/constraints/threshold.js');
|
|
58
|
+
engine.use(ThresholdPlugin([{ id: 'control.size.min', op: '>=', valuePx: 44, where: 'Touch target (WCAG / Apple HIG)' }]));
|
|
59
|
+
const allIds = new Set(Object.keys(initIds));
|
|
60
|
+
const issues = engine.evaluate(allIds);
|
|
61
|
+
const errs = issues.filter((i: ConstraintIssue) => i.level === 'error');
|
|
62
|
+
const warns = issues.filter((i: ConstraintIssue) => i.level !== 'error');
|
|
63
|
+
if (errs.length) anyErrors = true;
|
|
64
|
+
totalErrors += errs.length; totalWarnings += warns.length;
|
|
65
|
+
const rulesEvaluated = errs.length + warns.length; pushRow(bp ?? 'global', { rules: rulesEvaluated, warnings: warns.length, errors: errs.length });
|
|
66
|
+
const dur = globalThis.performance.now() - tStart;
|
|
67
|
+
perBpTimings.push({ bp: bp ?? 'global', ms: dur });
|
|
68
|
+
console.log(`validate${bp ? ` [bp=${bp}]` : ''}: ${errs.length} error(s), ${warns.length} warning(s)${_options.perf ? ` (${dur.toFixed(2)}ms)` : ''}`);
|
|
69
|
+
for (const it of issues) { const tag = it.level === 'error' ? 'ERROR' : 'WARN '; console.log(`${tag} ${it.rule} ${it.id}${it.where ? ' @ ' + it.where : ''}${bp ? ` [${bp}]` : ''} — ${it.message}`); }
|
|
70
|
+
}
|
|
71
|
+
const totalMs = globalThis.performance.now() - tStartTotal;
|
|
72
|
+
// Append aggregate total row if multiple scopes and not already added
|
|
73
|
+
if (rows.length > 1) {
|
|
74
|
+
const agg = rows.reduce((a,b)=>({ rules:a.rules+b.rules, warnings:a.warnings+b.warnings, errors:a.errors+b.errors }), { rules:0,warnings:0,errors:0 });
|
|
75
|
+
rows.push({ bp: 'TOTAL', ...agg });
|
|
76
|
+
}
|
|
77
|
+
if (_options.perf) {
|
|
78
|
+
console.log('[perf] per-breakpoint timings:');
|
|
79
|
+
for (const t of perBpTimings) console.log(` ${t.bp}: ${t.ms.toFixed(2)}ms`);
|
|
80
|
+
console.log(`[perf] total: ${totalMs.toFixed(2)}ms`);
|
|
81
|
+
}
|
|
82
|
+
if (summaryFmt === 'json') {
|
|
83
|
+
// Provide machine-readable aggregate separate from rows if TOTAL present
|
|
84
|
+
const totalRow = rows.find(r => r.bp === 'TOTAL');
|
|
85
|
+
const json = totalRow ? { rows, total: { rules: totalRow.rules, warnings: totalRow.warnings, errors: totalRow.errors } } : { rows };
|
|
86
|
+
console.log(JSON.stringify(json, null, 2));
|
|
87
|
+
} else if (summaryFmt === 'table') {
|
|
88
|
+
printSummaryTable(rows);
|
|
89
|
+
}
|
|
90
|
+
let code = anyErrors ? 1 : 0;
|
|
91
|
+
// Budget checks (do not override fail-on semantics unless budgets add failures)
|
|
92
|
+
const budgetTotal = (_options as any)['budget-total-ms'] ?? _options.budgetTotalMs;
|
|
93
|
+
const budgetPerBp = (_options as any)['budget-per-bp-ms'] ?? _options.budgetPerBpMs;
|
|
94
|
+
let budgetFailed = false;
|
|
95
|
+
if (budgetTotal != null && totalMs > budgetTotal) {
|
|
96
|
+
console.error(`[perf] total time ${totalMs.toFixed(2)}ms exceeded budget ${budgetTotal}ms`);
|
|
97
|
+
budgetFailed = true;
|
|
98
|
+
}
|
|
99
|
+
if (budgetPerBp != null) {
|
|
100
|
+
for (const t of perBpTimings) {
|
|
101
|
+
if (t.ms > budgetPerBp) {
|
|
102
|
+
console.error(`[perf] ${t.bp} time ${t.ms.toFixed(2)}ms exceeded per-breakpoint budget ${budgetPerBp}ms`);
|
|
103
|
+
budgetFailed = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (failOn === 'off') code = 0; else if (failOn === 'warn') code = (totalErrors + totalWarnings) > 0 ? 1 : 0; else code = totalErrors > 0 ? 1 : 0;
|
|
108
|
+
if (budgetFailed) code = Math.max(code, 1);
|
|
109
|
+
process.exit(code);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
112
|
+
console.error('validate: failed:', msg);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"why.d.ts","sourceRoot":"","sources":["why.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCnE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { flattenTokens } from '../../core/flatten.js';
|
|
3
|
+
import { explain } from '../../core/why.js';
|
|
4
|
+
import { loadTokens } from './utils.js';
|
|
5
|
+
export async function whyCommand(options) {
|
|
6
|
+
const tokensPath = options.tokens || 'tokens/tokens.json';
|
|
7
|
+
const tokens = loadTokens(tokensPath);
|
|
8
|
+
const { flat, edges } = flattenTokens(tokens);
|
|
9
|
+
const target = options.tokenId;
|
|
10
|
+
if (!flat[target]) {
|
|
11
|
+
console.error(`❌ Token not found: ${target}`);
|
|
12
|
+
const { suggestIds } = await import('../../core/cli-format.js');
|
|
13
|
+
const suggestions = suggestIds(target, Object.keys(flat));
|
|
14
|
+
if (suggestions.length > 0) {
|
|
15
|
+
console.log(`\nDid you mean:`);
|
|
16
|
+
suggestions.slice(0, 5).forEach(s => console.log(` ${s.id}`));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.log(`\nAvailable tokens:`);
|
|
20
|
+
Object.keys(flat).sort().slice(0, 10).forEach(id => console.log(` ${id}`));
|
|
21
|
+
if (Object.keys(flat).length > 10)
|
|
22
|
+
console.log(` ... and ${Object.keys(flat).length - 10} more`);
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
function safeLoad(p) { try {
|
|
27
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return {};
|
|
31
|
+
} }
|
|
32
|
+
const overrides = safeLoad('tokens/overrides/local.json');
|
|
33
|
+
const theme = safeLoad('themes/theme.json');
|
|
34
|
+
const report = explain(target, flat, edges, { overrides: overrides?.overrides ?? overrides, theme });
|
|
35
|
+
const format = options.format || 'json';
|
|
36
|
+
if (format === 'table') {
|
|
37
|
+
const { pad, trunc } = await import('../../core/cli-format.js');
|
|
38
|
+
console.log(`\n=== Token Analysis: ${target} ===`);
|
|
39
|
+
console.log(`Value: ${report.value}`);
|
|
40
|
+
console.log(`Raw: ${report.raw || 'N/A'}`);
|
|
41
|
+
console.log(`Provenance: ${report.provenance}`);
|
|
42
|
+
if (report.dependsOn && report.dependsOn.length > 0) {
|
|
43
|
+
console.log(`\nDependencies (${report.dependsOn.length}):`);
|
|
44
|
+
console.log(pad('TOKEN', 30) + pad('VALUE', 20) + 'TYPE');
|
|
45
|
+
console.log('-'.repeat(70));
|
|
46
|
+
report.dependsOn.forEach((depId) => { const dep = flat[depId]; if (dep)
|
|
47
|
+
console.log(pad(trunc(depId, 28), 30) + pad(trunc(String(dep.value), 18), 20) + (dep.type || 'unknown')); });
|
|
48
|
+
}
|
|
49
|
+
if (report.dependents && report.dependents.length > 0) {
|
|
50
|
+
console.log(`\nDependents (${report.dependents.length}):`);
|
|
51
|
+
console.log(pad('TOKEN', 30) + pad('VALUE', 20) + 'TYPE');
|
|
52
|
+
console.log('-'.repeat(70));
|
|
53
|
+
report.dependents.forEach((depId) => { const dep = flat[depId]; if (dep)
|
|
54
|
+
console.log(pad(trunc(depId, 28), 30) + pad(trunc(String(dep.value), 18), 20) + (dep.type || 'unknown')); });
|
|
55
|
+
}
|
|
56
|
+
if (report.refs && report.refs.length > 0)
|
|
57
|
+
console.log(`\nReferences: ${report.refs.join(', ')}`);
|
|
58
|
+
if (report.chain && report.chain.length > 0)
|
|
59
|
+
console.log(`\nReference Chain: ${report.chain.join(' → ')}`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log(JSON.stringify(report, null, 2));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { flattenTokens } from '../../core/flatten.js';
|
|
3
|
+
import { explain } from '../../core/why.js';
|
|
4
|
+
import type { WhyOptions } from '../types.js';
|
|
5
|
+
import { loadTokens } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export async function whyCommand(options: WhyOptions): Promise<void> {
|
|
8
|
+
const tokensPath = options.tokens || 'tokens/tokens.json';
|
|
9
|
+
const tokens = loadTokens(tokensPath);
|
|
10
|
+
const { flat, edges } = flattenTokens(tokens);
|
|
11
|
+
const target = options.tokenId;
|
|
12
|
+
if (!flat[target]) {
|
|
13
|
+
console.error(`❌ Token not found: ${target}`);
|
|
14
|
+
const { suggestIds } = await import('../../core/cli-format.js');
|
|
15
|
+
const suggestions = suggestIds(target, Object.keys(flat));
|
|
16
|
+
if (suggestions.length > 0) {
|
|
17
|
+
console.log(`\nDid you mean:`); suggestions.slice(0, 5).forEach(s => console.log(` ${s.id}`));
|
|
18
|
+
} else {
|
|
19
|
+
console.log(`\nAvailable tokens:`); Object.keys(flat).sort().slice(0, 10).forEach(id => console.log(` ${id}`));
|
|
20
|
+
if (Object.keys(flat).length > 10) console.log(` ... and ${Object.keys(flat).length - 10} more`);
|
|
21
|
+
}
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
function safeLoad(p: string) { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return {}; } }
|
|
25
|
+
const overrides = safeLoad('tokens/overrides/local.json');
|
|
26
|
+
const theme = safeLoad('themes/theme.json');
|
|
27
|
+
const report = explain(target, flat, edges, { overrides: overrides?.overrides ?? overrides, theme });
|
|
28
|
+
const format = options.format || 'json';
|
|
29
|
+
if (format === 'table') {
|
|
30
|
+
const { pad, trunc } = await import('../../core/cli-format.js');
|
|
31
|
+
console.log(`\n=== Token Analysis: ${target} ===`);
|
|
32
|
+
console.log(`Value: ${report.value}`); console.log(`Raw: ${report.raw || 'N/A'}`); console.log(`Provenance: ${report.provenance}`);
|
|
33
|
+
if (report.dependsOn && report.dependsOn.length > 0) {
|
|
34
|
+
console.log(`\nDependencies (${report.dependsOn.length}):`); console.log(pad('TOKEN', 30) + pad('VALUE', 20) + 'TYPE'); console.log('-'.repeat(70));
|
|
35
|
+
report.dependsOn.forEach((depId: string) => { const dep = flat[depId]; if (dep) console.log(pad(trunc(depId, 28), 30) + pad(trunc(String(dep.value), 18), 20) + (dep.type || 'unknown')); });
|
|
36
|
+
}
|
|
37
|
+
if (report.dependents && report.dependents.length > 0) {
|
|
38
|
+
console.log(`\nDependents (${report.dependents.length}):`); console.log(pad('TOKEN', 30) + pad('VALUE', 20) + 'TYPE'); console.log('-'.repeat(70));
|
|
39
|
+
report.dependents.forEach((depId: string) => { const dep = flat[depId]; if (dep) console.log(pad(trunc(depId, 28), 30) + pad(trunc(String(dep.value), 18), 20) + (dep.type || 'unknown')); });
|
|
40
|
+
}
|
|
41
|
+
if (report.refs && report.refs.length > 0) console.log(`\nReferences: ${report.refs.join(', ')}`);
|
|
42
|
+
if (report.chain && report.chain.length > 0) console.log(`\nReference Chain: ${report.chain.join(' → ')}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(JSON.stringify(report, null, 2));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const WcagRuleSchema: z.ZodObject<{
|
|
3
|
+
foreground: z.ZodString;
|
|
4
|
+
background: z.ZodString;
|
|
5
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
description: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
foreground: string;
|
|
9
|
+
background: string;
|
|
10
|
+
description?: string | undefined;
|
|
11
|
+
ratio?: number | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
foreground: string;
|
|
14
|
+
background: string;
|
|
15
|
+
description?: string | undefined;
|
|
16
|
+
ratio?: number | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const ConstraintsSchema: z.ZodObject<{
|
|
19
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
20
|
+
foreground: z.ZodString;
|
|
21
|
+
background: z.ZodString;
|
|
22
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
description: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
foreground: string;
|
|
26
|
+
background: string;
|
|
27
|
+
description?: string | undefined;
|
|
28
|
+
ratio?: number | undefined;
|
|
29
|
+
}, {
|
|
30
|
+
foreground: string;
|
|
31
|
+
background: string;
|
|
32
|
+
description?: string | undefined;
|
|
33
|
+
ratio?: number | undefined;
|
|
34
|
+
}>, "many">>;
|
|
35
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
36
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
37
|
+
foreground: z.ZodString;
|
|
38
|
+
background: z.ZodString;
|
|
39
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
description: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
foreground: string;
|
|
43
|
+
background: string;
|
|
44
|
+
description?: string | undefined;
|
|
45
|
+
ratio?: number | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
foreground: string;
|
|
48
|
+
background: string;
|
|
49
|
+
description?: string | undefined;
|
|
50
|
+
ratio?: number | undefined;
|
|
51
|
+
}>, "many">>;
|
|
52
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
53
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
54
|
+
foreground: z.ZodString;
|
|
55
|
+
background: z.ZodString;
|
|
56
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
57
|
+
description: z.ZodOptional<z.ZodString>;
|
|
58
|
+
}, "strip", z.ZodTypeAny, {
|
|
59
|
+
foreground: string;
|
|
60
|
+
background: string;
|
|
61
|
+
description?: string | undefined;
|
|
62
|
+
ratio?: number | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
foreground: string;
|
|
65
|
+
background: string;
|
|
66
|
+
description?: string | undefined;
|
|
67
|
+
ratio?: number | undefined;
|
|
68
|
+
}>, "many">>;
|
|
69
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
70
|
+
export declare const DcvConfigSchema: z.ZodObject<{
|
|
71
|
+
version: z.ZodOptional<z.ZodString>;
|
|
72
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
73
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
74
|
+
foreground: z.ZodString;
|
|
75
|
+
background: z.ZodString;
|
|
76
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
77
|
+
description: z.ZodOptional<z.ZodString>;
|
|
78
|
+
}, "strip", z.ZodTypeAny, {
|
|
79
|
+
foreground: string;
|
|
80
|
+
background: string;
|
|
81
|
+
description?: string | undefined;
|
|
82
|
+
ratio?: number | undefined;
|
|
83
|
+
}, {
|
|
84
|
+
foreground: string;
|
|
85
|
+
background: string;
|
|
86
|
+
description?: string | undefined;
|
|
87
|
+
ratio?: number | undefined;
|
|
88
|
+
}>, "many">>;
|
|
89
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
90
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
91
|
+
foreground: z.ZodString;
|
|
92
|
+
background: z.ZodString;
|
|
93
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
description: z.ZodOptional<z.ZodString>;
|
|
95
|
+
}, "strip", z.ZodTypeAny, {
|
|
96
|
+
foreground: string;
|
|
97
|
+
background: string;
|
|
98
|
+
description?: string | undefined;
|
|
99
|
+
ratio?: number | undefined;
|
|
100
|
+
}, {
|
|
101
|
+
foreground: string;
|
|
102
|
+
background: string;
|
|
103
|
+
description?: string | undefined;
|
|
104
|
+
ratio?: number | undefined;
|
|
105
|
+
}>, "many">>;
|
|
106
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
107
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
108
|
+
foreground: z.ZodString;
|
|
109
|
+
background: z.ZodString;
|
|
110
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
111
|
+
description: z.ZodOptional<z.ZodString>;
|
|
112
|
+
}, "strip", z.ZodTypeAny, {
|
|
113
|
+
foreground: string;
|
|
114
|
+
background: string;
|
|
115
|
+
description?: string | undefined;
|
|
116
|
+
ratio?: number | undefined;
|
|
117
|
+
}, {
|
|
118
|
+
foreground: string;
|
|
119
|
+
background: string;
|
|
120
|
+
description?: string | undefined;
|
|
121
|
+
ratio?: number | undefined;
|
|
122
|
+
}>, "many">>;
|
|
123
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
124
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
125
|
+
version: z.ZodOptional<z.ZodString>;
|
|
126
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
127
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
128
|
+
foreground: z.ZodString;
|
|
129
|
+
background: z.ZodString;
|
|
130
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
131
|
+
description: z.ZodOptional<z.ZodString>;
|
|
132
|
+
}, "strip", z.ZodTypeAny, {
|
|
133
|
+
foreground: string;
|
|
134
|
+
background: string;
|
|
135
|
+
description?: string | undefined;
|
|
136
|
+
ratio?: number | undefined;
|
|
137
|
+
}, {
|
|
138
|
+
foreground: string;
|
|
139
|
+
background: string;
|
|
140
|
+
description?: string | undefined;
|
|
141
|
+
ratio?: number | undefined;
|
|
142
|
+
}>, "many">>;
|
|
143
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
144
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
145
|
+
foreground: z.ZodString;
|
|
146
|
+
background: z.ZodString;
|
|
147
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
148
|
+
description: z.ZodOptional<z.ZodString>;
|
|
149
|
+
}, "strip", z.ZodTypeAny, {
|
|
150
|
+
foreground: string;
|
|
151
|
+
background: string;
|
|
152
|
+
description?: string | undefined;
|
|
153
|
+
ratio?: number | undefined;
|
|
154
|
+
}, {
|
|
155
|
+
foreground: string;
|
|
156
|
+
background: string;
|
|
157
|
+
description?: string | undefined;
|
|
158
|
+
ratio?: number | undefined;
|
|
159
|
+
}>, "many">>;
|
|
160
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
161
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
162
|
+
foreground: z.ZodString;
|
|
163
|
+
background: z.ZodString;
|
|
164
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
165
|
+
description: z.ZodOptional<z.ZodString>;
|
|
166
|
+
}, "strip", z.ZodTypeAny, {
|
|
167
|
+
foreground: string;
|
|
168
|
+
background: string;
|
|
169
|
+
description?: string | undefined;
|
|
170
|
+
ratio?: number | undefined;
|
|
171
|
+
}, {
|
|
172
|
+
foreground: string;
|
|
173
|
+
background: string;
|
|
174
|
+
description?: string | undefined;
|
|
175
|
+
ratio?: number | undefined;
|
|
176
|
+
}>, "many">>;
|
|
177
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
178
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
179
|
+
version: z.ZodOptional<z.ZodString>;
|
|
180
|
+
constraints: z.ZodOptional<z.ZodObject<{
|
|
181
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
182
|
+
foreground: z.ZodString;
|
|
183
|
+
background: z.ZodString;
|
|
184
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
185
|
+
description: z.ZodOptional<z.ZodString>;
|
|
186
|
+
}, "strip", z.ZodTypeAny, {
|
|
187
|
+
foreground: string;
|
|
188
|
+
background: string;
|
|
189
|
+
description?: string | undefined;
|
|
190
|
+
ratio?: number | undefined;
|
|
191
|
+
}, {
|
|
192
|
+
foreground: string;
|
|
193
|
+
background: string;
|
|
194
|
+
description?: string | undefined;
|
|
195
|
+
ratio?: number | undefined;
|
|
196
|
+
}>, "many">>;
|
|
197
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
198
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
199
|
+
foreground: z.ZodString;
|
|
200
|
+
background: z.ZodString;
|
|
201
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
202
|
+
description: z.ZodOptional<z.ZodString>;
|
|
203
|
+
}, "strip", z.ZodTypeAny, {
|
|
204
|
+
foreground: string;
|
|
205
|
+
background: string;
|
|
206
|
+
description?: string | undefined;
|
|
207
|
+
ratio?: number | undefined;
|
|
208
|
+
}, {
|
|
209
|
+
foreground: string;
|
|
210
|
+
background: string;
|
|
211
|
+
description?: string | undefined;
|
|
212
|
+
ratio?: number | undefined;
|
|
213
|
+
}>, "many">>;
|
|
214
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
215
|
+
wcag: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
216
|
+
foreground: z.ZodString;
|
|
217
|
+
background: z.ZodString;
|
|
218
|
+
ratio: z.ZodOptional<z.ZodNumber>;
|
|
219
|
+
description: z.ZodOptional<z.ZodString>;
|
|
220
|
+
}, "strip", z.ZodTypeAny, {
|
|
221
|
+
foreground: string;
|
|
222
|
+
background: string;
|
|
223
|
+
description?: string | undefined;
|
|
224
|
+
ratio?: number | undefined;
|
|
225
|
+
}, {
|
|
226
|
+
foreground: string;
|
|
227
|
+
background: string;
|
|
228
|
+
description?: string | undefined;
|
|
229
|
+
ratio?: number | undefined;
|
|
230
|
+
}>, "many">>;
|
|
231
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
232
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
233
|
+
export type DcvConfigParsed = z.infer<typeof DcvConfigSchema>;
|
|
234
|
+
export declare function validateConfig(raw: unknown): {
|
|
235
|
+
value?: DcvConfigParsed;
|
|
236
|
+
errors?: string[];
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=config-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-schema.d.ts","sourceRoot":"","sources":["config-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;EAKzB,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAEd,CAAC;AAEjB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAGZ,CAAC;AAEjB,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,KAAK,CAAC,EAAE,eAAe,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAM3F"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const WcagRuleSchema = z.object({
|
|
3
|
+
foreground: z.string(),
|
|
4
|
+
background: z.string(),
|
|
5
|
+
ratio: z.number().positive().optional(),
|
|
6
|
+
description: z.string().optional()
|
|
7
|
+
});
|
|
8
|
+
export const ConstraintsSchema = z.object({
|
|
9
|
+
wcag: z.array(WcagRuleSchema).optional()
|
|
10
|
+
}).passthrough();
|
|
11
|
+
export const DcvConfigSchema = z.object({
|
|
12
|
+
version: z.string().optional(),
|
|
13
|
+
constraints: ConstraintsSchema.optional()
|
|
14
|
+
}).passthrough();
|
|
15
|
+
export function validateConfig(raw) {
|
|
16
|
+
const res = DcvConfigSchema.safeParse(raw);
|
|
17
|
+
if (!res.success) {
|
|
18
|
+
return { errors: res.error.errors.map(e => `${e.path.join('.') || '<root>'}: ${e.message}`) };
|
|
19
|
+
}
|
|
20
|
+
return { value: res.data };
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const WcagRuleSchema = z.object({
|
|
4
|
+
foreground: z.string(),
|
|
5
|
+
background: z.string(),
|
|
6
|
+
ratio: z.number().positive().optional(),
|
|
7
|
+
description: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const ConstraintsSchema = z.object({
|
|
11
|
+
wcag: z.array(WcagRuleSchema).optional()
|
|
12
|
+
}).passthrough();
|
|
13
|
+
|
|
14
|
+
export const DcvConfigSchema = z.object({
|
|
15
|
+
version: z.string().optional(),
|
|
16
|
+
constraints: ConstraintsSchema.optional()
|
|
17
|
+
}).passthrough();
|
|
18
|
+
|
|
19
|
+
export type DcvConfigParsed = z.infer<typeof DcvConfigSchema>;
|
|
20
|
+
|
|
21
|
+
export function validateConfig(raw: unknown): { value?: DcvConfigParsed; errors?: string[] } {
|
|
22
|
+
const res = DcvConfigSchema.safeParse(raw);
|
|
23
|
+
if (!res.success) {
|
|
24
|
+
return { errors: res.error.errors.map(e => `${e.path.join('.')||'<root>'}: ${e.message}`) };
|
|
25
|
+
}
|
|
26
|
+
return { value: res.data };
|
|
27
|
+
}
|