@mseep/dembrandt 0.19.5

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +408 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +532 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/browser.d.ts +16 -0
  7. package/dist/lib/browser.js +27 -0
  8. package/dist/lib/browser.js.map +1 -0
  9. package/dist/lib/colors.d.ts +101 -0
  10. package/dist/lib/colors.js +405 -0
  11. package/dist/lib/colors.js.map +1 -0
  12. package/dist/lib/compare.d.ts +31 -0
  13. package/dist/lib/compare.js +46 -0
  14. package/dist/lib/compare.js.map +1 -0
  15. package/dist/lib/discovery.d.ts +31 -0
  16. package/dist/lib/discovery.js +243 -0
  17. package/dist/lib/discovery.js.map +1 -0
  18. package/dist/lib/drift.d.ts +64 -0
  19. package/dist/lib/drift.js +383 -0
  20. package/dist/lib/drift.js.map +1 -0
  21. package/dist/lib/dtcg/validate.d.ts +51 -0
  22. package/dist/lib/dtcg/validate.js +1403 -0
  23. package/dist/lib/dtcg/validate.js.map +1 -0
  24. package/dist/lib/exit-codes.d.ts +29 -0
  25. package/dist/lib/exit-codes.js +26 -0
  26. package/dist/lib/exit-codes.js.map +1 -0
  27. package/dist/lib/extractors/breakpoints.d.ts +5 -0
  28. package/dist/lib/extractors/breakpoints.js +450 -0
  29. package/dist/lib/extractors/breakpoints.js.map +1 -0
  30. package/dist/lib/extractors/colors.d.ts +2 -0
  31. package/dist/lib/extractors/colors.js +657 -0
  32. package/dist/lib/extractors/colors.js.map +1 -0
  33. package/dist/lib/extractors/components.d.ts +4 -0
  34. package/dist/lib/extractors/components.js +370 -0
  35. package/dist/lib/extractors/components.js.map +1 -0
  36. package/dist/lib/extractors/index.d.ts +9 -0
  37. package/dist/lib/extractors/index.js +1257 -0
  38. package/dist/lib/extractors/index.js.map +1 -0
  39. package/dist/lib/extractors/logo.d.ts +2 -0
  40. package/dist/lib/extractors/logo.js +626 -0
  41. package/dist/lib/extractors/logo.js.map +1 -0
  42. package/dist/lib/extractors/spacing.d.ts +4 -0
  43. package/dist/lib/extractors/spacing.js +163 -0
  44. package/dist/lib/extractors/spacing.js.map +1 -0
  45. package/dist/lib/extractors/teach.d.ts +1 -0
  46. package/dist/lib/extractors/teach.js +66 -0
  47. package/dist/lib/extractors/teach.js.map +1 -0
  48. package/dist/lib/extractors/typography.d.ts +1 -0
  49. package/dist/lib/extractors/typography.js +163 -0
  50. package/dist/lib/extractors/typography.js.map +1 -0
  51. package/dist/lib/findings.d.ts +34 -0
  52. package/dist/lib/findings.js +166 -0
  53. package/dist/lib/findings.js.map +1 -0
  54. package/dist/lib/formatters/dtcg.d.ts +10 -0
  55. package/dist/lib/formatters/dtcg.js +416 -0
  56. package/dist/lib/formatters/dtcg.js.map +1 -0
  57. package/dist/lib/formatters/html.d.ts +25 -0
  58. package/dist/lib/formatters/html.js +479 -0
  59. package/dist/lib/formatters/html.js.map +1 -0
  60. package/dist/lib/formatters/markdown.d.ts +5 -0
  61. package/dist/lib/formatters/markdown.js +568 -0
  62. package/dist/lib/formatters/markdown.js.map +1 -0
  63. package/dist/lib/formatters/pdf.d.ts +12 -0
  64. package/dist/lib/formatters/pdf.js +1121 -0
  65. package/dist/lib/formatters/pdf.js.map +1 -0
  66. package/dist/lib/formatters/terminal.d.ts +6 -0
  67. package/dist/lib/formatters/terminal.js +954 -0
  68. package/dist/lib/formatters/terminal.js.map +1 -0
  69. package/dist/lib/formatters/theme.d.ts +35 -0
  70. package/dist/lib/formatters/theme.js +37 -0
  71. package/dist/lib/formatters/theme.js.map +1 -0
  72. package/dist/lib/merger.d.ts +14 -0
  73. package/dist/lib/merger.js +362 -0
  74. package/dist/lib/merger.js.map +1 -0
  75. package/dist/lib/normalize.d.ts +29 -0
  76. package/dist/lib/normalize.js +59 -0
  77. package/dist/lib/normalize.js.map +1 -0
  78. package/dist/lib/robots.d.ts +12 -0
  79. package/dist/lib/robots.js +110 -0
  80. package/dist/lib/robots.js.map +1 -0
  81. package/dist/lib/run-summary.d.ts +40 -0
  82. package/dist/lib/run-summary.js +64 -0
  83. package/dist/lib/run-summary.js.map +1 -0
  84. package/dist/lib/types.d.ts +329 -0
  85. package/dist/lib/types.js +7 -0
  86. package/dist/lib/types.js.map +1 -0
  87. package/dist/lib/version.d.ts +134 -0
  88. package/dist/lib/version.js +153 -0
  89. package/dist/lib/version.js.map +1 -0
  90. package/dist/mcp-server.d.ts +11 -0
  91. package/dist/mcp-server.js +311 -0
  92. package/dist/mcp-server.js.map +1 -0
  93. package/dist/package.json +106 -0
  94. package/dist/test/_vitest-shim.d.ts +13 -0
  95. package/dist/test/_vitest-shim.js +23 -0
  96. package/dist/test/_vitest-shim.js.map +1 -0
  97. package/dist/test/cli.test.d.ts +1 -0
  98. package/dist/test/cli.test.js +24 -0
  99. package/dist/test/cli.test.js.map +1 -0
  100. package/dist/test/colors.test.d.ts +1 -0
  101. package/dist/test/colors.test.js +64 -0
  102. package/dist/test/colors.test.js.map +1 -0
  103. package/dist/test/compare.test.d.ts +1 -0
  104. package/dist/test/compare.test.js +57 -0
  105. package/dist/test/compare.test.js.map +1 -0
  106. package/dist/test/drift.test.d.ts +1 -0
  107. package/dist/test/drift.test.js +53 -0
  108. package/dist/test/drift.test.js.map +1 -0
  109. package/dist/test/dtcg-formatter.test.d.ts +1 -0
  110. package/dist/test/dtcg-formatter.test.js +48 -0
  111. package/dist/test/dtcg-formatter.test.js.map +1 -0
  112. package/dist/test/dtcg-validate.test.d.ts +1 -0
  113. package/dist/test/dtcg-validate.test.js +2129 -0
  114. package/dist/test/dtcg-validate.test.js.map +1 -0
  115. package/dist/test/exit-codes.test.d.ts +1 -0
  116. package/dist/test/exit-codes.test.js +53 -0
  117. package/dist/test/exit-codes.test.js.map +1 -0
  118. package/dist/test/findings.test.d.ts +1 -0
  119. package/dist/test/findings.test.js +77 -0
  120. package/dist/test/findings.test.js.map +1 -0
  121. package/dist/test/html.test.d.ts +1 -0
  122. package/dist/test/html.test.js +95 -0
  123. package/dist/test/html.test.js.map +1 -0
  124. package/dist/test/markdown.test.d.ts +1 -0
  125. package/dist/test/markdown.test.js +145 -0
  126. package/dist/test/markdown.test.js.map +1 -0
  127. package/dist/test/merger.test.d.ts +1 -0
  128. package/dist/test/merger.test.js +98 -0
  129. package/dist/test/merger.test.js.map +1 -0
  130. package/dist/test/normalize.test.d.ts +1 -0
  131. package/dist/test/normalize.test.js +47 -0
  132. package/dist/test/normalize.test.js.map +1 -0
  133. package/dist/test/run-summary.test.d.ts +1 -0
  134. package/dist/test/run-summary.test.js +45 -0
  135. package/dist/test/run-summary.test.js.map +1 -0
  136. package/dist/test/version.test.d.ts +1 -0
  137. package/dist/test/version.test.js +73 -0
  138. package/dist/test/version.test.js.map +1 -0
  139. package/package.json +106 -0
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@mseep/dembrandt",
3
+ "version": "0.19.5",
4
+ "description": "Extract design tokens and publicly visible CSS information from any website",
5
+ "mcpName": "io.github.dembrandt/dembrandt",
6
+ "main": "dist/index.js",
7
+ "type": "module",
8
+ "bin": {
9
+ "dembrandt": "dist/index.js",
10
+ "dembrandt-mcp": "dist/mcp-server.js"
11
+ },
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./dtcg": "./dist/lib/dtcg/validate.js",
15
+ "./drift": {
16
+ "types": "./dist/lib/drift.d.ts",
17
+ "default": "./dist/lib/drift.js"
18
+ },
19
+ "./report": {
20
+ "types": "./dist/lib/formatters/html.d.ts",
21
+ "default": "./dist/lib/formatters/html.js"
22
+ },
23
+ "./normalize": "./dist/lib/normalize.js",
24
+ "./types": {
25
+ "types": "./dist/lib/types.d.ts",
26
+ "default": "./dist/lib/types.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist/"
31
+ ],
32
+ "scripts": {
33
+ "start": "node dist/index.js",
34
+ "prepublishOnly": "npm run build",
35
+ "test": "npm run build && node --test dist/test/*.test.js",
36
+ "install-browser": "node tools/install-browser.mjs",
37
+ "qa:baseline": "npm run build && node test/qa.mjs --baseline",
38
+ "qa:diff": "npm run build && node test/qa.mjs --diff",
39
+ "qa:site": "npm run build && node test/qa.mjs --site",
40
+ "gold:label": "npm run build && node tools/gold-label.mjs",
41
+ "gold:run": "npm run build && node tools/gold-run.mjs",
42
+ "gold:judge": "npm run build && node tools/gold-ai-judge.mjs",
43
+ "gold:tune": "node tools/gold-tune.mjs",
44
+ "gold:cluster": "node tools/gold-cluster.mjs",
45
+ "gold:analyze": "node tools/gold-analyze.mjs",
46
+ "build": "tsc && cp package.json dist/package.json",
47
+ "lint": "eslint ."
48
+ },
49
+ "keywords": [
50
+ "design-tokens",
51
+ "design-system",
52
+ "branding",
53
+ "css-analysis",
54
+ "cli",
55
+ "playwright",
56
+ "mseep",
57
+ "mcp-server"
58
+ ],
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/dembrandt/dembrandt.git"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/dembrandt/dembrandt/issues"
65
+ },
66
+ "homepage": "https://dembrandt.com",
67
+ "author": "thevangelist <info@esajuhana.com>",
68
+ "license": "MIT",
69
+ "dependencies": {
70
+ "chalk": "^5.3.0",
71
+ "commander": "^11.1.0",
72
+ "ora": "^7.0.1",
73
+ "playwright-core": "^1.60.0"
74
+ },
75
+ "peerDependencies": {
76
+ "@modelcontextprotocol/sdk": "1.29.0",
77
+ "playwright": "^1.60.0",
78
+ "zod": "4.3.6"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "@modelcontextprotocol/sdk": {
82
+ "optional": true
83
+ },
84
+ "zod": {
85
+ "optional": true
86
+ },
87
+ "playwright": {
88
+ "optional": true
89
+ }
90
+ },
91
+ "engines": {
92
+ "node": ">=18.0.0"
93
+ },
94
+ "devDependencies": {
95
+ "@eslint/js": "10.0.1",
96
+ "@modelcontextprotocol/sdk": "1.29.0",
97
+ "@types/node": "25.9.2",
98
+ "eslint": "10.4.1",
99
+ "playwright": "^1.57.0",
100
+ "playwright-core": "1.60.0",
101
+ "typescript": "6.0.3",
102
+ "typescript-eslint": "8.60.1",
103
+ "zod": "4.3.6"
104
+ },
105
+ "publisher": "mseep"
106
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Minimal vitest-compatible shim over node:test + node:assert, so the DTCG
3
+ * validator's upstream test suite (written for vitest) runs verbatim under the
4
+ * project's node:test runner. Only the three matchers that suite actually uses
5
+ * are implemented (toBe, toContain, toHaveLength); no .not.
6
+ */
7
+ import { describe, it } from 'node:test';
8
+ export { describe, it };
9
+ export declare function expect(actual: any): {
10
+ toBe(expected: any): void;
11
+ toContain(expected: any): void;
12
+ toHaveLength(length: number): void;
13
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal vitest-compatible shim over node:test + node:assert, so the DTCG
3
+ * validator's upstream test suite (written for vitest) runs verbatim under the
4
+ * project's node:test runner. Only the three matchers that suite actually uses
5
+ * are implemented (toBe, toContain, toHaveLength); no .not.
6
+ */
7
+ import { describe, it } from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ export { describe, it };
10
+ export function expect(actual) {
11
+ return {
12
+ toBe(expected) {
13
+ assert.strictEqual(actual, expected);
14
+ },
15
+ toContain(expected) {
16
+ assert.ok(actual != null && typeof actual.includes === 'function' && actual.includes(expected), `expected ${JSON.stringify(actual)} to contain ${JSON.stringify(expected)}`);
17
+ },
18
+ toHaveLength(length) {
19
+ assert.strictEqual(actual?.length, length);
20
+ },
21
+ };
22
+ }
23
+ //# sourceMappingURL=_vitest-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_vitest-shim.js","sourceRoot":"","sources":["../../test/_vitest-shim.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAExB,MAAM,UAAU,MAAM,CAAC,MAAW;IAChC,OAAO;QACL,IAAI,CAAC,QAAa;YAChB,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;QACD,SAAS,CAAC,QAAa;YACrB,MAAM,CAAC,EAAE,CACP,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EACpF,YAAY,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAC5E,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,MAAc;YACzB,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ // Browser-free CLI plumbing tests: arg parsing, exit codes, help output. These
6
+ // run before any extraction, so they need no browser. Import resolution is now
7
+ // covered by tsc/build (an unresolved import fails compilation), so there is no
8
+ // separate runtime module-load test here.
9
+ const indexJs = fileURLToPath(new URL('../index.js', import.meta.url));
10
+ function run(args, opts = {}) {
11
+ return spawnSync('node', [indexJs, ...args], { encoding: 'utf8', ...opts });
12
+ }
13
+ test('--version exits 0 and prints a semver', () => {
14
+ const r = run(['--version']);
15
+ assert.equal(r.status, 0);
16
+ assert.match(r.stdout.trim(), /^\d+\.\d+\.\d+/);
17
+ });
18
+ test('--help exits 0 and shows the extraction usage', () => {
19
+ const r = run(['--help']);
20
+ assert.equal(r.status, 0);
21
+ assert.match(r.stdout, /Extract design tokens/);
22
+ assert.match(r.stdout, /--dtcg/);
23
+ });
24
+ //# sourceMappingURL=cli.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../test/cli.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,0CAA0C;AAE1C,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEvE,SAAS,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE;IAC1B,OAAO,SAAS,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACjD,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,gBAAgB,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IACzD,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ import { hexToRgb, relativeLuminance, computeWcag, convertColor, deltaE, deltaE2000 } from '../lib/colors.js';
4
+ test('hexToRgb parses 6, 3, and 8 digit hex', () => {
5
+ assert.deepEqual(hexToRgb('#ff0000'), { r: 255, g: 0, b: 0 });
6
+ assert.deepEqual(hexToRgb('#abc'), { r: 170, g: 187, b: 204 });
7
+ const withAlpha = hexToRgb('#ff000080');
8
+ assert.equal(withAlpha.r, 255);
9
+ assert.ok(Math.abs(withAlpha.a - 128 / 255) < 1e-9);
10
+ });
11
+ test('hexToRgb returns null for non-hex or malformed input', () => {
12
+ assert.equal(hexToRgb('rgb(0,0,0)'), null);
13
+ assert.equal(hexToRgb('#ff'), null);
14
+ assert.equal(hexToRgb(''), null);
15
+ });
16
+ test('relativeLuminance bounds: white ~1, black 0, invalid null', () => {
17
+ assert.ok(Math.abs(relativeLuminance('#ffffff') - 1) < 1e-6);
18
+ assert.equal(relativeLuminance('#000000'), 0);
19
+ assert.equal(relativeLuminance('not-a-color'), null);
20
+ });
21
+ test('deltaE (CIE76): 0 for identical, 999 for unparseable', () => {
22
+ assert.equal(deltaE('#ff0000', '#ff0000'), 0);
23
+ assert.equal(deltaE('garbage', '#fff'), 999);
24
+ });
25
+ test('deltaE2000: 0 identical, small for tiny shift, large for opposite hues', () => {
26
+ assert.equal(deltaE2000('#ff0000', '#ff0000'), 0);
27
+ assert.ok(deltaE2000('#ff0000', '#fe0000') < 1);
28
+ assert.ok(deltaE2000('#ff0000', '#0000ff') > 40);
29
+ });
30
+ test('deltaE2000 accepts rgb() input and matches hex', () => {
31
+ assert.equal(deltaE2000('rgb(255,0,0)', '#ff0000'), 0);
32
+ });
33
+ test('deltaE2000 returns 100 when a color cannot be parsed', () => {
34
+ assert.equal(deltaE2000('garbage', '#ffffff'), 100);
35
+ });
36
+ test('computeWcag: black on white is 21:1 and passes AA + AAA', () => {
37
+ const pairs = computeWcag([
38
+ { normalized: '#000000' },
39
+ { normalized: '#ffffff' },
40
+ ]);
41
+ assert.equal(pairs.length, 1);
42
+ assert.equal(pairs[0].ratio, 21);
43
+ assert.equal(pairs[0].aa, true);
44
+ assert.equal(pairs[0].aaa, true);
45
+ assert.equal(pairs[0].fg, '#ffffff'); // lighter is foreground here
46
+ assert.equal(pairs[0].bg, '#000000');
47
+ });
48
+ test('computeWcag ignores non-hex palette entries', () => {
49
+ const pairs = computeWcag([
50
+ { normalized: '#000000' },
51
+ { normalized: 'rgb(255,255,255)' }, // filtered: not hex
52
+ ]);
53
+ assert.equal(pairs.length, 0);
54
+ });
55
+ test('convertColor normalizes rgb, hsl, and hex to lowercase hex', () => {
56
+ assert.equal(convertColor('rgb(255,0,0)').hex, '#ff0000');
57
+ assert.equal(convertColor('hsl(0, 100%, 50%)').hex, '#ff0000');
58
+ assert.equal(convertColor('#ABC').hex, '#aabbcc');
59
+ });
60
+ test('convertColor flags alpha and returns null for junk', () => {
61
+ assert.equal(convertColor('rgba(0, 0, 0, 0.5)').hasAlpha, true);
62
+ assert.equal(convertColor('not-a-color'), null);
63
+ });
64
+ //# sourceMappingURL=colors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.test.js","sourceRoot":"","sources":["../../test/colors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { resolveCompare } from '../lib/compare.js';
4
+ /**
5
+ * resolveCompare dispatches `--compare <arg>` on file-vs-id with injectable deps,
6
+ * so both backends are exercised without a real filesystem or network: a local
7
+ * file diffs here; a baseline id POSTs to the App and uses its returned report.
8
+ */
9
+ function fixture(overrides = {}) {
10
+ return {
11
+ url: 'https://example.com/',
12
+ extractedAt: 't',
13
+ colors: { palette: [{ normalized: '#133174', count: 40, confidence: 'high' }], semantic: { primary: '#133174' }, cssVariables: {} },
14
+ typography: { styles: [], sources: {} },
15
+ spacing: { scaleType: 'base-8', commonValues: [] },
16
+ borderRadius: { values: [] },
17
+ borders: {}, shadows: [],
18
+ components: { buttons: [], inputs: [], links: [], badges: [] },
19
+ breakpoints: [], iconSystem: [], frameworks: [],
20
+ ...overrides,
21
+ };
22
+ }
23
+ test('local file path: diffs against the file here', async () => {
24
+ const baseline = fixture();
25
+ const candidate = fixture({ colors: { palette: [{ normalized: '#0a0a0a', count: 40, confidence: 'high' }], semantic: {}, cssVariables: {} } });
26
+ const r = await resolveCompare('baseline.json', candidate, {
27
+ isFile: () => true,
28
+ readFile: () => JSON.stringify(baseline),
29
+ });
30
+ assert.equal(r.mode, 'local');
31
+ assert.equal(r.source, 'baseline.json');
32
+ assert.equal(r.report.status, 'drift');
33
+ });
34
+ test('baseline id path: POSTs candidate to the App and returns its report', async () => {
35
+ const candidate = fixture();
36
+ let captured = null;
37
+ const fakeReport = { score: 7, status: 'stable', threshold: 10, summary: { changed: 0, added: 0, removed: 0 }, categories: [], changes: [] };
38
+ const fetchFn = (async (url, init) => {
39
+ captured = { url, body: JSON.parse(init.body) };
40
+ return { ok: true, json: async () => ({ drift: fakeReport }) };
41
+ });
42
+ const r = await resolveCompare('dembrandt.com', candidate, {
43
+ isFile: () => false,
44
+ fetchFn,
45
+ api: 'https://app.example.com/',
46
+ });
47
+ assert.equal(captured.url, 'https://app.example.com/api/app/drift');
48
+ assert.equal(captured.body.baselineId, 'dembrandt.com');
49
+ assert.ok(captured.body.candidate.colors, 'sends the candidate extraction');
50
+ assert.equal(r.mode, 'platform');
51
+ assert.equal(r.report.score, 7);
52
+ });
53
+ test('baseline id path: surfaces a platform error', async () => {
54
+ const fetchFn = (async () => ({ ok: false, status: 404, statusText: 'Not Found', json: async () => ({ error: 'baseline not found: nope' }) }));
55
+ await assert.rejects(() => resolveCompare('nope', fixture(), { isFile: () => false, fetchFn, api: 'https://app.example.com' }), /platform compare failed \(404\): baseline not found: nope/);
56
+ });
57
+ //# sourceMappingURL=compare.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.test.js","sourceRoot":"","sources":["../../test/compare.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { computeDrift } from '../lib/drift.js';
4
+ /**
5
+ * Low-confidence tokens are single-use, margin-of-detection elements the
6
+ * extractor is unsure about; they surface inconsistently between two
7
+ * extractions of the same design. computeDrift must ignore them so they never
8
+ * produce phantom drift.
9
+ */
10
+ function fixture(overrides = {}) {
11
+ return {
12
+ url: 'https://example.com/',
13
+ extractedAt: 't',
14
+ colors: { palette: [{ normalized: '#133174', count: 40, confidence: 'high' }], semantic: { primary: '#133174' }, cssVariables: {} },
15
+ typography: { styles: [], sources: {} },
16
+ spacing: { scaleType: 'base-8', commonValues: [] },
17
+ borderRadius: { values: [] },
18
+ borders: {}, shadows: [],
19
+ components: { buttons: [], inputs: [], links: [], badges: [] },
20
+ breakpoints: [], iconSystem: [], frameworks: [],
21
+ ...overrides,
22
+ };
23
+ }
24
+ test('a low-confidence shadow present in one extraction but not the other is not drift', () => {
25
+ const withShadow = fixture({
26
+ shadows: [{ shadow: 'rgb(128, 128, 128) 0px 0px 5px 0px', count: 1, confidence: 'low' }],
27
+ });
28
+ const without = fixture({ shadows: [] });
29
+ const report = computeDrift(withShadow, without);
30
+ assert.equal(report.status, 'stable');
31
+ assert.equal(report.summary.removed, 0);
32
+ assert.equal(report.changes.filter((c) => c.category === 'shadow').length, 0);
33
+ });
34
+ test('a low-confidence radius present in one extraction but not the other is not drift', () => {
35
+ const withRadius = fixture({
36
+ borderRadius: { values: [{ value: '2px', count: 1, confidence: 'low' }] },
37
+ });
38
+ const without = fixture({ borderRadius: { values: [] } });
39
+ const report = computeDrift(withRadius, without);
40
+ assert.equal(report.status, 'stable');
41
+ assert.equal(report.changes.filter((c) => c.category === 'radius').length, 0);
42
+ });
43
+ test('a high-confidence radius change is still real drift', () => {
44
+ const base = fixture({
45
+ borderRadius: { values: [{ value: '4px', count: 20, confidence: 'high' }] },
46
+ });
47
+ const changed = fixture({
48
+ borderRadius: { values: [{ value: '12px', count: 20, confidence: 'high' }] },
49
+ });
50
+ const report = computeDrift(base, changed);
51
+ assert.ok(report.changes.some((c) => c.category === 'radius'), 'high-confidence radius change must be reported');
52
+ });
53
+ //# sourceMappingURL=drift.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift.test.js","sourceRoot":"","sources":["../../test/drift.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from './_vitest-shim.js';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ import { toDtcgTokens } from '../lib/formatters/dtcg.js';
6
+ import { validateTokensObject } from '../lib/dtcg/validate.js';
7
+ import { SCHEMA_VERSION } from '../lib/version.js';
8
+ /**
9
+ * End-to-end guard: the DTCG formatter's own output must satisfy the DTCG
10
+ * validator. The validator does not run at extraction time (index.ts emits
11
+ * toDtcgTokens output unchecked), so this is the only gate that catches a
12
+ * formatter regression emitting a malformed token.
13
+ *
14
+ * The fixture is a small synthetic extraction, not a saved real crawl: it is
15
+ * hand-built to exercise all six exporters (color, typography, spacing, radius,
16
+ * border, shadow) in a few dozen lines, so it stays readable and is trivially
17
+ * edited when the contract changes. Any new token type the formatter emits is
18
+ * covered here as long as the fixture exercises it.
19
+ */
20
+ const fixturesDir = join(dirname(fileURLToPath(import.meta.url)), '../../test/fixtures');
21
+ function loadFixture(name) {
22
+ return JSON.parse(readFileSync(join(fixturesDir, name), 'utf8'));
23
+ }
24
+ const FIXTURE = 'extraction-synthetic.sample.json';
25
+ describe('DTCG formatter output is spec-valid', () => {
26
+ it(`${FIXTURE} is pinned to the current output contract`, () => {
27
+ // A fixture from an older schema would validate a historical extraction
28
+ // shape, not what the formatter sees today. Fail loudly when it drifts so
29
+ // the fixture gets updated instead of silently rotting.
30
+ const fixture = loadFixture(FIXTURE);
31
+ expect(fixture.meta.schemaVersion).toBe(SCHEMA_VERSION);
32
+ });
33
+ it(`${FIXTURE} -> toDtcgTokens -> validateTokensObject passes`, () => {
34
+ const tokens = toDtcgTokens(loadFixture(FIXTURE));
35
+ const result = validateTokensObject(tokens);
36
+ // join() surfaces the actual validator errors in the failure message.
37
+ expect(result.errors.join('; ')).toBe('');
38
+ expect(result.valid).toBe(true);
39
+ });
40
+ it('exercises all six exporters', () => {
41
+ const tokens = toDtcgTokens(loadFixture(FIXTURE));
42
+ for (const group of ['color', 'typography', 'spacing', 'radius', 'border', 'shadow']) {
43
+ // present means the exporter ran and emitted at least one token group
44
+ expect(typeof tokens[group]).toBe('object');
45
+ }
46
+ });
47
+ });
48
+ //# sourceMappingURL=dtcg-formatter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dtcg-formatter.test.js","sourceRoot":"","sources":["../../test/dtcg-formatter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;AAEzF,SAAS,WAAW,CAAC,IAAI;IACvB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAEnD,QAAQ,CAAC,qCAAqC,EAAE,GAAG,EAAE;IACnD,EAAE,CAAC,GAAG,OAAO,2CAA2C,EAAE,GAAG,EAAE;QAC7D,wEAAwE;QACxE,0EAA0E;QAC1E,wDAAwD;QACxD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,OAAO,iDAAiD,EAAE,GAAG,EAAE;QACnE,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC5C,sEAAsE;QACtE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrF,sEAAsE;YACtE,MAAM,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};