@muloka/figma-console-mcp 0.5.0 → 0.5.2

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 (111) hide show
  1. package/README.md +3 -1
  2. package/dist/core/port-discovery.d.ts +9 -0
  3. package/dist/core/port-discovery.d.ts.map +1 -1
  4. package/dist/core/port-discovery.js +40 -14
  5. package/dist/core/port-discovery.js.map +1 -1
  6. package/dist/core/websocket-connector.d.ts +2 -1
  7. package/dist/core/websocket-connector.d.ts.map +1 -1
  8. package/dist/core/websocket-connector.js +17 -2
  9. package/dist/core/websocket-connector.js.map +1 -1
  10. package/dist/core/websocket-server.d.ts +8 -1
  11. package/dist/core/websocket-server.d.ts.map +1 -1
  12. package/dist/core/websocket-server.js +18 -2
  13. package/dist/core/websocket-server.js.map +1 -1
  14. package/dist/core/write-tools.d.ts +3 -1
  15. package/dist/core/write-tools.d.ts.map +1 -1
  16. package/dist/core/write-tools.js +34 -14
  17. package/dist/core/write-tools.js.map +1 -1
  18. package/dist/local.d.ts +2 -1
  19. package/dist/local.d.ts.map +1 -1
  20. package/dist/local.js +10 -14
  21. package/dist/local.js.map +1 -1
  22. package/package.json +1 -1
  23. package/dist/apps/design-system-dashboard/mcp-app.html +0 -521
  24. package/dist/apps/token-browser/mcp-app.html +0 -410
  25. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +0 -277
  26. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +0 -357
  27. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +0 -341
  28. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +0 -230
  29. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +0 -92
  30. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +0 -308
  31. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +0 -349
  32. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +0 -40
  33. package/dist/cloudflare/apps/design-system-dashboard/server.js +0 -159
  34. package/dist/cloudflare/apps/token-browser/server.js +0 -136
  35. package/dist/cloudflare/browser-manager.js +0 -157
  36. package/dist/cloudflare/core/accessibility-tools.js +0 -361
  37. package/dist/cloudflare/core/annotation-tools.js +0 -230
  38. package/dist/cloudflare/core/cloud-websocket-connector.js +0 -419
  39. package/dist/cloudflare/core/cloud-websocket-relay.js +0 -198
  40. package/dist/cloudflare/core/comment-tools.js +0 -292
  41. package/dist/cloudflare/core/config.js +0 -153
  42. package/dist/cloudflare/core/console-monitor.js +0 -427
  43. package/dist/cloudflare/core/deep-component-tools.js +0 -128
  44. package/dist/cloudflare/core/design-code-tools.js +0 -2793
  45. package/dist/cloudflare/core/design-system-manifest.js +0 -265
  46. package/dist/cloudflare/core/design-system-tools.js +0 -882
  47. package/dist/cloudflare/core/diagnose-tool.js +0 -100
  48. package/dist/cloudflare/core/diff/changelog-formatter.js +0 -275
  49. package/dist/cloudflare/core/diff/diff-engine.js +0 -334
  50. package/dist/cloudflare/core/diff/property-compare.js +0 -36
  51. package/dist/cloudflare/core/diff/version-cache.js +0 -74
  52. package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -374
  53. package/dist/cloudflare/core/enrichment/index.js +0 -7
  54. package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
  55. package/dist/cloudflare/core/enrichment/style-resolver.js +0 -346
  56. package/dist/cloudflare/core/figjam-tools.js +0 -547
  57. package/dist/cloudflare/core/figma-api.js +0 -509
  58. package/dist/cloudflare/core/figma-connector.js +0 -7
  59. package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
  60. package/dist/cloudflare/core/figma-style-extractor.js +0 -311
  61. package/dist/cloudflare/core/figma-tools.js +0 -3353
  62. package/dist/cloudflare/core/identity.js +0 -96
  63. package/dist/cloudflare/core/library-tools.js +0 -580
  64. package/dist/cloudflare/core/logger.js +0 -53
  65. package/dist/cloudflare/core/port-discovery.js +0 -935
  66. package/dist/cloudflare/core/resolve-package-root.js +0 -11
  67. package/dist/cloudflare/core/slides-tools.js +0 -714
  68. package/dist/cloudflare/core/slot-tools.js +0 -334
  69. package/dist/cloudflare/core/snippet-injector.js +0 -96
  70. package/dist/cloudflare/core/tokens/alias-resolver.js +0 -205
  71. package/dist/cloudflare/core/tokens/config.js +0 -294
  72. package/dist/cloudflare/core/tokens/dialect.js +0 -232
  73. package/dist/cloudflare/core/tokens/figma-converter.js +0 -323
  74. package/dist/cloudflare/core/tokens/formatters/css-vars.js +0 -338
  75. package/dist/cloudflare/core/tokens/formatters/dtcg.js +0 -376
  76. package/dist/cloudflare/core/tokens/formatters/index.js +0 -45
  77. package/dist/cloudflare/core/tokens/formatters/json.js +0 -205
  78. package/dist/cloudflare/core/tokens/formatters/less.js +0 -4
  79. package/dist/cloudflare/core/tokens/formatters/scss.js +0 -258
  80. package/dist/cloudflare/core/tokens/formatters/stubs.js +0 -13
  81. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +0 -213
  82. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +0 -237
  83. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +0 -335
  84. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +0 -256
  85. package/dist/cloudflare/core/tokens/formatters/ts-module.js +0 -198
  86. package/dist/cloudflare/core/tokens/index.js +0 -16
  87. package/dist/cloudflare/core/tokens/parsers/css-vars.js +0 -4
  88. package/dist/cloudflare/core/tokens/parsers/dtcg.js +0 -280
  89. package/dist/cloudflare/core/tokens/parsers/index.js +0 -138
  90. package/dist/cloudflare/core/tokens/parsers/json.js +0 -7
  91. package/dist/cloudflare/core/tokens/parsers/scss.js +0 -4
  92. package/dist/cloudflare/core/tokens/parsers/stubs.js +0 -20
  93. package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +0 -4
  94. package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +0 -4
  95. package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +0 -4
  96. package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +0 -4
  97. package/dist/cloudflare/core/tokens/schemas.js +0 -152
  98. package/dist/cloudflare/core/tokens/transforms/color.js +0 -12
  99. package/dist/cloudflare/core/tokens/transforms/index.js +0 -29
  100. package/dist/cloudflare/core/tokens/transforms/size.js +0 -7
  101. package/dist/cloudflare/core/tokens/types.js +0 -18
  102. package/dist/cloudflare/core/tokens-tools.js +0 -1788
  103. package/dist/cloudflare/core/types/design-code.js +0 -4
  104. package/dist/cloudflare/core/types/enriched.js +0 -5
  105. package/dist/cloudflare/core/types/index.js +0 -4
  106. package/dist/cloudflare/core/variable-resolver.js +0 -85
  107. package/dist/cloudflare/core/version-tools.js +0 -1199
  108. package/dist/cloudflare/core/websocket-connector.js +0 -436
  109. package/dist/cloudflare/core/websocket-server.js +0 -1009
  110. package/dist/cloudflare/core/write-tools.js +0 -2506
  111. package/dist/cloudflare/index.js +0 -3010
