design-constraint-validator 1.1.0 → 2.1.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/README.md +90 -21
- package/adapters/decisionthemes.d.ts +44 -0
- package/adapters/decisionthemes.d.ts.map +1 -0
- package/adapters/decisionthemes.js +35 -0
- package/adapters/decisionthemes.ts +59 -0
- package/cli/commands/build.js +1 -1
- package/cli/commands/build.ts +1 -1
- package/cli/commands/graph.js +4 -4
- package/cli/commands/graph.ts +4 -4
- package/cli/commands/validate.d.ts.map +1 -1
- package/cli/commands/validate.js +43 -6
- package/cli/commands/validate.ts +41 -8
- package/cli/config-schema.d.ts +39 -0
- package/cli/config-schema.d.ts.map +1 -1
- package/cli/config-schema.js +4 -2
- package/cli/config-schema.ts +4 -2
- package/cli/config.d.ts.map +1 -1
- package/cli/config.js +8 -2
- package/cli/config.ts +8 -2
- package/cli/constraint-registry.d.ts +16 -0
- package/cli/constraint-registry.d.ts.map +1 -1
- package/cli/constraint-registry.js +64 -31
- package/cli/constraint-registry.ts +67 -31
- package/cli/dcv.js +8 -24
- package/cli/dcv.ts +8 -20
- package/cli/json-output.d.ts +8 -1
- package/cli/json-output.d.ts.map +1 -1
- package/cli/json-output.js +13 -4
- package/cli/json-output.ts +20 -4
- package/cli/types.d.ts +2 -0
- package/cli/types.d.ts.map +1 -1
- package/cli/types.ts +2 -0
- package/cli/validate-api.d.ts +40 -0
- package/cli/validate-api.d.ts.map +1 -0
- package/cli/validate-api.js +85 -0
- package/cli/validate-api.ts +126 -0
- package/cli/version-banner.d.ts +20 -0
- package/cli/version-banner.d.ts.map +1 -0
- package/cli/version-banner.js +49 -0
- package/cli/version-banner.ts +61 -0
- package/core/breakpoints.d.ts +8 -2
- package/core/breakpoints.d.ts.map +1 -1
- package/core/breakpoints.js +24 -3
- package/core/breakpoints.ts +22 -3
- package/core/color.js +4 -4
- package/core/color.ts +4 -4
- package/core/constraints/monotonic-lightness.d.ts.map +1 -1
- package/core/constraints/monotonic-lightness.js +9 -5
- package/core/constraints/monotonic-lightness.ts +9 -4
- package/core/constraints/wcag.d.ts.map +1 -1
- package/core/constraints/wcag.js +6 -0
- package/core/constraints/wcag.ts +6 -0
- package/core/dtcg.d.ts +38 -0
- package/core/dtcg.d.ts.map +1 -0
- package/core/dtcg.js +88 -0
- package/core/dtcg.ts +102 -0
- package/core/engine.d.ts +6 -0
- package/core/engine.d.ts.map +1 -1
- package/core/engine.ts +7 -0
- package/core/flatten.d.ts +5 -3
- package/core/flatten.d.ts.map +1 -1
- package/core/flatten.js +24 -10
- package/core/flatten.ts +39 -16
- package/core/image-export.d.ts.map +1 -1
- package/core/image-export.js +10 -7
- package/core/image-export.ts +9 -6
- package/core/index.d.ts +2 -0
- package/core/index.d.ts.map +1 -1
- package/core/index.js +4 -0
- package/core/index.ts +6 -0
- package/core/why.d.ts +1 -1
- package/core/why.d.ts.map +1 -1
- package/core/why.ts +1 -1
- package/mcp/contracts.d.ts +118 -0
- package/mcp/contracts.d.ts.map +1 -0
- package/mcp/contracts.js +30 -0
- package/mcp/contracts.ts +51 -0
- package/mcp/index.d.ts +9 -0
- package/mcp/index.d.ts.map +1 -0
- package/mcp/index.js +32 -0
- package/mcp/index.ts +70 -0
- package/mcp/tools.d.ts +52 -0
- package/mcp/tools.d.ts.map +1 -0
- package/mcp/tools.js +172 -0
- package/mcp/tools.ts +254 -0
- package/package.json +41 -26
- package/server.json +21 -0
- package/cli/constraints-loader.d.ts +0 -30
- package/cli/constraints-loader.d.ts.map +0 -1
- package/cli/constraints-loader.js +0 -58
- package/cli/constraints-loader.ts +0 -83
- package/cli/engine-helpers.d.ts +0 -41
- package/cli/engine-helpers.d.ts.map +0 -1
- package/cli/engine-helpers.js +0 -135
- package/cli/engine-helpers.ts +0 -133
- package/core/cross-axis-config.d.ts +0 -34
- package/core/cross-axis-config.d.ts.map +0 -1
- package/core/cross-axis-config.js +0 -173
- package/core/cross-axis-config.ts +0 -182
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/design-constraint-validator)
|
|
7
7
|
[](https://github.com/CseperkePapp/design-constraint-validator/actions/workflows/ci.yml)
|
|
8
8
|
[](https://github.com/CseperkePapp/design-constraint-validator/actions/workflows/sbom.yml)
|
|
9
|
+
[](SECURITY.md)
|
|
9
10
|
[](LICENSE)
|
|
10
11
|
[](#)
|
|
11
12
|
|
|
@@ -35,28 +36,54 @@ npx dcv --help
|
|
|
35
36
|
|
|
36
37
|
## Quick Start
|
|
37
38
|
|
|
39
|
+
DCV validates **your** tokens against **your** constraints. From an empty directory:
|
|
40
|
+
|
|
38
41
|
```bash
|
|
39
|
-
#
|
|
40
|
-
|
|
42
|
+
# 1. Your design tokens (DTCG-style "$value")
|
|
43
|
+
cat > tokens.json <<'JSON'
|
|
44
|
+
{
|
|
45
|
+
"color": {
|
|
46
|
+
"text": { "$value": "#888888" },
|
|
47
|
+
"bg": { "$value": "#999999" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
JSON
|
|
51
|
+
|
|
52
|
+
# 2. Your constraints — auto-discovered as dcv.config.json in the cwd
|
|
53
|
+
cat > dcv.config.json <<'JSON'
|
|
54
|
+
{
|
|
55
|
+
"constraints": {
|
|
56
|
+
"enableBuiltInWcagDefaults": false,
|
|
57
|
+
"enableBuiltInThreshold": false,
|
|
58
|
+
"wcag": [
|
|
59
|
+
{ "foreground": "color.text", "background": "color.bg", "ratio": 4.5, "description": "Body text on background" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
JSON
|
|
64
|
+
|
|
65
|
+
# 3. Validate (positional path or --tokens; exits non-zero on violations)
|
|
66
|
+
npx dcv validate tokens.json --summary table
|
|
41
67
|
|
|
42
|
-
# Explain
|
|
43
|
-
npx dcv why --format table
|
|
68
|
+
# Explain one token (the tokenId is required)
|
|
69
|
+
npx dcv why color.text --tokens tokens.json --format table
|
|
44
70
|
|
|
45
|
-
# Export dependency graph
|
|
46
|
-
npx dcv graph --format mermaid > graph.mmd
|
|
71
|
+
# Export the dependency graph
|
|
72
|
+
npx dcv graph --tokens tokens.json --format mermaid > graph.mmd
|
|
47
73
|
```
|
|
48
74
|
|
|
49
|
-
**Example
|
|
75
|
+
**Example output** (`validate`):
|
|
50
76
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Exit code: 1 (violations found)
|
|
77
|
+
```text
|
|
78
|
+
validate: 1 error(s), 0 warning(s)
|
|
79
|
+
ERROR wcag-contrast color.text|color.bg @ Body text on background — Contrast 1.24:1 < 4.5:1
|
|
80
|
+
scope rules warnings errors
|
|
81
|
+
------ ----- -------- ------
|
|
82
|
+
global 1 0 1
|
|
58
83
|
```
|
|
59
84
|
|
|
85
|
+
Exit code is `1` when violations are found, `0` when clean (use `--fail-on off` to always exit `0`). The built-in WCAG/threshold defaults target the bundled example token ids, so disable them (as above) when validating your own token names.
|
|
86
|
+
|
|
60
87
|
---
|
|
61
88
|
|
|
62
89
|
## Programmatic API
|
|
@@ -64,14 +91,15 @@ Exit code: 1 (violations found)
|
|
|
64
91
|
```ts
|
|
65
92
|
import { validate } from 'design-constraint-validator';
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
// Synchronous. Point at files, or pass `tokens` / `constraints` inline.
|
|
95
|
+
const result = validate({
|
|
96
|
+
tokensPath: './tokens.json',
|
|
97
|
+
configPath: './dcv.config.json', // omit to auto-discover dcv.config.json in the cwd
|
|
70
98
|
});
|
|
71
99
|
|
|
72
100
|
if (!result.ok) {
|
|
73
101
|
for (const v of result.violations) {
|
|
74
|
-
console.log(`[${v.
|
|
102
|
+
console.log(`[${v.ruleId}] ${v.message}`);
|
|
75
103
|
}
|
|
76
104
|
process.exitCode = 1;
|
|
77
105
|
}
|
|
@@ -81,6 +109,31 @@ See **[API Reference](docs/API.md)** for complete programmatic usage.
|
|
|
81
109
|
|
|
82
110
|
---
|
|
83
111
|
|
|
112
|
+
## Use from AI agents (MCP)
|
|
113
|
+
|
|
114
|
+
DCV ships a second binary, `dcv-mcp`, that exposes the validator over MCP stdio for agent clients. Add it to a Claude Desktop or generic MCP client config like this:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"dcv": {
|
|
120
|
+
"command": "npx",
|
|
121
|
+
"args": ["-y", "--package", "design-constraint-validator", "dcv-mcp"]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The server exposes exactly three JSON-returning tools:
|
|
128
|
+
|
|
129
|
+
- `validate` - validate inline `tokens` or a `tokensPath` against inline `constraints` or a config file.
|
|
130
|
+
- `why` - explain provenance, aliases, dependencies, dependents, and alias chain for one token id.
|
|
131
|
+
- `graph` - return token dependency `nodes` and `edges`.
|
|
132
|
+
|
|
133
|
+
Tool failures are returned as structured JSON: `{ "ok": false, "error": { "code": "...", "message": "..." } }`.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
84
137
|
## Documentation
|
|
85
138
|
|
|
86
139
|
### For Everyone
|
|
@@ -103,6 +156,7 @@ See **[API Reference](docs/API.md)** for complete programmatic usage.
|
|
|
103
156
|
- **[Prior Art / Method](docs/prior-art/)** - Design rationale (Decision Themes, receipts)
|
|
104
157
|
- **[AI Guide](docs/AI-GUIDE.md)** - Using DCV with ChatGPT/Claude/Copilot
|
|
105
158
|
- **[Contributing](CONTRIBUTING.md)** - Contribution guidelines
|
|
159
|
+
- **[Security](SECURITY.md)** - Supply chain security measures
|
|
106
160
|
|
|
107
161
|
---
|
|
108
162
|
|
|
@@ -140,12 +194,26 @@ Adapters normalize common ecosystems:
|
|
|
140
194
|
|
|
141
195
|
- **Style Dictionary** - See [examples/style-dictionary/](examples/style-dictionary/)
|
|
142
196
|
- **Tokens Studio JSON** - See [examples/tokens-studio/](examples/tokens-studio/)
|
|
143
|
-
- **DTCG** (Design Tokens Community Group) - See [examples/dtcg/](examples/dtcg/)
|
|
197
|
+
- **DTCG** (Design Tokens Community Group) — reads the **2025.10 stable spec** (structured sRGB colors, structured dimensions, `{alias}` references, `$extensions` passthrough; non-sRGB spaces warn rather than mis-calculate; composite types out of scope). See [examples/dtcg/](examples/dtcg/)
|
|
144
198
|
|
|
145
199
|
Full adapter documentation: **[Adapters](docs/Adapters.md)**
|
|
146
200
|
|
|
147
201
|
---
|
|
148
202
|
|
|
203
|
+
## DCV & DecisionThemes
|
|
204
|
+
|
|
205
|
+
DCV is the **standalone validation engine** — use it for any token system.
|
|
206
|
+
|
|
207
|
+
**DecisionThemes** (coming 2026) is a complete design system framework built on DCV:
|
|
208
|
+
- **5-axis decision model** (Tone, Emphasis, Size, Density, Shape)
|
|
209
|
+
- **VT/DT pipeline** (Value Themes + Decision Themes → deterministic CSS configs)
|
|
210
|
+
- **Studio UI** + **Hub marketplace** for sharing Decision Systems
|
|
211
|
+
|
|
212
|
+
DCV powers DecisionThemes' validation layer — but works perfectly standalone.
|
|
213
|
+
Preview: [www.decisionthemes.com](https://www.decisionthemes.com)
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
149
217
|
## Method & Prior Art
|
|
150
218
|
|
|
151
219
|
The Design Constraint Validator engine is based on a theming and validation method published as **defensive prior art**.
|
|
@@ -166,7 +234,8 @@ These documents keep the method openly implementable and prevent patent lock-up.
|
|
|
166
234
|
DCV generates CycloneDX-compliant SBOMs for supply chain transparency:
|
|
167
235
|
|
|
168
236
|
- **CI Builds:** SBOM artifacts on every CI run (90-day retention)
|
|
169
|
-
- **
|
|
237
|
+
- **Version tags:** SBOM artifacts for release tags
|
|
238
|
+
- **GitHub Releases:** SBOM files (JSON + XML) attached when a GitHub Release is created
|
|
170
239
|
- **Manual:** Run `npx @cyclonedx/cyclonedx-npm` in project root
|
|
171
240
|
|
|
172
241
|
**Download:**
|
|
@@ -180,7 +249,7 @@ DCV generates CycloneDX-compliant SBOMs for supply chain transparency:
|
|
|
180
249
|
- Plugin API for **custom constraints**
|
|
181
250
|
- **VS Code** diagnostics (inline explain)
|
|
182
251
|
- **Cross-axis packs** (typography × weight × contrast)
|
|
183
|
-
- **
|
|
252
|
+
- **Signed / attestable receipts** — `dcv validate --receipt` already emits environment + input content hashes today; cryptographic **signing** is the roadmap part
|
|
184
253
|
- UI graph explorer (node inspector, violations focus)
|
|
185
254
|
|
|
186
255
|
---
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DecisionThemes Adapter (Placeholder)
|
|
3
|
+
*
|
|
4
|
+
* This adapter will integrate with the DecisionThemes framework (coming 2026).
|
|
5
|
+
* It transforms VT (Value Themes) + DT (Decision Themes) into flat tokens for DCV validation.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.decisionthemes.com
|
|
8
|
+
* @see docs/Adapters.md for implementation details
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { decisionthemesAdapter } from './adapters/decisionthemes.js';
|
|
13
|
+
*
|
|
14
|
+
* const { tokens, policy } = decisionthemesAdapter({
|
|
15
|
+
* vt: { ... }, // Value Themes (raw values)
|
|
16
|
+
* dt: { ... } // Decision Themes (formulas)
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // tokens = flat token object for DCV validation
|
|
20
|
+
* // policy = auto-generated constraints from 5-axis model
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface VT {
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}
|
|
26
|
+
export interface DT {
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
}
|
|
29
|
+
export interface DecisionThemesInput {
|
|
30
|
+
vt: VT;
|
|
31
|
+
dt: DT;
|
|
32
|
+
}
|
|
33
|
+
export interface DecisionThemesOutput {
|
|
34
|
+
tokens: Record<string, any>;
|
|
35
|
+
policy?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Transform DecisionThemes (VT+DT) into DCV-compatible tokens
|
|
39
|
+
*
|
|
40
|
+
* @param input - VT (values) + DT (decisions)
|
|
41
|
+
* @returns Flat tokens + optional policy JSON
|
|
42
|
+
*/
|
|
43
|
+
export declare function decisionthemesAdapter(_input: DecisionThemesInput): DecisionThemesOutput;
|
|
44
|
+
//# sourceMappingURL=decisionthemes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisionthemes.d.ts","sourceRoot":"","sources":["decisionthemes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,EAAE;IAEjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,EAAE;IAEjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,EAAE,CAAC;IACP,EAAE,EAAE,EAAE,CAAC;CACR;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,oBAAoB,CASvF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DecisionThemes Adapter (Placeholder)
|
|
3
|
+
*
|
|
4
|
+
* This adapter will integrate with the DecisionThemes framework (coming 2026).
|
|
5
|
+
* It transforms VT (Value Themes) + DT (Decision Themes) into flat tokens for DCV validation.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.decisionthemes.com
|
|
8
|
+
* @see docs/Adapters.md for implementation details
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { decisionthemesAdapter } from './adapters/decisionthemes.js';
|
|
13
|
+
*
|
|
14
|
+
* const { tokens, policy } = decisionthemesAdapter({
|
|
15
|
+
* vt: { ... }, // Value Themes (raw values)
|
|
16
|
+
* dt: { ... } // Decision Themes (formulas)
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // tokens = flat token object for DCV validation
|
|
20
|
+
* // policy = auto-generated constraints from 5-axis model
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Transform DecisionThemes (VT+DT) into DCV-compatible tokens
|
|
25
|
+
*
|
|
26
|
+
* @param input - VT (values) + DT (decisions)
|
|
27
|
+
* @returns Flat tokens + optional policy JSON
|
|
28
|
+
*/
|
|
29
|
+
export function decisionthemesAdapter(_input) {
|
|
30
|
+
// TODO: Implement when DecisionThemes integration is ready
|
|
31
|
+
// This will call the DecisionThemes resolver/compute engine to process _input
|
|
32
|
+
throw new Error('DecisionThemes adapter not yet implemented. ' +
|
|
33
|
+
'This is a placeholder for future integration with the DecisionThemes framework. ' +
|
|
34
|
+
'See https://www.decisionthemes.com for updates.');
|
|
35
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DecisionThemes Adapter (Placeholder)
|
|
3
|
+
*
|
|
4
|
+
* This adapter will integrate with the DecisionThemes framework (coming 2026).
|
|
5
|
+
* It transforms VT (Value Themes) + DT (Decision Themes) into flat tokens for DCV validation.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.decisionthemes.com
|
|
8
|
+
* @see docs/Adapters.md for implementation details
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { decisionthemesAdapter } from './adapters/decisionthemes.js';
|
|
13
|
+
*
|
|
14
|
+
* const { tokens, policy } = decisionthemesAdapter({
|
|
15
|
+
* vt: { ... }, // Value Themes (raw values)
|
|
16
|
+
* dt: { ... } // Decision Themes (formulas)
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // tokens = flat token object for DCV validation
|
|
20
|
+
* // policy = auto-generated constraints from 5-axis model
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export interface VT {
|
|
25
|
+
// Value Themes - raw design values
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DT {
|
|
30
|
+
// Decision Themes - formulas and decision mappings
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DecisionThemesInput {
|
|
35
|
+
vt: VT;
|
|
36
|
+
dt: DT;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DecisionThemesOutput {
|
|
40
|
+
tokens: Record<string, any>;
|
|
41
|
+
policy?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Transform DecisionThemes (VT+DT) into DCV-compatible tokens
|
|
46
|
+
*
|
|
47
|
+
* @param input - VT (values) + DT (decisions)
|
|
48
|
+
* @returns Flat tokens + optional policy JSON
|
|
49
|
+
*/
|
|
50
|
+
export function decisionthemesAdapter(_input: DecisionThemesInput): DecisionThemesOutput {
|
|
51
|
+
// TODO: Implement when DecisionThemes integration is ready
|
|
52
|
+
// This will call the DecisionThemes resolver/compute engine to process _input
|
|
53
|
+
|
|
54
|
+
throw new Error(
|
|
55
|
+
'DecisionThemes adapter not yet implemented. ' +
|
|
56
|
+
'This is a placeholder for future integration with the DecisionThemes framework. ' +
|
|
57
|
+
'See https://www.decisionthemes.com for updates.'
|
|
58
|
+
);
|
|
59
|
+
}
|
package/cli/commands/build.js
CHANGED
|
@@ -6,7 +6,7 @@ import { emitJSON } from '../../adapters/json.js';
|
|
|
6
6
|
import { emitJS } from '../../adapters/js.js';
|
|
7
7
|
export async function buildCommand(options) {
|
|
8
8
|
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
9
|
-
const tokens = loadTokensWithBreakpoint();
|
|
9
|
+
const tokens = loadTokensWithBreakpoint(undefined, options.tokens);
|
|
10
10
|
const { flat } = flattenTokens(tokens);
|
|
11
11
|
let allValues = Object.fromEntries(Object.values(flat).map(t => [t.id, t.value]));
|
|
12
12
|
if (options.theme) {
|
package/cli/commands/build.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { BuildOptions } from '../types.js';
|
|
|
8
8
|
|
|
9
9
|
export async function buildCommand(options: BuildOptions & { [k: string]: any }): Promise<void> {
|
|
10
10
|
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
11
|
-
const tokens = loadTokensWithBreakpoint();
|
|
11
|
+
const tokens = loadTokensWithBreakpoint(undefined, options.tokens);
|
|
12
12
|
const { flat } = flattenTokens(tokens);
|
|
13
13
|
let allValues = Object.fromEntries(Object.values(flat).map(t => [t.id, (t as FlatToken).value]));
|
|
14
14
|
if (options.theme) {
|
package/cli/commands/graph.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { flattenTokens } from '../../core/flatten.js';
|
|
3
3
|
import { exportGraphImage } from '../../core/image-export.js';
|
|
4
|
-
import {
|
|
4
|
+
import { setupConstraints } from '../constraint-registry.js';
|
|
5
5
|
import { loadConfig } from '../config.js';
|
|
6
6
|
// Local helper for non-poset dependency graphs
|
|
7
7
|
function generateDependencyGraph(edges, format) {
|
|
@@ -100,7 +100,7 @@ export async function graphCommand(options) {
|
|
|
100
100
|
let edgeLabels;
|
|
101
101
|
if (onlyViolations || highlightViolations || labelViolations) {
|
|
102
102
|
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
103
|
-
const tokens = loadTokensWithBreakpoint(breakpoint);
|
|
103
|
+
const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens);
|
|
104
104
|
const { flattenTokens } = await import('../../core/flatten.js');
|
|
105
105
|
const { Engine } = await import('../../core/engine.js');
|
|
106
106
|
const { MonotonicPlugin, parseSize } = await import('../../core/constraints/monotonic.js');
|
|
@@ -127,7 +127,7 @@ export async function graphCommand(options) {
|
|
|
127
127
|
if (cfgRes.ok) {
|
|
128
128
|
const config = cfgRes.value;
|
|
129
129
|
const knownIds = new Set(Object.keys(flat));
|
|
130
|
-
|
|
130
|
+
setupConstraints(engine, { config, bp: breakpoint }, { knownIds });
|
|
131
131
|
const runtimeIssues = engine.evaluate(allIdsInHasse);
|
|
132
132
|
issues.push(...runtimeIssues);
|
|
133
133
|
}
|
|
@@ -211,7 +211,7 @@ export async function graphCommand(options) {
|
|
|
211
211
|
}
|
|
212
212
|
for (const breakpoint of plan) {
|
|
213
213
|
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
214
|
-
const tokens = loadTokensWithBreakpoint(breakpoint);
|
|
214
|
+
const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens);
|
|
215
215
|
const { edges } = flattenTokens(tokens);
|
|
216
216
|
let filteredEdges = edges;
|
|
217
217
|
if (options.filter) {
|
package/cli/commands/graph.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import type { GraphOptions } from '../types.js';
|
|
3
3
|
import { flattenTokens, type FlatToken } from '../../core/flatten.js';
|
|
4
4
|
import { exportGraphImage } from '../../core/image-export.js';
|
|
5
|
-
import {
|
|
5
|
+
import { setupConstraints } from '../constraint-registry.js';
|
|
6
6
|
import { loadConfig } from '../config.js';
|
|
7
7
|
|
|
8
8
|
// Local helper for non-poset dependency graphs
|
|
@@ -69,7 +69,7 @@ export async function graphCommand(options: GraphOptions): Promise<void> {
|
|
|
69
69
|
let highlight: { nodes: Set<string>; edges: Set<string>; color?: string } | undefined; let edgeLabels: Map<string,string> | undefined;
|
|
70
70
|
if (onlyViolations || highlightViolations || labelViolations) {
|
|
71
71
|
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
72
|
-
const tokens = loadTokensWithBreakpoint(breakpoint);
|
|
72
|
+
const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens);
|
|
73
73
|
const { flattenTokens } = await import('../../core/flatten.js');
|
|
74
74
|
const { Engine } = await import('../../core/engine.js');
|
|
75
75
|
const { MonotonicPlugin, parseSize } = await import('../../core/constraints/monotonic.js');
|
|
@@ -98,7 +98,7 @@ export async function graphCommand(options: GraphOptions): Promise<void> {
|
|
|
98
98
|
if (cfgRes.ok) {
|
|
99
99
|
const config = cfgRes.value;
|
|
100
100
|
const knownIds = new Set(Object.keys(flat as Record<string, FlatToken>));
|
|
101
|
-
|
|
101
|
+
setupConstraints(engine, { config, bp: breakpoint }, { knownIds });
|
|
102
102
|
const runtimeIssues = engine.evaluate(allIdsInHasse);
|
|
103
103
|
issues.push(...runtimeIssues);
|
|
104
104
|
}
|
|
@@ -170,7 +170,7 @@ export async function graphCommand(options: GraphOptions): Promise<void> {
|
|
|
170
170
|
return; }
|
|
171
171
|
for (const breakpoint of plan) {
|
|
172
172
|
const { loadTokensWithBreakpoint } = await import('../../core/breakpoints.js');
|
|
173
|
-
const tokens = loadTokensWithBreakpoint(breakpoint); const { edges } = flattenTokens(tokens);
|
|
173
|
+
const tokens = loadTokensWithBreakpoint(breakpoint, options.tokens); const { edges } = flattenTokens(tokens);
|
|
174
174
|
let filteredEdges = edges;
|
|
175
175
|
if (options.filter) { const filterRegex = new RegExp(options.filter); filteredEdges = edges.filter(([from,to]) => filterRegex.test(from) || filterRegex.test(to)); }
|
|
176
176
|
const format = options.format || 'json'; const graph = generateDependencyGraph(filteredEdges, format);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["validate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["validate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAOnD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAoM9E"}
|
package/cli/commands/validate.js
CHANGED
|
@@ -5,8 +5,11 @@ import { parseBreakpoints, loadTokensWithBreakpoint, mergeTokens } from '../../c
|
|
|
5
5
|
import { createValidationResult, createValidationReceipt, writeJsonOutput } from '../json-output.js';
|
|
6
6
|
import { readFileSync, existsSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
-
import { setupConstraints } from '../constraint-registry.js';
|
|
8
|
+
import { setupConstraints, collectReferencedIds } from '../constraint-registry.js';
|
|
9
|
+
import { printVersionBanner } from '../version-banner.js';
|
|
9
10
|
export async function validateCommand(_options) {
|
|
11
|
+
// Show version banner (subtle, dimmed)
|
|
12
|
+
printVersionBanner({ quiet: _options.format === 'json' });
|
|
10
13
|
try {
|
|
11
14
|
const bps = parseBreakpoints(process.argv);
|
|
12
15
|
const crossAxisDebug = process.argv.includes('--cross-axis-debug');
|
|
@@ -14,6 +17,20 @@ export async function validateCommand(_options) {
|
|
|
14
17
|
let anyErrors = false;
|
|
15
18
|
let totalErrors = 0;
|
|
16
19
|
let totalWarnings = 0;
|
|
20
|
+
// Coverage tracking for the no-match note (never silently "pass" a file whose
|
|
21
|
+
// tokens are referenced by no active constraint).
|
|
22
|
+
let anyConstraintMatched = false;
|
|
23
|
+
let coverageKnownAll = true;
|
|
24
|
+
let tokenCount = 0;
|
|
25
|
+
// Reconcile the tokens path: the positional `[tokens-path]` is an alias for
|
|
26
|
+
// --tokens. The flag wins; warn on a genuine mismatch. undefined => loader default.
|
|
27
|
+
const flagTokens = _options.tokens;
|
|
28
|
+
const posTokens = _options['tokens-path'];
|
|
29
|
+
if (flagTokens && posTokens && flagTokens !== posTokens) {
|
|
30
|
+
console.error(`warning: both --tokens (${flagTokens}) and positional tokens path (${posTokens}) provided; using --tokens`);
|
|
31
|
+
}
|
|
32
|
+
const tokensPath = flagTokens ?? posTokens;
|
|
33
|
+
const constraintsDir = _options['constraints-dir'] ?? 'themes';
|
|
17
34
|
const argv = process.argv.slice(2);
|
|
18
35
|
const failOnIdx = argv.indexOf('--fail-on');
|
|
19
36
|
const failOn = _options.failOn ?? (failOnIdx >= 0 ? argv[failOnIdx + 1] : 'error');
|
|
@@ -52,7 +69,7 @@ export async function validateCommand(_options) {
|
|
|
52
69
|
const tStartTotal = globalThis.performance.now();
|
|
53
70
|
for (const bp of plan) {
|
|
54
71
|
const tStart = globalThis.performance.now();
|
|
55
|
-
let tokens = loadTokensWithBreakpoint(bp);
|
|
72
|
+
let tokens = loadTokensWithBreakpoint(bp, tokensPath);
|
|
56
73
|
// Optional theme overlay (tokens/themes/<name>.json), mirroring build behavior
|
|
57
74
|
if (_options.theme) {
|
|
58
75
|
const themePath = join('tokens/themes', `${_options.theme}.json`);
|
|
@@ -75,7 +92,20 @@ export async function validateCommand(_options) {
|
|
|
75
92
|
const engine = new Engine(init, edges);
|
|
76
93
|
const knownIds = new Set(Object.keys(init));
|
|
77
94
|
// Discover and attach all constraints via centralized registry
|
|
78
|
-
setupConstraints(engine, { config, bp, constraintsDir
|
|
95
|
+
const sources = setupConstraints(engine, { config, bp, constraintsDir }, { knownIds, crossAxisDebug });
|
|
96
|
+
// Track whether any active constraint actually references a token in this file.
|
|
97
|
+
const coverage = collectReferencedIds(sources);
|
|
98
|
+
if (!coverage.coverageKnown)
|
|
99
|
+
coverageKnownAll = false;
|
|
100
|
+
tokenCount = Math.max(tokenCount, knownIds.size);
|
|
101
|
+
if (!anyConstraintMatched) {
|
|
102
|
+
for (const id of coverage.ids) {
|
|
103
|
+
if (knownIds.has(id)) {
|
|
104
|
+
anyConstraintMatched = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
79
109
|
const allIds = new Set(Object.keys(init));
|
|
80
110
|
const issues = engine.evaluate(allIds);
|
|
81
111
|
const errs = issues.filter((i) => i.level === 'error');
|
|
@@ -101,6 +131,14 @@ export async function validateCommand(_options) {
|
|
|
101
131
|
}
|
|
102
132
|
}
|
|
103
133
|
const totalMs = globalThis.performance.now() - tStartTotal;
|
|
134
|
+
// No-match note: tokens were validated but no active constraint referenced any
|
|
135
|
+
// of them. Surfaced loudly (stderr + JSON `note`) so a foreign file can never
|
|
136
|
+
// silently report "0 errors" when nothing was actually checked.
|
|
137
|
+
const noMatchNote = (tokenCount > 0 && coverageKnownAll && !anyConstraintMatched)
|
|
138
|
+
? `No active constraint references any of the ${tokenCount} validated token(s) — nothing was checked. Define constraints in dcv.config.json (constraints.wcag / constraints.thresholds) or point --constraints-dir at your order/cross-axis files.`
|
|
139
|
+
: undefined;
|
|
140
|
+
if (noMatchNote)
|
|
141
|
+
console.error(`note: ${noMatchNote}`);
|
|
104
142
|
// Append aggregate total row if multiple scopes and not already added
|
|
105
143
|
if (rows.length > 1) {
|
|
106
144
|
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 });
|
|
@@ -119,11 +157,10 @@ export async function validateCommand(_options) {
|
|
|
119
157
|
}
|
|
120
158
|
// Handle JSON output mode
|
|
121
159
|
if (outputFormat === 'json') {
|
|
122
|
-
const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion);
|
|
160
|
+
const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion, noMatchNote);
|
|
123
161
|
// If receipt requested, generate full receipt
|
|
124
162
|
if (_options.receipt) {
|
|
125
|
-
const tokensFile =
|
|
126
|
-
const constraintsDir = 'themes';
|
|
163
|
+
const tokensFile = tokensPath ?? 'tokens/tokens.example.json';
|
|
127
164
|
const receipt = createValidationReceipt(result, tokensFile, constraintsDir, bps[0], failOn);
|
|
128
165
|
writeJsonOutput(receipt, _options.receipt);
|
|
129
166
|
}
|
package/cli/commands/validate.ts
CHANGED
|
@@ -7,14 +7,31 @@ import type { ValidateOptions } from '../types.js';
|
|
|
7
7
|
import { createValidationResult, createValidationReceipt, writeJsonOutput } from '../json-output.js';
|
|
8
8
|
import { readFileSync, existsSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
import { setupConstraints } from '../constraint-registry.js';
|
|
10
|
+
import { setupConstraints, collectReferencedIds } from '../constraint-registry.js';
|
|
11
|
+
import { printVersionBanner } from '../version-banner.js';
|
|
11
12
|
|
|
12
13
|
export async function validateCommand(_options: ValidateOptions): Promise<void> {
|
|
14
|
+
// Show version banner (subtle, dimmed)
|
|
15
|
+
printVersionBanner({ quiet: _options.format === 'json' });
|
|
16
|
+
|
|
13
17
|
try {
|
|
14
18
|
const bps = parseBreakpoints(process.argv);
|
|
15
19
|
const crossAxisDebug = process.argv.includes('--cross-axis-debug');
|
|
16
20
|
const plan: (Breakpoint | undefined)[] = bps.length ? bps : [undefined];
|
|
17
21
|
let anyErrors = false; let totalErrors = 0; let totalWarnings = 0;
|
|
22
|
+
// Coverage tracking for the no-match note (never silently "pass" a file whose
|
|
23
|
+
// tokens are referenced by no active constraint).
|
|
24
|
+
let anyConstraintMatched = false; let coverageKnownAll = true; let tokenCount = 0;
|
|
25
|
+
|
|
26
|
+
// Reconcile the tokens path: the positional `[tokens-path]` is an alias for
|
|
27
|
+
// --tokens. The flag wins; warn on a genuine mismatch. undefined => loader default.
|
|
28
|
+
const flagTokens = _options.tokens;
|
|
29
|
+
const posTokens = _options['tokens-path'];
|
|
30
|
+
if (flagTokens && posTokens && flagTokens !== posTokens) {
|
|
31
|
+
console.error(`warning: both --tokens (${flagTokens}) and positional tokens path (${posTokens}) provided; using --tokens`);
|
|
32
|
+
}
|
|
33
|
+
const tokensPath = flagTokens ?? posTokens;
|
|
34
|
+
const constraintsDir = _options['constraints-dir'] ?? 'themes';
|
|
18
35
|
const argv = process.argv.slice(2);
|
|
19
36
|
const failOnIdx = argv.indexOf('--fail-on');
|
|
20
37
|
type FailOn = 'off' | 'warn' | 'error';
|
|
@@ -52,7 +69,7 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
|
|
|
52
69
|
const tStartTotal = globalThis.performance.now();
|
|
53
70
|
for (const bp of plan) {
|
|
54
71
|
const tStart = globalThis.performance.now();
|
|
55
|
-
let tokens: TokenNode = loadTokensWithBreakpoint(bp);
|
|
72
|
+
let tokens: TokenNode = loadTokensWithBreakpoint(bp, tokensPath);
|
|
56
73
|
// Optional theme overlay (tokens/themes/<name>.json), mirroring build behavior
|
|
57
74
|
if (_options.theme) {
|
|
58
75
|
const themePath = join('tokens/themes', `${_options.theme}.json`);
|
|
@@ -75,12 +92,22 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
|
|
|
75
92
|
const knownIds = new Set(Object.keys(init));
|
|
76
93
|
|
|
77
94
|
// Discover and attach all constraints via centralized registry
|
|
78
|
-
setupConstraints(
|
|
95
|
+
const sources = setupConstraints(
|
|
79
96
|
engine,
|
|
80
|
-
{ config, bp, constraintsDir
|
|
97
|
+
{ config, bp, constraintsDir },
|
|
81
98
|
{ knownIds, crossAxisDebug },
|
|
82
99
|
);
|
|
83
100
|
|
|
101
|
+
// Track whether any active constraint actually references a token in this file.
|
|
102
|
+
const coverage = collectReferencedIds(sources);
|
|
103
|
+
if (!coverage.coverageKnown) coverageKnownAll = false;
|
|
104
|
+
tokenCount = Math.max(tokenCount, knownIds.size);
|
|
105
|
+
if (!anyConstraintMatched) {
|
|
106
|
+
for (const id of coverage.ids) {
|
|
107
|
+
if (knownIds.has(id)) { anyConstraintMatched = true; break; }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
const allIds = new Set(Object.keys(init));
|
|
85
112
|
const issues = engine.evaluate(allIds);
|
|
86
113
|
const errs = issues.filter((i: ConstraintIssue) => i.level === 'error');
|
|
@@ -103,6 +130,13 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
|
|
|
103
130
|
}
|
|
104
131
|
}
|
|
105
132
|
const totalMs = globalThis.performance.now() - tStartTotal;
|
|
133
|
+
// No-match note: tokens were validated but no active constraint referenced any
|
|
134
|
+
// of them. Surfaced loudly (stderr + JSON `note`) so a foreign file can never
|
|
135
|
+
// silently report "0 errors" when nothing was actually checked.
|
|
136
|
+
const noMatchNote = (tokenCount > 0 && coverageKnownAll && !anyConstraintMatched)
|
|
137
|
+
? `No active constraint references any of the ${tokenCount} validated token(s) — nothing was checked. Define constraints in dcv.config.json (constraints.wcag / constraints.thresholds) or point --constraints-dir at your order/cross-axis files.`
|
|
138
|
+
: undefined;
|
|
139
|
+
if (noMatchNote) console.error(`note: ${noMatchNote}`);
|
|
106
140
|
// Append aggregate total row if multiple scopes and not already added
|
|
107
141
|
if (rows.length > 1) {
|
|
108
142
|
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 });
|
|
@@ -121,12 +155,11 @@ export async function validateCommand(_options: ValidateOptions): Promise<void>
|
|
|
121
155
|
|
|
122
156
|
// Handle JSON output mode
|
|
123
157
|
if (outputFormat === 'json') {
|
|
124
|
-
const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion);
|
|
125
|
-
|
|
158
|
+
const result = createValidationResult(allErrors, allWarnings, totalMs, engineVersion, noMatchNote);
|
|
159
|
+
|
|
126
160
|
// If receipt requested, generate full receipt
|
|
127
161
|
if (_options.receipt) {
|
|
128
|
-
const tokensFile =
|
|
129
|
-
const constraintsDir = 'themes';
|
|
162
|
+
const tokensFile = tokensPath ?? 'tokens/tokens.example.json';
|
|
130
163
|
const receipt = createValidationReceipt(result, tokensFile, constraintsDir, bps[0], failOn);
|
|
131
164
|
writeJsonOutput(receipt, _options.receipt);
|
|
132
165
|
} else {
|