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
@@ -1,80 +1,80 @@
1
- import { loadTokens, outputResult } from './utils.js';
2
- import type { PatchApplyOptions } from '../types.js';
3
- import type { TokenNode } from '../../core/flatten.js';
4
- import fs from 'node:fs';
5
- import { flattenTokens } from '../../core/flatten.js';
6
- import { createHash } from 'node:crypto';
7
-
8
- interface PatchDocumentV1 {
9
- version: 1;
10
- changes: Array<{ id: string; from: any; to: any; type: 'modify'|'add'|'remove' }>;
11
- patch: Record<string, any>;
12
- baseTokensHash?: string;
13
- }
14
-
15
- function applyChange(root: any, id: string, to: any, type: 'modify'|'add'|'remove') {
16
- const parts = id.split('.');
17
- let cur: any = root;
18
- for (let i = 0; i < parts.length; i++) {
19
- const p = parts[i];
20
- if (i === parts.length - 1) {
21
- if (type === 'remove') {
22
- if (cur[p] && typeof cur[p] === 'object') {
23
- delete cur[p].$value; // delete leaf value
24
- }
25
- } else {
26
- if (!cur[p] || typeof cur[p] !== 'object') cur[p] = {};
27
- cur[p].$value = to;
28
- }
29
- } else {
30
- if (!cur[p] || typeof cur[p] !== 'object') cur[p] = {};
31
- cur = cur[p];
32
- }
33
- }
34
- }
35
-
36
- export async function patchApplyCommand(opts: PatchApplyOptions): Promise<void> {
37
- const tokens: TokenNode = loadTokens(opts.tokens || 'tokens/tokens.example.json');
38
- // Compute current base tokens hash for drift detection (same logic as buildPatch)
39
- function computeBaseHash(toks: TokenNode): string {
40
- const flat = flattenTokens(JSON.parse(JSON.stringify(toks))).flat as Record<string, any>;
41
- const values: Record<string, any> = {};
42
- Object.keys(flat).sort().forEach(id => { values[id] = flat[id]?.value; });
43
- // Keep deterministic ordering
44
- const ordered = Object.keys(values).sort().reduce((acc, k) => { acc[k] = values[k]; return acc; }, {} as Record<string, any>);
45
- return createHash('sha256').update(JSON.stringify(ordered)).digest('hex');
46
- }
47
- // Parse patch
48
- let patchDoc: PatchDocumentV1;
49
- if (fs.existsSync(opts.patch)) {
50
- patchDoc = JSON.parse(fs.readFileSync(opts.patch, 'utf8'));
51
- } else if (opts.patch.trim().startsWith('{')) {
52
- patchDoc = JSON.parse(opts.patch);
53
- } else {
54
- throw new Error(`Patch not found: ${opts.patch}`);
55
- }
56
- if (patchDoc.version !== 1) throw new Error('Unsupported patch version');
57
- if (patchDoc.baseTokensHash) {
58
- const currentHash = computeBaseHash(tokens);
59
- if (currentHash !== patchDoc.baseTokensHash) {
60
- console.warn(`⚠ Base tokens hash mismatch. Patch built against ${patchDoc.baseTokensHash} but current base is ${currentHash}. Proceeding (use --dry-run to inspect first).`);
61
- }
62
- }
63
-
64
- // Apply changes
65
- for (const c of patchDoc.changes) {
66
- applyChange(tokens, c.id, c.to, c.type);
67
- }
68
-
69
- if (opts.dryRun) {
70
- outputResult(tokens, 'json');
71
- return;
72
- }
73
-
74
- if (opts.output) {
75
- fs.writeFileSync(opts.output, JSON.stringify(tokens, null, 2));
76
- if (!opts.quiet) console.log(`✔ Patch applied to ${opts.output}`);
77
- } else {
78
- outputResult(tokens, 'json');
79
- }
80
- }
1
+ import { loadTokens, outputResult } from './utils.js';
2
+ import type { PatchApplyOptions } from '../types.js';
3
+ import type { TokenNode } from '../../core/flatten.js';
4
+ import fs from 'node:fs';
5
+ import { flattenTokens } from '../../core/flatten.js';
6
+ import { createHash } from 'node:crypto';
7
+
8
+ interface PatchDocumentV1 {
9
+ version: 1;
10
+ changes: Array<{ id: string; from: any; to: any; type: 'modify'|'add'|'remove' }>;
11
+ patch: Record<string, any>;
12
+ baseTokensHash?: string;
13
+ }
14
+
15
+ function applyChange(root: any, id: string, to: any, type: 'modify'|'add'|'remove') {
16
+ const parts = id.split('.');
17
+ let cur: any = root;
18
+ for (let i = 0; i < parts.length; i++) {
19
+ const p = parts[i];
20
+ if (i === parts.length - 1) {
21
+ if (type === 'remove') {
22
+ if (cur[p] && typeof cur[p] === 'object') {
23
+ delete cur[p].$value; // delete leaf value
24
+ }
25
+ } else {
26
+ if (!cur[p] || typeof cur[p] !== 'object') cur[p] = {};
27
+ cur[p].$value = to;
28
+ }
29
+ } else {
30
+ if (!cur[p] || typeof cur[p] !== 'object') cur[p] = {};
31
+ cur = cur[p];
32
+ }
33
+ }
34
+ }
35
+
36
+ export async function patchApplyCommand(opts: PatchApplyOptions): Promise<void> {
37
+ const tokens: TokenNode = loadTokens(opts.tokens || 'tokens/tokens.example.json');
38
+ // Compute current base tokens hash for drift detection (same logic as buildPatch)
39
+ function computeBaseHash(toks: TokenNode): string {
40
+ const flat = flattenTokens(JSON.parse(JSON.stringify(toks))).flat as Record<string, any>;
41
+ const values: Record<string, any> = {};
42
+ Object.keys(flat).sort().forEach(id => { values[id] = flat[id]?.value; });
43
+ // Keep deterministic ordering
44
+ const ordered = Object.keys(values).sort().reduce((acc, k) => { acc[k] = values[k]; return acc; }, {} as Record<string, any>);
45
+ return createHash('sha256').update(JSON.stringify(ordered)).digest('hex');
46
+ }
47
+ // Parse patch
48
+ let patchDoc: PatchDocumentV1;
49
+ if (fs.existsSync(opts.patch)) {
50
+ patchDoc = JSON.parse(fs.readFileSync(opts.patch, 'utf8'));
51
+ } else if (opts.patch.trim().startsWith('{')) {
52
+ patchDoc = JSON.parse(opts.patch);
53
+ } else {
54
+ throw new Error(`Patch not found: ${opts.patch}`);
55
+ }
56
+ if (patchDoc.version !== 1) throw new Error('Unsupported patch version');
57
+ if (patchDoc.baseTokensHash) {
58
+ const currentHash = computeBaseHash(tokens);
59
+ if (currentHash !== patchDoc.baseTokensHash) {
60
+ console.warn(`⚠ Base tokens hash mismatch. Patch built against ${patchDoc.baseTokensHash} but current base is ${currentHash}. Proceeding (use --dry-run to inspect first).`);
61
+ }
62
+ }
63
+
64
+ // Apply changes
65
+ for (const c of patchDoc.changes) {
66
+ applyChange(tokens, c.id, c.to, c.type);
67
+ }
68
+
69
+ if (opts.dryRun) {
70
+ outputResult(tokens, 'json');
71
+ return;
72
+ }
73
+
74
+ if (opts.output) {
75
+ fs.writeFileSync(opts.output, JSON.stringify(tokens, null, 2));
76
+ if (!opts.quiet) console.log(`✔ Patch applied to ${opts.output}`);
77
+ } else {
78
+ outputResult(tokens, 'json');
79
+ }
80
+ }
@@ -1,22 +1,22 @@
1
- import type { TokenNode } from '../../core/flatten.js';
2
- import { loadTokens, outputResult } from './utils.js';
3
- import { buildPatch } from '../../core/patch.js';
4
- import type { PatchOptions } from '../types.js';
5
-
6
- export async function patchCommand(opts: PatchOptions): Promise<void> {
7
- const tokens: TokenNode = loadTokens(opts.tokens || 'tokens/tokens.example.json');
8
- // For now just accept a flat overrides JSON file if provided
9
- let overrides: Record<string, any> | undefined;
10
- if (opts.overrides) {
11
- const fs = await import('node:fs');
12
- if (fs.existsSync(opts.overrides)) {
13
- overrides = JSON.parse(fs.readFileSync(opts.overrides, 'utf8'));
14
- } else if (opts.overrides.startsWith('{')) {
15
- overrides = JSON.parse(opts.overrides);
16
- } else {
17
- throw new Error(`Overrides not found: ${opts.overrides}`);
18
- }
19
- }
20
- const patchDoc = buildPatch({ tokens, overrides, baseFile: opts.tokens });
21
- outputResult(patchDoc, opts.format || 'json', opts.output);
22
- }
1
+ import type { TokenNode } from '../../core/flatten.js';
2
+ import { loadTokens, outputResult } from './utils.js';
3
+ import { buildPatch } from '../../core/patch.js';
4
+ import type { PatchOptions } from '../types.js';
5
+
6
+ export async function patchCommand(opts: PatchOptions): Promise<void> {
7
+ const tokens: TokenNode = loadTokens(opts.tokens || 'tokens/tokens.example.json');
8
+ // For now just accept a flat overrides JSON file if provided
9
+ let overrides: Record<string, any> | undefined;
10
+ if (opts.overrides) {
11
+ const fs = await import('node:fs');
12
+ if (fs.existsSync(opts.overrides)) {
13
+ overrides = JSON.parse(fs.readFileSync(opts.overrides, 'utf8'));
14
+ } else if (opts.overrides.startsWith('{')) {
15
+ overrides = JSON.parse(opts.overrides);
16
+ } else {
17
+ throw new Error(`Overrides not found: ${opts.overrides}`);
18
+ }
19
+ }
20
+ const patchDoc = buildPatch({ tokens, overrides, baseFile: opts.tokens });
21
+ outputResult(patchDoc, opts.format || 'json', opts.output);
22
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["set.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiB,UAAU,EAAe,MAAM,aAAa,CAAC;AAkE1E,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAyJnE"}
1
+ {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["set.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiB,UAAU,EAAe,MAAM,aAAa,CAAC;AAmE1E,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsKnE"}
@@ -1,9 +1,10 @@
1
1
  import { join } from 'node:path';
2
2
  import { readFileSync, existsSync } from 'node:fs';
3
3
  import { loadConfig } from '../config.js';
4
- import { createEngine } from '../engine-helpers.js';
4
+ import { Engine } from '../../core/engine.js';
5
5
  import { flattenTokens } from '../../core/flatten.js';
6
6
  import { loadTokens, outputResult } from './utils.js';
7
+ import { setupConstraints } from '../constraint-registry.js';
7
8
  // Lightweight suggestion helpers (kept local – why command uses core formatter instead)
8
9
  function levenshtein(a, b) {
9
10
  const al = a.length, bl = b.length;
@@ -81,9 +82,16 @@ export async function setCommand(options) {
81
82
  }
82
83
  const config = cfgRes.value;
83
84
  const tokens = loadTokens(tokensPath);
84
- const engine = createEngine(tokens, config);
85
- const { flat: flatAll } = flattenTokens(tokens);
86
- const knownIds = new Set(Object.keys(flatAll));
85
+ // Create engine with flattened tokens
86
+ const { flat, edges } = flattenTokens(tokens);
87
+ const init = {};
88
+ for (const t of Object.values(flat)) {
89
+ init[t.id] = t.value;
90
+ }
91
+ const engine = new Engine(init, edges);
92
+ const knownIds = new Set(Object.keys(init));
93
+ // Discover and attach all constraints via centralized registry
94
+ setupConstraints(engine, { config, constraintsDir: 'themes' }, { knownIds });
87
95
  function ensureKnownOrSuggest(id) {
88
96
  if (!knownIds.has(id)) {
89
97
  const suggestions = suggestIds(id, Array.from(knownIds));