design-constraint-validator 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +229 -659
  3. package/adapters/README.md +46 -46
  4. package/adapters/css.ts +116 -116
  5. package/adapters/decisionthemes.d.ts +44 -0
  6. package/adapters/decisionthemes.d.ts.map +1 -0
  7. package/adapters/decisionthemes.js +35 -0
  8. package/adapters/decisionthemes.ts +59 -0
  9. package/adapters/js.ts +14 -14
  10. package/adapters/json.ts +45 -45
  11. package/cli/build-css.ts +32 -32
  12. package/cli/commands/build.ts +65 -65
  13. package/cli/commands/graph.d.ts.map +1 -1
  14. package/cli/commands/graph.js +26 -10
  15. package/cli/commands/graph.ts +180 -137
  16. package/cli/commands/index.ts +7 -7
  17. package/cli/commands/patch-apply.ts +80 -80
  18. package/cli/commands/patch.ts +22 -22
  19. package/cli/commands/set.d.ts.map +1 -1
  20. package/cli/commands/set.js +12 -4
  21. package/cli/commands/set.ts +239 -225
  22. package/cli/commands/utils.ts +50 -50
  23. package/cli/commands/validate.d.ts.map +1 -1
  24. package/cli/commands/validate.js +89 -33
  25. package/cli/commands/validate.ts +180 -115
  26. package/cli/commands/why.d.ts.map +1 -1
  27. package/cli/commands/why.js +86 -20
  28. package/cli/commands/why.ts +158 -46
  29. package/cli/config-schema.ts +27 -27
  30. package/cli/config.ts +35 -35
  31. package/cli/constraint-registry.d.ts +101 -0
  32. package/cli/constraint-registry.d.ts.map +1 -0
  33. package/cli/constraint-registry.js +225 -0
  34. package/cli/constraint-registry.ts +304 -0
  35. package/cli/constraints-loader.d.ts.map +1 -0
  36. package/cli/cross-axis-loader.d.ts +91 -0
  37. package/cli/cross-axis-loader.d.ts.map +1 -0
  38. package/cli/cross-axis-loader.js +222 -0
  39. package/cli/cross-axis-loader.ts +289 -0
  40. package/cli/dcv.js +4 -0
  41. package/cli/dcv.ts +111 -107
  42. package/cli/engine-helpers.d.ts.map +1 -1
  43. package/cli/graph-poset.ts +74 -74
  44. package/cli/json-output.d.ts +69 -0
  45. package/cli/json-output.d.ts.map +1 -0
  46. package/cli/json-output.js +109 -0
  47. package/cli/json-output.ts +184 -0
  48. package/cli/result.ts +27 -27
  49. package/cli/run.ts +54 -54
  50. package/cli/smoke-test.ts +40 -40
  51. package/cli/types.d.ts +6 -0
  52. package/cli/types.d.ts.map +1 -1
  53. package/cli/types.ts +84 -78
  54. package/cli/version-banner.d.ts +20 -0
  55. package/cli/version-banner.d.ts.map +1 -0
  56. package/cli/version-banner.js +49 -0
  57. package/cli/version-banner.ts +61 -0
  58. package/core/breakpoints.ts +50 -50
  59. package/core/cli-format.ts +31 -31
  60. package/core/color.ts +148 -148
  61. package/core/constraints/cross-axis.ts +114 -114
  62. package/core/constraints/monotonic-lightness.ts +38 -38
  63. package/core/constraints/monotonic.ts +74 -74
  64. package/core/constraints/threshold.ts +43 -43
  65. package/core/constraints/wcag.ts +70 -70
  66. package/core/cross-axis-config.d.ts.map +1 -1
  67. package/core/engine.d.ts +95 -0
  68. package/core/engine.d.ts.map +1 -1
  69. package/core/engine.js +22 -0
  70. package/core/engine.ts +167 -65
  71. package/core/flatten.ts +116 -116
  72. package/core/image-export.ts +48 -48
  73. package/core/index.d.ts +9 -30
  74. package/core/index.d.ts.map +1 -1
  75. package/core/index.js +7 -54
  76. package/core/index.ts +10 -72
  77. package/core/patch.ts +134 -134
  78. package/core/poset.ts +311 -311
  79. package/core/why.ts +63 -63
  80. package/package.json +96 -90
  81. package/themes/color.lg.order.json +15 -15
  82. package/themes/color.md.order.json +15 -15
  83. package/themes/color.order.json +15 -15
  84. package/themes/color.sm.order.json +15 -15
  85. package/themes/cross-axis.rules.json +35 -35
  86. package/themes/cross-axis.sm.rules.json +12 -12
  87. package/themes/layout.lg.order.json +18 -18
  88. package/themes/layout.md.order.json +18 -18
  89. package/themes/layout.order.json +18 -18
  90. package/themes/layout.sm.order.json +18 -18
  91. package/themes/spacing.order.json +14 -14
  92. package/themes/typography.lg.order.json +15 -15
  93. package/themes/typography.md.order.json +15 -15
  94. package/themes/typography.order.json +15 -15
  95. package/themes/typography.sm.order.json +15 -15
  96. package/cli/engine-helpers.d.ts +0 -8
  97. package/cli/engine-helpers.js +0 -70
  98. package/cli/engine-helpers.ts +0 -61
  99. package/core/cross-axis-config.d.ts +0 -5
  100. package/core/cross-axis-config.js +0 -144
  101. package/core/cross-axis-config.ts +0 -152
  102. package/dist/test-overrides-removal.json +0 -4
  103. package/dist/tmp.patch.json +0 -35
  104. package/tokens/overrides/base.json +0 -22
  105. package/tokens/overrides/lg.json +0 -20
  106. package/tokens/overrides/md.json +0 -16
  107. package/tokens/overrides/sm.json +0 -16
  108. package/tokens/overrides/viol.color.json +0 -6
  109. package/tokens/overrides/viol.typography.json +0 -6
  110. package/tokens/tokens.demo-violations.json +0 -116
  111. package/tokens/tokens.example.json +0 -128
  112. package/tokens/tokens.json +0 -67
  113. package/tokens/tokens.multi-violations.json +0 -21
  114. package/tokens/tokens.schema.d.ts +0 -2298
  115. package/tokens/tokens.schema.d.ts.map +0 -1
  116. package/tokens/tokens.schema.js +0 -148
  117. package/tokens/tokens.schema.ts +0 -196
  118. package/tokens/tokens.test.json +0 -38
  119. package/tokens/tokens.touch-violation.json +0 -8
  120. package/tokens/typography.classes.css +0 -11
  121. package/tokens/typography.css +0 -20
