@mp3wizard/figma-console-mcp 1.23.1 → 1.27.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 (213) hide show
  1. package/README.md +49 -33
  2. package/dist/cloudflare/core/config.js +0 -8
  3. package/dist/cloudflare/core/console-monitor.js +3 -3
  4. package/dist/cloudflare/core/diagnose-tool.js +96 -0
  5. package/dist/cloudflare/core/figma-tools.js +69 -229
  6. package/dist/cloudflare/core/identity.js +96 -0
  7. package/dist/cloudflare/core/tokens/alias-resolver.js +98 -0
  8. package/dist/cloudflare/core/tokens/config.js +284 -0
  9. package/dist/cloudflare/core/tokens/figma-converter.js +195 -0
  10. package/dist/cloudflare/core/tokens/formatters/css-vars.js +329 -0
  11. package/dist/cloudflare/core/tokens/formatters/dtcg.js +300 -0
  12. package/dist/cloudflare/core/tokens/formatters/index.js +45 -0
  13. package/dist/cloudflare/core/tokens/formatters/json.js +7 -0
  14. package/dist/cloudflare/core/tokens/formatters/less.js +4 -0
  15. package/dist/cloudflare/core/tokens/formatters/scss.js +4 -0
  16. package/dist/cloudflare/core/tokens/formatters/stubs.js +11 -0
  17. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +4 -0
  18. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +4 -0
  19. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +4 -0
  20. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +4 -0
  21. package/dist/cloudflare/core/tokens/formatters/ts-module.js +4 -0
  22. package/dist/cloudflare/core/tokens/index.js +15 -0
  23. package/dist/cloudflare/core/tokens/parsers/css-vars.js +4 -0
  24. package/dist/cloudflare/core/tokens/parsers/dtcg.js +253 -0
  25. package/dist/cloudflare/core/tokens/parsers/index.js +138 -0
  26. package/dist/cloudflare/core/tokens/parsers/json.js +7 -0
  27. package/dist/cloudflare/core/tokens/parsers/scss.js +4 -0
  28. package/dist/cloudflare/core/tokens/parsers/stubs.js +13 -0
  29. package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +4 -0
  30. package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +4 -0
  31. package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +4 -0
  32. package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +4 -0
  33. package/dist/cloudflare/core/tokens/schemas.js +148 -0
  34. package/dist/cloudflare/core/tokens/transforms/color.js +12 -0
  35. package/dist/cloudflare/core/tokens/transforms/index.js +29 -0
  36. package/dist/cloudflare/core/tokens/transforms/size.js +7 -0
  37. package/dist/cloudflare/core/tokens/types.js +18 -0
  38. package/dist/cloudflare/core/tokens-tools.js +849 -0
  39. package/dist/cloudflare/core/version-tools.js +151 -7
  40. package/dist/cloudflare/core/websocket-server.js +77 -55
  41. package/dist/cloudflare/index.js +37 -26
  42. package/dist/core/config.d.ts.map +1 -1
  43. package/dist/core/config.js +0 -8
  44. package/dist/core/config.js.map +1 -1
  45. package/dist/core/console-monitor.d.ts +2 -2
  46. package/dist/core/console-monitor.d.ts.map +1 -1
  47. package/dist/core/console-monitor.js +3 -3
  48. package/dist/core/console-monitor.js.map +1 -1
  49. package/dist/core/diagnose-tool.d.ts +33 -0
  50. package/dist/core/diagnose-tool.d.ts.map +1 -0
  51. package/dist/core/diagnose-tool.js +97 -0
  52. package/dist/core/diagnose-tool.js.map +1 -0
  53. package/dist/core/diff/diff-engine.d.ts +14 -0
  54. package/dist/core/diff/diff-engine.d.ts.map +1 -1
  55. package/dist/core/diff/diff-engine.js.map +1 -1
  56. package/dist/core/figma-connector.d.ts +1 -1
  57. package/dist/core/figma-connector.d.ts.map +1 -1
  58. package/dist/core/figma-tools.d.ts +1 -2
  59. package/dist/core/figma-tools.d.ts.map +1 -1
  60. package/dist/core/figma-tools.js +69 -229
  61. package/dist/core/figma-tools.js.map +1 -1
  62. package/dist/core/identity.d.ts +41 -0
  63. package/dist/core/identity.d.ts.map +1 -0
  64. package/dist/core/identity.js +97 -0
  65. package/dist/core/identity.js.map +1 -0
  66. package/dist/core/tokens/alias-resolver.d.ts +40 -0
  67. package/dist/core/tokens/alias-resolver.d.ts.map +1 -0
  68. package/dist/core/tokens/alias-resolver.js +99 -0
  69. package/dist/core/tokens/alias-resolver.js.map +1 -0
  70. package/dist/core/tokens/config.d.ts +352 -0
  71. package/dist/core/tokens/config.d.ts.map +1 -0
  72. package/dist/core/tokens/config.js +285 -0
  73. package/dist/core/tokens/config.js.map +1 -0
  74. package/dist/core/tokens/figma-converter.d.ts +81 -0
  75. package/dist/core/tokens/figma-converter.d.ts.map +1 -0
  76. package/dist/core/tokens/figma-converter.js +196 -0
  77. package/dist/core/tokens/figma-converter.js.map +1 -0
  78. package/dist/core/tokens/formatters/css-vars.d.ts +24 -0
  79. package/dist/core/tokens/formatters/css-vars.d.ts.map +1 -0
  80. package/dist/core/tokens/formatters/css-vars.js +330 -0
  81. package/dist/core/tokens/formatters/css-vars.js.map +1 -0
  82. package/dist/core/tokens/formatters/dtcg.d.ts +28 -0
  83. package/dist/core/tokens/formatters/dtcg.d.ts.map +1 -0
  84. package/dist/core/tokens/formatters/dtcg.js +301 -0
  85. package/dist/core/tokens/formatters/dtcg.js.map +1 -0
  86. package/dist/core/tokens/formatters/index.d.ts +30 -0
  87. package/dist/core/tokens/formatters/index.d.ts.map +1 -0
  88. package/dist/core/tokens/formatters/index.js +46 -0
  89. package/dist/core/tokens/formatters/index.js.map +1 -0
  90. package/dist/core/tokens/formatters/json.d.ts +5 -0
  91. package/dist/core/tokens/formatters/json.d.ts.map +1 -0
  92. package/dist/core/tokens/formatters/json.js +8 -0
  93. package/dist/core/tokens/formatters/json.js.map +1 -0
  94. package/dist/core/tokens/formatters/less.d.ts +4 -0
  95. package/dist/core/tokens/formatters/less.d.ts.map +1 -0
  96. package/dist/core/tokens/formatters/less.js +5 -0
  97. package/dist/core/tokens/formatters/less.js.map +1 -0
  98. package/dist/core/tokens/formatters/scss.d.ts +4 -0
  99. package/dist/core/tokens/formatters/scss.d.ts.map +1 -0
  100. package/dist/core/tokens/formatters/scss.js +5 -0
  101. package/dist/core/tokens/formatters/scss.js.map +1 -0
  102. package/dist/core/tokens/formatters/stubs.d.ts +9 -0
  103. package/dist/core/tokens/formatters/stubs.d.ts.map +1 -0
  104. package/dist/core/tokens/formatters/stubs.js +12 -0
  105. package/dist/core/tokens/formatters/stubs.js.map +1 -0
  106. package/dist/core/tokens/formatters/style-dictionary-v3.d.ts +4 -0
  107. package/dist/core/tokens/formatters/style-dictionary-v3.d.ts.map +1 -0
  108. package/dist/core/tokens/formatters/style-dictionary-v3.js +5 -0
  109. package/dist/core/tokens/formatters/style-dictionary-v3.js.map +1 -0
  110. package/dist/core/tokens/formatters/tailwind-v3.d.ts +4 -0
  111. package/dist/core/tokens/formatters/tailwind-v3.d.ts.map +1 -0
  112. package/dist/core/tokens/formatters/tailwind-v3.js +5 -0
  113. package/dist/core/tokens/formatters/tailwind-v3.js.map +1 -0
  114. package/dist/core/tokens/formatters/tailwind-v4.d.ts +4 -0
  115. package/dist/core/tokens/formatters/tailwind-v4.d.ts.map +1 -0
  116. package/dist/core/tokens/formatters/tailwind-v4.js +5 -0
  117. package/dist/core/tokens/formatters/tailwind-v4.js.map +1 -0
  118. package/dist/core/tokens/formatters/tokens-studio.d.ts +4 -0
  119. package/dist/core/tokens/formatters/tokens-studio.d.ts.map +1 -0
  120. package/dist/core/tokens/formatters/tokens-studio.js +5 -0
  121. package/dist/core/tokens/formatters/tokens-studio.js.map +1 -0
  122. package/dist/core/tokens/formatters/ts-module.d.ts +4 -0
  123. package/dist/core/tokens/formatters/ts-module.d.ts.map +1 -0
  124. package/dist/core/tokens/formatters/ts-module.js +5 -0
  125. package/dist/core/tokens/formatters/ts-module.js.map +1 -0
  126. package/dist/core/tokens/index.d.ts +17 -0
  127. package/dist/core/tokens/index.d.ts.map +1 -0
  128. package/dist/core/tokens/index.js +16 -0
  129. package/dist/core/tokens/index.js.map +1 -0
  130. package/dist/core/tokens/parsers/css-vars.d.ts +3 -0
  131. package/dist/core/tokens/parsers/css-vars.d.ts.map +1 -0
  132. package/dist/core/tokens/parsers/css-vars.js +5 -0
  133. package/dist/core/tokens/parsers/css-vars.js.map +1 -0
  134. package/dist/core/tokens/parsers/dtcg.d.ts +21 -0
  135. package/dist/core/tokens/parsers/dtcg.d.ts.map +1 -0
  136. package/dist/core/tokens/parsers/dtcg.js +254 -0
  137. package/dist/core/tokens/parsers/dtcg.js.map +1 -0
  138. package/dist/core/tokens/parsers/index.d.ts +37 -0
  139. package/dist/core/tokens/parsers/index.d.ts.map +1 -0
  140. package/dist/core/tokens/parsers/index.js +139 -0
  141. package/dist/core/tokens/parsers/index.js.map +1 -0
  142. package/dist/core/tokens/parsers/json.d.ts +4 -0
  143. package/dist/core/tokens/parsers/json.d.ts.map +1 -0
  144. package/dist/core/tokens/parsers/json.js +8 -0
  145. package/dist/core/tokens/parsers/json.js.map +1 -0
  146. package/dist/core/tokens/parsers/scss.d.ts +3 -0
  147. package/dist/core/tokens/parsers/scss.d.ts.map +1 -0
  148. package/dist/core/tokens/parsers/scss.js +5 -0
  149. package/dist/core/tokens/parsers/scss.js.map +1 -0
  150. package/dist/core/tokens/parsers/stubs.d.ts +11 -0
  151. package/dist/core/tokens/parsers/stubs.d.ts.map +1 -0
  152. package/dist/core/tokens/parsers/stubs.js +14 -0
  153. package/dist/core/tokens/parsers/stubs.js.map +1 -0
  154. package/dist/core/tokens/parsers/style-dictionary-v3.d.ts +3 -0
  155. package/dist/core/tokens/parsers/style-dictionary-v3.d.ts.map +1 -0
  156. package/dist/core/tokens/parsers/style-dictionary-v3.js +5 -0
  157. package/dist/core/tokens/parsers/style-dictionary-v3.js.map +1 -0
  158. package/dist/core/tokens/parsers/tailwind-v3.d.ts +3 -0
  159. package/dist/core/tokens/parsers/tailwind-v3.d.ts.map +1 -0
  160. package/dist/core/tokens/parsers/tailwind-v3.js +5 -0
  161. package/dist/core/tokens/parsers/tailwind-v3.js.map +1 -0
  162. package/dist/core/tokens/parsers/tailwind-v4.d.ts +3 -0
  163. package/dist/core/tokens/parsers/tailwind-v4.d.ts.map +1 -0
  164. package/dist/core/tokens/parsers/tailwind-v4.js +5 -0
  165. package/dist/core/tokens/parsers/tailwind-v4.js.map +1 -0
  166. package/dist/core/tokens/parsers/tokens-studio.d.ts +3 -0
  167. package/dist/core/tokens/parsers/tokens-studio.d.ts.map +1 -0
  168. package/dist/core/tokens/parsers/tokens-studio.js +5 -0
  169. package/dist/core/tokens/parsers/tokens-studio.js.map +1 -0
  170. package/dist/core/tokens/schemas.d.ts +152 -0
  171. package/dist/core/tokens/schemas.d.ts.map +1 -0
  172. package/dist/core/tokens/schemas.js +149 -0
  173. package/dist/core/tokens/schemas.js.map +1 -0
  174. package/dist/core/tokens/transforms/color.d.ts +9 -0
  175. package/dist/core/tokens/transforms/color.d.ts.map +1 -0
  176. package/dist/core/tokens/transforms/color.js +13 -0
  177. package/dist/core/tokens/transforms/color.js.map +1 -0
  178. package/dist/core/tokens/transforms/index.d.ts +36 -0
  179. package/dist/core/tokens/transforms/index.d.ts.map +1 -0
  180. package/dist/core/tokens/transforms/index.js +30 -0
  181. package/dist/core/tokens/transforms/index.js.map +1 -0
  182. package/dist/core/tokens/transforms/size.d.ts +7 -0
  183. package/dist/core/tokens/transforms/size.d.ts.map +1 -0
  184. package/dist/core/tokens/transforms/size.js +8 -0
  185. package/dist/core/tokens/transforms/size.js.map +1 -0
  186. package/dist/core/tokens/types.d.ts +228 -0
  187. package/dist/core/tokens/types.d.ts.map +1 -0
  188. package/dist/core/tokens/types.js +19 -0
  189. package/dist/core/tokens/types.js.map +1 -0
  190. package/dist/core/tokens-tools.d.ts +42 -0
  191. package/dist/core/tokens-tools.d.ts.map +1 -0
  192. package/dist/core/tokens-tools.js +850 -0
  193. package/dist/core/tokens-tools.js.map +1 -0
  194. package/dist/core/types/index.d.ts +0 -8
  195. package/dist/core/types/index.d.ts.map +1 -1
  196. package/dist/core/version-tools.d.ts +30 -1
  197. package/dist/core/version-tools.d.ts.map +1 -1
  198. package/dist/core/version-tools.js +151 -7
  199. package/dist/core/version-tools.js.map +1 -1
  200. package/dist/core/websocket-connector.d.ts +1 -1
  201. package/dist/core/websocket-connector.d.ts.map +1 -1
  202. package/dist/core/websocket-server.d.ts +47 -3
  203. package/dist/core/websocket-server.d.ts.map +1 -1
  204. package/dist/core/websocket-server.js +77 -55
  205. package/dist/core/websocket-server.js.map +1 -1
  206. package/dist/local.d.ts +0 -12
  207. package/dist/local.d.ts.map +1 -1
  208. package/dist/local.js +967 -3406
  209. package/dist/local.js.map +1 -1
  210. package/figma-desktop-bridge/code.js +59 -63
  211. package/figma-desktop-bridge/ui.html +85 -11
  212. package/package.json +12 -30
  213. package/figma-desktop-bridge/ui-full.html +0 -1353
