design-constraint-validator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +659 -0
  3. package/adapters/README.md +46 -0
  4. package/adapters/css.d.ts +44 -0
  5. package/adapters/css.d.ts.map +1 -0
  6. package/adapters/css.js +97 -0
  7. package/adapters/css.ts +116 -0
  8. package/adapters/js.d.ts +3 -0
  9. package/adapters/js.d.ts.map +1 -0
  10. package/adapters/js.js +15 -0
  11. package/adapters/js.ts +14 -0
  12. package/adapters/json.d.ts +18 -0
  13. package/adapters/json.d.ts.map +1 -0
  14. package/adapters/json.js +35 -0
  15. package/adapters/json.ts +45 -0
  16. package/cli/build-css.d.ts +2 -0
  17. package/cli/build-css.d.ts.map +1 -0
  18. package/cli/build-css.js +23 -0
  19. package/cli/build-css.ts +32 -0
  20. package/cli/commands/build.d.ts +5 -0
  21. package/cli/commands/build.d.ts.map +1 -0
  22. package/cli/commands/build.js +89 -0
  23. package/cli/commands/build.ts +65 -0
  24. package/cli/commands/graph.d.ts +3 -0
  25. package/cli/commands/graph.d.ts.map +1 -0
  26. package/cli/commands/graph.js +219 -0
  27. package/cli/commands/graph.ts +137 -0
  28. package/cli/commands/index.d.ts +8 -0
  29. package/cli/commands/index.d.ts.map +1 -0
  30. package/cli/commands/index.js +7 -0
  31. package/cli/commands/index.ts +7 -0
  32. package/cli/commands/patch-apply.d.ts +3 -0
  33. package/cli/commands/patch-apply.d.ts.map +1 -0
  34. package/cli/commands/patch-apply.js +75 -0
  35. package/cli/commands/patch-apply.ts +80 -0
  36. package/cli/commands/patch.d.ts +3 -0
  37. package/cli/commands/patch.d.ts.map +1 -0
  38. package/cli/commands/patch.js +21 -0
  39. package/cli/commands/patch.ts +22 -0
  40. package/cli/commands/set.d.ts +3 -0
  41. package/cli/commands/set.d.ts.map +1 -0
  42. package/cli/commands/set.js +286 -0
  43. package/cli/commands/set.ts +225 -0
  44. package/cli/commands/utils.d.ts +4 -0
  45. package/cli/commands/utils.d.ts.map +1 -0
  46. package/cli/commands/utils.js +51 -0
  47. package/cli/commands/utils.ts +50 -0
  48. package/cli/commands/validate.d.ts +3 -0
  49. package/cli/commands/validate.d.ts.map +1 -0
  50. package/cli/commands/validate.js +131 -0
  51. package/cli/commands/validate.ts +115 -0
  52. package/cli/commands/why.d.ts +3 -0
  53. package/cli/commands/why.d.ts.map +1 -0
  54. package/cli/commands/why.js +64 -0
  55. package/cli/commands/why.ts +46 -0
  56. package/cli/config-schema.d.ts +238 -0
  57. package/cli/config-schema.d.ts.map +1 -0
  58. package/cli/config-schema.js +21 -0
  59. package/cli/config-schema.ts +27 -0
  60. package/cli/config.d.ts +4 -0
  61. package/cli/config.d.ts.map +1 -0
  62. package/cli/config.js +37 -0
  63. package/cli/config.ts +35 -0
  64. package/cli/dcv.d.ts +3 -0
  65. package/cli/dcv.d.ts.map +1 -0
  66. package/cli/dcv.js +86 -0
  67. package/cli/dcv.ts +107 -0
  68. package/cli/engine-helpers.d.ts +8 -0
  69. package/cli/engine-helpers.d.ts.map +1 -0
  70. package/cli/engine-helpers.js +70 -0
  71. package/cli/engine-helpers.ts +61 -0
  72. package/cli/graph-poset.d.ts +9 -0
  73. package/cli/graph-poset.d.ts.map +1 -0
  74. package/cli/graph-poset.js +58 -0
  75. package/cli/graph-poset.ts +74 -0
  76. package/cli/index.d.ts +3 -0
  77. package/cli/index.d.ts.map +1 -0
  78. package/cli/index.js +2 -0
  79. package/cli/index.ts +2 -0
  80. package/cli/result.d.ts +17 -0
  81. package/cli/result.d.ts.map +1 -0
  82. package/cli/result.js +29 -0
  83. package/cli/result.ts +27 -0
  84. package/cli/run.d.ts +3 -0
  85. package/cli/run.d.ts.map +1 -0
  86. package/cli/run.js +47 -0
  87. package/cli/run.ts +54 -0
  88. package/cli/smoke-test.d.ts +2 -0
  89. package/cli/smoke-test.d.ts.map +1 -0
  90. package/cli/smoke-test.js +33 -0
  91. package/cli/smoke-test.ts +40 -0
  92. package/cli/types.d.ts +86 -0
  93. package/cli/types.d.ts.map +1 -0
  94. package/cli/types.js +1 -0
  95. package/cli/types.ts +78 -0
  96. package/core/breakpoints.d.ts +12 -0
  97. package/core/breakpoints.d.ts.map +1 -0
  98. package/core/breakpoints.js +48 -0
  99. package/core/breakpoints.ts +50 -0
  100. package/core/cli-format.d.ts +8 -0
  101. package/core/cli-format.d.ts.map +1 -0
  102. package/core/cli-format.js +29 -0
  103. package/core/cli-format.ts +31 -0
  104. package/core/color.d.ts +14 -0
  105. package/core/color.d.ts.map +1 -0
  106. package/core/color.js +136 -0
  107. package/core/color.ts +148 -0
  108. package/core/constraints/cross-axis.d.ts +33 -0
  109. package/core/constraints/cross-axis.d.ts.map +1 -0
  110. package/core/constraints/cross-axis.js +93 -0
  111. package/core/constraints/cross-axis.ts +114 -0
  112. package/core/constraints/monotonic-lightness.d.ts +5 -0
  113. package/core/constraints/monotonic-lightness.d.ts.map +1 -0
  114. package/core/constraints/monotonic-lightness.js +37 -0
  115. package/core/constraints/monotonic-lightness.ts +38 -0
  116. package/core/constraints/monotonic.d.ts +7 -0
  117. package/core/constraints/monotonic.d.ts.map +1 -0
  118. package/core/constraints/monotonic.js +65 -0
  119. package/core/constraints/monotonic.ts +74 -0
  120. package/core/constraints/threshold.d.ts +10 -0
  121. package/core/constraints/threshold.d.ts.map +1 -0
  122. package/core/constraints/threshold.js +36 -0
  123. package/core/constraints/threshold.ts +43 -0
  124. package/core/constraints/wcag.d.ts +11 -0
  125. package/core/constraints/wcag.d.ts.map +1 -0
  126. package/core/constraints/wcag.js +53 -0
  127. package/core/constraints/wcag.ts +70 -0
  128. package/core/cross-axis-config.d.ts +5 -0
  129. package/core/cross-axis-config.d.ts.map +1 -0
  130. package/core/cross-axis-config.js +144 -0
  131. package/core/cross-axis-config.ts +152 -0
  132. package/core/engine.d.ts +32 -0
  133. package/core/engine.d.ts.map +1 -0
  134. package/core/engine.js +46 -0
  135. package/core/engine.ts +65 -0
  136. package/core/flatten.d.ts +20 -0
  137. package/core/flatten.d.ts.map +1 -0
  138. package/core/flatten.js +80 -0
  139. package/core/flatten.ts +116 -0
  140. package/core/image-export.d.ts +10 -0
  141. package/core/image-export.d.ts.map +1 -0
  142. package/core/image-export.js +43 -0
  143. package/core/image-export.ts +48 -0
  144. package/core/index.d.ts +31 -0
  145. package/core/index.d.ts.map +1 -0
  146. package/core/index.js +54 -0
  147. package/core/index.ts +72 -0
  148. package/core/patch.d.ts +28 -0
  149. package/core/patch.d.ts.map +1 -0
  150. package/core/patch.js +110 -0
  151. package/core/patch.ts +134 -0
  152. package/core/poset.d.ts +41 -0
  153. package/core/poset.d.ts.map +1 -0
  154. package/core/poset.js +275 -0
  155. package/core/poset.ts +311 -0
  156. package/core/why.d.ts +17 -0
  157. package/core/why.d.ts.map +1 -0
  158. package/core/why.js +45 -0
  159. package/core/why.ts +63 -0
  160. package/dist/test-overrides-removal.json +4 -0
  161. package/dist/tmp.patch.json +35 -0
  162. package/package.json +90 -0
  163. package/themes/color.lg.order.json +15 -0
  164. package/themes/color.md.order.json +15 -0
  165. package/themes/color.order.json +15 -0
  166. package/themes/color.sm.order.json +15 -0
  167. package/themes/cross-axis.rules.json +36 -0
  168. package/themes/cross-axis.sm.rules.json +12 -0
  169. package/themes/layout.lg.order.json +18 -0
  170. package/themes/layout.md.order.json +18 -0
  171. package/themes/layout.order.json +18 -0
  172. package/themes/layout.sm.order.json +18 -0
  173. package/themes/spacing.order.json +14 -0
  174. package/themes/typography.lg.order.json +15 -0
  175. package/themes/typography.md.order.json +15 -0
  176. package/themes/typography.order.json +15 -0
  177. package/themes/typography.sm.order.json +15 -0
  178. package/tokens/overrides/base.json +22 -0
  179. package/tokens/overrides/lg.json +20 -0
  180. package/tokens/overrides/md.json +16 -0
  181. package/tokens/overrides/sm.json +16 -0
  182. package/tokens/overrides/viol.color.json +6 -0
  183. package/tokens/overrides/viol.typography.json +6 -0
  184. package/tokens/tokens.demo-violations.json +116 -0
  185. package/tokens/tokens.example.json +128 -0
  186. package/tokens/tokens.json +67 -0
  187. package/tokens/tokens.multi-violations.json +21 -0
  188. package/tokens/tokens.schema.d.ts +2298 -0
  189. package/tokens/tokens.schema.d.ts.map +1 -0
  190. package/tokens/tokens.schema.js +148 -0
  191. package/tokens/tokens.schema.ts +196 -0
  192. package/tokens/tokens.test.json +38 -0
  193. package/tokens/tokens.touch-violation.json +8 -0
  194. package/tokens/typography.classes.css +11 -0
  195. package/tokens/typography.css +20 -0