package/core/index.js CHANGED
@@ -1,54 +1,7 @@
1
- export class Engine {
2
- values = new Map();
3
- graph = new Map();
4
- plugins = [];
5
- constructor(initValues, edges) {
6
- for (const [k, v] of Object.entries(initValues))
7
- this.values.set(k, v);
8
- for (const [from, to] of edges) {
9
- if (!this.graph.has(from))
10
- this.graph.set(from, new Set());
11
- this.graph.get(from).add(to);
12
- if (!this.graph.has(to))
13
- this.graph.set(to, new Set());
14
- }
15
- }
16
- set(id, value) { this.values.set(id, value); }
17
- get(id) { return this.values.get(id); }
18
- use(plugin) {
19
- this.plugins.push(plugin);
20
- return this;
21
- }
22
- affected(start) {
23
- const seen = new Set();
24
- const stack = [start];
25
- while (stack.length) {
26
- const n = stack.pop();
27
- if (seen.has(n))
28
- continue;
29
- seen.add(n);
30
- for (const d of this.graph.get(n) ?? [])
31
- stack.push(d);
32
- }
33
- seen.delete(start);
34
- return seen;
35
- }
36
- evaluate(ids) {
37
- const candidates = new Set(ids);
38
- const issues = [];
39
- // Run all registered constraint plugins
40
- for (const plugin of this.plugins) {
41
- issues.push(...plugin.evaluate(this, candidates));
42
- }
43
- return issues;
44
- }
45
- /** Apply a single change and return a batch: affected set + issues + patch */
46
- commit(id, value) {
47
- this.set(id, value);
48
- const A = this.affected(id);
49
- const issues = this.evaluate([id, ...A]);
50
- const patch = {};
51
- patch[id] = value; // include dependents if you compute derived values
52
- return { affected: Array.from(A), issues, patch };
53
- }
54
- }
1
+ /**
2
+ * Public API exports for Design Constraint Validator core.
3
+ *
4
+ * This file re-exports the main types and classes from the core engine.
5
+ * See engine.ts for full implementation with Phase 3C enhancements.
6
+ */
7
+ export { Engine } from "./engine.js";
package/core/index.ts CHANGED
@@ -1,72 +1,10 @@
1
- export type TokenId = string; // "dimension.spacing.scale.4"
2
- export type TokenValue = string | number;
3
- export type Graph = Map<TokenId, Set<TokenId>>; // edges: id -> dependents
4
-
5
- export type ConstraintIssue = {
6
- id: TokenId;
7
- rule: string; // "wcag-contrast"
8
- level: "error" | "warn";
9
- message: string;
10
- };
11
-
12
- export type ConstraintPlugin = {
13
- id: string;
14
- evaluate(engine: Engine, candidates: Set<TokenId>): ConstraintIssue[];
15
- };
16
-
17
- export class Engine {
18
- private values = new Map<TokenId, TokenValue>();
19
- private graph: Graph = new Map();
20
- private plugins: ConstraintPlugin[] = [];
21
-
22
- constructor(initValues: Record<TokenId, TokenValue>, edges: Array<[TokenId, TokenId]>) {
23
- for (const [k,v] of Object.entries(initValues)) this.values.set(k, v);
24
- for (const [from,to] of edges) {
25
- if (!this.graph.has(from)) this.graph.set(from, new Set());
26
- this.graph.get(from)!.add(to);
27
- if (!this.graph.has(to)) this.graph.set(to, new Set());
28
- }
29
- }
30
-
31
- set(id: TokenId, value: TokenValue) { this.values.set(id, value); }
32
- get(id: TokenId) { return this.values.get(id); }
33
-
34
- use(plugin: ConstraintPlugin): this {
35
- this.plugins.push(plugin);
36
- return this;
37
- }
38
-
39
- affected(start: TokenId): Set<TokenId> {
40
- const seen = new Set<TokenId>(); const stack = [start];
41
- while (stack.length) {
42
- const n = stack.pop()!;
43
- if (seen.has(n)) continue;
44
- seen.add(n);
45
- for (const d of this.graph.get(n) ?? []) stack.push(d);
46
- }
47
- seen.delete(start);
48
- return seen;
49
- }
50
-
51
- evaluate(ids: Iterable<TokenId>): ConstraintIssue[] {
52
- const candidates = new Set(ids);
53
- const issues: ConstraintIssue[] = [];
54
-
55
- // Run all registered constraint plugins
56
- for (const plugin of this.plugins) {
57
- issues.push(...plugin.evaluate(this, candidates));
58
- }
59
-
60
- return issues;
61
- }
62
-
63
- /** Apply a single change and return a batch: affected set + issues + patch */
64
- commit(id: TokenId, value: TokenValue) {
65
- this.set(id, value);
66
- const A = this.affected(id);
67
- const issues = this.evaluate([id, ...A]);
68
- const patch: Record<TokenId, TokenValue> = {};
69
- patch[id] = value; // include dependents if you compute derived values
70
- return { affected: Array.from(A), issues, patch };
71
- }
72
- }
1
+ /**
2
+ * Public API exports for Design Constraint Validator core.
3
+ *
4
+ * This file re-exports the main types and classes from the core engine.
5
+ * See engine.ts for full implementation with Phase 3C enhancements.
6
+ */
7
+
8
+ export type { TokenId, TokenValue } from "./flatten.js";
9
+ export type { ConstraintIssue, ConstraintPlugin, Graph } from "./engine.js";
10
+ export { Engine } from "./engine.js";
package/core/patch.ts CHANGED
@@ -1,134 +1,134 @@
1
- import { createHash } from 'node:crypto';
2
- import type { TokenNode, TokenValue } from './flatten.js';
3
- import { flattenTokens } from './flatten.js';
4
-
5
- export interface PatchChange {
6
- id: string;
7
- from: TokenValue | null | undefined;
8
- to: TokenValue | null | undefined;
9
- type: 'modify' | 'add' | 'remove';
10
- }
11
-
12
- export interface PatchDocument {
13
- version: 1;
14
- generatedAt: string; // ISO
15
- baseFile?: string;
16
- breakpoint?: string;
17
- changes: PatchChange[];
18
- patch: Record<string, TokenValue | null | undefined>; // id -> to value
19
- hash: string; // sha256 of canonical patch object (patch + ids)
20
- baseTokensHash?: string; // sha256 of canonical flattened base tokens (id -> value)
21
- meta?: Record<string, any>;
22
- }
23
-
24
- export interface BuildPatchOptions {
25
- tokens: TokenNode; // raw hierarchical tokens
26
- baseFile?: string; // filename hint
27
- overrides?: Record<string, any>; // flat override map id->value
28
- breakpoint?: string; // reserved for future responsive diffing
29
- includeUnchanged?: boolean; // debug flag
30
- }
31
-
32
- function canonicalString(obj: any): string {
33
- return JSON.stringify(obj, Object.keys(obj).sort(), 2);
34
- }
35
-
36
- export function applyFlatOverrides(tokens: TokenNode, overrides?: Record<string, any>): void {
37
- if (!overrides) return;
38
- for (const [id, val] of Object.entries(overrides)) {
39
- const parts = id.split('.');
40
- let cur: any = tokens;
41
- for (let i = 0; i < parts.length; i++) {
42
- const p = parts[i];
43
- if (cur == null || typeof cur !== 'object') break;
44
- if (!(p in cur)) break;
45
- if (i === parts.length - 1) {
46
- const leaf = cur[p];
47
- if (leaf && typeof leaf === 'object' && Object.prototype.hasOwnProperty.call(leaf, '$value')) {
48
- if (val === null) {
49
- // Do not delete here; handled later to keep reference resolution intact.
50
- } else {
51
- leaf.$value = val;
52
- }
53
- }
54
- } else {
55
- cur = cur[p];
56
- }
57
- }
58
- }
59
- }
60
-
61
- export function buildPatch(opts: BuildPatchOptions): PatchDocument {
62
- const cloned = JSON.parse(JSON.stringify(opts.tokens));
63
- // Flatten original
64
- const baseFlat = flattenTokens(cloned as any).flat as Record<string, any>;
65
- // Canonical base tokens hash (id -> value) for drift detection when applying patch later
66
- const baseFlatValues: Record<string, any> = {};
67
- Object.keys(baseFlat).sort().forEach(id => { baseFlatValues[id] = baseFlat[id]?.value; });
68
- const baseTokensHash = createHash('sha256').update(canonicalString(baseFlatValues)).digest('hex');
69
- // Apply overrides on a fresh clone for diffing
70
- const modified = JSON.parse(JSON.stringify(opts.tokens));
71
- const removalIds = new Set<string>();
72
- if (opts.overrides) {
73
- for (const [id, v] of Object.entries(opts.overrides)) {
74
- if (v === null) removalIds.add(id);
75
- }
76
- }
77
- applyFlatOverrides(modified as any, opts.overrides);
78
- const modFlat = flattenTokens(modified as any).flat as Record<string, any>;
79
- // Post-process removals: remove from modFlat so diff sees them as missing
80
- for (const id of removalIds) {
81
- delete modFlat[id];
82
- }
83
-
84
- const changes: PatchChange[] = [];
85
- const patch: Record<string, TokenValue | null | undefined> = {};
86
- const visited = new Set<string>();
87
- for (const id of Object.keys(baseFlat)) {
88
- visited.add(id);
89
- const before = baseFlat[id]?.value;
90
- const after = modFlat[id]?.value;
91
- if (removalIds.has(id)) {
92
- // Skip here; removal handled later
93
- continue;
94
- }
95
- if (before !== after) {
96
- changes.push({ id, from: before, to: after, type: 'modify' });
97
- patch[id] = after;
98
- } else if (opts.includeUnchanged) {
99
- changes.push({ id, from: before, to: after, type: 'modify' });
100
- }
101
- }
102
- // Added ids (override referencing unknown id -> treat as add)
103
- if (opts.overrides) {
104
- for (const id of Object.keys(opts.overrides)) {
105
- if (visited.has(id)) continue;
106
- patch[id] = opts.overrides[id];
107
- changes.push({ id, from: null, to: opts.overrides[id], type: 'add' });
108
- }
109
- }
110
- // Removed ids (present in base, absent after overrides application)
111
- for (const id of Object.keys(baseFlat)) {
112
- if (!(id in modFlat)) {
113
- const before = baseFlat[id]?.value;
114
- patch[id] = null;
115
- changes.push({ id, from: before, to: null, type: 'remove' });
116
- }
117
- }
118
- // Sort deterministic
119
- changes.sort((a, b) => a.id.localeCompare(b.id));
120
- const patchSorted: Record<string, any> = {};
121
- Object.keys(patch).sort().forEach(k => { patchSorted[k] = patch[k]; });
122
- const hash = createHash('sha256').update(canonicalString(patchSorted)).digest('hex');
123
- return {
124
- version: 1,
125
- generatedAt: new Date().toISOString(),
126
- baseFile: opts.baseFile,
127
- breakpoint: opts.breakpoint,
128
- changes,
129
- patch: patchSorted,
130
- hash,
131
- baseTokensHash,
132
- meta: { changeCount: changes.length }
133
- };
134
- }
1
+ import { createHash } from 'node:crypto';
2
+ import type { TokenNode, TokenValue } from './flatten.js';
3
+ import { flattenTokens } from './flatten.js';
4
+
5
+ export interface PatchChange {
6
+ id: string;
7
+ from: TokenValue | null | undefined;
8
+ to: TokenValue | null | undefined;
9
+ type: 'modify' | 'add' | 'remove';
10
+ }
11
+
12
+ export interface PatchDocument {
13
+ version: 1;
14
+ generatedAt: string; // ISO
15
+ baseFile?: string;
16
+ breakpoint?: string;
17
+ changes: PatchChange[];
18
+ patch: Record<string, TokenValue | null | undefined>; // id -> to value
19
+ hash: string; // sha256 of canonical patch object (patch + ids)
20
+ baseTokensHash?: string; // sha256 of canonical flattened base tokens (id -> value)
21
+ meta?: Record<string, any>;
22
+ }
23
+
24
+ export interface BuildPatchOptions {
25
+ tokens: TokenNode; // raw hierarchical tokens
26
+ baseFile?: string; // filename hint
27
+ overrides?: Record<string, any>; // flat override map id->value
28
+ breakpoint?: string; // reserved for future responsive diffing
29
+ includeUnchanged?: boolean; // debug flag
30
+ }
31
+
32
+ function canonicalString(obj: any): string {
33
+ return JSON.stringify(obj, Object.keys(obj).sort(), 2);
34
+ }
35
+
36
+ export function applyFlatOverrides(tokens: TokenNode, overrides?: Record<string, any>): void {
37
+ if (!overrides) return;
38
+ for (const [id, val] of Object.entries(overrides)) {
39
+ const parts = id.split('.');
40
+ let cur: any = tokens;
41
+ for (let i = 0; i < parts.length; i++) {
42
+ const p = parts[i];
43
+ if (cur == null || typeof cur !== 'object') break;
44
+ if (!(p in cur)) break;
45
+ if (i === parts.length - 1) {
46
+ const leaf = cur[p];
47
+ if (leaf && typeof leaf === 'object' && Object.prototype.hasOwnProperty.call(leaf, '$value')) {
48
+ if (val === null) {
49
+ // Do not delete here; handled later to keep reference resolution intact.
50
+ } else {
51
+ leaf.$value = val;
52
+ }
53
+ }
54
+ } else {
55
+ cur = cur[p];
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ export function buildPatch(opts: BuildPatchOptions): PatchDocument {
62
+ const cloned = JSON.parse(JSON.stringify(opts.tokens));
63
+ // Flatten original
64
+ const baseFlat = flattenTokens(cloned as any).flat as Record<string, any>;
65
+ // Canonical base tokens hash (id -> value) for drift detection when applying patch later
66
+ const baseFlatValues: Record<string, any> = {};
67
+ Object.keys(baseFlat).sort().forEach(id => { baseFlatValues[id] = baseFlat[id]?.value; });
68
+ const baseTokensHash = createHash('sha256').update(canonicalString(baseFlatValues)).digest('hex');
69
+ // Apply overrides on a fresh clone for diffing
70
+ const modified = JSON.parse(JSON.stringify(opts.tokens));
71
+ const removalIds = new Set<string>();
72
+ if (opts.overrides) {
73
+ for (const [id, v] of Object.entries(opts.overrides)) {
74
+ if (v === null) removalIds.add(id);
75
+ }
76
+ }
77
+ applyFlatOverrides(modified as any, opts.overrides);
78
+ const modFlat = flattenTokens(modified as any).flat as Record<string, any>;
79
+ // Post-process removals: remove from modFlat so diff sees them as missing
80
+ for (const id of removalIds) {
81
+ delete modFlat[id];
82
+ }
83
+
84
+ const changes: PatchChange[] = [];
85
+ const patch: Record<string, TokenValue | null | undefined> = {};
86
+ const visited = new Set<string>();
87
+ for (const id of Object.keys(baseFlat)) {
88
+ visited.add(id);
89
+ const before = baseFlat[id]?.value;
90
+ const after = modFlat[id]?.value;
91
+ if (removalIds.has(id)) {
92
+ // Skip here; removal handled later
93
+ continue;
94
+ }
95
+ if (before !== after) {
96
+ changes.push({ id, from: before, to: after, type: 'modify' });
97
+ patch[id] = after;
98
+ } else if (opts.includeUnchanged) {
99
+ changes.push({ id, from: before, to: after, type: 'modify' });
100
+ }
101
+ }
102
+ // Added ids (override referencing unknown id -> treat as add)
103
+ if (opts.overrides) {
104
+ for (const id of Object.keys(opts.overrides)) {
105
+ if (visited.has(id)) continue;
106
+ patch[id] = opts.overrides[id];
107
+ changes.push({ id, from: null, to: opts.overrides[id], type: 'add' });
108
+ }
109
+ }
110
+ // Removed ids (present in base, absent after overrides application)
111
+ for (const id of Object.keys(baseFlat)) {
112
+ if (!(id in modFlat)) {
113
+ const before = baseFlat[id]?.value;
114
+ patch[id] = null;
115
+ changes.push({ id, from: before, to: null, type: 'remove' });
116
+ }
117
+ }
118
+ // Sort deterministic
119
+ changes.sort((a, b) => a.id.localeCompare(b.id));
120
+ const patchSorted: Record<string, any> = {};
121
+ Object.keys(patch).sort().forEach(k => { patchSorted[k] = patch[k]; });
122
+ const hash = createHash('sha256').update(canonicalString(patchSorted)).digest('hex');
123
+ return {
124
+ version: 1,
125
+ generatedAt: new Date().toISOString(),
126
+ baseFile: opts.baseFile,
127
+ breakpoint: opts.breakpoint,
128
+ changes,
129
+ patch: patchSorted,
130
+ hash,
131
+ baseTokensHash,
132
+ meta: { changeCount: changes.length }
133
+ };
134
+ }