figma-local 1.4.0 → 1.6.0
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/package.json +1 -1
- package/src/index.js +146 -14
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -8537,6 +8537,7 @@ program
|
|
|
8537
8537
|
.option('--tokens', 'Show only the design tokens (colors, spacing, etc.) used in the frame')
|
|
8538
8538
|
.option('--selection', 'Read whatever is currently selected in Figma')
|
|
8539
8539
|
.option('--link <url>', 'Read a specific node from a Figma selection link (e.g. copied via "Copy link to selection")')
|
|
8540
|
+
.option('--page', 'Read the entire current page — all frames with full structure, tokens, and specs')
|
|
8540
8541
|
.option('--stage <n>', 'Run a specific stage: 1 (list frames), 2 (structure), 3 (tokens)')
|
|
8541
8542
|
.addHelpText('after', `
|
|
8542
8543
|
Examples:
|
|
@@ -8545,6 +8546,7 @@ Examples:
|
|
|
8545
8546
|
fig read "Login Screen" --tokens Show only the design tokens used by Login Screen
|
|
8546
8547
|
fig read --selection Read the node you currently have selected in Figma
|
|
8547
8548
|
fig read --link "https://..." Read a node from a Figma selection link
|
|
8549
|
+
fig read --page Read the entire page with all frames, structure, and tokens
|
|
8548
8550
|
fig read --json Output raw JSON (useful for piping to other tools)
|
|
8549
8551
|
`)
|
|
8550
8552
|
.action(async (frameName, options) => {
|
|
@@ -8560,14 +8562,14 @@ Examples:
|
|
|
8560
8562
|
if (!sel || sel.length === 0) return { error: 'Nothing selected in Figma. Select a frame or layer first.' };
|
|
8561
8563
|
const node = sel[0];
|
|
8562
8564
|
function walk(n, depth) {
|
|
8563
|
-
if (depth >
|
|
8565
|
+
if (depth > 20) return { type: n.type, name: n.name };
|
|
8564
8566
|
const obj = { type: n.type, name: n.name };
|
|
8565
8567
|
if (n.width) obj.w = Math.round(n.width);
|
|
8566
8568
|
if (n.height) obj.h = Math.round(n.height);
|
|
8567
|
-
if (n.type === 'TEXT') obj.text = n.characters
|
|
8569
|
+
if (n.type === 'TEXT') obj.text = n.characters;
|
|
8568
8570
|
if (n.fills && n.fills.length) obj.fills = n.fills.map(f => f.type === 'SOLID' ? { r: Math.round(f.color.r*255), g: Math.round(f.color.g*255), b: Math.round(f.color.b*255) } : { type: f.type });
|
|
8569
8571
|
if (n.cornerRadius) obj.radius = n.cornerRadius;
|
|
8570
|
-
if (n.children) obj.children = n.children.
|
|
8572
|
+
if (n.children) obj.children = n.children.map(c => walk(c, depth + 1));
|
|
8571
8573
|
return obj;
|
|
8572
8574
|
}
|
|
8573
8575
|
return { selected: sel.length, node: walk(node, 0) };
|
|
@@ -8598,14 +8600,14 @@ Examples:
|
|
|
8598
8600
|
const node = figma.getNodeById('${nodeId}');
|
|
8599
8601
|
if (!node) return { error: 'Node ${nodeId} not found on the current page.' };
|
|
8600
8602
|
function walk(n, depth) {
|
|
8601
|
-
if (depth >
|
|
8603
|
+
if (depth > 20) return { type: n.type, name: n.name };
|
|
8602
8604
|
const obj = { type: n.type, name: n.name };
|
|
8603
8605
|
if (n.width) obj.w = Math.round(n.width);
|
|
8604
8606
|
if (n.height) obj.h = Math.round(n.height);
|
|
8605
|
-
if (n.type === 'TEXT') obj.text = n.characters
|
|
8607
|
+
if (n.type === 'TEXT') obj.text = n.characters;
|
|
8606
8608
|
if (n.fills && n.fills.length) obj.fills = n.fills.map(f => f.type === 'SOLID' ? { r: Math.round(f.color.r*255), g: Math.round(f.color.g*255), b: Math.round(f.color.b*255) } : { type: f.type });
|
|
8607
8609
|
if (n.cornerRadius) obj.radius = n.cornerRadius;
|
|
8608
|
-
if (n.children) obj.children = n.children.
|
|
8610
|
+
if (n.children) obj.children = n.children.map(c => walk(c, depth + 1));
|
|
8609
8611
|
return obj;
|
|
8610
8612
|
}
|
|
8611
8613
|
return { nodeId: '${nodeId}', node: walk(node, 0) };
|
|
@@ -8620,6 +8622,136 @@ Examples:
|
|
|
8620
8622
|
return;
|
|
8621
8623
|
}
|
|
8622
8624
|
|
|
8625
|
+
// --page: read the entire current page with all frames
|
|
8626
|
+
if (options.page) {
|
|
8627
|
+
spinner.text = 'Reading entire page...';
|
|
8628
|
+
const pageCode = `(function() {
|
|
8629
|
+
const page = figma.currentPage;
|
|
8630
|
+
function walk(n, depth) {
|
|
8631
|
+
if (depth > 20) return { type: n.type, name: n.name };
|
|
8632
|
+
var obj = { type: n.type, name: n.name, id: n.id };
|
|
8633
|
+
if (n.width) obj.w = Math.round(n.width);
|
|
8634
|
+
if (n.height) obj.h = Math.round(n.height);
|
|
8635
|
+
if (n.x !== undefined) obj.x = Math.round(n.x);
|
|
8636
|
+
if (n.y !== undefined) obj.y = Math.round(n.y);
|
|
8637
|
+
if (n.type === 'TEXT') obj.text = n.characters;
|
|
8638
|
+
if (n.fills && n.fills.length) {
|
|
8639
|
+
obj.fills = n.fills.map(function(f) {
|
|
8640
|
+
if (f.type === 'SOLID') return { type: 'SOLID', r: Math.round(f.color.r*255), g: Math.round(f.color.g*255), b: Math.round(f.color.b*255), a: f.opacity !== undefined ? f.opacity : 1 };
|
|
8641
|
+
return { type: f.type };
|
|
8642
|
+
});
|
|
8643
|
+
}
|
|
8644
|
+
if (n.strokes && n.strokes.length) {
|
|
8645
|
+
obj.strokes = n.strokes.map(function(s) {
|
|
8646
|
+
if (s.type === 'SOLID') return { type: 'SOLID', r: Math.round(s.color.r*255), g: Math.round(s.color.g*255), b: Math.round(s.color.b*255) };
|
|
8647
|
+
return { type: s.type };
|
|
8648
|
+
});
|
|
8649
|
+
if (n.strokeWeight) obj.strokeWeight = n.strokeWeight;
|
|
8650
|
+
}
|
|
8651
|
+
if (n.cornerRadius) obj.radius = n.cornerRadius;
|
|
8652
|
+
if (n.opacity !== undefined && n.opacity < 1) obj.opacity = n.opacity;
|
|
8653
|
+
if (n.layoutMode) {
|
|
8654
|
+
obj.layout = { mode: n.layoutMode };
|
|
8655
|
+
if (n.itemSpacing) obj.layout.gap = n.itemSpacing;
|
|
8656
|
+
if (n.paddingTop || n.paddingRight || n.paddingBottom || n.paddingLeft) {
|
|
8657
|
+
obj.layout.padding = { top: n.paddingTop || 0, right: n.paddingRight || 0, bottom: n.paddingBottom || 0, left: n.paddingLeft || 0 };
|
|
8658
|
+
}
|
|
8659
|
+
if (n.primaryAxisAlignItems) obj.layout.mainAlign = n.primaryAxisAlignItems;
|
|
8660
|
+
if (n.counterAxisAlignItems) obj.layout.crossAlign = n.counterAxisAlignItems;
|
|
8661
|
+
}
|
|
8662
|
+
if (n.type === 'TEXT') {
|
|
8663
|
+
obj.typography = {};
|
|
8664
|
+
try { obj.typography.family = n.fontName.family; } catch(e) {}
|
|
8665
|
+
try { obj.typography.style = n.fontName.style; } catch(e) {}
|
|
8666
|
+
try { obj.typography.size = n.fontSize; } catch(e) {}
|
|
8667
|
+
try { obj.typography.weight = n.fontWeight; } catch(e) {}
|
|
8668
|
+
try {
|
|
8669
|
+
if (n.lineHeight && n.lineHeight.value) obj.typography.lineHeight = n.lineHeight.unit === 'PERCENT' ? n.lineHeight.value + '%' : n.lineHeight.value;
|
|
8670
|
+
} catch(e) {}
|
|
8671
|
+
try { if (n.letterSpacing && n.letterSpacing.value) obj.typography.letterSpacing = n.letterSpacing.value; } catch(e) {}
|
|
8672
|
+
}
|
|
8673
|
+
if (n.effects && n.effects.length) {
|
|
8674
|
+
obj.effects = n.effects.map(function(e) {
|
|
8675
|
+
var eff = { type: e.type };
|
|
8676
|
+
if (e.radius) eff.radius = e.radius;
|
|
8677
|
+
if (e.offset) eff.offset = { x: e.offset.x, y: e.offset.y };
|
|
8678
|
+
if (e.color) eff.color = { r: Math.round(e.color.r*255), g: Math.round(e.color.g*255), b: Math.round(e.color.b*255), a: e.color.a };
|
|
8679
|
+
return eff;
|
|
8680
|
+
});
|
|
8681
|
+
}
|
|
8682
|
+
// Variable bindings
|
|
8683
|
+
try {
|
|
8684
|
+
var vars = {};
|
|
8685
|
+
var bindings = n.boundVariables;
|
|
8686
|
+
if (bindings) {
|
|
8687
|
+
Object.keys(bindings).forEach(function(prop) {
|
|
8688
|
+
try {
|
|
8689
|
+
var binding = bindings[prop];
|
|
8690
|
+
if (Array.isArray(binding)) binding = binding[0];
|
|
8691
|
+
if (binding && binding.id) {
|
|
8692
|
+
var v = figma.variables.getVariableById(binding.id);
|
|
8693
|
+
if (v) {
|
|
8694
|
+
var info = { name: v.name };
|
|
8695
|
+
try {
|
|
8696
|
+
var collection = figma.variables.getVariableCollectionById(v.variableCollectionId);
|
|
8697
|
+
if (collection && collection.modes && collection.modes.length > 0) {
|
|
8698
|
+
info.values = {};
|
|
8699
|
+
collection.modes.forEach(function(mode) {
|
|
8700
|
+
try {
|
|
8701
|
+
var mVal = v.valuesByMode[mode.modeId];
|
|
8702
|
+
if (mVal && mVal.type === 'VARIABLE_ALIAS') {
|
|
8703
|
+
try {
|
|
8704
|
+
var aliased = figma.variables.getVariableById(mVal.id);
|
|
8705
|
+
info.values[mode.name] = aliased ? aliased.name : 'alias';
|
|
8706
|
+
} catch(e3) { info.values[mode.name] = 'alias'; }
|
|
8707
|
+
} else {
|
|
8708
|
+
info.values[mode.name] = mVal;
|
|
8709
|
+
}
|
|
8710
|
+
} catch(e4) {}
|
|
8711
|
+
});
|
|
8712
|
+
}
|
|
8713
|
+
} catch(e5) {}
|
|
8714
|
+
vars[prop] = info;
|
|
8715
|
+
}
|
|
8716
|
+
}
|
|
8717
|
+
} catch(e) {}
|
|
8718
|
+
});
|
|
8719
|
+
}
|
|
8720
|
+
if (Object.keys(vars).length > 0) obj.variables = vars;
|
|
8721
|
+
} catch(e) {}
|
|
8722
|
+
// Component info
|
|
8723
|
+
if (n.type === 'COMPONENT') obj.isComponent = true;
|
|
8724
|
+
if (n.type === 'INSTANCE') {
|
|
8725
|
+
obj.isInstance = true;
|
|
8726
|
+
try { obj.componentName = n.mainComponent ? n.mainComponent.name : undefined; } catch(e) {}
|
|
8727
|
+
}
|
|
8728
|
+
if (n.children) obj.children = n.children.map(function(c) { return walk(c, depth + 1); });
|
|
8729
|
+
return obj;
|
|
8730
|
+
}
|
|
8731
|
+
var frames = [];
|
|
8732
|
+
for (var i = 0; i < page.children.length; i++) {
|
|
8733
|
+
frames.push(walk(page.children[i], 0));
|
|
8734
|
+
}
|
|
8735
|
+
return { page: page.name, frameCount: frames.length, frames: frames };
|
|
8736
|
+
})()`;
|
|
8737
|
+
const result = await daemonExec('eval', { code: pageCode });
|
|
8738
|
+
if (result.error) {
|
|
8739
|
+
spinner.fail(result.error);
|
|
8740
|
+
process.exit(1);
|
|
8741
|
+
}
|
|
8742
|
+
spinner.succeed(`Read page "${result.page}" — ${result.frameCount} top-level frames`);
|
|
8743
|
+
if (options.json) {
|
|
8744
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8745
|
+
} else {
|
|
8746
|
+
// Pretty print each frame
|
|
8747
|
+
for (const frame of result.frames) {
|
|
8748
|
+
console.log(chalk.cyan(`\n━━━ ${frame.name} [${frame.type}] ${frame.w || ''}x${frame.h || ''} ━━━`));
|
|
8749
|
+
console.log(formatSelectionResult({ node: frame }));
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
return;
|
|
8753
|
+
}
|
|
8754
|
+
|
|
8623
8755
|
// Stage 1: metadata — always run, cheapest call
|
|
8624
8756
|
spinner.text = 'Scanning canvas...';
|
|
8625
8757
|
const metadata = await daemonExec('eval', { code: STAGE1_METADATA });
|
|
@@ -8999,7 +9131,7 @@ const INSPECT_CODE = `(function() {
|
|
|
8999
9131
|
// Children summary
|
|
9000
9132
|
if (node.children && node.children.length > 0) {
|
|
9001
9133
|
spec.childCount = node.children.length;
|
|
9002
|
-
spec.children = node.children.
|
|
9134
|
+
spec.children = node.children.map(function(c) {
|
|
9003
9135
|
return { name: c.name, type: c.type, w: c.width ? Math.round(c.width) : undefined, h: c.height ? Math.round(c.height) : undefined };
|
|
9004
9136
|
});
|
|
9005
9137
|
}
|
|
@@ -9007,9 +9139,9 @@ const INSPECT_CODE = `(function() {
|
|
|
9007
9139
|
return spec;
|
|
9008
9140
|
}
|
|
9009
9141
|
|
|
9010
|
-
// Inspect all selected nodes
|
|
9142
|
+
// Inspect all selected nodes
|
|
9011
9143
|
var results = [];
|
|
9012
|
-
var count =
|
|
9144
|
+
var count = sel.length;
|
|
9013
9145
|
for (var i = 0; i < count; i++) {
|
|
9014
9146
|
results.push(inspectNode(sel[i]));
|
|
9015
9147
|
}
|
|
@@ -9083,7 +9215,7 @@ Examples:
|
|
|
9083
9215
|
${options.node ? `node = figma.getNodeById('${options.node}');` : ''}
|
|
9084
9216
|
${options.link ? `node = figma.getNodeById('${parseNodeIdFromLink(options.link)}');` : ''}
|
|
9085
9217
|
if (!node || !node.children) return [];
|
|
9086
|
-
return node.children.
|
|
9218
|
+
return node.children.map(function(c) { return c.id; });
|
|
9087
9219
|
})()`;
|
|
9088
9220
|
const childIds = await daemonExec('eval', { code: childIdsCode });
|
|
9089
9221
|
if (Array.isArray(childIds)) {
|
|
@@ -9199,7 +9331,7 @@ function formatInspectSpec(spec) {
|
|
|
9199
9331
|
if (t.textAlign) lines.push(` Text align: ${t.textAlign}`);
|
|
9200
9332
|
if (t.textDecoration) lines.push(` Decoration: ${t.textDecoration}`);
|
|
9201
9333
|
if (t.textTransform) lines.push(` Transform: ${t.textTransform}`);
|
|
9202
|
-
if (t.content) lines.push(` Content: "${t.content
|
|
9334
|
+
if (t.content) lines.push(` Content: "${t.content}"`);
|
|
9203
9335
|
}
|
|
9204
9336
|
|
|
9205
9337
|
// Effects
|
|
@@ -9530,7 +9662,7 @@ function formatCSS(node) {
|
|
|
9530
9662
|
}
|
|
9531
9663
|
lines.push('}');
|
|
9532
9664
|
if (node.text) {
|
|
9533
|
-
lines.push(chalk.gray(`/* Content: "${node.text
|
|
9665
|
+
lines.push(chalk.gray(`/* Content: "${node.text}" */`));
|
|
9534
9666
|
}
|
|
9535
9667
|
return lines.join('\n');
|
|
9536
9668
|
}
|
|
@@ -9610,7 +9742,7 @@ function formatTailwind(node) {
|
|
|
9610
9742
|
const lines = [];
|
|
9611
9743
|
lines.push(chalk.gray(`{/* ${node.name} */}`));
|
|
9612
9744
|
lines.push(`className="${classes.join(' ')}"`);
|
|
9613
|
-
if (node.text) lines.push(chalk.gray(`{/* "${node.text
|
|
9745
|
+
if (node.text) lines.push(chalk.gray(`{/* "${node.text}" */}`));
|
|
9614
9746
|
return lines.join('\n');
|
|
9615
9747
|
}
|
|
9616
9748
|
|
|
@@ -9889,7 +10021,7 @@ const DOCUMENT_CODE = `(function() {
|
|
|
9889
10021
|
}
|
|
9890
10022
|
|
|
9891
10023
|
function extractNode(node, depth) {
|
|
9892
|
-
if (depth >
|
|
10024
|
+
if (depth > 30) return null;
|
|
9893
10025
|
var n = { name: node.name, type: node.type };
|
|
9894
10026
|
|
|
9895
10027
|
// Dimensions
|