@usefragments/core 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.
- package/LICENSE +21 -0
- package/dist/chunk-243QYRUF.js +2145 -0
- package/dist/chunk-243QYRUF.js.map +1 -0
- package/dist/chunk-3LLRNCPX.js +111 -0
- package/dist/chunk-3LLRNCPX.js.map +1 -0
- package/dist/chunk-EIYNNS77.js +48 -0
- package/dist/chunk-EIYNNS77.js.map +1 -0
- package/dist/chunk-JNBFJ34I.js +326 -0
- package/dist/chunk-JNBFJ34I.js.map +1 -0
- package/dist/chunk-R3X7UBHU.js +228 -0
- package/dist/chunk-R3X7UBHU.js.map +1 -0
- package/dist/chunk-SH4KPIYH.js +393 -0
- package/dist/chunk-SH4KPIYH.js.map +1 -0
- package/dist/chunk-V4VQB57N.js +56 -0
- package/dist/chunk-V4VQB57N.js.map +1 -0
- package/dist/chunk-X34IA4LR.js +319 -0
- package/dist/chunk-X34IA4LR.js.map +1 -0
- package/dist/chunk-YF65VYRY.js +493 -0
- package/dist/chunk-YF65VYRY.js.map +1 -0
- package/dist/codes/index.d.ts +40 -0
- package/dist/codes/index.js +23 -0
- package/dist/codes/index.js.map +1 -0
- package/dist/compiled-types/index.d.ts +5 -0
- package/dist/compiled-types/index.js +1 -0
- package/dist/compiled-types/index.js.map +1 -0
- package/dist/generate/index.d.ts +47 -0
- package/dist/generate/index.js +11 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/governance-BOa3KyiJ.d.ts +3279 -0
- package/dist/graph/index.d.ts +66 -0
- package/dist/graph/index.js +17 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +11388 -0
- package/dist/index.js +8711 -0
- package/dist/index.js.map +1 -0
- package/dist/package-identity.d.ts +42 -0
- package/dist/package-identity.js +23 -0
- package/dist/package-identity.js.map +1 -0
- package/dist/preview/index.d.ts +484 -0
- package/dist/preview/index.js +1129 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/react-types.d.ts +22 -0
- package/dist/react-types.js +1 -0
- package/dist/react-types.js.map +1 -0
- package/dist/registry.d.ts +2367 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/dist/schemas/index.d.ts +2584 -0
- package/dist/schemas/index.js +55 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/severity.d.ts +18 -0
- package/dist/severity.js +27 -0
- package/dist/severity.js.map +1 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +63 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/topology/index.d.ts +48 -0
- package/dist/topology/index.js +7 -0
- package/dist/topology/index.js.map +1 -0
- package/dist/types-xJ2xyp_G.d.ts +119 -0
- package/package.json +120 -0
- package/src/__tests__/contract-parser.test.ts +614 -0
- package/src/__tests__/dtcg-generators.test.ts +439 -0
- package/src/__tests__/dtcg-parser.test.ts +884 -0
- package/src/__tests__/preview-runtime.test.tsx +111 -0
- package/src/__tests__/schema.test.ts +31 -0
- package/src/__tests__/severity.test.ts +34 -0
- package/src/__tests__/style-comparison.test.ts +662 -0
- package/src/__tests__/tokens-compat.test.ts +51 -0
- package/src/agent-format.test.ts +105 -0
- package/src/agent-format.ts +141 -0
- package/src/bundle.ts +149 -0
- package/src/codes/__tests__/codes.test.ts +73 -0
- package/src/codes/codes.ts +463 -0
- package/src/codes/index.ts +7 -0
- package/src/codes/source-finding-bridge.ts +113 -0
- package/src/compiled-types/index.ts +293 -0
- package/src/component-contract.ts +231 -0
- package/src/component-discovery.ts +80 -0
- package/src/composition.test.ts +262 -0
- package/src/composition.ts +318 -0
- package/src/config-paths.test.ts +78 -0
- package/src/config-paths.ts +47 -0
- package/src/config.ts +18 -0
- package/src/conform-prove.test.ts +274 -0
- package/src/conform-prove.ts +326 -0
- package/src/conform.ts +160 -0
- package/src/constants.ts +114 -0
- package/src/context.ts +2 -0
- package/src/contract/fixture.ts +110 -0
- package/src/contract/hash.test.ts +86 -0
- package/src/contract/hash.ts +195 -0
- package/src/contract/index.ts +43 -0
- package/src/contract/preimage.test.ts +254 -0
- package/src/contract/preimage.ts +365 -0
- package/src/contract/stamp.test.ts +100 -0
- package/src/contract/stamp.ts +105 -0
- package/src/contract-parser.ts +141 -0
- package/src/defineFragment.test.ts +257 -0
- package/src/defineFragment.ts +293 -0
- package/src/dtcg-generators.ts +324 -0
- package/src/dtcg-parser.ts +1 -0
- package/src/dtcg.ts +237 -0
- package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
- package/src/facts/builders.ts +835 -0
- package/src/facts/compile.ts +422 -0
- package/src/facts/fact-index.ts +394 -0
- package/src/facts/facts.test.ts +494 -0
- package/src/facts/ids.ts +125 -0
- package/src/facts/index.ts +119 -0
- package/src/facts/types.ts +614 -0
- package/src/figma.ts +263 -0
- package/src/fragment-types.ts +214 -0
- package/src/generate/generate.test.ts +594 -0
- package/src/generate/index.ts +438 -0
- package/src/governance-integrity.test.ts +186 -0
- package/src/governance-integrity.ts +282 -0
- package/src/governance.test.ts +300 -0
- package/src/governance.ts +605 -0
- package/src/governance.type-test.ts +75 -0
- package/src/graph/engine.ts +446 -0
- package/src/graph/graph.test.ts +746 -0
- package/src/graph/index.ts +32 -0
- package/src/graph/serialization.ts +55 -0
- package/src/graph/types.ts +178 -0
- package/src/index.ts +891 -0
- package/src/local-canonical.ts +102 -0
- package/src/mcp-snapshot.ts +222 -0
- package/src/package-identity-match.ts +33 -0
- package/src/package-identity.generated.ts +233 -0
- package/src/package-identity.test.ts +99 -0
- package/src/package-identity.ts +157 -0
- package/src/performance-presets.ts +142 -0
- package/src/preview/comparison-artifact.test.ts +228 -0
- package/src/preview/comparison-artifact.ts +181 -0
- package/src/preview/comparison-context.ts +93 -0
- package/src/preview/comparison-evidence.ts +112 -0
- package/src/preview/comparison-format.ts +21 -0
- package/src/preview/comparison-presentation.ts +187 -0
- package/src/preview/comparison-references.test.ts +430 -0
- package/src/preview/comparison-references.ts +72 -0
- package/src/preview/comparison-score.ts +59 -0
- package/src/preview/comparison-styles.ts +197 -0
- package/src/preview/comparison.test.ts +559 -0
- package/src/preview/comparison.ts +488 -0
- package/src/preview/index.ts +10 -0
- package/src/preview/types.ts +309 -0
- package/src/preview/validation.test.ts +290 -0
- package/src/preview/validation.ts +356 -0
- package/src/preview-runtime.tsx +144 -0
- package/src/raw-html-canonical.test.ts +41 -0
- package/src/raw-html-canonical.ts +179 -0
- package/src/react-types.ts +21 -0
- package/src/registry-install-plan.ts +344 -0
- package/src/registry.test.ts +245 -0
- package/src/registry.ts +369 -0
- package/src/rules/a11y-required-accessible-name.ts +93 -0
- package/src/rules/color-math.test.ts +72 -0
- package/src/rules/color-math.ts +192 -0
- package/src/rules/components-forbidden-prop-value.ts +108 -0
- package/src/rules/components-prefer-library.ts +943 -0
- package/src/rules/components-unknown-prop.ts +94 -0
- package/src/rules/composition-pattern-schema.ts +125 -0
- package/src/rules/composition-pattern.test.ts +320 -0
- package/src/rules/composition-pattern.ts +271 -0
- package/src/rules/emit-gate.test.ts +42 -0
- package/src/rules/emit-gate.ts +60 -0
- package/src/rules/finding.ts +54 -0
- package/src/rules/index.ts +235 -0
- package/src/rules/jsx-preferred-component.ts +68 -0
- package/src/rules/jsx-preferred-import-path.ts +67 -0
- package/src/rules/presets.ts +57 -0
- package/src/rules/props-invalid-value.ts +115 -0
- package/src/rules/rules.test.ts +3636 -0
- package/src/rules/spacing-resolution.ts +184 -0
- package/src/rules/styles-no-raw-color.ts +329 -0
- package/src/rules/styles-no-raw-dimensions.ts +357 -0
- package/src/rules/styles-no-raw-spacing.ts +236 -0
- package/src/rules/styles-no-raw-typography.ts +237 -0
- package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
- package/src/rules/tailwind-arbitrary-color.ts +93 -0
- package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
- package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
- package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
- package/src/rules/tailwind-forbidden-palette.ts +107 -0
- package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
- package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.ts +82 -0
- package/src/rules/tailwind-rule-utils.ts +106 -0
- package/src/rules/tailwind-unknown-class.test.ts +157 -0
- package/src/rules/tailwind-unknown-class.ts +121 -0
- package/src/rules/taxonomy.test.ts +238 -0
- package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
- package/src/rules/tiers.ts +111 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
- package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
- package/src/rules/tokens-require-dual-fallback.ts +78 -0
- package/src/rules/types.ts +27 -0
- package/src/rules/utils.test.ts +110 -0
- package/src/rules/utils.ts +599 -0
- package/src/schema.ts +427 -0
- package/src/schemas/index.ts +275 -0
- package/src/schemas/schemas.test.ts +124 -0
- package/src/severity.ts +54 -0
- package/src/storyAdapter.test.ts +571 -0
- package/src/storyAdapter.ts +761 -0
- package/src/storyFilters.test.ts +350 -0
- package/src/storyFilters.ts +253 -0
- package/src/storybook-csf.ts +11 -0
- package/src/style-comparison.ts +423 -0
- package/src/test-utils.ts +68 -0
- package/src/token-parser.ts +2 -0
- package/src/token-types.ts +287 -0
- package/src/tokens/categories.ts +131 -0
- package/src/tokens/color.ts +193 -0
- package/src/tokens/design-token-parser.test.ts +159 -0
- package/src/tokens/design-token-parser.ts +402 -0
- package/src/tokens/dtcg.ts +428 -0
- package/src/tokens/index.ts +130 -0
- package/src/tokens/lookup.ts +19 -0
- package/src/tokens/scss.ts +171 -0
- package/src/tokens/tailwind-v4.ts +187 -0
- package/src/tokens/types.ts +43 -0
- package/src/topology/index.ts +9 -0
- package/src/topology/resolve-area.test.ts +174 -0
- package/src/topology/resolve-area.ts +215 -0
- package/src/types.ts +1057 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DTCGGroup,
|
|
3
|
+
DTCGTokenFile,
|
|
4
|
+
DTCGTokenType,
|
|
5
|
+
ResolvedDTCGToken,
|
|
6
|
+
} from "../dtcg.js";
|
|
7
|
+
import { hexToRgb } from "./color.js";
|
|
8
|
+
import type { ParsedToken, TokenParseOutput } from "./types.js";
|
|
9
|
+
|
|
10
|
+
const DTCG_META_KEYS = new Set([
|
|
11
|
+
"$type",
|
|
12
|
+
"$value",
|
|
13
|
+
"$description",
|
|
14
|
+
"$deprecated",
|
|
15
|
+
"$extensions",
|
|
16
|
+
"$extends",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const MAX_ALIAS_DEPTH = 10;
|
|
20
|
+
|
|
21
|
+
export function isDTCGFile(filePath: string): boolean {
|
|
22
|
+
return (
|
|
23
|
+
filePath.endsWith(".tokens.json") ||
|
|
24
|
+
filePath.endsWith(".tokens") ||
|
|
25
|
+
filePath.endsWith("design-tokens.json") ||
|
|
26
|
+
filePath.endsWith("tokens.json")
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mapDTCGTypeToCategory(type: DTCGTokenType, tokenPath: string): string {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case "color":
|
|
33
|
+
return "colors";
|
|
34
|
+
case "dimension":
|
|
35
|
+
if (/radius/i.test(tokenPath)) return "radius";
|
|
36
|
+
return "spacing";
|
|
37
|
+
case "fontFamily":
|
|
38
|
+
case "fontWeight":
|
|
39
|
+
return "typography";
|
|
40
|
+
case "shadow":
|
|
41
|
+
return "shadows";
|
|
42
|
+
case "border":
|
|
43
|
+
return "borders";
|
|
44
|
+
case "duration":
|
|
45
|
+
case "cubicBezier":
|
|
46
|
+
case "transition":
|
|
47
|
+
return "transitions";
|
|
48
|
+
case "typography":
|
|
49
|
+
return "typography";
|
|
50
|
+
case "gradient":
|
|
51
|
+
return "colors";
|
|
52
|
+
case "strokeStyle":
|
|
53
|
+
return "borders";
|
|
54
|
+
case "number":
|
|
55
|
+
return "other";
|
|
56
|
+
default:
|
|
57
|
+
return "other";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function colorValueToCSS(value: unknown): string {
|
|
62
|
+
if (typeof value === "string") return value;
|
|
63
|
+
if (value && typeof value === "object") {
|
|
64
|
+
const obj = value as Record<string, unknown>;
|
|
65
|
+
if (typeof obj.hex === "string") {
|
|
66
|
+
if (obj.alpha !== undefined && typeof obj.alpha === "number" && obj.alpha < 1) {
|
|
67
|
+
const rgb = hexToRgb(obj.hex);
|
|
68
|
+
if (rgb) {
|
|
69
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${obj.alpha})`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return obj.hex;
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(obj.components)) {
|
|
75
|
+
const comps = obj.components as number[];
|
|
76
|
+
if (comps.length >= 3) {
|
|
77
|
+
const alpha = obj.alpha ?? (comps.length >= 4 ? comps[3] : 1);
|
|
78
|
+
if (typeof alpha === "number" && alpha < 1) {
|
|
79
|
+
return `rgba(${Math.round(comps[0] * 255)}, ${Math.round(comps[1] * 255)}, ${Math.round(comps[2] * 255)}, ${alpha})`;
|
|
80
|
+
}
|
|
81
|
+
if (comps.every((c) => c <= 1)) {
|
|
82
|
+
return `rgb(${Math.round(comps[0] * 255)}, ${Math.round(comps[1] * 255)}, ${Math.round(comps[2] * 255)})`;
|
|
83
|
+
}
|
|
84
|
+
return `rgb(${comps[0]}, ${comps[1]}, ${comps[2]})`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return String(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function dimensionValueToCSS(value: unknown): string {
|
|
92
|
+
if (typeof value === "string") return value;
|
|
93
|
+
if (value && typeof value === "object") {
|
|
94
|
+
const obj = value as Record<string, unknown>;
|
|
95
|
+
if (typeof obj.value === "number" && typeof obj.unit === "string") {
|
|
96
|
+
return `${obj.value}${obj.unit}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return String(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function shadowValueToCSS(value: unknown): string {
|
|
103
|
+
if (typeof value === "string") return value;
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
return value.map((v) => shadowSingleToCSS(v)).join(", ");
|
|
106
|
+
}
|
|
107
|
+
return shadowSingleToCSS(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function shadowSingleToCSS(value: unknown): string {
|
|
111
|
+
if (typeof value === "string") return value;
|
|
112
|
+
if (value && typeof value === "object") {
|
|
113
|
+
const obj = value as Record<string, unknown>;
|
|
114
|
+
const parts: string[] = [];
|
|
115
|
+
if (obj.inset) parts.push("inset");
|
|
116
|
+
parts.push(dimensionValueToCSS(obj.offsetX));
|
|
117
|
+
parts.push(dimensionValueToCSS(obj.offsetY));
|
|
118
|
+
parts.push(dimensionValueToCSS(obj.blur));
|
|
119
|
+
if (obj.spread !== undefined) parts.push(dimensionValueToCSS(obj.spread));
|
|
120
|
+
parts.push(colorValueToCSS(obj.color));
|
|
121
|
+
return parts.join(" ");
|
|
122
|
+
}
|
|
123
|
+
return String(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function borderValueToCSS(value: unknown): string {
|
|
127
|
+
if (typeof value === "string") return value;
|
|
128
|
+
if (value && typeof value === "object") {
|
|
129
|
+
const obj = value as Record<string, unknown>;
|
|
130
|
+
return `${dimensionValueToCSS(obj.width)} ${obj.style ?? "solid"} ${colorValueToCSS(obj.color)}`;
|
|
131
|
+
}
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function typographyValueToCSS(value: unknown): string {
|
|
136
|
+
if (typeof value === "string") return value;
|
|
137
|
+
if (value && typeof value === "object") {
|
|
138
|
+
const obj = value as Record<string, unknown>;
|
|
139
|
+
const parts: string[] = [];
|
|
140
|
+
if (obj.fontWeight) parts.push(String(obj.fontWeight));
|
|
141
|
+
if (obj.fontSize) parts.push(dimensionValueToCSS(obj.fontSize));
|
|
142
|
+
if (obj.lineHeight) parts.push(`/ ${obj.lineHeight}`);
|
|
143
|
+
if (obj.fontFamily) {
|
|
144
|
+
const family = Array.isArray(obj.fontFamily)
|
|
145
|
+
? obj.fontFamily.join(", ")
|
|
146
|
+
: String(obj.fontFamily);
|
|
147
|
+
parts.push(family);
|
|
148
|
+
}
|
|
149
|
+
return parts.join(" ");
|
|
150
|
+
}
|
|
151
|
+
return String(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function cubicBezierValueToCSS(value: unknown): string {
|
|
155
|
+
if (typeof value === "string") return value;
|
|
156
|
+
if (Array.isArray(value) && value.length === 4) {
|
|
157
|
+
return `cubic-bezier(${value.join(", ")})`;
|
|
158
|
+
}
|
|
159
|
+
return String(value);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function transitionValueToCSS(value: unknown): string {
|
|
163
|
+
if (typeof value === "string") return value;
|
|
164
|
+
if (value && typeof value === "object") {
|
|
165
|
+
const obj = value as Record<string, unknown>;
|
|
166
|
+
const parts: string[] = [];
|
|
167
|
+
if (obj.duration) parts.push(String(obj.duration));
|
|
168
|
+
if (obj.timingFunction) parts.push(cubicBezierValueToCSS(obj.timingFunction));
|
|
169
|
+
if (obj.delay) parts.push(String(obj.delay));
|
|
170
|
+
return parts.join(" ");
|
|
171
|
+
}
|
|
172
|
+
return String(value);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function gradientValueToCSS(value: unknown): string {
|
|
176
|
+
if (typeof value === "string") return value;
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
const stops = value.map((stop) => {
|
|
179
|
+
if (stop && typeof stop === "object") {
|
|
180
|
+
const s = stop as Record<string, unknown>;
|
|
181
|
+
const color = colorValueToCSS(s.color);
|
|
182
|
+
const position = typeof s.position === "number" ? ` ${s.position * 100}%` : "";
|
|
183
|
+
return `${color}${position}`;
|
|
184
|
+
}
|
|
185
|
+
return String(stop);
|
|
186
|
+
});
|
|
187
|
+
return `linear-gradient(${stops.join(", ")})`;
|
|
188
|
+
}
|
|
189
|
+
return String(value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function fontFamilyValueToCSS(value: unknown): string {
|
|
193
|
+
if (typeof value === "string") return value;
|
|
194
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
195
|
+
return String(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function valueToCSS(type: DTCGTokenType, value: unknown): string {
|
|
199
|
+
switch (type) {
|
|
200
|
+
case "color":
|
|
201
|
+
return colorValueToCSS(value);
|
|
202
|
+
case "dimension":
|
|
203
|
+
return dimensionValueToCSS(value);
|
|
204
|
+
case "shadow":
|
|
205
|
+
return shadowValueToCSS(value);
|
|
206
|
+
case "border":
|
|
207
|
+
return borderValueToCSS(value);
|
|
208
|
+
case "typography":
|
|
209
|
+
return typographyValueToCSS(value);
|
|
210
|
+
case "cubicBezier":
|
|
211
|
+
return cubicBezierValueToCSS(value);
|
|
212
|
+
case "transition":
|
|
213
|
+
return transitionValueToCSS(value);
|
|
214
|
+
case "gradient":
|
|
215
|
+
return gradientValueToCSS(value);
|
|
216
|
+
case "fontFamily":
|
|
217
|
+
return fontFamilyValueToCSS(value);
|
|
218
|
+
case "fontWeight":
|
|
219
|
+
case "duration":
|
|
220
|
+
case "number":
|
|
221
|
+
return String(value);
|
|
222
|
+
case "strokeStyle":
|
|
223
|
+
return typeof value === "string" ? value : String(value);
|
|
224
|
+
default:
|
|
225
|
+
return String(value);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isAlias(value: unknown): value is string {
|
|
230
|
+
return typeof value === "string" && /^\{.+\}$/.test(value);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function resolveAliasPath(alias: string): string {
|
|
234
|
+
return alias.slice(1, -1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function resolveAlias(
|
|
238
|
+
alias: string,
|
|
239
|
+
root: DTCGTokenFile,
|
|
240
|
+
visited: Set<string>,
|
|
241
|
+
depth: number,
|
|
242
|
+
): unknown {
|
|
243
|
+
if (depth > MAX_ALIAS_DEPTH) {
|
|
244
|
+
throw new Error(`Circular alias detected: ${alias} (max depth ${MAX_ALIAS_DEPTH} reached)`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const path = resolveAliasPath(alias);
|
|
248
|
+
|
|
249
|
+
if (visited.has(path)) {
|
|
250
|
+
throw new Error(`Circular alias detected: ${[...visited, path].join(" -> ")}`);
|
|
251
|
+
}
|
|
252
|
+
visited.add(path);
|
|
253
|
+
|
|
254
|
+
const parts = path.split(".");
|
|
255
|
+
let current: unknown = root;
|
|
256
|
+
|
|
257
|
+
for (const part of parts) {
|
|
258
|
+
if (current && typeof current === "object" && part in (current as Record<string, unknown>)) {
|
|
259
|
+
current = (current as Record<string, unknown>)[part];
|
|
260
|
+
} else {
|
|
261
|
+
throw new Error(`Alias reference "${alias}" could not be resolved: "${part}" not found in path "${path}"`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (current && typeof current === "object" && "$value" in (current as Record<string, unknown>)) {
|
|
266
|
+
const resolvedValue = (current as Record<string, unknown>).$value;
|
|
267
|
+
if (isAlias(resolvedValue)) {
|
|
268
|
+
return resolveAlias(resolvedValue, root, visited, depth + 1);
|
|
269
|
+
}
|
|
270
|
+
return resolvedValue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (isAlias(current)) {
|
|
274
|
+
return resolveAlias(current, root, visited, depth + 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return current;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function resolveExtends(
|
|
281
|
+
group: DTCGGroup,
|
|
282
|
+
root: DTCGTokenFile,
|
|
283
|
+
visited: Set<string>,
|
|
284
|
+
): DTCGGroup {
|
|
285
|
+
if (!group.$extends) return group;
|
|
286
|
+
|
|
287
|
+
const extendsPath = group.$extends;
|
|
288
|
+
if (visited.has(extendsPath)) {
|
|
289
|
+
throw new Error(`Circular $extends detected: ${[...visited, extendsPath].join(" -> ")}`);
|
|
290
|
+
}
|
|
291
|
+
visited.add(extendsPath);
|
|
292
|
+
|
|
293
|
+
const parts = extendsPath.split(".");
|
|
294
|
+
let parent: unknown = root;
|
|
295
|
+
for (const part of parts) {
|
|
296
|
+
if (parent && typeof parent === "object" && part in (parent as Record<string, unknown>)) {
|
|
297
|
+
parent = (parent as Record<string, unknown>)[part];
|
|
298
|
+
} else {
|
|
299
|
+
throw new Error(`$extends reference "${extendsPath}" could not be resolved`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!parent || typeof parent !== "object") {
|
|
304
|
+
throw new Error(`$extends target "${extendsPath}" is not a group`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const resolvedParent = resolveExtends(parent as DTCGGroup, root, visited);
|
|
308
|
+
const merged: Record<string, unknown> = { ...resolvedParent };
|
|
309
|
+
for (const [key, value] of Object.entries(group)) {
|
|
310
|
+
if (key === "$extends") continue;
|
|
311
|
+
merged[key] = value;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return merged as DTCGGroup;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function walkTokenTree(
|
|
318
|
+
node: DTCGGroup,
|
|
319
|
+
root: DTCGTokenFile,
|
|
320
|
+
path: string[],
|
|
321
|
+
inheritedType: DTCGTokenType | undefined,
|
|
322
|
+
tokens: ResolvedDTCGToken[],
|
|
323
|
+
): void {
|
|
324
|
+
const resolved = resolveExtends(node, root, new Set());
|
|
325
|
+
const currentType = resolved.$type ?? inheritedType;
|
|
326
|
+
|
|
327
|
+
for (const [key, child] of Object.entries(resolved)) {
|
|
328
|
+
if (DTCG_META_KEYS.has(key)) continue;
|
|
329
|
+
if (typeof child !== "object" || child === null) continue;
|
|
330
|
+
|
|
331
|
+
const childObj = child as Record<string, unknown>;
|
|
332
|
+
const childPath = [...path, key];
|
|
333
|
+
|
|
334
|
+
if ("$value" in childObj) {
|
|
335
|
+
const tokenType = (childObj.$type as DTCGTokenType | undefined) ?? currentType;
|
|
336
|
+
if (!tokenType) continue;
|
|
337
|
+
|
|
338
|
+
let rawValue = childObj.$value;
|
|
339
|
+
if (isAlias(rawValue)) {
|
|
340
|
+
try {
|
|
341
|
+
rawValue = resolveAlias(rawValue, root, new Set(), 0);
|
|
342
|
+
} catch {
|
|
343
|
+
// Keep raw alias value if resolution fails.
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
tokens.push({
|
|
348
|
+
path: childPath.join("."),
|
|
349
|
+
type: tokenType,
|
|
350
|
+
rawValue,
|
|
351
|
+
cssValue: valueToCSS(tokenType, rawValue),
|
|
352
|
+
description: childObj.$description as string | undefined,
|
|
353
|
+
deprecated: childObj.$deprecated as boolean | string | undefined,
|
|
354
|
+
extensions: childObj.$extensions as Record<string, unknown> | undefined,
|
|
355
|
+
});
|
|
356
|
+
} else {
|
|
357
|
+
walkTokenTree(childObj as DTCGGroup, root, childPath, currentType, tokens);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function detectDTCGPrefix(tokens: ResolvedDTCGToken[], root: DTCGTokenFile): string {
|
|
363
|
+
const extensions = root.$extensions as Record<string, unknown> | undefined;
|
|
364
|
+
if (extensions) {
|
|
365
|
+
const fragmentsExt = extensions["com.usefragments"] as Record<string, unknown> | undefined;
|
|
366
|
+
if (fragmentsExt?.prefix && typeof fragmentsExt.prefix === "string") {
|
|
367
|
+
const p = fragmentsExt.prefix.replace(/-$/, "");
|
|
368
|
+
return `--${p}-`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const topLevelKeys = Object.keys(root).filter((k) => !DTCG_META_KEYS.has(k));
|
|
373
|
+
if (topLevelKeys.length === 1) {
|
|
374
|
+
return `--${topLevelKeys[0]}-`;
|
|
375
|
+
}
|
|
376
|
+
if (topLevelKeys.length > 1) {
|
|
377
|
+
return "--";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (tokens.length === 0) return "--";
|
|
381
|
+
|
|
382
|
+
const firstParts = tokens[0].path.split(".");
|
|
383
|
+
if (firstParts.length > 0) {
|
|
384
|
+
return `--${firstParts[0]}-`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return "--";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function tokenPathToCSSName(path: string, prefix: string): string {
|
|
391
|
+
const suffix = path.replace(/\./g, "-");
|
|
392
|
+
const normalizedPrefix = prefix.endsWith("-") ? prefix : `${prefix}-`;
|
|
393
|
+
const prefixBase = normalizedPrefix.replace(/^--/, "").replace(/-$/, "");
|
|
394
|
+
if (suffix.startsWith(prefixBase + "-") || suffix === prefixBase) {
|
|
395
|
+
return `--${suffix}`;
|
|
396
|
+
}
|
|
397
|
+
return `${normalizedPrefix}${suffix}`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function parseDtcgTokens(content: string, filePath = "tokens.tokens.json"): TokenParseOutput {
|
|
401
|
+
const root: DTCGTokenFile = JSON.parse(content);
|
|
402
|
+
const resolvedTokens: ResolvedDTCGToken[] = [];
|
|
403
|
+
walkTokenTree(root, root, [], undefined, resolvedTokens);
|
|
404
|
+
|
|
405
|
+
const prefix = detectDTCGPrefix(resolvedTokens, root);
|
|
406
|
+
const categories: Record<string, ParsedToken[]> = {};
|
|
407
|
+
|
|
408
|
+
for (const token of resolvedTokens) {
|
|
409
|
+
const category = mapDTCGTypeToCategory(token.type, token.path);
|
|
410
|
+
const parsed: ParsedToken = {
|
|
411
|
+
name: tokenPathToCSSName(token.path, prefix),
|
|
412
|
+
value: token.cssValue,
|
|
413
|
+
category,
|
|
414
|
+
description: token.description,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
categories[category] ??= [];
|
|
418
|
+
categories[category].push(parsed);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
prefix,
|
|
423
|
+
categories,
|
|
424
|
+
total: resolvedTokens.length,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export const parseDTCGFile = parseDtcgTokens;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { DesignToken, TokenCategory } from "../token-types.js";
|
|
2
|
+
import { isDTCGFile, parseDtcgTokens } from "./dtcg.js";
|
|
3
|
+
import { parseCssTokens, parseScssTokens, detectTokenPrefix } from "./scss.js";
|
|
4
|
+
import { containsTailwindV4Theme, parseTailwindV4Theme } from "./tailwind-v4.js";
|
|
5
|
+
import type { ParsedToken, ParseTokensOptions, TokenFormat, TokenParseOutput } from "./types.js";
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
DesignTokenParseOptions,
|
|
9
|
+
ParsedToken,
|
|
10
|
+
ParseTokensOptions,
|
|
11
|
+
ThemeParserResult,
|
|
12
|
+
TokenFormat,
|
|
13
|
+
TokenParseOutput,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
inferTokenCategory,
|
|
18
|
+
inferTokenGroup,
|
|
19
|
+
normalizeTokenGroupComment,
|
|
20
|
+
} from "./categories.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
calculateDeltaE,
|
|
24
|
+
colorSimilarity,
|
|
25
|
+
familyDistance,
|
|
26
|
+
hexToRgb,
|
|
27
|
+
isColorLike,
|
|
28
|
+
nearestByDeltaE,
|
|
29
|
+
normalizeColor,
|
|
30
|
+
parseColor,
|
|
31
|
+
parseColorToRgb,
|
|
32
|
+
parseRgb,
|
|
33
|
+
rgbToHex,
|
|
34
|
+
type DeltaEMatch,
|
|
35
|
+
type NearestByDeltaEOptions,
|
|
36
|
+
type RGB,
|
|
37
|
+
type RGBA,
|
|
38
|
+
} from "./color.js";
|
|
39
|
+
|
|
40
|
+
export { type TokenLookup } from "./lookup.js";
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
detectTokenPrefix,
|
|
44
|
+
parseCssTokens,
|
|
45
|
+
parseScssTokens,
|
|
46
|
+
parseScssVariables,
|
|
47
|
+
parseTokenFile,
|
|
48
|
+
resolveTokenValue,
|
|
49
|
+
} from "./scss.js";
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
isDTCGFile,
|
|
53
|
+
parseDTCGFile,
|
|
54
|
+
parseDtcgTokens,
|
|
55
|
+
} from "./dtcg.js";
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
containsTailwindV4Theme,
|
|
59
|
+
parseTailwindV4Theme,
|
|
60
|
+
} from "./tailwind-v4.js";
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
normalizeTokenValue,
|
|
64
|
+
parseDesignTokenContent,
|
|
65
|
+
resolveDesignTokenValue,
|
|
66
|
+
} from "./design-token-parser.js";
|
|
67
|
+
|
|
68
|
+
export function parseTokens(
|
|
69
|
+
content: string,
|
|
70
|
+
formatOrOptions: TokenFormat | ParseTokensOptions = "auto",
|
|
71
|
+
): TokenParseOutput {
|
|
72
|
+
const options =
|
|
73
|
+
typeof formatOrOptions === "string"
|
|
74
|
+
? { format: formatOrOptions }
|
|
75
|
+
: formatOrOptions;
|
|
76
|
+
const format = options.format ?? "auto";
|
|
77
|
+
const filePath = options.filePath ?? "";
|
|
78
|
+
|
|
79
|
+
if (format === "dtcg" || (format === "auto" && isDTCGFile(filePath))) {
|
|
80
|
+
return parseDtcgTokens(content, filePath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (format === "tailwind-v4" || (format === "auto" && containsTailwindV4Theme(content))) {
|
|
84
|
+
return designTokensToParseOutput(parseTailwindV4Theme(content, filePath).tokens);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (format === "css") {
|
|
88
|
+
return parseCssTokens(content, filePath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return parseScssTokens(content, filePath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function designTokensToParseOutput(tokens: DesignToken[]): TokenParseOutput {
|
|
95
|
+
const categories: Record<string, ParsedToken[]> = {};
|
|
96
|
+
|
|
97
|
+
for (const token of tokens) {
|
|
98
|
+
const category = tokenCategoryToGroup(token.category);
|
|
99
|
+
categories[category] ??= [];
|
|
100
|
+
categories[category].push({
|
|
101
|
+
name: token.name,
|
|
102
|
+
value: token.rawValue,
|
|
103
|
+
resolvedValue:
|
|
104
|
+
token.resolvedValue !== token.rawValue ? token.resolvedValue : undefined,
|
|
105
|
+
category,
|
|
106
|
+
description: token.description,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
prefix: detectTokenPrefix(tokens.map((token) => token.name)),
|
|
112
|
+
categories,
|
|
113
|
+
total: tokens.length,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function tokenCategoryToGroup(category: TokenCategory): string {
|
|
118
|
+
switch (category) {
|
|
119
|
+
case "color":
|
|
120
|
+
return "colors";
|
|
121
|
+
case "shadow":
|
|
122
|
+
return "shadows";
|
|
123
|
+
case "border":
|
|
124
|
+
return "borders";
|
|
125
|
+
case "animation":
|
|
126
|
+
return "transitions";
|
|
127
|
+
default:
|
|
128
|
+
return category;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DesignToken, TokenUsageSummary } from "../token-types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Token registry interface for style comparison.
|
|
5
|
+
* Subset of registry methods needed by the comparison engine.
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenLookup {
|
|
8
|
+
findByValue(value: string, theme?: string): string[];
|
|
9
|
+
getToken(name: string): DesignToken | undefined;
|
|
10
|
+
calculateUsageSummary(
|
|
11
|
+
styleDiffs: Array<{
|
|
12
|
+
property: string;
|
|
13
|
+
figma: string;
|
|
14
|
+
rendered: string;
|
|
15
|
+
match: boolean;
|
|
16
|
+
}>,
|
|
17
|
+
theme?: string,
|
|
18
|
+
): TokenUsageSummary;
|
|
19
|
+
}
|