@muloka/figma-console-mcp 0.5.0 → 0.5.1

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 (107) hide show
  1. package/README.md +2 -0
  2. package/dist/core/websocket-connector.d.ts +2 -1
  3. package/dist/core/websocket-connector.d.ts.map +1 -1
  4. package/dist/core/websocket-connector.js +17 -2
  5. package/dist/core/websocket-connector.js.map +1 -1
  6. package/dist/core/websocket-server.d.ts +8 -1
  7. package/dist/core/websocket-server.d.ts.map +1 -1
  8. package/dist/core/websocket-server.js +18 -2
  9. package/dist/core/websocket-server.js.map +1 -1
  10. package/dist/core/write-tools.d.ts +3 -1
  11. package/dist/core/write-tools.d.ts.map +1 -1
  12. package/dist/core/write-tools.js +34 -14
  13. package/dist/core/write-tools.js.map +1 -1
  14. package/dist/local.d.ts +2 -1
  15. package/dist/local.d.ts.map +1 -1
  16. package/dist/local.js +5 -4
  17. package/dist/local.js.map +1 -1
  18. package/package.json +1 -1
  19. package/dist/apps/design-system-dashboard/mcp-app.html +0 -521
  20. package/dist/apps/token-browser/mcp-app.html +0 -410
  21. package/dist/cloudflare/apps/design-system-dashboard/scoring/accessibility.js +0 -277
  22. package/dist/cloudflare/apps/design-system-dashboard/scoring/component-metadata.js +0 -357
  23. package/dist/cloudflare/apps/design-system-dashboard/scoring/consistency.js +0 -341
  24. package/dist/cloudflare/apps/design-system-dashboard/scoring/coverage.js +0 -230
  25. package/dist/cloudflare/apps/design-system-dashboard/scoring/engine.js +0 -92
  26. package/dist/cloudflare/apps/design-system-dashboard/scoring/naming-semantics.js +0 -308
  27. package/dist/cloudflare/apps/design-system-dashboard/scoring/token-architecture.js +0 -349
  28. package/dist/cloudflare/apps/design-system-dashboard/scoring/types.js +0 -40
  29. package/dist/cloudflare/apps/design-system-dashboard/server.js +0 -159
  30. package/dist/cloudflare/apps/token-browser/server.js +0 -136
  31. package/dist/cloudflare/browser-manager.js +0 -157
  32. package/dist/cloudflare/core/accessibility-tools.js +0 -361
  33. package/dist/cloudflare/core/annotation-tools.js +0 -230
  34. package/dist/cloudflare/core/cloud-websocket-connector.js +0 -419
  35. package/dist/cloudflare/core/cloud-websocket-relay.js +0 -198
  36. package/dist/cloudflare/core/comment-tools.js +0 -292
  37. package/dist/cloudflare/core/config.js +0 -153
  38. package/dist/cloudflare/core/console-monitor.js +0 -427
  39. package/dist/cloudflare/core/deep-component-tools.js +0 -128
  40. package/dist/cloudflare/core/design-code-tools.js +0 -2793
  41. package/dist/cloudflare/core/design-system-manifest.js +0 -265
  42. package/dist/cloudflare/core/design-system-tools.js +0 -882
  43. package/dist/cloudflare/core/diagnose-tool.js +0 -100
  44. package/dist/cloudflare/core/diff/changelog-formatter.js +0 -275
  45. package/dist/cloudflare/core/diff/diff-engine.js +0 -334
  46. package/dist/cloudflare/core/diff/property-compare.js +0 -36
  47. package/dist/cloudflare/core/diff/version-cache.js +0 -74
  48. package/dist/cloudflare/core/enrichment/enrichment-service.js +0 -374
  49. package/dist/cloudflare/core/enrichment/index.js +0 -7
  50. package/dist/cloudflare/core/enrichment/relationship-mapper.js +0 -351
  51. package/dist/cloudflare/core/enrichment/style-resolver.js +0 -346
  52. package/dist/cloudflare/core/figjam-tools.js +0 -547
  53. package/dist/cloudflare/core/figma-api.js +0 -509
  54. package/dist/cloudflare/core/figma-connector.js +0 -7
  55. package/dist/cloudflare/core/figma-reconstruction-spec.js +0 -402
  56. package/dist/cloudflare/core/figma-style-extractor.js +0 -311
  57. package/dist/cloudflare/core/figma-tools.js +0 -3353
  58. package/dist/cloudflare/core/identity.js +0 -96
  59. package/dist/cloudflare/core/library-tools.js +0 -580
  60. package/dist/cloudflare/core/logger.js +0 -53
  61. package/dist/cloudflare/core/port-discovery.js +0 -935
  62. package/dist/cloudflare/core/resolve-package-root.js +0 -11
  63. package/dist/cloudflare/core/slides-tools.js +0 -714
  64. package/dist/cloudflare/core/slot-tools.js +0 -334
  65. package/dist/cloudflare/core/snippet-injector.js +0 -96
  66. package/dist/cloudflare/core/tokens/alias-resolver.js +0 -205
  67. package/dist/cloudflare/core/tokens/config.js +0 -294
  68. package/dist/cloudflare/core/tokens/dialect.js +0 -232
  69. package/dist/cloudflare/core/tokens/figma-converter.js +0 -323
  70. package/dist/cloudflare/core/tokens/formatters/css-vars.js +0 -338
  71. package/dist/cloudflare/core/tokens/formatters/dtcg.js +0 -376
  72. package/dist/cloudflare/core/tokens/formatters/index.js +0 -45
  73. package/dist/cloudflare/core/tokens/formatters/json.js +0 -205
  74. package/dist/cloudflare/core/tokens/formatters/less.js +0 -4
  75. package/dist/cloudflare/core/tokens/formatters/scss.js +0 -258
  76. package/dist/cloudflare/core/tokens/formatters/stubs.js +0 -13
  77. package/dist/cloudflare/core/tokens/formatters/style-dictionary-v3.js +0 -213
  78. package/dist/cloudflare/core/tokens/formatters/tailwind-v3.js +0 -237
  79. package/dist/cloudflare/core/tokens/formatters/tailwind-v4.js +0 -335
  80. package/dist/cloudflare/core/tokens/formatters/tokens-studio.js +0 -256
  81. package/dist/cloudflare/core/tokens/formatters/ts-module.js +0 -198
  82. package/dist/cloudflare/core/tokens/index.js +0 -16
  83. package/dist/cloudflare/core/tokens/parsers/css-vars.js +0 -4
  84. package/dist/cloudflare/core/tokens/parsers/dtcg.js +0 -280
  85. package/dist/cloudflare/core/tokens/parsers/index.js +0 -138
  86. package/dist/cloudflare/core/tokens/parsers/json.js +0 -7
  87. package/dist/cloudflare/core/tokens/parsers/scss.js +0 -4
  88. package/dist/cloudflare/core/tokens/parsers/stubs.js +0 -20
  89. package/dist/cloudflare/core/tokens/parsers/style-dictionary-v3.js +0 -4
  90. package/dist/cloudflare/core/tokens/parsers/tailwind-v3.js +0 -4
  91. package/dist/cloudflare/core/tokens/parsers/tailwind-v4.js +0 -4
  92. package/dist/cloudflare/core/tokens/parsers/tokens-studio.js +0 -4
  93. package/dist/cloudflare/core/tokens/schemas.js +0 -152
  94. package/dist/cloudflare/core/tokens/transforms/color.js +0 -12
  95. package/dist/cloudflare/core/tokens/transforms/index.js +0 -29
  96. package/dist/cloudflare/core/tokens/transforms/size.js +0 -7
  97. package/dist/cloudflare/core/tokens/types.js +0 -18
  98. package/dist/cloudflare/core/tokens-tools.js +0 -1788
  99. package/dist/cloudflare/core/types/design-code.js +0 -4
  100. package/dist/cloudflare/core/types/enriched.js +0 -5
  101. package/dist/cloudflare/core/types/index.js +0 -4
  102. package/dist/cloudflare/core/variable-resolver.js +0 -85
  103. package/dist/cloudflare/core/version-tools.js +0 -1199
  104. package/dist/cloudflare/core/websocket-connector.js +0 -436
  105. package/dist/cloudflare/core/websocket-server.js +0 -1009
  106. package/dist/cloudflare/core/write-tools.js +0 -2506
  107. 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
- }