@@ -1,258 +0,0 @@
1
- /**
2
- * SCSS variables formatter.
3
- *
4
- * Output shape:
5
- *
6
- * // tokens.scss
7
- * $ds-color-primary: #4085F2;
8
- * $ds-spacing-md: 16px;
9
- *
10
- * // map for runtime mode access
11
- * $ds-colors: (
12
- * "primary": $ds-color-primary,
13
- * ...
14
- * );
15
- *
16
- * Modes: SCSS doesn't have CSS's runtime cascading, so multi-mode output
17
- * either emits one file per mode (splitByMode: true) or generates SCSS
18
- * maps keyed by mode name that consumers can `map-get` from.
19
- *
20
- * Composite tokens (typography, shadow) expand into multiple primitive
21
- * variables since SCSS variables hold a single value.
22
- */
23
- import { buildTokenIndex, referenceTargetPath } from "../alias-resolver.js";
24
- export function formatScss(doc, opts) {
25
- const warnings = [];
26
- const files = [];
27
- // Whole-document index so set-qualified alias references resolve to the
28
- // target token's own path when generating $variable names.
29
- const tokenIndex = buildTokenIndex(doc, warnings);
30
- const splitByMode = opts.target.splitByMode ?? false;
31
- const splitByCollection = opts.target.splitByCollection ?? false;
32
- const prefix = opts.target.prefix ?? "";
33
- if (splitByMode && splitByCollection) {
34
- for (const set of doc.sets) {
35
- for (const mode of set.modes) {
36
- files.push({
37
- path: filenameFor(opts, set, mode),
38
- content: renderSingleMode([set], mode, prefix, tokenIndex, warnings),
39
- });
40
- }
41
- }
42
- }
43
- else if (splitByMode) {
44
- const allModes = new Set();
45
- for (const set of doc.sets)
46
- for (const m of set.modes)
47
- allModes.add(m);
48
- for (const mode of allModes) {
49
- const sets = doc.sets.filter((s) => s.modes.includes(mode));
50
- files.push({
51
- path: filenameFor(opts, undefined, mode),
52
- content: renderSingleMode(sets, mode, prefix, tokenIndex, warnings),
53
- });
54
- }
55
- }
56
- else if (splitByCollection) {
57
- for (const set of doc.sets) {
58
- files.push({
59
- path: filenameFor(opts, set),
60
- content: renderAllModes([set], prefix, tokenIndex, warnings),
61
- });
62
- }
63
- }
64
- else {
65
- files.push({
66
- path: filenameFor(opts),
67
- content: renderAllModes(doc.sets, prefix, tokenIndex, warnings),
68
- });
69
- }
70
- return { files, warnings };
71
- }
72
- function filenameFor(opts, set, mode) {
73
- if (opts.target.filename)
74
- return opts.target.filename;
75
- const parts = [];
76
- if (set)
77
- parts.push(slugify(set.name));
78
- if (mode)
79
- parts.push(slugify(mode));
80
- if (parts.length === 0)
81
- parts.push("tokens");
82
- return `_${parts.join(".")}.scss`;
83
- }
84
- function slugify(s) {
85
- return s
86
- .trim()
87
- .toLowerCase()
88
- .replace(/[^a-z0-9]+/g, "-")
89
- .replace(/^-+|-+$/g, "");
90
- }
91
- function varName(path, prefix) {
92
- return `$${prefix}${path.map(slugify).join("-")}`;
93
- }
94
- function renderSingleMode(sets, mode, prefix, tokenIndex, warnings) {
95
- const lines = [];
96
- lines.push("// Generated by figma-console-mcp — do not edit by hand");
97
- lines.push(`// Mode: ${mode}`);
98
- lines.push("");
99
- for (const set of sets) {
100
- if (!set.modes.includes(mode))
101
- continue;
102
- lines.push(`// — ${set.name} —`);
103
- for (const token of set.tokens) {
104
- const value = token.values[mode];
105
- if (!value)
106
- continue;
107
- emitSassLines(token, value, prefix, tokenIndex, lines, warnings);
108
- }
109
- lines.push("");
110
- }
111
- return lines.join("\n");
112
- }
113
- function renderAllModes(sets, prefix, tokenIndex, warnings) {
114
- const lines = [];
115
- lines.push("// Generated by figma-console-mcp — do not edit by hand");
116
- lines.push("");
117
- // For multi-mode sets, emit a primary variable + a mode-keyed map.
118
- // Single-mode sets emit straight variables.
119
- for (const set of sets) {
120
- lines.push(`// — ${set.name} —`);
121
- const isMultiMode = set.modes.length > 1;
122
- const primaryMode = pickPrimaryMode(set.modes);
123
- for (const token of set.tokens) {
124
- // Primary value as the bare variable.
125
- const primary = token.values[primaryMode];
126
- if (primary)
127
- emitSassLines(token, primary, prefix, tokenIndex, lines, warnings);
128
- // Other modes as a map: $ds-color-primary--modes: ("Dark": #..., "Vibrant": #...)
129
- if (isMultiMode) {
130
- const otherModes = set.modes.filter((m) => m !== primaryMode);
131
- const entries = [];
132
- for (const mode of otherModes) {
133
- const v = token.values[mode];
134
- if (!v)
135
- continue;
136
- const formatted = scssValueFor(v, token, prefix, tokenIndex, warnings);
137
- if (formatted !== null) {
138
- entries.push(` "${mode}": ${formatted}`);
139
- }
140
- }
141
- if (entries.length > 0) {
142
- const mapName = `${varName(token.path, prefix)}--modes`;
143
- lines.push(`${mapName}: (`);
144
- lines.push(entries.join(",\n"));
145
- lines.push(`);`);
146
- }
147
- }
148
- }
149
- lines.push("");
150
- }
151
- return lines.join("\n");
152
- }
153
- function pickPrimaryMode(modes) {
154
- return modes.find((m) => /^(default|light|value)$/i.test(m)) ?? modes[0];
155
- }
156
- function emitSassLines(token, value, prefix, tokenIndex, out, warnings) {
157
- const sassName = varName(token.path, prefix);
158
- if (value.reference) {
159
- const bareRef = value.reference.replace(/^\{|\}$/g, "");
160
- const libMatch = bareRef.match(/^__library:(.+)$/);
161
- if (libMatch || bareRef === "unknown") {
162
- const originalId = libMatch ? libMatch[1] : "unknown";
163
- warnings.push(`Skipped ${token.path.join(".")} in SCSS — cross-library variable ${originalId}.`);
164
- out.push(`// ${sassName}: skipped — cross-library alias to ${originalId}`);
165
- return;
166
- }
167
- // Resolve set-qualified references to the target token's own path.
168
- const refPath = referenceTargetPath(value.reference, tokenIndex);
169
- out.push(`${sassName}: ${varName(refPath, prefix)};`);
170
- return;
171
- }
172
- if (value.literal === undefined || value.literal === null) {
173
- warnings.push(`Token ${token.path.join(".")} has no value.`);
174
- return;
175
- }
176
- if (token.type === "typography" && typeof value.literal === "object") {
177
- const t = value.literal;
178
- for (const sub of ["fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing"]) {
179
- if (t[sub] !== undefined) {
180
- out.push(`${sassName}-${kebab(sub)}: ${formatScssLiteral(t[sub], token.type)};`);
181
- }
182
- }
183
- return;
184
- }
185
- if (token.type === "shadow" && typeof value.literal === "object") {
186
- const css = renderShadow(value.literal);
187
- if (css)
188
- out.push(`${sassName}: ${css};`);
189
- return;
190
- }
191
- out.push(`${sassName}: ${formatScssLiteral(value.literal, token.type)};`);
192
- }
193
- function scssValueFor(value, token, prefix, tokenIndex, _warnings) {
194
- if (value.reference) {
195
- const bareRef = value.reference.replace(/^\{|\}$/g, "");
196
- if (bareRef.startsWith("__library:") || bareRef === "unknown")
197
- return null;
198
- // Resolve set-qualified references to the target token's own path.
199
- return varName(referenceTargetPath(value.reference, tokenIndex), prefix);
200
- }
201
- if (value.literal === undefined || value.literal === null)
202
- return null;
203
- return formatScssLiteral(value.literal, token.type);
204
- }
205
- function kebab(s) {
206
- return s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
207
- }
208
- function formatScssLiteral(value, type) {
209
- if (typeof value === "number") {
210
- if (type === "dimension")
211
- return `${value}px`;
212
- return String(value);
213
- }
214
- if (typeof value === "string") {
215
- if (type === "color")
216
- return value;
217
- if (type === "fontFamily" || type === "string") {
218
- return needsQuoting(value) ? JSON.stringify(value) : value;
219
- }
220
- return value;
221
- }
222
- if (typeof value === "boolean")
223
- return String(value);
224
- return JSON.stringify(value);
225
- }
226
- function needsQuoting(s) {
227
- if (/^["']/.test(s))
228
- return false;
229
- if (/^[\d.]+([a-z%]+)?$/.test(s))
230
- return false;
231
- if (/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(s))
232
- return false;
233
- return true;
234
- }
235
- function renderShadow(shadow) {
236
- if (Array.isArray(shadow)) {
237
- return shadow.map(renderShadow).filter(Boolean).join(", ");
238
- }
239
- if (!shadow || typeof shadow !== "object")
240
- return null;
241
- const s = shadow;
242
- const inset = s.inset ? "inset " : "";
243
- const x = withPx(s.offsetX);
244
- const y = withPx(s.offsetY);
245
- const blur = withPx(s.blur);
246
- const spread = s.spread !== undefined ? ` ${withPx(s.spread)}` : "";
247
- const color = s.color;
248
- if (!x || !y || !blur || typeof color !== "string")
249
- return null;
250
- return `${inset}${x} ${y} ${blur}${spread} ${color}`;
251
- }
252
- function withPx(v) {
253
- if (typeof v === "number")
254
- return `${v}px`;
255
- if (typeof v === "string")
256
- return v;
257
- return "";
258
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Shared NotImplementedError used by formatter stubs. Most formatters are
3
- * implemented end-to-end as of v1.28.0; this stub remains for niche targets
4
- * (e.g. Less) that haven't drawn enough demand to warrant implementation.
5
- */
6
- export class FormatterNotImplementedError extends Error {
7
- constructor(formatName) {
8
- super(`[figma-console-mcp] The ${formatName} formatter is not implemented. ` +
9
- `Currently supported formatters: dtcg, css-vars, tailwind-v4, tailwind-v3, scss, ts-module, json-flat, json-nested, style-dictionary-v3, tokens-studio. ` +
10
- `If you need ${formatName} support, open an issue with your use case so we can prioritize it.`);
11
- this.name = "FormatterNotImplementedError";
12
- }
13
- }
@@ -1,213 +0,0 @@
1
- /**
2
- * Style Dictionary v3 source-format JSON formatter.
3
- *
4
- * SD v3 uses bare `value` / `type` fields (no `$` prefix, that's the DTCG
5
- * convention SD v4 adopted). Output is structurally similar to DTCG but
6
- * without the dollar signs, and groups have no special meta — just
7
- * nested objects.
8
- *
9
- * Output shape:
10
- *
11
- * {
12
- * "color": {
13
- * "primary": {
14
- * "value": "#4085F2",
15
- * "type": "color",
16
- * "comment": "Primary brand color"
17
- * },
18
- * "brand": {
19
- * "blue": { "value": "#0066FF", "type": "color" }
20
- * }
21
- * },
22
- * "spacing": {
23
- * "md": { "value": "16px", "type": "size" }
24
- * }
25
- * }
26
- *
27
- * SD v3 type names differ slightly from DTCG:
28
- *
29
- * DTCG type → SD v3 type
30
- * ----------------------------
31
- * dimension → size (or spacing for spacing tokens)
32
- * color → color
33
- * fontFamily → string
34
- * fontWeight → number
35
- *
36
- * Aliases use SD v3's `{path.to.token}` syntax (same as DTCG, which copied
37
- * it from SD).
38
- *
39
- * For back-compat with cbds-components / blocks / czi-edu / eddie-design-system
40
- * which still use SD v3's bare-key source format.
41
- */
42
- import { buildTokenIndex, referenceTargetPath } from "../alias-resolver.js";
43
- export function formatStyleDictionaryV3(doc, opts) {
44
- const warnings = [];
45
- const files = [];
46
- // Whole-document index so set-qualified alias references resolve to the
47
- // target token's own path (SD v3 trees are path-based, no set groups).
48
- const tokenIndex = buildTokenIndex(doc, warnings);
49
- const splitByMode = opts.target.splitByMode ?? false;
50
- const splitByCollection = opts.target.splitByCollection ?? false;
51
- if (splitByMode && splitByCollection) {
52
- for (const set of doc.sets) {
53
- for (const mode of set.modes) {
54
- files.push({
55
- path: filenameFor(opts, set, mode),
56
- content: renderSdJson([set], [mode], tokenIndex, warnings),
57
- });
58
- }
59
- }
60
- }
61
- else if (splitByMode) {
62
- const allModes = new Set();
63
- for (const set of doc.sets)
64
- for (const m of set.modes)
65
- allModes.add(m);
66
- for (const mode of allModes) {
67
- const sets = doc.sets.filter((s) => s.modes.includes(mode));
68
- files.push({
69
- path: filenameFor(opts, undefined, mode),
70
- content: renderSdJson(sets, [mode], tokenIndex, warnings),
71
- });
72
- }
73
- }
74
- else if (splitByCollection) {
75
- for (const set of doc.sets) {
76
- files.push({
77
- path: filenameFor(opts, set),
78
- content: renderSdJson([set], set.modes, tokenIndex, warnings),
79
- });
80
- }
81
- }
82
- else {
83
- const allModes = new Set();
84
- for (const set of doc.sets)
85
- for (const m of set.modes)
86
- allModes.add(m);
87
- files.push({
88
- path: filenameFor(opts),
89
- content: renderSdJson(doc.sets, [...allModes], tokenIndex, warnings),
90
- });
91
- }
92
- return { files, warnings };
93
- }
94
- function filenameFor(opts, set, mode) {
95
- if (opts.target.filename)
96
- return opts.target.filename;
97
- const parts = [];
98
- if (set)
99
- parts.push(slugify(set.name));
100
- if (mode)
101
- parts.push(slugify(mode));
102
- if (parts.length === 0)
103
- parts.push("tokens");
104
- return `${parts.join(".")}.sd.json`;
105
- }
106
- function slugify(s) {
107
- return s
108
- .trim()
109
- .toLowerCase()
110
- .replace(/[^a-z0-9]+/g, "-")
111
- .replace(/^-+|-+$/g, "");
112
- }
113
- /**
114
- * Map DTCG type names to SD v3 conventions.
115
- */
116
- function sdTypeFor(token) {
117
- const lower = token.path[0]?.toLowerCase() ?? "";
118
- if (token.type === "dimension") {
119
- if (lower.startsWith("spacing") || lower === "space")
120
- return "spacing";
121
- return "size";
122
- }
123
- if (token.type === "color")
124
- return "color";
125
- if (token.type === "fontFamily")
126
- return "string";
127
- if (token.type === "fontWeight")
128
- return "number";
129
- if (token.type === "duration")
130
- return "time";
131
- if (token.type === "number")
132
- return "number";
133
- if (token.type === "string")
134
- return "string";
135
- if (token.type === "boolean")
136
- return "boolean";
137
- // Composites / less-common types: pass through.
138
- return token.type;
139
- }
140
- function renderSdJson(sets, modes, tokenIndex, warnings) {
141
- const tree = {};
142
- const primaryMode = pickPrimaryMode(modes);
143
- for (const set of sets) {
144
- for (const token of set.tokens) {
145
- // Filter to modes the file is supposed to cover.
146
- const usableModes = modes.filter((m) => token.values[m]);
147
- if (usableModes.length === 0)
148
- continue;
149
- // SD v3 has no native multi-mode encoding, so we pick the primary
150
- // mode's value. If splitByMode is being used (single mode in this
151
- // file), that primary will match the file's mode. Otherwise, the
152
- // chosen primary is the first available.
153
- const valueMode = usableModes.includes(primaryMode)
154
- ? primaryMode
155
- : usableModes[0];
156
- const tokenValue = token.values[valueMode];
157
- const sdValue = sdValueFor(tokenValue, token, tokenIndex, warnings);
158
- if (sdValue === undefined)
159
- continue;
160
- // Walk the path, creating nested groups.
161
- let cursor = tree;
162
- for (let i = 0; i < token.path.length - 1; i++) {
163
- const segment = token.path[i];
164
- if (!cursor[segment] || typeof cursor[segment] !== "object") {
165
- cursor[segment] = {};
166
- }
167
- cursor = cursor[segment];
168
- }
169
- const leafKey = token.path[token.path.length - 1];
170
- const leaf = {
171
- value: sdValue,
172
- type: sdTypeFor(token),
173
- };
174
- if (token.description)
175
- leaf.comment = token.description;
176
- cursor[leafKey] = leaf;
177
- }
178
- }
179
- return JSON.stringify(sortKeys(tree), null, 2) + "\n";
180
- }
181
- function pickPrimaryMode(modes) {
182
- return modes.find((m) => /^(default|light|value)$/i.test(m)) ?? modes[0];
183
- }
184
- function sdValueFor(value, token, tokenIndex, warnings) {
185
- if (value.reference) {
186
- const bare = value.reference.replace(/^\{|\}$/g, "");
187
- if (bare.startsWith("__library:") || bare === "unknown") {
188
- warnings.push(`Skipped ${token.path.join(".")} in Style Dictionary v3 — cross-library alias unresolved.`);
189
- return undefined;
190
- }
191
- // SD v3 uses the same `{path.to.token}` alias syntax as DTCG, but its
192
- // tree has no set groups — resolve set-qualified references to the
193
- // target token's own path.
194
- return `{${referenceTargetPath(value.reference, tokenIndex).join(".")}}`;
195
- }
196
- if (value.literal === undefined || value.literal === null)
197
- return undefined;
198
- if (typeof value.literal === "number" && token.type === "dimension") {
199
- return `${value.literal}px`;
200
- }
201
- return value.literal;
202
- }
203
- function sortKeys(node) {
204
- if (node === null || typeof node !== "object" || Array.isArray(node)) {
205
- return node;
206
- }
207
- const obj = node;
208
- const sorted = {};
209
- const keys = Object.keys(obj).sort();
210
- for (const k of keys)
211
- sorted[k] = sortKeys(obj[k]);
212
- return sorted;
213
- }