design-constraint-validator 1.0.0 → 1.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.
Files changed (116) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +215 -659
  3. package/adapters/README.md +46 -46
  4. package/adapters/css.ts +116 -116
  5. package/adapters/js.ts +14 -14
  6. package/adapters/json.ts +45 -45
  7. package/cli/build-css.ts +32 -32
  8. package/cli/commands/build.ts +65 -65
  9. package/cli/commands/graph.d.ts.map +1 -1
  10. package/cli/commands/graph.js +26 -10
  11. package/cli/commands/graph.ts +180 -137
  12. package/cli/commands/index.ts +7 -7
  13. package/cli/commands/patch-apply.ts +80 -80
  14. package/cli/commands/patch.ts +22 -22
  15. package/cli/commands/set.d.ts.map +1 -1
  16. package/cli/commands/set.js +12 -4
  17. package/cli/commands/set.ts +239 -225
  18. package/cli/commands/utils.ts +50 -50
  19. package/cli/commands/validate.d.ts.map +1 -1
  20. package/cli/commands/validate.js +86 -33
  21. package/cli/commands/validate.ts +176 -115
  22. package/cli/commands/why.d.ts.map +1 -1
  23. package/cli/commands/why.js +86 -20
  24. package/cli/commands/why.ts +158 -46
  25. package/cli/config-schema.ts +27 -27
  26. package/cli/config.ts +35 -35
  27. package/cli/constraint-registry.d.ts +101 -0
  28. package/cli/constraint-registry.d.ts.map +1 -0
  29. package/cli/constraint-registry.js +225 -0
  30. package/cli/constraint-registry.ts +304 -0
  31. package/cli/constraints-loader.d.ts +30 -0
  32. package/cli/constraints-loader.d.ts.map +1 -0
  33. package/cli/constraints-loader.js +58 -0
  34. package/cli/constraints-loader.ts +83 -0
  35. package/cli/cross-axis-loader.d.ts +91 -0
  36. package/cli/cross-axis-loader.d.ts.map +1 -0
  37. package/cli/cross-axis-loader.js +222 -0
  38. package/cli/cross-axis-loader.ts +289 -0
  39. package/cli/dcv.js +4 -0
  40. package/cli/dcv.ts +111 -107
  41. package/cli/engine-helpers.d.ts +33 -0
  42. package/cli/engine-helpers.d.ts.map +1 -1
  43. package/cli/engine-helpers.js +87 -22
  44. package/cli/engine-helpers.ts +133 -61
  45. package/cli/graph-poset.ts +74 -74
  46. package/cli/json-output.d.ts +64 -0
  47. package/cli/json-output.d.ts.map +1 -0
  48. package/cli/json-output.js +107 -0
  49. package/cli/json-output.ts +177 -0
  50. package/cli/result.ts +27 -27
  51. package/cli/run.ts +54 -54
  52. package/cli/smoke-test.ts +40 -40
  53. package/cli/types.d.ts +6 -0
  54. package/cli/types.d.ts.map +1 -1
  55. package/cli/types.ts +84 -78
  56. package/core/breakpoints.ts +50 -50
  57. package/core/cli-format.ts +31 -31
  58. package/core/color.ts +148 -148
  59. package/core/constraints/cross-axis.ts +114 -114
  60. package/core/constraints/monotonic-lightness.ts +38 -38
  61. package/core/constraints/monotonic.ts +74 -74
  62. package/core/constraints/threshold.ts +43 -43
  63. package/core/constraints/wcag.ts +70 -70
  64. package/core/cross-axis-config.d.ts +29 -0
  65. package/core/cross-axis-config.d.ts.map +1 -1
  66. package/core/cross-axis-config.js +29 -0
  67. package/core/cross-axis-config.ts +181 -151
  68. package/core/engine.d.ts +95 -0
  69. package/core/engine.d.ts.map +1 -1
  70. package/core/engine.js +22 -0
  71. package/core/engine.ts +167 -65
  72. package/core/flatten.ts +116 -116
  73. package/core/image-export.ts +48 -48
  74. package/core/index.d.ts +9 -30
  75. package/core/index.d.ts.map +1 -1
  76. package/core/index.js +7 -54
  77. package/core/index.ts +10 -72
  78. package/core/patch.ts +134 -134
  79. package/core/poset.ts +311 -311
  80. package/core/why.ts +63 -63
  81. package/package.json +96 -90
  82. package/themes/color.lg.order.json +15 -15
  83. package/themes/color.md.order.json +15 -15
  84. package/themes/color.order.json +15 -15
  85. package/themes/color.sm.order.json +15 -15
  86. package/themes/cross-axis.rules.json +35 -35
  87. package/themes/cross-axis.sm.rules.json +12 -12
  88. package/themes/layout.lg.order.json +18 -18
  89. package/themes/layout.md.order.json +18 -18
  90. package/themes/layout.order.json +18 -18
  91. package/themes/layout.sm.order.json +18 -18
  92. package/themes/spacing.order.json +14 -14
  93. package/themes/typography.lg.order.json +15 -15
  94. package/themes/typography.md.order.json +15 -15
  95. package/themes/typography.order.json +15 -15
  96. package/themes/typography.sm.order.json +15 -15
  97. package/dist/test-overrides-removal.json +0 -4
  98. package/dist/tmp.patch.json +0 -35
  99. package/tokens/overrides/base.json +0 -22
  100. package/tokens/overrides/lg.json +0 -20
  101. package/tokens/overrides/md.json +0 -16
  102. package/tokens/overrides/sm.json +0 -16
  103. package/tokens/overrides/viol.color.json +0 -6
  104. package/tokens/overrides/viol.typography.json +0 -6
  105. package/tokens/tokens.demo-violations.json +0 -116
  106. package/tokens/tokens.example.json +0 -128
  107. package/tokens/tokens.json +0 -67
  108. package/tokens/tokens.multi-violations.json +0 -21
  109. package/tokens/tokens.schema.d.ts +0 -2298
  110. package/tokens/tokens.schema.d.ts.map +0 -1
  111. package/tokens/tokens.schema.js +0 -148
  112. package/tokens/tokens.schema.ts +0 -196
  113. package/tokens/tokens.test.json +0 -38
  114. package/tokens/tokens.touch-violation.json +0 -8
  115. package/tokens/typography.classes.css +0 -11
  116. 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
+ }