openfig-cli 0.3.19 → 0.3.21
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/lib/core/fig-deck.mjs +52 -0
- package/lib/rasterizer/svg-builder.mjs +16 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
package/lib/core/fig-deck.mjs
CHANGED
|
@@ -278,12 +278,64 @@ export class FigDeck {
|
|
|
278
278
|
});
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Validate deck integrity before saving. Warns about issues that would
|
|
283
|
+
* cause Figma to fail silently (blank slides, missing symbols, etc.).
|
|
284
|
+
*/
|
|
285
|
+
validate() {
|
|
286
|
+
const warnings = [];
|
|
287
|
+
const symbols = this.getSymbols().filter(s => s.phase !== 'REMOVED');
|
|
288
|
+
|
|
289
|
+
// Check for variant name / variantPropSpecs mismatch
|
|
290
|
+
const byKey = new Map();
|
|
291
|
+
for (const sym of symbols) {
|
|
292
|
+
if (!sym.componentKey) continue;
|
|
293
|
+
if (!byKey.has(sym.componentKey)) byKey.set(sym.componentKey, []);
|
|
294
|
+
byKey.get(sym.componentKey).push(sym);
|
|
295
|
+
}
|
|
296
|
+
for (const [key, variants] of byKey) {
|
|
297
|
+
if (variants.length < 2) continue;
|
|
298
|
+
const specValues = new Set();
|
|
299
|
+
for (const sym of variants) {
|
|
300
|
+
if (sym.variantPropSpecs) {
|
|
301
|
+
for (const spec of sym.variantPropSpecs) specValues.add(spec.value);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const sym of variants) {
|
|
305
|
+
const parts = (sym.name || '').split(', ').map(p => p.split('=')[1]).filter(Boolean);
|
|
306
|
+
for (const val of parts) {
|
|
307
|
+
if (!specValues.has(val)) {
|
|
308
|
+
const id = `${sym.guid.sessionID}:${sym.guid.localID}`;
|
|
309
|
+
warnings.push(`SYMBOL ${id} "${sym.name}": variant value "${val}" not in variantPropSpecs — Figma will show blank slides`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Check for instances referencing missing symbols
|
|
316
|
+
for (const node of this.message.nodeChanges) {
|
|
317
|
+
if (node.type !== 'INSTANCE' || node.phase === 'REMOVED') continue;
|
|
318
|
+
const sid = node.symbolData?.symbolID;
|
|
319
|
+
if (!sid) continue;
|
|
320
|
+
const symId = `${sid.sessionID}:${sid.localID}`;
|
|
321
|
+
const sym = this.getNode(symId);
|
|
322
|
+
if (!sym || sym.type !== 'SYMBOL') {
|
|
323
|
+
const nid = `${node.guid.sessionID}:${node.guid.localID}`;
|
|
324
|
+
warnings.push(`INSTANCE ${nid} "${node.name}": references missing SYMBOL ${symId}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for (const w of warnings) console.warn(`⚠️ ${w}`);
|
|
329
|
+
return warnings;
|
|
330
|
+
}
|
|
331
|
+
|
|
281
332
|
/**
|
|
282
333
|
* Save as a .deck (ZIP archive).
|
|
283
334
|
* @param {string} outPath - Output file path
|
|
284
335
|
* @param {object} opts - { imagesDir, thumbnail, meta }
|
|
285
336
|
*/
|
|
286
337
|
async saveDeck(outPath, opts = {}) {
|
|
338
|
+
this.validate();
|
|
287
339
|
const figBuf = await this.encodeFig();
|
|
288
340
|
const absOut = resolve(outPath);
|
|
289
341
|
|
|
@@ -1102,6 +1102,22 @@ function renderInstance(deck, node) {
|
|
|
1102
1102
|
const symbol = deck.getNode(symNid);
|
|
1103
1103
|
if (!symbol) return renderPlaceholder(deck, node);
|
|
1104
1104
|
|
|
1105
|
+
// Figma-parity: reject instances of symbols with invalid variant specs.
|
|
1106
|
+
// Figma Desktop silently shows blank slides for these. We match that behavior
|
|
1107
|
+
// deliberately so our preview catches invalid decks instead of hiding them.
|
|
1108
|
+
// Root cause: older tooling created SYMBOL variants with names (e.g.
|
|
1109
|
+
// "Size=Wide") that don't exist in the component set's variantPropSpecs.
|
|
1110
|
+
if (symbol.componentKey && symbol.variantPropSpecs) {
|
|
1111
|
+
const specValues = new Set(symbol.variantPropSpecs.map(s => s.value));
|
|
1112
|
+
const nameValues = (symbol.name || '').split(', ').map(p => p.split('=')[1]).filter(Boolean);
|
|
1113
|
+
const invalid = nameValues.some(v => !specValues.has(v));
|
|
1114
|
+
if (invalid) {
|
|
1115
|
+
const nid = `${node.guid.sessionID}:${node.guid.localID}`;
|
|
1116
|
+
console.warn(`⚠️ Skipping INSTANCE ${nid}: symbol ${symNid} "${symbol.name}" has invalid variant specs — Figma would show blank`);
|
|
1117
|
+
return renderPlaceholder(deck, node);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1105
1121
|
// Temporarily apply symbolOverrides so rendered content reflects overrides.
|
|
1106
1122
|
// Override guidPaths may reference library-original IDs (e.g. 100:656) rather
|
|
1107
1123
|
// than local node IDs (e.g. 1:1131). Nodes expose their library ID via the
|
package/manifest.json
CHANGED