@@ -0,0 +1,330 @@
1
+ /**
2
+ * CSS custom properties formatter.
3
+ *
4
+ * Converts a TokenDocument to one or more CSS files containing `:root { ... }`
5
+ * (and optionally per-mode selectors like `.dark { ... }`). The first
6
+ * "real" non-DTCG output, completing the canonical-to-runtime pipeline.
7
+ *
8
+ * Behavior:
9
+ * - Each token becomes a CSS custom property. Path joined with `-`,
10
+ * optionally prefixed. `color/primary` → `--color-primary`.
11
+ * - Aliases resolve to `var(--target-token)` so CSS cascading still works.
12
+ * - Composite tokens (typography, shadow) expand into multiple primitive
13
+ * custom properties since CSS doesn't natively express composites.
14
+ * - Single-mode tokens go in `:root`.
15
+ * - Multi-mode tokens emit per-mode selectors. Heuristic: a mode named
16
+ * `Light`/`Default` becomes `:root`; `Dark` becomes `.dark` (Tailwind
17
+ * convention); other modes become `[data-theme="<name>"]`.
18
+ * - splitByMode emits one file per mode with just that mode's values.
19
+ * - splitByCollection emits one file per set.
20
+ */
21
+ export function formatCssVars(doc, opts) {
22
+ const warnings = [];
23
+ const files = [];
24
+ const splitByMode = opts.target.splitByMode ?? false;
25
+ const splitByCollection = opts.target.splitByCollection ?? false;
26
+ const prefix = opts.target.prefix ?? "";
27
+ if (splitByMode && splitByCollection) {
28
+ // One file per (set, mode) pair.
29
+ for (const set of doc.sets) {
30
+ for (const mode of set.modes) {
31
+ files.push({
32
+ path: filenameFor(opts, set, mode),
33
+ content: renderSingleSelector(doc.sets.filter((s) => s.name === set.name), mode, selectorFor(mode), prefix, warnings),
34
+ });
35
+ }
36
+ }
37
+ }
38
+ else if (splitByMode) {
39
+ // One file per mode, all sets combined under that mode's selector.
40
+ const allModes = new Set();
41
+ for (const set of doc.sets)
42
+ for (const m of set.modes)
43
+ allModes.add(m);
44
+ for (const mode of allModes) {
45
+ const setsWithMode = doc.sets.filter((s) => s.modes.includes(mode));
46
+ files.push({
47
+ path: filenameFor(opts, undefined, mode),
48
+ content: renderSingleSelector(setsWithMode, mode, selectorFor(mode), prefix, warnings),
49
+ });
50
+ }
51
+ }
52
+ else if (splitByCollection) {
53
+ // One file per set, all modes combined as separate selectors within.
54
+ for (const set of doc.sets) {
55
+ files.push({
56
+ path: filenameFor(opts, set),
57
+ content: renderMultiSelector([set], prefix, warnings),
58
+ });
59
+ }
60
+ }
61
+ else {
62
+ // Single file with everything.
63
+ files.push({
64
+ path: filenameFor(opts),
65
+ content: renderMultiSelector(doc.sets, prefix, warnings),
66
+ });
67
+ }
68
+ return { files, warnings };
69
+ }
70
+ function filenameFor(opts, set, mode) {
71
+ if (opts.target.filename)
72
+ return opts.target.filename;
73
+ const parts = [];
74
+ if (set)
75
+ parts.push(slugify(set.name));
76
+ if (mode)
77
+ parts.push(slugify(mode));
78
+ if (parts.length === 0)
79
+ parts.push("tokens");
80
+ return `${parts.join(".")}.css`;
81
+ }
82
+ function slugify(s) {
83
+ return s
84
+ .trim()
85
+ .toLowerCase()
86
+ .replace(/[^a-z0-9]+/g, "-")
87
+ .replace(/^-+|-+$/g, "");
88
+ }
89
+ /**
90
+ * Map a mode name to a CSS selector. Conventional defaults that match what
91
+ * most CSS frameworks expect:
92
+ * - Default / Light / Value → `:root` (the document root, runs always)
93
+ * - Dark → `.dark` (matches Tailwind's darkMode: "class" config)
94
+ * - anything else → `[data-theme="<name>"]` (general escape hatch)
95
+ */
96
+ function selectorFor(mode) {
97
+ const lower = mode.toLowerCase();
98
+ if (lower === "default" || lower === "light" || lower === "value") {
99
+ return ":root";
100
+ }
101
+ if (lower === "dark") {
102
+ return ".dark";
103
+ }
104
+ return `[data-theme="${slugify(mode)}"]`;
105
+ }
106
+ /**
107
+ * Render all tokens across the given sets under a single selector. Used when
108
+ * one file holds one mode's worth of vars (splitByMode output).
109
+ */
110
+ function renderSingleSelector(sets, mode, selector, prefix, warnings) {
111
+ const lines = [];
112
+ lines.push(`/* Generated by figma-console-mcp — do not edit by hand */`);
113
+ lines.push(`${selector} {`);
114
+ for (const set of sets) {
115
+ for (const token of set.tokens) {
116
+ const value = token.values[mode];
117
+ if (!value)
118
+ continue;
119
+ emitTokenLines(token, value, prefix, lines, warnings);
120
+ }
121
+ }
122
+ lines.push(`}`);
123
+ lines.push("");
124
+ return lines.join("\n");
125
+ }
126
+ /**
127
+ * Render multiple sets across multiple modes in one file. Each mode gets its
128
+ * own selector block.
129
+ */
130
+ function renderMultiSelector(sets, prefix, warnings) {
131
+ const lines = [];
132
+ lines.push(`/* Generated by figma-console-mcp — do not edit by hand */`);
133
+ // Collect all (mode, selector) pairs across all sets.
134
+ const modeToSelector = new Map();
135
+ for (const set of sets) {
136
+ for (const mode of set.modes) {
137
+ if (!modeToSelector.has(mode)) {
138
+ modeToSelector.set(mode, selectorFor(mode));
139
+ }
140
+ }
141
+ }
142
+ for (const [mode, selector] of modeToSelector) {
143
+ lines.push(`${selector} {`);
144
+ for (const set of sets) {
145
+ if (!set.modes.includes(mode))
146
+ continue;
147
+ for (const token of set.tokens) {
148
+ const value = token.values[mode];
149
+ if (!value)
150
+ continue;
151
+ emitTokenLines(token, value, prefix, lines, warnings);
152
+ }
153
+ }
154
+ lines.push(`}`);
155
+ lines.push("");
156
+ }
157
+ return lines.join("\n");
158
+ }
159
+ /**
160
+ * Emit one or more CSS custom property declarations for a single token.
161
+ * Primitives emit one line; composite tokens (typography, shadow) expand
162
+ * into multiple lines.
163
+ */
164
+ function emitTokenLines(token, value, prefix, out, warnings) {
165
+ // Every path segment must be a valid CSS identifier — slugify each to
166
+ // normalize spaces, dots, and other special characters that show up in
167
+ // real Figma variable names (e.g. "tailwind colors/purple/50").
168
+ const cssName = `--${prefix}${pathToCssName(token.path)}`;
169
+ if (value.reference) {
170
+ // Detect the cross-library alias sentinel ({__library:VariableID:...}).
171
+ // Emitting var() for these produces broken CSS — the target doesn't
172
+ // live in this file's variable set. Emit a clear comment that names
173
+ // the original library variable ID instead, so the user can decide
174
+ // how to handle it (manual literal, library import, etc.).
175
+ const bareRef = value.reference.replace(/^\{|\}$/g, "");
176
+ const libMatch = bareRef.match(/^__library:(.+)$/);
177
+ if (libMatch || bareRef === "unknown") {
178
+ const originalId = libMatch ? libMatch[1] : "unknown";
179
+ warnings.push(`Skipped ${token.path.join(".")} in CSS — references cross-library variable ${originalId}.`);
180
+ out.push(` /* ${cssName}: skipped — cross-library alias to ${originalId} */`);
181
+ return;
182
+ }
183
+ // Alias → var(--other) so CSS cascading semantics are preserved. The
184
+ // target path goes through the same slugify treatment as the source.
185
+ const refPath = bareRef.split(".");
186
+ const targetCssName = pathToCssName(refPath);
187
+ out.push(` ${cssName}: var(--${prefix}${targetCssName});`);
188
+ return;
189
+ }
190
+ if (value.literal === undefined || value.literal === null) {
191
+ warnings.push(`Token ${token.path.join(".")} has no value — emitting nothing.`);
192
+ return;
193
+ }
194
+ // Composite tokens — expand into multiple primitive vars.
195
+ if (token.type === "typography" && typeof value.literal === "object") {
196
+ const t = value.literal;
197
+ for (const subField of ["fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing"]) {
198
+ if (t[subField] !== undefined) {
199
+ out.push(` ${cssName}-${kebab(subField)}: ${formatCssValue(t[subField], token.type)};`);
200
+ }
201
+ }
202
+ return;
203
+ }
204
+ if (token.type === "shadow" && typeof value.literal === "object") {
205
+ const shadowCss = renderShadow(value.literal);
206
+ if (shadowCss) {
207
+ out.push(` ${cssName}: ${shadowCss};`);
208
+ }
209
+ else {
210
+ warnings.push(`Token ${token.path.join(".")} has an unparseable shadow value — skipping.`);
211
+ }
212
+ return;
213
+ }
214
+ // Primitives.
215
+ out.push(` ${cssName}: ${formatCssValue(value.literal, token.type)};`);
216
+ }
217
+ function kebab(s) {
218
+ return s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
219
+ }
220
+ /**
221
+ * Convert a path array to a CSS-safe custom property name. Each segment is
222
+ * slugified (lowercase, non-alphanumerics → hyphens) before joining.
223
+ *
224
+ * Examples:
225
+ * ["color", "primary"] → "color-primary"
226
+ * ["tailwind colors", "purple", "50"] → "tailwind-colors-purple-50"
227
+ * ["UI", "background-default"] → "ui-background-default"
228
+ */
229
+ function pathToCssName(path) {
230
+ return path.map((seg) => slugify(seg)).join("-");
231
+ }
232
+ /**
233
+ * Render a primitive value as a CSS literal.
234
+ * - Numbers gain a "px" suffix when the token type is `dimension`.
235
+ * - Color-typed strings pass through (hex, oklch, rgba, hsl).
236
+ * - fontFamily-typed strings get quoted (multi-word family names are
237
+ * invalid CSS otherwise — e.g. "Geist Mono" must be `"Geist Mono"`).
238
+ * - Plain string tokens get quoted too, since CSS treats unquoted
239
+ * identifiers as keywords. Strings that look like a CSS color or unit
240
+ * stay unquoted.
241
+ * - Booleans render as their string form.
242
+ */
243
+ function formatCssValue(value, type) {
244
+ if (typeof value === "number") {
245
+ if (type === "dimension")
246
+ return `${value}px`;
247
+ return String(value);
248
+ }
249
+ if (typeof value === "string") {
250
+ // Color-typed values are always pre-formatted CSS color literals.
251
+ if (type === "color")
252
+ return value;
253
+ // fontFamily and plain strings need quoting unless they look like a
254
+ // CSS-safe identifier with no special characters. Quote conservatively
255
+ // — over-quoting is fine, under-quoting breaks the cascade.
256
+ if (type === "fontFamily" || type === "string") {
257
+ return needsQuoting(value) ? JSON.stringify(value) : value;
258
+ }
259
+ // Dimensions and other types: pass through (already formatted upstream).
260
+ return value;
261
+ }
262
+ if (typeof value === "boolean")
263
+ return String(value);
264
+ return JSON.stringify(value);
265
+ }
266
+ /**
267
+ * Returns true if a string value needs to be wrapped in CSS quotes. Multi-word
268
+ * values, values with special characters, or values that aren't pure
269
+ * alphanumeric identifiers all need quoting.
270
+ */
271
+ function needsQuoting(s) {
272
+ // Anything that already has quotes is fine.
273
+ if (/^["']/.test(s))
274
+ return false;
275
+ // Pure-numeric or unit-bearing values don't need quotes (they're not
276
+ // identifiers).
277
+ if (/^[\d.]+([a-z%]+)?$/.test(s))
278
+ return false;
279
+ // CSS keyword identifiers (single word, alphanumerics + hyphen only).
280
+ if (/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(s)) {
281
+ // Reserved CSS keywords that shouldn't be quoted (the user may have
282
+ // legitimately written e.g. "inherit", "currentColor", "transparent").
283
+ const cssKeywords = new Set([
284
+ "inherit",
285
+ "initial",
286
+ "unset",
287
+ "revert",
288
+ "currentColor",
289
+ "transparent",
290
+ "none",
291
+ "auto",
292
+ ]);
293
+ if (cssKeywords.has(s))
294
+ return false;
295
+ // Any other single identifier — for font-family this is OK unquoted
296
+ // ("Inter" works) but it's safer to quote for clarity. Keep unquoted
297
+ // to match the more common convention.
298
+ return false;
299
+ }
300
+ // Multi-word, special chars, etc. → quote.
301
+ return true;
302
+ }
303
+ function renderShadow(shadow) {
304
+ if (Array.isArray(shadow)) {
305
+ return shadow
306
+ .map((s) => renderShadow(s))
307
+ .filter(Boolean)
308
+ .join(", ");
309
+ }
310
+ if (!shadow || typeof shadow !== "object")
311
+ return null;
312
+ const s = shadow;
313
+ const inset = s.inset ? "inset " : "";
314
+ const x = withPx(s.offsetX);
315
+ const y = withPx(s.offsetY);
316
+ const blur = withPx(s.blur);
317
+ const spread = s.spread !== undefined ? ` ${withPx(s.spread)}` : "";
318
+ const color = s.color;
319
+ if (!x || !y || !blur || typeof color !== "string")
320
+ return null;
321
+ return `${inset}${x} ${y} ${blur}${spread} ${color}`;
322
+ }
323
+ function withPx(v) {
324
+ if (typeof v === "number")
325
+ return `${v}px`;
326
+ if (typeof v === "string")
327
+ return v;
328
+ return "";
329
+ }
330
+ //# sourceMappingURL=css-vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css-vars.js","sourceRoot":"","sources":["../../../../src/core/tokens/formatters/css-vars.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DTCG (Design Tokens Community Group) JSON formatter.
3
+ *
4
+ * Produces W3C-spec DTCG output (https://tr.designtokens.org/format/) with
5
+ * three nuances:
6
+ *
7
+ * 1. Multi-mode tokens. The DTCG v1 spec doesn't natively express modes;
8
+ * we use a separate file per mode (driven by `splitByMode: true`) or a
9
+ * single file with values keyed by mode under a vendor extension when
10
+ * splitByMode is false. The split-file approach is the recommended
11
+ * pattern in the broader DTCG community and is what Style Dictionary
12
+ * v4, Tokens Studio, and Figma's announced native export all use.
13
+ *
14
+ * 2. $extensions["figma-console-mcp"]. We stash Figma variable IDs and
15
+ * last-synced values here for non-destructive round-trip. Other DTCG
16
+ * tools preserve $extensions verbatim.
17
+ *
18
+ * 3. Composite tokens (typography, shadow, gradient) emit DTCG's
19
+ * structured $value form. Aliases emit `"$value": "{path.to.target}"`.
20
+ *
21
+ * This formatter is the canonical output — the format every other
22
+ * formatter (CSS variables today, Tailwind/SCSS/etc. in future minor
23
+ * versions) ultimately derives from.
24
+ */
25
+ import type { TokenDocument } from "../types.js";
26
+ import type { FormatOptions, FormatResult } from "./index.js";
27
+ export declare function formatDtcg(doc: TokenDocument, opts: FormatOptions): FormatResult;
28
+ //# sourceMappingURL=dtcg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dtcg.d.ts","sourceRoot":"","sources":["../../../../src/core/tokens/formatters/dtcg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAEV,aAAa,EAGd,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAoB9D,wBAAgB,UAAU,CACxB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,aAAa,GAClB,YAAY,CA+Dd"}
@@ -0,0 +1,301 @@
1
+ /**
2
+ * DTCG (Design Tokens Community Group) JSON formatter.
3
+ *
4
+ * Produces W3C-spec DTCG output (https://tr.designtokens.org/format/) with
5
+ * three nuances:
6
+ *
7
+ * 1. Multi-mode tokens. The DTCG v1 spec doesn't natively express modes;
8
+ * we use a separate file per mode (driven by `splitByMode: true`) or a
9
+ * single file with values keyed by mode under a vendor extension when
10
+ * splitByMode is false. The split-file approach is the recommended
11
+ * pattern in the broader DTCG community and is what Style Dictionary
12
+ * v4, Tokens Studio, and Figma's announced native export all use.
13
+ *
14
+ * 2. $extensions["figma-console-mcp"]. We stash Figma variable IDs and
15
+ * last-synced values here for non-destructive round-trip. Other DTCG
16
+ * tools preserve $extensions verbatim.
17
+ *
18
+ * 3. Composite tokens (typography, shadow, gradient) emit DTCG's
19
+ * structured $value form. Aliases emit `"$value": "{path.to.target}"`.
20
+ *
21
+ * This formatter is the canonical output — the format every other
22
+ * formatter (CSS variables today, Tailwind/SCSS/etc. in future minor
23
+ * versions) ultimately derives from.
24
+ */
25
+ import { FIGMA_MCP_EXTENSION_KEY } from "../types.js";
26
+ import { formatDtcgReference } from "../alias-resolver.js";
27
+ export function formatDtcg(doc, opts) {
28
+ const warnings = [];
29
+ const files = [];
30
+ // Figure out which sets and modes to emit, and how they map to files.
31
+ // Three layout strategies:
32
+ // 1. splitByMode + splitByCollection → one file per (set, mode) pair
33
+ // 2. splitByMode → one file per mode, all sets merged
34
+ // 3. splitByCollection → one file per set, all modes in one tree
35
+ // 4. neither → one file with everything
36
+ const splitByMode = opts.target.splitByMode ?? false;
37
+ const splitByCollection = opts.target.splitByCollection ?? false;
38
+ if (splitByMode && splitByCollection) {
39
+ for (const set of doc.sets) {
40
+ for (const mode of set.modes) {
41
+ const fileTokens = set.tokens
42
+ .map((t) => projectTokenToMode(t, mode, warnings))
43
+ .filter((t) => t !== null);
44
+ files.push({
45
+ path: filenameFor(opts, set, mode),
46
+ content: serializeAsDtcg({ sets: [{ ...set, modes: [mode], tokens: fileTokens }], meta: doc.meta }, warnings, mode),
47
+ });
48
+ }
49
+ }
50
+ }
51
+ else if (splitByMode) {
52
+ const allModes = new Set();
53
+ for (const set of doc.sets)
54
+ for (const m of set.modes)
55
+ allModes.add(m);
56
+ for (const mode of allModes) {
57
+ const fileSets = doc.sets
58
+ .filter((s) => s.modes.includes(mode))
59
+ .map((s) => ({
60
+ ...s,
61
+ modes: [mode],
62
+ tokens: s.tokens
63
+ .map((t) => projectTokenToMode(t, mode, warnings))
64
+ .filter((t) => t !== null),
65
+ }));
66
+ files.push({
67
+ path: filenameFor(opts, undefined, mode),
68
+ content: serializeAsDtcg({ sets: fileSets, meta: doc.meta }, warnings, mode),
69
+ });
70
+ }
71
+ }
72
+ else if (splitByCollection) {
73
+ for (const set of doc.sets) {
74
+ files.push({
75
+ path: filenameFor(opts, set),
76
+ content: serializeAsDtcg({ sets: [set], meta: doc.meta }, warnings),
77
+ });
78
+ }
79
+ }
80
+ else {
81
+ files.push({
82
+ path: filenameFor(opts),
83
+ content: serializeAsDtcg(doc, warnings),
84
+ });
85
+ }
86
+ return { files, warnings };
87
+ }
88
+ /**
89
+ * Project a token's values down to a single mode. Returns null if the token
90
+ * has no value for the requested mode (skip rather than emit a blank).
91
+ */
92
+ function projectTokenToMode(token, mode, warnings) {
93
+ const value = token.values[mode];
94
+ if (!value) {
95
+ // Token wasn't defined for this mode. Could happen when sets share tokens
96
+ // but only some have multi-mode values. Skip silently — not an error.
97
+ return null;
98
+ }
99
+ return { ...token, values: { [mode]: value } };
100
+ }
101
+ /**
102
+ * Compute the output filename for a given (set?, mode?) tuple based on the
103
+ * target options.
104
+ */
105
+ function filenameFor(opts, set, mode) {
106
+ // Caller-specified filename wins.
107
+ if (opts.target.filename)
108
+ return opts.target.filename;
109
+ const parts = [];
110
+ if (set)
111
+ parts.push(slugify(set.name));
112
+ if (mode)
113
+ parts.push(slugify(mode));
114
+ if (parts.length === 0)
115
+ parts.push("tokens");
116
+ return `${parts.join(".")}.tokens.json`;
117
+ }
118
+ function slugify(s) {
119
+ return s
120
+ .trim()
121
+ .toLowerCase()
122
+ .replace(/[^a-z0-9]+/g, "-")
123
+ .replace(/^-+|-+$/g, "");
124
+ }
125
+ /**
126
+ * Serialize a TokenDocument as DTCG JSON. Returns a pretty-printed JSON string
127
+ * with stable key order so git diffs stay minimal across runs.
128
+ *
129
+ * When `fileMode` is provided (splitByMode output), it's stamped into
130
+ * document-level $extensions so the parser can recover which mode this
131
+ * file represents — otherwise the parser sees only `$value` literals and
132
+ * labels them "Default", which breaks round-trip diffs on multi-mode files.
133
+ */
134
+ function serializeAsDtcg(doc, warnings, fileMode) {
135
+ // Build the nested DTCG group tree by walking every token's path and
136
+ // building groups along the way.
137
+ const tree = {};
138
+ // Document-level $extensions: stash file-level metadata (Figma file key,
139
+ // export timestamp, MCP version, optionally the file's mode for
140
+ // splitByMode output) so round-trip preserves it.
141
+ const mcpDocMeta = {};
142
+ if (doc.meta?.figmaFileKey)
143
+ mcpDocMeta.figmaFileKey = doc.meta.figmaFileKey;
144
+ if (doc.meta?.exportedAt)
145
+ mcpDocMeta.exportedAt = doc.meta.exportedAt;
146
+ if (doc.meta?.mcpVersion)
147
+ mcpDocMeta.mcpVersion = doc.meta.mcpVersion;
148
+ if (fileMode)
149
+ mcpDocMeta.fileMode = fileMode;
150
+ if (Object.keys(mcpDocMeta).length > 0) {
151
+ tree.$extensions = { [FIGMA_MCP_EXTENSION_KEY]: mcpDocMeta };
152
+ }
153
+ for (const set of doc.sets) {
154
+ // Each set lives under a top-level group named after the set. Set-level
155
+ // metadata (Figma collection ID, original name, etc.) goes in that
156
+ // group's $extensions so round-trip recovers the original name even
157
+ // after we slugify it for the JSON key.
158
+ const setKey = setKeyFor(set);
159
+ let setGroup = tree[setKey];
160
+ if (!setGroup) {
161
+ setGroup = {};
162
+ if (set.description)
163
+ setGroup.$description = set.description;
164
+ const mcpMeta = {};
165
+ if (set.meta?.figmaCollectionId) {
166
+ mcpMeta.figmaCollectionId = set.meta.figmaCollectionId;
167
+ }
168
+ // Always stash the original name when it differs from the slug — this
169
+ // is what makes diff matching work after round-trip.
170
+ if (set.name !== setKey) {
171
+ mcpMeta.originalName = set.name;
172
+ }
173
+ if (Object.keys(mcpMeta).length > 0) {
174
+ setGroup.$extensions = { [FIGMA_MCP_EXTENSION_KEY]: mcpMeta };
175
+ }
176
+ tree[setKey] = setGroup;
177
+ }
178
+ for (const token of set.tokens) {
179
+ writeTokenIntoTree(setGroup, token, set.modes, warnings);
180
+ }
181
+ }
182
+ return JSON.stringify(sortKeys(tree), null, 2) + "\n";
183
+ }
184
+ /**
185
+ * Key used for the top-level set group in DTCG. We slugify the set name to
186
+ * keep it a valid JSON key under any consumer's expectations. The original
187
+ * (un-slugged) name is preserved in the set's $extensions so round-trip
188
+ * recovers it.
189
+ */
190
+ function setKeyFor(set) {
191
+ return slugify(set.name);
192
+ }
193
+ /**
194
+ * Insert a token into the DTCG group tree at the right nested path.
195
+ * Creates intermediate groups as needed.
196
+ */
197
+ function writeTokenIntoTree(root, token, setModes, warnings) {
198
+ let cursor = root;
199
+ for (let i = 0; i < token.path.length - 1; i++) {
200
+ const segment = token.path[i];
201
+ let next = cursor[segment];
202
+ if (!next || isToken(next)) {
203
+ next = {};
204
+ cursor[segment] = next;
205
+ }
206
+ cursor = next;
207
+ }
208
+ const leafKey = token.path[token.path.length - 1];
209
+ cursor[leafKey] = renderToken(token, setModes, warnings);
210
+ }
211
+ function isToken(node) {
212
+ return "$value" in node;
213
+ }
214
+ /**
215
+ * Convert an internal Token to its DTCG-encoded leaf form.
216
+ *
217
+ * Single-mode token: emits `{ $value, $type, ... }`.
218
+ * Multi-mode token: emits one extension stash with all mode values, because
219
+ * vanilla DTCG doesn't have a native multi-mode encoding. Callers who want
220
+ * one-file-per-mode should set splitByMode at the formatter level.
221
+ */
222
+ function renderToken(token, setModes, warnings) {
223
+ const result = {
224
+ $value: "",
225
+ $type: token.type,
226
+ };
227
+ if (token.description)
228
+ result.$description = token.description;
229
+ const modeKeys = Object.keys(token.values);
230
+ const isSingleMode = modeKeys.length === 1;
231
+ if (isSingleMode) {
232
+ const onlyValue = token.values[modeKeys[0]];
233
+ result.$value = encodeValue(onlyValue, token, warnings);
234
+ }
235
+ else {
236
+ // Multi-mode in a single file: pick the first mode as the canonical
237
+ // $value, stash the rest in $extensions for round-trip.
238
+ const primaryMode = setModes[0] in token.values ? setModes[0] : modeKeys[0];
239
+ result.$value = encodeValue(token.values[primaryMode], token, warnings);
240
+ const otherModes = {};
241
+ for (const m of modeKeys) {
242
+ if (m === primaryMode)
243
+ continue;
244
+ otherModes[m] = encodeValue(token.values[m], token, warnings);
245
+ }
246
+ if (Object.keys(otherModes).length > 0) {
247
+ mergeExtension(result, "modes", otherModes);
248
+ }
249
+ }
250
+ // Preserve any pre-existing extensions (e.g. studio.tokens, our own metadata).
251
+ if (token.extensions) {
252
+ for (const [vendor, payload] of Object.entries(token.extensions)) {
253
+ if (vendor === FIGMA_MCP_EXTENSION_KEY) {
254
+ mergeExtension(result, vendor, payload);
255
+ }
256
+ else {
257
+ mergeExtension(result, vendor, payload);
258
+ }
259
+ }
260
+ }
261
+ return result;
262
+ }
263
+ function encodeValue(value, token, warnings) {
264
+ if (value.reference) {
265
+ return formatDtcgReference(value.reference.replace(/^\{|\}$/g, "").split("."));
266
+ }
267
+ if (value.literal === undefined) {
268
+ warnings.push(`Token ${token.path.join(".")} has neither literal nor reference — emitting empty string.`);
269
+ return "";
270
+ }
271
+ return value.literal;
272
+ }
273
+ function mergeExtension(token, key, payload) {
274
+ token.$extensions ??= {};
275
+ token.$extensions[key] = payload;
276
+ }
277
+ /**
278
+ * Recursively sort object keys for stable serialization (so git diffs only
279
+ * show meaningful changes). $-prefixed keys come first (DTCG convention),
280
+ * then alphabetical.
281
+ */
282
+ function sortKeys(node) {
283
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
284
+ return node;
285
+ }
286
+ const obj = node;
287
+ const sorted = {};
288
+ const keys = Object.keys(obj).sort((a, b) => {
289
+ const aDollar = a.startsWith("$");
290
+ const bDollar = b.startsWith("$");
291
+ if (aDollar && !bDollar)
292
+ return -1;
293
+ if (!aDollar && bDollar)
294
+ return 1;
295
+ return a.localeCompare(b);
296
+ });
297
+ for (const k of keys)
298
+ sorted[k] = sortKeys(obj[k]);
299
+ return sorted;
300
+ }
301
+ //# sourceMappingURL=dtcg.js.map