@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,402 +0,0 @@
1
- /**
2
- * Figma Reconstruction Spec
3
- *
4
- * Generates node tree construction specifications compatible with the
5
- * Figma Component Reconstructor plugin. This format differs from metadata
6
- * export by providing all properties needed to programmatically recreate
7
- * components in Figma.
8
- */
9
- /**
10
- * Convert Figma paint array to reconstruction spec format
11
- */
12
- export function convertFills(fills) {
13
- if (!fills || fills === 'mixed')
14
- return [];
15
- return fills
16
- .filter((fill) => fill.visible !== false)
17
- .map((fill) => {
18
- if (fill.type === 'SOLID') {
19
- return {
20
- type: 'SOLID',
21
- color: {
22
- r: fill.color?.r ?? 0,
23
- g: fill.color?.g ?? 0,
24
- b: fill.color?.b ?? 0,
25
- a: fill.opacity ?? 1,
26
- },
27
- opacity: fill.opacity ?? 1,
28
- visible: fill.visible ?? true,
29
- };
30
- }
31
- if (fill.type.startsWith('GRADIENT')) {
32
- return {
33
- type: fill.type,
34
- gradientStops: fill.gradientStops?.map((stop) => ({
35
- color: {
36
- r: stop.color?.r ?? 0,
37
- g: stop.color?.g ?? 0,
38
- b: stop.color?.b ?? 0,
39
- a: stop.color?.a ?? 1,
40
- },
41
- position: stop.position ?? 0,
42
- })) || [],
43
- opacity: fill.opacity ?? 1,
44
- visible: fill.visible ?? true,
45
- };
46
- }
47
- if (fill.type === 'IMAGE') {
48
- return {
49
- type: 'IMAGE',
50
- scaleMode: fill.scaleMode || 'FILL',
51
- imageRef: fill.imageRef,
52
- opacity: fill.opacity ?? 1,
53
- visible: fill.visible ?? true,
54
- };
55
- }
56
- // Default fallback
57
- return null;
58
- })
59
- .filter((f) => f !== null);
60
- }
61
- /**
62
- * Convert Figma stroke array to reconstruction spec format
63
- */
64
- export function convertStrokes(strokes) {
65
- // Strokes use same format as fills
66
- return convertFills(strokes);
67
- }
68
- /**
69
- * Convert REST API constraint values to Plugin API constraint values
70
- * REST API: LEFT, RIGHT, TOP, BOTTOM, CENTER, SCALE
71
- * Plugin API: MIN, MAX, CENTER, STRETCH, SCALE
72
- */
73
- function convertConstraintValue(value) {
74
- const mapping = {
75
- 'LEFT': 'MIN',
76
- 'RIGHT': 'MAX',
77
- 'TOP': 'MIN',
78
- 'BOTTOM': 'MAX',
79
- 'CENTER': 'CENTER',
80
- 'STRETCH': 'STRETCH',
81
- 'SCALE': 'SCALE',
82
- };
83
- return mapping[value] || value;
84
- }
85
- /**
86
- * Convert Figma effects array to reconstruction spec format
87
- */
88
- export function convertEffects(effects) {
89
- if (!effects || effects === 'mixed')
90
- return [];
91
- return effects
92
- .filter((effect) => effect.visible !== false)
93
- .map((effect) => {
94
- if (effect.type === 'DROP_SHADOW' || effect.type === 'INNER_SHADOW') {
95
- return {
96
- type: effect.type,
97
- color: {
98
- r: effect.color?.r ?? 0,
99
- g: effect.color?.g ?? 0,
100
- b: effect.color?.b ?? 0,
101
- a: effect.color?.a ?? 1,
102
- },
103
- offset: effect.offset || { x: 0, y: 0 },
104
- radius: effect.radius ?? 0,
105
- spread: effect.spread,
106
- visible: effect.visible ?? true,
107
- };
108
- }
109
- if (effect.type === 'LAYER_BLUR' || effect.type === 'BACKGROUND_BLUR') {
110
- return {
111
- type: effect.type,
112
- radius: effect.radius ?? 0,
113
- visible: effect.visible ?? true,
114
- };
115
- }
116
- return null;
117
- })
118
- .filter((e) => e !== null);
119
- }
120
- /**
121
- * Recursively extract node specification for reconstruction
122
- */
123
- export function extractNodeSpec(node) {
124
- const spec = {
125
- name: node.name,
126
- type: node.type,
127
- };
128
- // INSTANCE → FRAME conversion for plugin compatibility
129
- // Plugin cannot create instance nodes, so we convert them to frames
130
- // This preserves visual properties and children for the "sketchpad" workflow
131
- if (spec.type === 'INSTANCE') {
132
- spec.type = 'FRAME';
133
- // All visual properties (fills, strokes, layout) will be copied from the instance
134
- // Children will be processed recursively and also converted if needed
135
- }
136
- // Position - provide defaults if missing
137
- if ('x' in node && typeof node.x === 'number') {
138
- spec.x = node.x;
139
- }
140
- else if (node.type !== 'GROUP' && node.type !== 'SECTION') {
141
- spec.x = 0;
142
- }
143
- if ('y' in node && typeof node.y === 'number') {
144
- spec.y = node.y;
145
- }
146
- else if (node.type !== 'GROUP' && node.type !== 'SECTION') {
147
- spec.y = 0;
148
- }
149
- // Layout sizing for children in auto-layout parents
150
- // These properties tell the plugin HOW the child should size itself (HUG content vs FIXED vs FILL)
151
- if ('layoutSizingHorizontal' in node) {
152
- spec.layoutSizingHorizontal = node.layoutSizingHorizontal;
153
- }
154
- if ('layoutSizingVertical' in node) {
155
- spec.layoutSizingVertical = node.layoutSizingVertical;
156
- }
157
- // Dimensions - required for most node types to be reconstructable
158
- // IMPORTANT: Skip explicit dimensions for children with HUG sizing in auto-layout
159
- // The plugin will calculate dimensions based on sizing mode + content
160
- const hasHugSizing = node.layoutSizingHorizontal === 'HUG' || node.layoutSizingVertical === 'HUG';
161
- const isParentNode = node.type === 'COMPONENT' || node.type === 'FRAME' || node.type === 'INSTANCE';
162
- const skipDimensions = hasHugSizing && !isParentNode;
163
- if (!skipDimensions) {
164
- // Check both direct properties (Desktop Bridge) and absoluteBoundingBox (REST API)
165
- if ('width' in node && typeof node.width === 'number') {
166
- spec.width = node.width;
167
- }
168
- else if ('absoluteBoundingBox' in node && node.absoluteBoundingBox && typeof node.absoluteBoundingBox.width === 'number') {
169
- spec.width = node.absoluteBoundingBox.width;
170
- }
171
- else if (node.type !== 'GROUP' && node.type !== 'SECTION') {
172
- // Default width for nodes that need it
173
- spec.width = node.type === 'TEXT' ? 100 :
174
- node.type === 'COMPONENT_SET' ? 200 :
175
- node.type === 'ELLIPSE' ? 8 : 50;
176
- }
177
- if ('height' in node && typeof node.height === 'number') {
178
- spec.height = node.height;
179
- }
180
- else if ('absoluteBoundingBox' in node && node.absoluteBoundingBox && typeof node.absoluteBoundingBox.height === 'number') {
181
- spec.height = node.absoluteBoundingBox.height;
182
- }
183
- else if (node.type !== 'GROUP' && node.type !== 'SECTION') {
184
- // Default height for nodes that need it
185
- spec.height = node.type === 'TEXT' ? 20 :
186
- node.type === 'COMPONENT_SET' ? 100 :
187
- node.type === 'ELLIPSE' ? 8 : 50;
188
- }
189
- }
190
- // Visual properties (only include what plugin spec needs)
191
- if ('opacity' in node && typeof node.opacity === 'number' && node.opacity !== 1) {
192
- spec.opacity = node.opacity;
193
- }
194
- // Fills (only include if present and non-empty)
195
- if ('fills' in node && node.fills !== 'mixed') {
196
- const convertedFills = convertFills(node.fills);
197
- if (convertedFills.length > 0) {
198
- spec.fills = convertedFills;
199
- }
200
- }
201
- // Strokes (only include if present and non-empty)
202
- if ('strokes' in node && node.strokes !== 'mixed') {
203
- const convertedStrokes = convertStrokes(node.strokes);
204
- if (convertedStrokes.length > 0) {
205
- spec.strokes = convertedStrokes;
206
- // Only include stroke weight if there are strokes
207
- if ('strokeWeight' in node && typeof node.strokeWeight === 'number') {
208
- spec.strokeWeight = node.strokeWeight;
209
- }
210
- }
211
- }
212
- // Effects (only include if present and non-empty)
213
- if ('effects' in node && node.effects !== 'mixed') {
214
- const convertedEffects = convertEffects(node.effects);
215
- if (convertedEffects.length > 0) {
216
- spec.effects = convertedEffects;
217
- }
218
- }
219
- // Corner radius
220
- if ('cornerRadius' in node && typeof node.cornerRadius === 'number') {
221
- spec.cornerRadius = node.cornerRadius;
222
- }
223
- if ('rectangleCornerRadii' in node && Array.isArray(node.rectangleCornerRadii)) {
224
- spec.rectangleCornerRadii = node.rectangleCornerRadii;
225
- }
226
- // Layout properties (for FRAME, COMPONENT, INSTANCE)
227
- // Plugin spec requires: layoutMode, primaryAxisSizingMode, counterAxisSizingMode, itemSpacing, padding*
228
- if ('layoutMode' in node && node.layoutMode !== 'NONE') {
229
- spec.layoutMode = node.layoutMode;
230
- // Sizing modes (REQUIRED for plugin spec)
231
- if ('primaryAxisSizingMode' in node)
232
- spec.primaryAxisSizingMode = node.primaryAxisSizingMode;
233
- if ('counterAxisSizingMode' in node)
234
- spec.counterAxisSizingMode = node.counterAxisSizingMode;
235
- // Spacing
236
- if ('itemSpacing' in node)
237
- spec.itemSpacing = node.itemSpacing;
238
- // Padding (all four sides)
239
- if ('paddingLeft' in node)
240
- spec.paddingLeft = node.paddingLeft;
241
- if ('paddingRight' in node)
242
- spec.paddingRight = node.paddingRight;
243
- if ('paddingTop' in node)
244
- spec.paddingTop = node.paddingTop;
245
- if ('paddingBottom' in node)
246
- spec.paddingBottom = node.paddingBottom;
247
- }
248
- // TEXT node specific properties
249
- // REST API returns text styling in a 'style' object
250
- if (node.type === 'TEXT') {
251
- if ('characters' in node)
252
- spec.characters = node.characters;
253
- // Check both direct properties (Plugin API) and style object (REST API)
254
- if ('fontSize' in node && typeof node.fontSize === 'number') {
255
- spec.fontSize = node.fontSize;
256
- }
257
- else if (node.style?.fontSize && typeof node.style.fontSize === 'number') {
258
- spec.fontSize = node.style.fontSize;
259
- }
260
- if ('fontName' in node) {
261
- spec.fontName = node.fontName;
262
- }
263
- else if (node.style?.fontFamily && node.style?.fontWeight) {
264
- spec.fontName = {
265
- family: node.style.fontFamily,
266
- style: node.style.fontWeight
267
- };
268
- }
269
- if ('textAlignHorizontal' in node) {
270
- spec.textAlignHorizontal = node.textAlignHorizontal;
271
- }
272
- else if (node.style?.textAlignHorizontal) {
273
- spec.textAlignHorizontal = node.style.textAlignHorizontal;
274
- }
275
- if ('textAlignVertical' in node) {
276
- spec.textAlignVertical = node.textAlignVertical;
277
- }
278
- else if (node.style?.textAlignVertical) {
279
- spec.textAlignVertical = node.style.textAlignVertical;
280
- }
281
- if ('letterSpacing' in node) {
282
- spec.letterSpacing = node.letterSpacing;
283
- }
284
- else if (node.style?.letterSpacing) {
285
- spec.letterSpacing = node.style.letterSpacing;
286
- }
287
- if ('lineHeight' in node) {
288
- spec.lineHeight = node.lineHeight;
289
- }
290
- else if (node.style?.lineHeight) {
291
- spec.lineHeight = node.style.lineHeight;
292
- }
293
- }
294
- // Variant properties (for COMPONENT in a COMPONENT_SET)
295
- if (node.type === 'COMPONENT' && 'variantProperties' in node && node.variantProperties) {
296
- spec.variantProperties = node.variantProperties;
297
- }
298
- // Children (recursive)
299
- if ('children' in node && Array.isArray(node.children)) {
300
- spec.children = node.children.map((child) => extractNodeSpec(child));
301
- }
302
- return spec;
303
- }
304
- /**
305
- * Validate that the reconstruction spec has required fields
306
- */
307
- export function validateReconstructionSpec(spec) {
308
- const errors = [];
309
- // Required fields
310
- if (!spec.name || typeof spec.name !== 'string') {
311
- errors.push('Missing or invalid required field: name');
312
- }
313
- if (!spec.type || typeof spec.type !== 'string') {
314
- errors.push('Missing or invalid required field: type');
315
- }
316
- // Valid node types
317
- const validTypes = [
318
- 'FRAME',
319
- 'COMPONENT',
320
- 'COMPONENT_SET',
321
- 'INSTANCE',
322
- 'TEXT',
323
- 'RECTANGLE',
324
- 'ELLIPSE',
325
- 'POLYGON',
326
- 'STAR',
327
- 'VECTOR',
328
- 'LINE',
329
- 'GROUP',
330
- 'SECTION',
331
- 'SLICE',
332
- 'BOOLEAN_OPERATION',
333
- ];
334
- if (spec.type && !validTypes.includes(spec.type)) {
335
- errors.push(`Invalid node type: ${spec.type}. Must be one of: ${validTypes.join(', ')}`);
336
- }
337
- // Validate dimensions if present
338
- if ('width' in spec && (typeof spec.width !== 'number' || spec.width < 0)) {
339
- errors.push('Invalid width: must be a non-negative number');
340
- }
341
- if ('height' in spec && (typeof spec.height !== 'number' || spec.height < 0)) {
342
- errors.push('Invalid height: must be a non-negative number');
343
- }
344
- // Validate opacity if present
345
- if ('opacity' in spec && (typeof spec.opacity !== 'number' || spec.opacity < 0 || spec.opacity > 1)) {
346
- errors.push('Invalid opacity: must be a number between 0 and 1');
347
- }
348
- // Validate colors in fills
349
- if (spec.fills && Array.isArray(spec.fills)) {
350
- spec.fills.forEach((fill, index) => {
351
- if (fill.type === 'SOLID' && fill.color) {
352
- const { r, g, b, a } = fill.color;
353
- if (typeof r !== 'number' || r < 0 || r > 1 ||
354
- typeof g !== 'number' || g < 0 || g > 1 ||
355
- typeof b !== 'number' || b < 0 || b > 1 ||
356
- typeof a !== 'number' || a < 0 || a > 1) {
357
- errors.push(`Invalid color in fills[${index}]: RGB values must be between 0 and 1`);
358
- }
359
- }
360
- });
361
- }
362
- // Recursively validate children
363
- if (spec.children && Array.isArray(spec.children)) {
364
- spec.children.forEach((child, index) => {
365
- const childValidation = validateReconstructionSpec(child);
366
- if (!childValidation.valid) {
367
- errors.push(`Errors in children[${index}]: ${childValidation.errors.join(', ')}`);
368
- }
369
- });
370
- }
371
- return {
372
- valid: errors.length === 0,
373
- errors,
374
- };
375
- }
376
- /**
377
- * Extract a specific variant from a COMPONENT_SET by name
378
- */
379
- export function extractVariant(componentSet, variantName) {
380
- if (!componentSet.children || !Array.isArray(componentSet.children)) {
381
- throw new Error('Invalid COMPONENT_SET: no children array');
382
- }
383
- const variant = componentSet.children.find((child) => child.type === 'COMPONENT' && child.name === variantName);
384
- if (!variant) {
385
- const availableVariants = componentSet.children
386
- .filter((c) => c.type === 'COMPONENT')
387
- .map((c) => c.name);
388
- throw new Error(`Variant "${variantName}" not found. Available variants: ${availableVariants.join(', ')}`);
389
- }
390
- return extractNodeSpec(variant);
391
- }
392
- /**
393
- * Get list of available variants in a COMPONENT_SET
394
- */
395
- export function listVariants(componentSet) {
396
- if (!componentSet.children || !Array.isArray(componentSet.children)) {
397
- return [];
398
- }
399
- return componentSet.children
400
- .filter((child) => child.type === 'COMPONENT')
401
- .map((child) => child.name);
402
- }