@sangheepark/figma-ds-mcp 0.2.6 → 0.2.7
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 +88 -10
- package/package.json +1 -1
|
@@ -234,9 +234,14 @@ function enrichSpec(traversal, mapping) {
|
|
|
234
234
|
node.style['line-height'] = `${Math.round(lh * 100)}%`;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
// _ds_type 제거 (downstream 미소비 — build 전에 정리)
|
|
238
|
+
if ('_ds_type' in node) {
|
|
239
|
+
delete node['_ds_type'];
|
|
240
|
+
}
|
|
237
241
|
// Step 6: CSS → Figma 정규화
|
|
238
|
-
// 6-pre: overflow
|
|
239
|
-
|
|
242
|
+
// 6-pre: overflow → layout.clip-content 변환 (6-A 필터링 전에 처리)
|
|
243
|
+
const overflow = node.style && node.style['overflow'];
|
|
244
|
+
if (overflow === 'hidden' || overflow === 'scroll' || overflow === 'auto') {
|
|
240
245
|
node.layout = node.layout || {};
|
|
241
246
|
node.layout['clip-content'] = true;
|
|
242
247
|
delete node.style['overflow'];
|
|
@@ -541,6 +546,53 @@ function extractRepeatComponents(spec, defaultCount = 3) {
|
|
|
541
546
|
walk(page);
|
|
542
547
|
return { page, components: Array.from(extracted.values()) };
|
|
543
548
|
}
|
|
549
|
+
function countNodes(node) {
|
|
550
|
+
let count = 1;
|
|
551
|
+
if (node.children) {
|
|
552
|
+
for (const child of node.children)
|
|
553
|
+
count += countNodes(child);
|
|
554
|
+
}
|
|
555
|
+
return count;
|
|
556
|
+
}
|
|
557
|
+
function splitSections(pageSpec, components, outputDir) {
|
|
558
|
+
mkdirSync(join(outputDir, 'components'), { recursive: true });
|
|
559
|
+
mkdirSync(join(outputDir, 'sections'), { recursive: true });
|
|
560
|
+
// Save components
|
|
561
|
+
const compEntries = [];
|
|
562
|
+
for (const comp of components) {
|
|
563
|
+
const name = comp.name || `Component_${compEntries.length}`;
|
|
564
|
+
const relPath = `components/${name}.json`;
|
|
565
|
+
writeFileSync(join(outputDir, relPath), JSON.stringify(comp, null, 2));
|
|
566
|
+
compEntries.push({ name, specPath: relPath, nodeCount: countNodes(comp) });
|
|
567
|
+
}
|
|
568
|
+
// Split page into sections: use _section markers or top-level children
|
|
569
|
+
const sectionEntries = [];
|
|
570
|
+
const rootLayout = { ...(pageSpec.layout || {}) };
|
|
571
|
+
const rootStyle = pageSpec.style ? { ...pageSpec.style } : undefined;
|
|
572
|
+
const pageName = pageSpec.name || 'Page';
|
|
573
|
+
if (pageSpec.children && pageSpec.children.length > 0) {
|
|
574
|
+
for (let i = 0; i < pageSpec.children.length; i++) {
|
|
575
|
+
const child = pageSpec.children[i];
|
|
576
|
+
const name = child.name || `Section_${i}`;
|
|
577
|
+
const relPath = `sections/${name}.json`;
|
|
578
|
+
writeFileSync(join(outputDir, relPath), JSON.stringify(child, null, 2));
|
|
579
|
+
sectionEntries.push({ name, specPath: relPath, nodeCount: countNodes(child), order: i });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
let totalNodes = 1; // root
|
|
583
|
+
for (const c of compEntries)
|
|
584
|
+
totalNodes += c.nodeCount;
|
|
585
|
+
for (const s of sectionEntries)
|
|
586
|
+
totalNodes += s.nodeCount;
|
|
587
|
+
const manifest = {
|
|
588
|
+
page: { name: pageName, rootLayout, rootStyle },
|
|
589
|
+
components: compEntries,
|
|
590
|
+
sections: sectionEntries,
|
|
591
|
+
stats: { totalNodes, components: compEntries.length, sections: sectionEntries.length },
|
|
592
|
+
};
|
|
593
|
+
writeFileSync(join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
594
|
+
return manifest;
|
|
595
|
+
}
|
|
544
596
|
// fontWeight name → CSS number (reverse of display names in data files)
|
|
545
597
|
const FONT_WEIGHT_NAME_TO_NUM = {
|
|
546
598
|
thin: '100', hairline: '100',
|
|
@@ -912,16 +964,28 @@ Run after validate_traversal. If outputPath is provided, spec is saved to file a
|
|
|
912
964
|
}],
|
|
913
965
|
};
|
|
914
966
|
});
|
|
967
|
+
// Helper: resolve traversals from either JSON param or file paths
|
|
968
|
+
function resolveTraversals(traversals, traversalPaths) {
|
|
969
|
+
if (traversalPaths && traversalPaths.length > 0) {
|
|
970
|
+
return traversalPaths.map(p => JSON.parse(readFileSync(p, 'utf-8')));
|
|
971
|
+
}
|
|
972
|
+
if (traversals && traversals.length > 0) {
|
|
973
|
+
return traversals;
|
|
974
|
+
}
|
|
975
|
+
throw new Error('Either traversals or traversalPaths must be provided');
|
|
976
|
+
}
|
|
915
977
|
// generate_mapping — D5
|
|
916
978
|
server.tool('generate_mapping', `Generate mapping.json from traversal JSON + blueprint data files. Zero LLM involvement.
|
|
917
979
|
Reads *-component-sets.json and *-style.json from dataDir. Matches components (3-stage: exact → case-insensitive → word-split), tokens (HEX → variable), text styles (fontSize/fontWeight → style name).
|
|
918
980
|
Saves result to outputPath. Replaces ds-data-lookup sub-agent.`, {
|
|
919
|
-
traversals: z.array(z.record(z.unknown())).describe('Array of traversal JSON root nodes'),
|
|
981
|
+
traversals: z.array(z.record(z.unknown())).optional().describe('Array of traversal JSON root nodes'),
|
|
982
|
+
traversalPaths: z.array(z.string()).optional().describe('File paths to traversal JSON files (alternative to traversals — avoids large param)'),
|
|
920
983
|
dataDir: z.string().describe('Path to blueprint data directory (e.g., .claude/blueprint/data/)'),
|
|
921
984
|
outputPath: z.string().describe('File path to save mapping.json'),
|
|
922
|
-
}, async ({ traversals, dataDir, outputPath }) => {
|
|
985
|
+
}, async ({ traversals, traversalPaths, dataDir, outputPath }) => {
|
|
923
986
|
try {
|
|
924
|
-
const
|
|
987
|
+
const tNodes = resolveTraversals(traversals, traversalPaths);
|
|
988
|
+
const result = generateMapping(tNodes, dataDir);
|
|
925
989
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
926
990
|
writeFileSync(outputPath, JSON.stringify(result, null, 2));
|
|
927
991
|
// Summary for context (not the full mapping)
|
|
@@ -933,7 +997,7 @@ Saves result to outputPath. Replaces ds-data-lookup sub-agent.`, {
|
|
|
933
997
|
allDs.add(node._ds);
|
|
934
998
|
node.children?.forEach(collectDs);
|
|
935
999
|
}
|
|
936
|
-
|
|
1000
|
+
tNodes.forEach(collectDs);
|
|
937
1001
|
const unmatchedDs = [];
|
|
938
1002
|
for (const ds of allDs) {
|
|
939
1003
|
if (!matchedDs.has(ds) && !reviewDs.has(ds))
|
|
@@ -974,13 +1038,14 @@ Saves result to outputPath. Replaces ds-data-lookup sub-agent.`, {
|
|
|
974
1038
|
server.tool('run_pipeline', `Run the full spec pipeline: generate_mapping → validate → enrich → gate → tree in one call.
|
|
975
1039
|
Supports 2-pass pattern: Pass 1 returns reviewNeeded (ambiguous matches). Agent decides, then Pass 2 with resolutions auto-applies decisions.
|
|
976
1040
|
If no reviewNeeded, completes in 1 pass.`, {
|
|
977
|
-
traversals: z.array(z.record(z.unknown())).describe('Array of traversal JSON root nodes'),
|
|
1041
|
+
traversals: z.array(z.record(z.unknown())).optional().describe('Array of traversal JSON root nodes'),
|
|
1042
|
+
traversalPaths: z.array(z.string()).optional().describe('File paths to traversal JSON files (alternative to traversals — avoids large param)'),
|
|
978
1043
|
dataDir: z.string().describe('Path to blueprint data directory (e.g., .claude/blueprint/data/)'),
|
|
979
1044
|
outputPath: z.string().describe('File path to save spec.json (mapping.json saved alongside)'),
|
|
980
1045
|
resolutions: z.record(z.string()).optional().describe('Agent-decided mappings: { dsName: resolvedName }. Pass 2 only.'),
|
|
981
|
-
}, async ({ traversals, dataDir, outputPath, resolutions }) => {
|
|
1046
|
+
}, async ({ traversals, traversalPaths, dataDir, outputPath, resolutions }) => {
|
|
982
1047
|
try {
|
|
983
|
-
let tNodes = traversals;
|
|
1048
|
+
let tNodes = resolveTraversals(traversals, traversalPaths);
|
|
984
1049
|
// Step 0: Apply resolutions — replace _ds in traversal nodes
|
|
985
1050
|
if (resolutions && Object.keys(resolutions).length > 0) {
|
|
986
1051
|
// Deep clone to avoid mutating input
|
|
@@ -1070,8 +1135,12 @@ If no reviewNeeded, completes in 1 pass.`, {
|
|
|
1070
1135
|
// Step 3.5: Extract _repeat components
|
|
1071
1136
|
// Components are placed before pages so builder creates them first
|
|
1072
1137
|
const finalSpecs = [];
|
|
1138
|
+
const allComponents = [];
|
|
1139
|
+
const allPages = [];
|
|
1073
1140
|
for (const spec of specs) {
|
|
1074
1141
|
const { page, components } = extractRepeatComponents(spec);
|
|
1142
|
+
allComponents.push(...components);
|
|
1143
|
+
allPages.push(page);
|
|
1075
1144
|
finalSpecs.push(...components, page);
|
|
1076
1145
|
}
|
|
1077
1146
|
// Step 4: Gate check each spec
|
|
@@ -1084,13 +1153,20 @@ If no reviewNeeded, completes in 1 pass.`, {
|
|
|
1084
1153
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
1085
1154
|
const trees = [];
|
|
1086
1155
|
const allStats = [];
|
|
1156
|
+
// Also produce manifest + section files for incremental build
|
|
1157
|
+
const outputDir = dirname(outputPath);
|
|
1158
|
+
const manifests = [];
|
|
1159
|
+
for (const page of allPages) {
|
|
1160
|
+
const manifest = splitSections(page, allComponents, outputDir);
|
|
1161
|
+
manifests.push(manifest);
|
|
1162
|
+
}
|
|
1163
|
+
// Save full specs (legacy format) alongside manifest
|
|
1087
1164
|
if (finalSpecs.length === 1) {
|
|
1088
1165
|
writeFileSync(outputPath, JSON.stringify(finalSpecs[0], null, 2));
|
|
1089
1166
|
trees.push(specToTree(finalSpecs[0]));
|
|
1090
1167
|
allStats.push(countSpecNodes(finalSpecs[0]));
|
|
1091
1168
|
}
|
|
1092
1169
|
else {
|
|
1093
|
-
// Multiple specs → save each with index
|
|
1094
1170
|
for (let i = 0; i < finalSpecs.length; i++) {
|
|
1095
1171
|
const specPath = outputPath.replace(/\.json$/, `_${i}.json`);
|
|
1096
1172
|
writeFileSync(specPath, JSON.stringify(finalSpecs[i], null, 2));
|
|
@@ -1118,7 +1194,9 @@ If no reviewNeeded, completes in 1 pass.`, {
|
|
|
1118
1194
|
text: JSON.stringify({
|
|
1119
1195
|
success: allGatesPass,
|
|
1120
1196
|
savedTo: outputPath,
|
|
1197
|
+
manifestSavedTo: join(outputDir, 'manifest.json'),
|
|
1121
1198
|
mappingSavedTo: mappingPath,
|
|
1199
|
+
manifest: manifests.length === 1 ? manifests[0] : manifests,
|
|
1122
1200
|
tree: trees.length === 1 ? trees[0] : trees,
|
|
1123
1201
|
stats: allStats.length === 1 ? allStats[0] : allStats,
|
|
1124
1202
|
gateResult: gateResults.length === 1 ? gateResults[0] : gateResults,
|
package/package.json
CHANGED