@sangheepark/figma-ds-mcp 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/pipeline-tools.js +92 -11
- package/dist/tools/utility-tools.js +39 -12
- package/package.json +1 -1
|
@@ -277,13 +277,13 @@ function enrichSpec(traversal, mapping) {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
walk(spec, 'root');
|
|
280
|
-
// Step 6-C: root frame sizing (
|
|
280
|
+
// Step 6-C: root frame sizing (viewport defaults: 1440×900 PC)
|
|
281
281
|
if (spec.layout) {
|
|
282
282
|
if (spec.layout.width === 'fill') {
|
|
283
283
|
spec.layout.width = '1440';
|
|
284
284
|
}
|
|
285
285
|
if (spec.layout.height === 'fill') {
|
|
286
|
-
spec.layout.height = '
|
|
286
|
+
spec.layout.height = '900';
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
return { spec, errors };
|
|
@@ -467,6 +467,80 @@ function gateCheck(spec) {
|
|
|
467
467
|
const pass = results.every(r => r.pass);
|
|
468
468
|
return { pass, results };
|
|
469
469
|
}
|
|
470
|
+
// --- _repeat: Component Auto-Extraction ---
|
|
471
|
+
function extractRepeatComponents(spec, defaultCount = 3) {
|
|
472
|
+
const extracted = new Map(); // name → component spec
|
|
473
|
+
function deepClone(obj) {
|
|
474
|
+
return JSON.parse(JSON.stringify(obj));
|
|
475
|
+
}
|
|
476
|
+
function getRepeatCount(node) {
|
|
477
|
+
if (typeof node._repeat === 'number' && node._repeat > 0)
|
|
478
|
+
return node._repeat;
|
|
479
|
+
return defaultCount;
|
|
480
|
+
}
|
|
481
|
+
// Depth-first walk: process children first so nested _repeat resolves before parent
|
|
482
|
+
function walk(node) {
|
|
483
|
+
// Process children first (depth-first)
|
|
484
|
+
if (node.children) {
|
|
485
|
+
for (const child of node.children) {
|
|
486
|
+
walk(child);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Now process this node's children for _repeat
|
|
490
|
+
if (!node.children)
|
|
491
|
+
return;
|
|
492
|
+
const newChildren = [];
|
|
493
|
+
for (const child of node.children) {
|
|
494
|
+
if (!child._repeat) {
|
|
495
|
+
newChildren.push(child);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const count = getRepeatCount(child);
|
|
499
|
+
const nodeAny = child;
|
|
500
|
+
const hasLibraryKey = !!nodeAny['library-key'];
|
|
501
|
+
const hasDs = !!child._ds;
|
|
502
|
+
// Library instance: just duplicate N times, no component extraction
|
|
503
|
+
if (hasLibraryKey || hasDs) {
|
|
504
|
+
const cleanChild = deepClone(child);
|
|
505
|
+
delete cleanChild._repeat;
|
|
506
|
+
for (let i = 0; i < count; i++) {
|
|
507
|
+
const instance = deepClone(cleanChild);
|
|
508
|
+
if (instance.name)
|
|
509
|
+
instance.name = `${instance.name}_${i + 1}`;
|
|
510
|
+
newChildren.push(instance);
|
|
511
|
+
}
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
// Custom frame: extract as component + insert instances
|
|
515
|
+
const compName = child.name || `RepeatComponent_${extracted.size + 1}`;
|
|
516
|
+
if (!extracted.has(compName)) {
|
|
517
|
+
// Extract as component
|
|
518
|
+
const compSpec = deepClone(child);
|
|
519
|
+
compSpec.type = 'component';
|
|
520
|
+
compSpec.name = compName;
|
|
521
|
+
delete compSpec._repeat;
|
|
522
|
+
extracted.set(compName, compSpec);
|
|
523
|
+
}
|
|
524
|
+
// Insert N instances in place
|
|
525
|
+
for (let i = 0; i < count; i++) {
|
|
526
|
+
newChildren.push({
|
|
527
|
+
type: 'instance',
|
|
528
|
+
component: compName,
|
|
529
|
+
name: `${compName}_${i + 1}`,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
node.children = newChildren;
|
|
534
|
+
}
|
|
535
|
+
const page = deepClone(spec);
|
|
536
|
+
// Skip if root itself has _repeat (standalone component, no extraction needed)
|
|
537
|
+
if (page._repeat) {
|
|
538
|
+
delete page._repeat;
|
|
539
|
+
return { page, components: [] };
|
|
540
|
+
}
|
|
541
|
+
walk(page);
|
|
542
|
+
return { page, components: Array.from(extracted.values()) };
|
|
543
|
+
}
|
|
470
544
|
// fontWeight name → CSS number (reverse of display names in data files)
|
|
471
545
|
const FONT_WEIGHT_NAME_TO_NUM = {
|
|
472
546
|
thin: '100', hairline: '100',
|
|
@@ -993,9 +1067,16 @@ If no reviewNeeded, completes in 1 pass.`, {
|
|
|
993
1067
|
}],
|
|
994
1068
|
};
|
|
995
1069
|
}
|
|
1070
|
+
// Step 3.5: Extract _repeat components
|
|
1071
|
+
// Components are placed before pages so builder creates them first
|
|
1072
|
+
const finalSpecs = [];
|
|
1073
|
+
for (const spec of specs) {
|
|
1074
|
+
const { page, components } = extractRepeatComponents(spec);
|
|
1075
|
+
finalSpecs.push(...components, page);
|
|
1076
|
+
}
|
|
996
1077
|
// Step 4: Gate check each spec
|
|
997
1078
|
const gateResults = [];
|
|
998
|
-
for (const spec of
|
|
1079
|
+
for (const spec of finalSpecs) {
|
|
999
1080
|
gateResults.push(gateCheck(spec));
|
|
1000
1081
|
}
|
|
1001
1082
|
const allGatesPass = gateResults.every(g => g.pass);
|
|
@@ -1003,18 +1084,18 @@ If no reviewNeeded, completes in 1 pass.`, {
|
|
|
1003
1084
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
1004
1085
|
const trees = [];
|
|
1005
1086
|
const allStats = [];
|
|
1006
|
-
if (
|
|
1007
|
-
writeFileSync(outputPath, JSON.stringify(
|
|
1008
|
-
trees.push(specToTree(
|
|
1009
|
-
allStats.push(countSpecNodes(
|
|
1087
|
+
if (finalSpecs.length === 1) {
|
|
1088
|
+
writeFileSync(outputPath, JSON.stringify(finalSpecs[0], null, 2));
|
|
1089
|
+
trees.push(specToTree(finalSpecs[0]));
|
|
1090
|
+
allStats.push(countSpecNodes(finalSpecs[0]));
|
|
1010
1091
|
}
|
|
1011
1092
|
else {
|
|
1012
1093
|
// Multiple specs → save each with index
|
|
1013
|
-
for (let i = 0; i <
|
|
1094
|
+
for (let i = 0; i < finalSpecs.length; i++) {
|
|
1014
1095
|
const specPath = outputPath.replace(/\.json$/, `_${i}.json`);
|
|
1015
|
-
writeFileSync(specPath, JSON.stringify(
|
|
1016
|
-
trees.push(specToTree(
|
|
1017
|
-
allStats.push(countSpecNodes(
|
|
1096
|
+
writeFileSync(specPath, JSON.stringify(finalSpecs[i], null, 2));
|
|
1097
|
+
trees.push(specToTree(finalSpecs[i]));
|
|
1098
|
+
allStats.push(countSpecNodes(finalSpecs[i]));
|
|
1018
1099
|
}
|
|
1019
1100
|
}
|
|
1020
1101
|
// Collect unresolved
|
|
@@ -57,20 +57,47 @@ function analyzeLayoutWarnings(spec) {
|
|
|
57
57
|
return warnings;
|
|
58
58
|
}
|
|
59
59
|
export function registerUtilityTools(server, bridge) {
|
|
60
|
-
// get_status — Check connection status
|
|
60
|
+
// get_status — Check connection status + bridge version/capabilities
|
|
61
61
|
server.tool('get_status', 'Check if the Figma Bridge plugin is connected and ready', {}, async () => {
|
|
62
62
|
const connected = bridge.isConnected();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
}
|
|
63
|
+
if (!connected) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: JSON.stringify({
|
|
68
|
+
connected: false,
|
|
69
|
+
message: 'Figma Bridge plugin is NOT connected. Please open the "Code to Figma Bridge" plugin in Figma.',
|
|
70
|
+
}, null, 2),
|
|
71
|
+
}],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const bridgeStatus = await bridge.send('bridge-get-status');
|
|
76
|
+
return {
|
|
77
|
+
content: [{
|
|
78
|
+
type: 'text',
|
|
79
|
+
text: JSON.stringify({
|
|
80
|
+
connected: true,
|
|
81
|
+
message: 'Figma Bridge plugin is connected and ready.',
|
|
82
|
+
bridge: {
|
|
83
|
+
version: bridgeStatus.version || 'unknown',
|
|
84
|
+
capabilities: bridgeStatus.capabilities || [],
|
|
85
|
+
},
|
|
86
|
+
}, null, 2),
|
|
87
|
+
}],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return {
|
|
92
|
+
content: [{
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: JSON.stringify({
|
|
95
|
+
connected: true,
|
|
96
|
+
message: 'Figma Bridge plugin is connected but bridge-get-status failed. Bridge plugin dist may need rebuild.',
|
|
97
|
+
}, null, 2),
|
|
98
|
+
}],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
74
101
|
});
|
|
75
102
|
// validate_spec — Validate a JSON spec without creating anything
|
|
76
103
|
server.tool('validate_spec', 'Validate a component/variable/style spec (JSON) without creating anything in Figma. Returns validation errors if any.', {
|
package/package.json
CHANGED