@@ -0,0 +1,4 @@
1
+ import { type Result } from './result.js';
2
+ import type { DcvConfig } from './types.js';
3
+ export declare function loadConfig(configPath?: string): Result<DcvConfig, string>;
4
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CA6BzE"}
package/cli/config.js ADDED
@@ -0,0 +1,37 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { validateConfig } from './config-schema.js';
3
+ import { ok, err } from './result.js';
4
+ export function loadConfig(configPath) {
5
+ const candidates = configPath ? [configPath] : [
6
+ 'dcv.config.json',
7
+ 'dcv.config.js',
8
+ '.dcvrc.json',
9
+ 'package.json'
10
+ ];
11
+ for (const p of candidates) {
12
+ if (!existsSync(p))
13
+ continue;
14
+ try {
15
+ const rawTxt = readFileSync(p, 'utf8');
16
+ let raw = JSON.parse(rawTxt);
17
+ if (p === 'package.json' && raw && typeof raw === 'object') {
18
+ const pkg = raw;
19
+ if ('dcv' in pkg) {
20
+ raw = pkg.dcv;
21
+ }
22
+ else {
23
+ continue; // No dcv config in package.json
24
+ }
25
+ }
26
+ const { value, errors } = validateConfig(raw);
27
+ if (errors)
28
+ return err(`Config validation failed in ${p}:\n - ${errors.join('\n - ')}`);
29
+ return ok(value);
30
+ }
31
+ catch (e) {
32
+ const msg = e instanceof Error ? e.message : String(e);
33
+ return err(`Failed reading config ${p}: ${msg}`);
34
+ }
35
+ }
36
+ return ok({});
37
+ }
package/cli/config.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { validateConfig } from './config-schema.js';
3
+ import { ok, err, type Result } from './result.js';
4
+ import type { DcvConfig } from './types.js';
5
+
6
+ export function loadConfig(configPath?: string): Result<DcvConfig, string> {
7
+ const candidates = configPath ? [configPath] : [
8
+ 'dcv.config.json',
9
+ 'dcv.config.js',
10
+ '.dcvrc.json',
11
+ 'package.json'
12
+ ];
13
+ for (const p of candidates) {
14
+ if (!existsSync(p)) continue;
15
+ try {
16
+ const rawTxt = readFileSync(p, 'utf8');
17
+ let raw: unknown = JSON.parse(rawTxt);
18
+ if (p === 'package.json' && raw && typeof raw === 'object') {
19
+ const pkg = raw as Record<string, unknown>;
20
+ if ('dcv' in pkg) {
21
+ raw = pkg.dcv;
22
+ } else {
23
+ continue; // No dcv config in package.json
24
+ }
25
+ }
26
+ const { value, errors } = validateConfig(raw);
27
+ if (errors) return err(`Config validation failed in ${p}:\n - ${errors.join('\n - ')}`);
28
+ return ok(value!);
29
+ } catch (e) {
30
+ const msg = e instanceof Error ? e.message : String(e);
31
+ return err(`Failed reading config ${p}: ${msg}`);
32
+ }
33
+ }
34
+ return ok({});
35
+ }
package/cli/dcv.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=dcv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dcv.d.ts","sourceRoot":"","sources":["dcv.ts"],"names":[],"mappings":""}
package/cli/dcv.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // Clean minimal DCV CLI entrypoint
3
+ import yargs from 'yargs/yargs';
4
+ import { hideBin } from 'yargs/helpers';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { createRequire } from 'module';
7
+ import path from 'node:path';
8
+ import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
9
+ // Early intercept: experimental graph diff helper
10
+ (() => {
11
+ const args = process.argv.slice(2);
12
+ if (args[0] === 'graph' && args[1] === 'diff') {
13
+ const pass = args.slice(2);
14
+ const require = createRequire(import.meta.url);
15
+ const tsx = (() => { try {
16
+ return require.resolve('tsx/dist/cli.mjs');
17
+ }
18
+ catch {
19
+ return path.resolve('node_modules/tsx/dist/cli.mjs');
20
+ } })();
21
+ const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
22
+ process.exit(r.status ?? 0);
23
+ }
24
+ })();
25
+ const cli = yargs(hideBin(process.argv))
26
+ .scriptName('dcv')
27
+ .parserConfiguration({ 'camel-case-expansion': false })
28
+ .option('quiet', { type: 'boolean' });
29
+ cli.command('set <expressions..>', 'Set token values', y => y
30
+ .positional('expressions', { type: 'string', array: true })
31
+ .option('dry-run', { type: 'boolean', default: false })
32
+ .option('write', { type: 'boolean' })
33
+ .option('json', { type: 'string' })
34
+ .option('unset', { type: 'array', string: true })
35
+ .option('format', { type: 'string', choices: ['json', 'css', 'js'], default: 'json' })
36
+ .option('output', { type: 'string' })
37
+ .option('theme', { type: 'string' })
38
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => setCommand(a));
39
+ cli.command('build', 'Build token outputs', y => y
40
+ .option('format', { type: 'string', choices: ['css', 'json', 'js'], default: 'css' })
41
+ .option('all-formats', { type: 'boolean', default: false })
42
+ .option('output', { type: 'string' })
43
+ .option('mapper', { type: 'string' })
44
+ .option('theme', { type: 'string' })
45
+ .option('dry-run', { type: 'boolean', default: false })
46
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => buildCommand(a));
47
+ cli.command('validate', 'Validate constraints', y => y
48
+ .option('fail-on', { type: 'string', choices: ['off', 'warn', 'error'], default: 'error' })
49
+ .option('summary', { type: 'string', choices: ['none', 'table', 'json'], default: 'none' })
50
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
51
+ .option('breakpoint', { type: 'string' })
52
+ .option('all-breakpoints', { type: 'boolean' })
53
+ .option('perf', { type: 'boolean', describe: 'Print timing info' })
54
+ .option('budget-total-ms', { type: 'number', describe: 'Fail if total validation exceeds this (ms)' })
55
+ .option('budget-per-bp-ms', { type: 'number', describe: 'Fail if any single breakpoint exceeds this (ms)' }), a => validateCommand(a));
56
+ cli.command('graph', 'Generate dependency / constraint graph', y => y
57
+ .option('format', { type: 'string', choices: ['json', 'mermaid', 'dot', 'svg', 'png'], default: 'json' })
58
+ .option('bundle', { type: 'boolean', describe: 'When used with --hasse export mermaid+dot (+image if svg/png requested)' })
59
+ .option('hasse', { type: 'string' })
60
+ .option('filter-prefix', { type: 'string' })
61
+ .option('exclude-prefix', { type: 'string' })
62
+ .option('only-violations', { type: 'boolean' })
63
+ .option('highlight-violations', { type: 'boolean' })
64
+ .option('label-violations', { type: 'boolean' })
65
+ .option('label-truncate', { type: 'number', default: 0 })
66
+ .option('min-severity', { type: 'string', choices: ['warn', 'error'], default: 'warn' })
67
+ .option('focus', { type: 'string' })
68
+ .option('radius', { type: 'number', default: 1 })
69
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => graphCommand(a));
70
+ cli.command('why <tokenId>', 'Explain token provenance', y => y
71
+ .positional('tokenId', { type: 'string', demandOption: true })
72
+ .option('format', { type: 'string', choices: ['json', 'table'], default: 'json' })
73
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => whyCommand(a));
74
+ cli.command('patch', 'Export patch (diff) from overrides', y => y
75
+ .option('overrides', { type: 'string', describe: 'Path or inline JSON of flat overrides' })
76
+ .option('format', { type: 'string', choices: ['json', 'css', 'js'], default: 'json' })
77
+ .option('output', { type: 'string' })
78
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }), a => patchCommand(a));
79
+ cli.command('patch:apply <patch>', 'Apply patch document to tokens', y => y
80
+ .positional('patch', { type: 'string', demandOption: true })
81
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
82
+ .option('output', { type: 'string', describe: 'Write updated tokens to this file' })
83
+ .option('dry-run', { type: 'boolean', default: false }), a => patchApplyCommand(a));
84
+ cli.help().alias('h', 'help').strict().wrap(cli.terminalWidth());
85
+ cli.parse();
86
+ // EOF
package/cli/dcv.ts ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // Clean minimal DCV CLI entrypoint
3
+ import yargs from 'yargs/yargs';
4
+ import { hideBin } from 'yargs/helpers';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { createRequire } from 'module';
7
+ import path from 'node:path';
8
+ import { type SetOptions, type BuildOptions, type ValidateOptions, type GraphOptions, type WhyOptions, type PatchOptions, type PatchApplyOptions } from './types.js';
9
+ import { setCommand, buildCommand, validateCommand, graphCommand, whyCommand, patchCommand, patchApplyCommand } from './commands/index.js';
10
+
11
+ // Early intercept: experimental graph diff helper
12
+ (() => {
13
+ const args = process.argv.slice(2);
14
+ if (args[0] === 'graph' && args[1] === 'diff') {
15
+ const pass = args.slice(2);
16
+ const require = createRequire(import.meta.url);
17
+ const tsx = (() => { try { return require.resolve('tsx/dist/cli.mjs'); } catch { return path.resolve('node_modules/tsx/dist/cli.mjs'); }})();
18
+ const r = spawnSync(process.execPath, [tsx, path.resolve('scripts/graph-diff.ts'), ...pass], { stdio: 'inherit', env: process.env });
19
+ process.exit(r.status ?? 0);
20
+ }
21
+ })();
22
+
23
+ const cli = yargs(hideBin(process.argv))
24
+ .scriptName('dcv')
25
+ .parserConfiguration({ 'camel-case-expansion': false })
26
+ .option('quiet', { type: 'boolean' });
27
+
28
+ cli.command<SetOptions>('set <expressions..>', 'Set token values', y => y
29
+ .positional('expressions', { type: 'string', array: true })
30
+ .option('dry-run', { type: 'boolean', default: false })
31
+ .option('write', { type: 'boolean' })
32
+ .option('json', { type: 'string' })
33
+ .option('unset', { type: 'array', string: true })
34
+ .option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
35
+ .option('output', { type: 'string' })
36
+ .option('theme', { type: 'string' })
37
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
38
+ a => setCommand(a)
39
+ );
40
+
41
+ cli.command<BuildOptions>('build', 'Build token outputs', y => y
42
+ .option('format', { type: 'string', choices: ['css','json','js'], default: 'css' })
43
+ .option('all-formats', { type: 'boolean', default: false })
44
+ .option('output', { type: 'string' })
45
+ .option('mapper', { type: 'string' })
46
+ .option('theme', { type: 'string' })
47
+ .option('dry-run', { type: 'boolean', default: false })
48
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
49
+ a => buildCommand(a)
50
+ );
51
+
52
+ cli.command<ValidateOptions>('validate', 'Validate constraints', y => y
53
+ .option('fail-on', { type: 'string', choices: ['off','warn','error'], default: 'error' })
54
+ .option('summary', { type: 'string', choices: ['none','table','json'], default: 'none' })
55
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
56
+ .option('breakpoint', { type: 'string' })
57
+ .option('all-breakpoints', { type: 'boolean' })
58
+ .option('perf', { type: 'boolean', describe: 'Print timing info' })
59
+ .option('budget-total-ms', { type: 'number', describe: 'Fail if total validation exceeds this (ms)' })
60
+ .option('budget-per-bp-ms', { type: 'number', describe: 'Fail if any single breakpoint exceeds this (ms)' }),
61
+ a => validateCommand(a)
62
+ );
63
+
64
+ cli.command<GraphOptions>('graph', 'Generate dependency / constraint graph', y => y
65
+ .option('format', { type: 'string', choices: ['json','mermaid','dot','svg','png'], default: 'json' })
66
+ .option('bundle', { type: 'boolean', describe: 'When used with --hasse export mermaid+dot (+image if svg/png requested)' })
67
+ .option('hasse', { type: 'string' })
68
+ .option('filter-prefix', { type: 'string' })
69
+ .option('exclude-prefix', { type: 'string' })
70
+ .option('only-violations', { type: 'boolean' })
71
+ .option('highlight-violations', { type: 'boolean' })
72
+ .option('label-violations', { type: 'boolean' })
73
+ .option('label-truncate', { type: 'number', default: 0 })
74
+ .option('min-severity', { type: 'string', choices: ['warn','error'], default: 'warn' })
75
+ .option('focus', { type: 'string' })
76
+ .option('radius', { type: 'number', default: 1 })
77
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
78
+ a => graphCommand(a)
79
+ );
80
+
81
+ cli.command<WhyOptions>('why <tokenId>', 'Explain token provenance', y => y
82
+ .positional('tokenId', { type: 'string', demandOption: true })
83
+ .option('format', { type: 'string', choices: ['json','table'], default: 'json' })
84
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
85
+ a => whyCommand(a)
86
+ );
87
+
88
+ cli.command<PatchOptions>('patch', 'Export patch (diff) from overrides', y => y
89
+ .option('overrides', { type: 'string', describe: 'Path or inline JSON of flat overrides' })
90
+ .option('format', { type: 'string', choices: ['json','css','js'], default: 'json' })
91
+ .option('output', { type: 'string' })
92
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' }),
93
+ a => patchCommand(a)
94
+ );
95
+
96
+ cli.command<PatchApplyOptions>('patch:apply <patch>', 'Apply patch document to tokens', y => y
97
+ .positional('patch', { type: 'string', demandOption: true })
98
+ .option('tokens', { type: 'string', default: 'tokens/tokens.example.json' })
99
+ .option('output', { type: 'string', describe: 'Write updated tokens to this file' })
100
+ .option('dry-run', { type: 'boolean', default: false }),
101
+ a => patchApplyCommand(a)
102
+ );
103
+
104
+ cli.help().alias('h','help').strict().wrap(cli.terminalWidth());
105
+ cli.parse();
106
+
107
+ // EOF
@@ -0,0 +1,8 @@
1
+ import { type TokenNode } from '../core/flatten.js';
2
+ import { Engine } from '../core/engine.js';
3
+ import { loadTokensWithBreakpoint, type Breakpoint } from '../core/breakpoints.js';
4
+ import type { DcvConfig } from './types.js';
5
+ export declare function createEngine(tokensRoot: TokenNode, config?: DcvConfig): Engine;
6
+ export declare function createValidationEngine(tokensRoot: TokenNode, bp: Breakpoint | undefined, config: DcvConfig): Engine;
7
+ export { loadTokensWithBreakpoint };
8
+ //# sourceMappingURL=engine-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-helpers.d.ts","sourceRoot":"","sources":["engine-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI3C,OAAO,EAA8B,wBAAwB,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC/G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,wBAAgB,YAAY,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,GAAE,SAAc,GAAG,MAAM,CA8BlF;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,CAkBnH;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { flattenTokens } from '../core/flatten.js';
2
+ import { Engine } from '../core/engine.js';
3
+ import { MonotonicPlugin, parseSize as parseSizePx } from '../core/constraints/monotonic.js';
4
+ import { MonotonicLightness } from '../core/constraints/monotonic-lightness.js';
5
+ import { WcagContrastPlugin } from '../core/constraints/wcag.js';
6
+ import { loadOrders as loadOrdersBP, loadTokensWithBreakpoint } from '../core/breakpoints.js';
7
+ export function createEngine(tokensRoot, config = {}) {
8
+ const { flat, edges } = flattenTokens(tokensRoot);
9
+ const init = {};
10
+ for (const [id, token] of Object.entries(flat))
11
+ init[id] = token.value;
12
+ const engine = new Engine(init, edges);
13
+ function loadOrders(path) {
14
+ try {
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ return JSON.parse(require('node:fs').readFileSync(path, 'utf8')).order;
17
+ }
18
+ catch {
19
+ return [];
20
+ }
21
+ }
22
+ const typOrders = loadOrders('themes/typography.order.json');
23
+ const spacingOrders = loadOrders('themes/spacing.order.json');
24
+ const layoutOrders = loadOrders('themes/layout.order.json');
25
+ const colorOrders = loadOrders('themes/color.order.json');
26
+ if (typOrders.length)
27
+ engine.use(MonotonicPlugin(typOrders, parseSizePx, 'monotonic-typography'));
28
+ if (spacingOrders.length)
29
+ engine.use(MonotonicPlugin(spacingOrders, parseSizePx, 'monotonic-spacing'));
30
+ if (layoutOrders.length)
31
+ engine.use(MonotonicPlugin(layoutOrders, parseSizePx, 'monotonic-layout'));
32
+ if (colorOrders.length)
33
+ engine.use(MonotonicLightness(colorOrders));
34
+ if (config.constraints?.wcag) {
35
+ const wcagRules = config.constraints.wcag.map((r) => ({ fg: r.foreground, bg: r.background, min: r.ratio || 4.5, where: r.description || 'Unknown' }));
36
+ engine.use(WcagContrastPlugin(wcagRules));
37
+ }
38
+ const defaultWcagPairs = [
39
+ { fg: 'color.role.text.default', bg: 'color.role.bg.surface', min: 4.5, where: 'Body text on surface' },
40
+ { fg: 'color.role.accent.default', bg: 'color.role.bg.surface', min: 3.0, where: 'Accent on surface' },
41
+ { fg: 'color.role.focus.ring', bg: 'color.role.bg.surface', min: 3.0, where: 'Focus ring on surface', backdrop: '#ffffff' }
42
+ ];
43
+ engine.use(WcagContrastPlugin(defaultWcagPairs));
44
+ return engine;
45
+ }
46
+ export function createValidationEngine(tokensRoot, bp, config) {
47
+ const { flat, edges } = flattenTokens(tokensRoot);
48
+ const init = {};
49
+ for (const t of Object.values(flat))
50
+ init[t.id] = t.value;
51
+ const engine = new Engine(init, edges);
52
+ const typ = loadOrdersBP('typography', bp);
53
+ const spc = loadOrdersBP('spacing', bp);
54
+ const lay = loadOrdersBP('layout', bp);
55
+ const col = loadOrdersBP('color', bp);
56
+ if (typ.length)
57
+ engine.use(MonotonicPlugin(typ, parseSizePx, 'monotonic-typography'));
58
+ if (spc.length)
59
+ engine.use(MonotonicPlugin(spc, parseSizePx, 'monotonic-spacing'));
60
+ if (lay.length)
61
+ engine.use(MonotonicPlugin(lay, parseSizePx, 'monotonic-layout'));
62
+ if (col.length)
63
+ engine.use(MonotonicLightness(col));
64
+ if (config.constraints?.wcag) {
65
+ const wcagRules = config.constraints.wcag.map((rule) => ({ fg: rule.foreground, bg: rule.background, min: rule.ratio || 4.5, where: rule.description || 'Unknown' }));
66
+ engine.use(WcagContrastPlugin(wcagRules));
67
+ }
68
+ return engine;
69
+ }
70
+ export { loadTokensWithBreakpoint };
@@ -0,0 +1,61 @@
1
+ import { flattenTokens, type TokenNode, type FlatToken } from '../core/flatten.js';
2
+ import { Engine } from '../core/engine.js';
3
+ import { MonotonicPlugin, parseSize as parseSizePx } from '../core/constraints/monotonic.js';
4
+ import { MonotonicLightness } from '../core/constraints/monotonic-lightness.js';
5
+ import { WcagContrastPlugin } from '../core/constraints/wcag.js';
6
+ import { loadOrders as loadOrdersBP, loadTokensWithBreakpoint, type Breakpoint } from '../core/breakpoints.js';
7
+ import type { DcvConfig } from './types.js';
8
+
9
+ export function createEngine(tokensRoot: TokenNode, config: DcvConfig = {}): Engine {
10
+ const { flat, edges } = flattenTokens(tokensRoot);
11
+ const init: Record<string, string | number> = {};
12
+ for (const [id, token] of Object.entries(flat)) init[id] = (token as FlatToken).value;
13
+ const engine = new Engine(init, edges);
14
+ function loadOrders(path: string) {
15
+ try {
16
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
17
+ return JSON.parse(require('node:fs').readFileSync(path, 'utf8')).order as [string, '<='|'>=', string][];
18
+ } catch { return []; }
19
+ }
20
+ const typOrders = loadOrders('themes/typography.order.json');
21
+ const spacingOrders = loadOrders('themes/spacing.order.json');
22
+ const layoutOrders = loadOrders('themes/layout.order.json');
23
+ const colorOrders = loadOrders('themes/color.order.json');
24
+ if (typOrders.length) engine.use(MonotonicPlugin(typOrders, parseSizePx, 'monotonic-typography'));
25
+ if (spacingOrders.length) engine.use(MonotonicPlugin(spacingOrders, parseSizePx, 'monotonic-spacing'));
26
+ if (layoutOrders.length) engine.use(MonotonicPlugin(layoutOrders, parseSizePx, 'monotonic-layout'));
27
+ if (colorOrders.length) engine.use(MonotonicLightness(colorOrders));
28
+ if (config.constraints?.wcag) {
29
+ const wcagRules = config.constraints.wcag.map((r: any) => ({ fg: r.foreground, bg: r.background, min: r.ratio || 4.5, where: r.description || 'Unknown' }));
30
+ engine.use(WcagContrastPlugin(wcagRules));
31
+ }
32
+ const defaultWcagPairs = [
33
+ { fg: 'color.role.text.default', bg: 'color.role.bg.surface', min: 4.5, where: 'Body text on surface' },
34
+ { fg: 'color.role.accent.default', bg: 'color.role.bg.surface', min: 3.0, where: 'Accent on surface' },
35
+ { fg: 'color.role.focus.ring', bg: 'color.role.bg.surface', min: 3.0, where: 'Focus ring on surface', backdrop: '#ffffff' }
36
+ ];
37
+ engine.use(WcagContrastPlugin(defaultWcagPairs));
38
+ return engine;
39
+ }
40
+
41
+ export function createValidationEngine(tokensRoot: TokenNode, bp: Breakpoint | undefined, config: DcvConfig): Engine {
42
+ const { flat, edges } = flattenTokens(tokensRoot);
43
+ const init: Record<string, string | number> = {};
44
+ for (const t of Object.values(flat)) init[(t as FlatToken).id] = (t as FlatToken).value;
45
+ const engine = new Engine(init, edges);
46
+ const typ = loadOrdersBP('typography', bp);
47
+ const spc = loadOrdersBP('spacing', bp);
48
+ const lay = loadOrdersBP('layout', bp);
49
+ const col = loadOrdersBP('color', bp);
50
+ if (typ.length) engine.use(MonotonicPlugin(typ, parseSizePx, 'monotonic-typography'));
51
+ if (spc.length) engine.use(MonotonicPlugin(spc, parseSizePx, 'monotonic-spacing'));
52
+ if (lay.length) engine.use(MonotonicPlugin(lay, parseSizePx, 'monotonic-layout'));
53
+ if (col.length) engine.use(MonotonicLightness(col));
54
+ if (config.constraints?.wcag) {
55
+ const wcagRules = config.constraints.wcag.map((rule: any) => ({ fg: rule.foreground, bg: rule.background, min: rule.ratio || 4.5, where: rule.description || 'Unknown' }));
56
+ engine.use(WcagContrastPlugin(wcagRules));
57
+ }
58
+ return engine;
59
+ }
60
+
61
+ export { loadTokensWithBreakpoint };
@@ -0,0 +1,9 @@
1
+ import { type Order } from "../core/poset.js";
2
+ export interface OrderFile {
3
+ $description?: string;
4
+ order: Order[];
5
+ }
6
+ export declare function loadOrderFile(path: string): OrderFile;
7
+ export declare function generateHasseDiagram(orderPath: string, outputPath: string, format?: 'mermaid' | 'dot'): void;
8
+ export declare function generateAllHasseDiagrams(themesDir?: string, outputDir?: string): void;
9
+ //# sourceMappingURL=graph-poset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-poset.d.ts","sourceRoot":"","sources":["graph-poset.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8E,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE1H,MAAM,WAAW,SAAS;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,EAAE,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAKrD;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,GAAG,KAAiB,GAAG,IAAI,CAgCvH;AAED,wBAAgB,wBAAwB,CAAC,SAAS,GAAE,MAAiB,EAAE,SAAS,GAAE,MAAsB,GAAG,IAAI,CAuB9G"}
@@ -0,0 +1,58 @@
1
+ // cli/graph-poset.ts
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
3
+ import { buildPoset, transitiveReduction, toMermaidHasse, toDotHasse, validatePoset } from "../core/poset.js";
4
+ export function loadOrderFile(path) {
5
+ if (!existsSync(path)) {
6
+ throw new Error(`Order file not found: ${path}`);
7
+ }
8
+ return JSON.parse(readFileSync(path, "utf8"));
9
+ }
10
+ export function generateHasseDiagram(orderPath, outputPath, format = 'mermaid') {
11
+ const orderData = loadOrderFile(orderPath);
12
+ const { order, $description } = orderData;
13
+ // Build and validate poset
14
+ const poset = buildPoset(order);
15
+ const validation = validatePoset(poset);
16
+ if (!validation.valid) {
17
+ console.error("⚠️ Warning: Poset contains cycles:");
18
+ for (const cycle of validation.cycles || []) {
19
+ console.error(` ${cycle.join(" → ")}`);
20
+ }
21
+ }
22
+ // Generate Hasse diagram (transitive reduction)
23
+ const hasse = transitiveReduction(poset);
24
+ const title = $description || "Poset Hierarchy";
25
+ const diagram = format === 'mermaid'
26
+ ? toMermaidHasse(hasse, { title })
27
+ : toDotHasse(hasse, { title });
28
+ // Ensure output directory exists
29
+ mkdirSync(outputPath.split('/').slice(0, -1).join('/'), { recursive: true });
30
+ writeFileSync(outputPath, diagram);
31
+ console.log(`✅ Generated ${format.toUpperCase()} Hasse diagram: ${outputPath}`);
32
+ if (!validation.valid) {
33
+ console.log(`⚠️ Note: Diagram shows cycles that should be resolved`);
34
+ }
35
+ }
36
+ export function generateAllHasseDiagrams(themesDir = "themes", outputDir = "dist/graphs") {
37
+ const orderFiles = [
38
+ { file: "typography.order.json", name: "typography" },
39
+ { file: "spacing.order.json", name: "spacing" }
40
+ ];
41
+ mkdirSync(outputDir, { recursive: true });
42
+ for (const { file, name } of orderFiles) {
43
+ const orderPath = `${themesDir}/${file}`;
44
+ if (existsSync(orderPath)) {
45
+ try {
46
+ // Generate both Mermaid and DOT formats
47
+ generateHasseDiagram(orderPath, `${outputDir}/${name}-hasse.mmd`, 'mermaid');
48
+ generateHasseDiagram(orderPath, `${outputDir}/${name}-hasse.dot`, 'dot');
49
+ }
50
+ catch (error) {
51
+ console.error(`❌ Failed to generate diagram for ${name}:`, error);
52
+ }
53
+ }
54
+ else {
55
+ console.log(`⏭️ Skipping ${name} (no order file found)`);
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,74 @@
1
+ // cli/graph-poset.ts
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
3
+ import { buildPoset, transitiveReduction, toMermaidHasse, toDotHasse, validatePoset, type Order } from "../core/poset.js";
4
+
5
+ export interface OrderFile {
6
+ $description?: string;
7
+ order: Order[];
8
+ }
9
+
10
+ export function loadOrderFile(path: string): OrderFile {
11
+ if (!existsSync(path)) {
12
+ throw new Error(`Order file not found: ${path}`);
13
+ }
14
+ return JSON.parse(readFileSync(path, "utf8"));
15
+ }
16
+
17
+ export function generateHasseDiagram(orderPath: string, outputPath: string, format: 'mermaid' | 'dot' = 'mermaid'): void {
18
+ const orderData = loadOrderFile(orderPath);
19
+ const { order, $description } = orderData;
20
+
21
+ // Build and validate poset
22
+ const poset = buildPoset(order);
23
+ const validation = validatePoset(poset);
24
+
25
+ if (!validation.valid) {
26
+ console.error("⚠️ Warning: Poset contains cycles:");
27
+ for (const cycle of validation.cycles || []) {
28
+ console.error(` ${cycle.join(" → ")}`);
29
+ }
30
+ }
31
+
32
+ // Generate Hasse diagram (transitive reduction)
33
+ const hasse = transitiveReduction(poset);
34
+
35
+ const title = $description || "Poset Hierarchy";
36
+ const diagram = format === 'mermaid'
37
+ ? toMermaidHasse(hasse, { title })
38
+ : toDotHasse(hasse, { title });
39
+
40
+ // Ensure output directory exists
41
+ mkdirSync(outputPath.split('/').slice(0, -1).join('/'), { recursive: true });
42
+
43
+ writeFileSync(outputPath, diagram);
44
+ console.log(`✅ Generated ${format.toUpperCase()} Hasse diagram: ${outputPath}`);
45
+
46
+ if (!validation.valid) {
47
+ console.log(`⚠️ Note: Diagram shows cycles that should be resolved`);
48
+ }
49
+ }
50
+
51
+ export function generateAllHasseDiagrams(themesDir: string = "themes", outputDir: string = "dist/graphs"): void {
52
+ const orderFiles = [
53
+ { file: "typography.order.json", name: "typography" },
54
+ { file: "spacing.order.json", name: "spacing" }
55
+ ];
56
+
57
+ mkdirSync(outputDir, { recursive: true });
58
+
59
+ for (const { file, name } of orderFiles) {
60
+ const orderPath = `${themesDir}/${file}`;
61
+
62
+ if (existsSync(orderPath)) {
63
+ try {
64
+ // Generate both Mermaid and DOT formats
65
+ generateHasseDiagram(orderPath, `${outputDir}/${name}-hasse.mmd`, 'mermaid');
66
+ generateHasseDiagram(orderPath, `${outputDir}/${name}-hasse.dot`, 'dot');
67
+ } catch (error) {
68
+ console.error(`❌ Failed to generate diagram for ${name}:`, error);
69
+ }
70
+ } else {
71
+ console.log(`⏭️ Skipping ${name} (no order file found)`);
72
+ }
73
+ }
74
+ }
package/cli/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import './dcv.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AACA,OAAO,UAAU,CAAC"}
package/cli/index.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import './dcv.js';
package/cli/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import './dcv.js';
@@ -0,0 +1,17 @@
1
+ export type Ok<T> = {
2
+ ok: true;
3
+ value: T;
4
+ };
5
+ export type Err<E> = {
6
+ ok: false;
7
+ error: E;
8
+ };
9
+ export type Result<T, E = Error> = Ok<T> | Err<E>;
10
+ export declare function ok<T>(value: T): Ok<T>;
11
+ export declare function err<E>(error: E): Err<E>;
12
+ export declare function wrap<T>(fn: () => T): Result<T, unknown>;
13
+ export declare function wrapAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>>;
14
+ export declare function map<T, U, E>(r: Result<T, E>, f: (v: T) => U): Result<U, E>;
15
+ export declare function chain<T, U, E>(r: Result<T, E>, f: (v: T) => Result<U, E>): Result<U, E>;
16
+ export declare function getOrThrow<T, E>(r: Result<T, E>): T;
17
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["result.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,EAAE,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAC3C,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAC7C,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAElD,wBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAgC;AACtE,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAiC;AAEzE,wBAAgB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAEvD;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAEpF;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAE1E;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAEvF;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAGnD"}