figma-metadata-extractor 1.0.10 → 1.0.12
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/index.cjs +155 -35
- package/dist/index.d.ts +1 -1
- package/dist/index.js +155 -35
- package/package.json +67 -67
package/dist/index.cjs
CHANGED
|
@@ -289,15 +289,20 @@ class FigmaService {
|
|
|
289
289
|
*/
|
|
290
290
|
filterValidImages(images) {
|
|
291
291
|
if (!images) return {};
|
|
292
|
-
return Object.fromEntries(
|
|
292
|
+
return Object.fromEntries(
|
|
293
|
+
Object.entries(images).filter(([, value]) => !!value)
|
|
294
|
+
);
|
|
293
295
|
}
|
|
294
296
|
async request(endpoint) {
|
|
295
297
|
try {
|
|
296
298
|
Logger.log(`Calling ${this.baseUrl}${endpoint}`);
|
|
297
299
|
const headers = this.getAuthHeaders();
|
|
298
|
-
return await fetchWithRetry(
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
return await fetchWithRetry(
|
|
301
|
+
`${this.baseUrl}${endpoint}`,
|
|
302
|
+
{
|
|
303
|
+
headers
|
|
304
|
+
}
|
|
305
|
+
);
|
|
301
306
|
} catch (error) {
|
|
302
307
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
303
308
|
throw new Error(
|
|
@@ -372,7 +377,9 @@ class FigmaService {
|
|
|
372
377
|
const sanitizedPath = path.normalize(localPath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
373
378
|
resolvedPath = path.resolve(sanitizedPath);
|
|
374
379
|
if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
|
|
375
|
-
throw new Error(
|
|
380
|
+
throw new Error(
|
|
381
|
+
"Invalid path specified. Directory traversal is not allowed."
|
|
382
|
+
);
|
|
376
383
|
}
|
|
377
384
|
}
|
|
378
385
|
const downloadPromises = [];
|
|
@@ -384,25 +391,45 @@ class FigmaService {
|
|
|
384
391
|
);
|
|
385
392
|
if (imageFills.length > 0) {
|
|
386
393
|
const fillUrls = await this.getImageFillUrls(fileKey);
|
|
387
|
-
const fillDownloads = imageFills.map(
|
|
388
|
-
|
|
389
|
-
|
|
394
|
+
const fillDownloads = imageFills.map(
|
|
395
|
+
({
|
|
396
|
+
imageRef,
|
|
390
397
|
fileName,
|
|
391
|
-
resolvedPath,
|
|
392
|
-
imageUrl,
|
|
393
398
|
needsCropping,
|
|
394
399
|
cropTransform,
|
|
395
|
-
requiresImageDimensions
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
400
|
+
requiresImageDimensions
|
|
401
|
+
}) => {
|
|
402
|
+
const imageUrl = fillUrls[imageRef];
|
|
403
|
+
if (!imageUrl) {
|
|
404
|
+
Logger.log(
|
|
405
|
+
`Skipping image fill with missing URL for imageRef: ${imageRef}`
|
|
406
|
+
);
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return downloadAndProcessImage(
|
|
410
|
+
fileName,
|
|
411
|
+
resolvedPath,
|
|
412
|
+
imageUrl,
|
|
413
|
+
needsCropping,
|
|
414
|
+
cropTransform,
|
|
415
|
+
requiresImageDimensions,
|
|
416
|
+
returnBuffer
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
).filter(
|
|
420
|
+
(promise) => promise !== null
|
|
421
|
+
);
|
|
399
422
|
if (fillDownloads.length > 0) {
|
|
400
423
|
downloadPromises.push(Promise.all(fillDownloads));
|
|
401
424
|
}
|
|
402
425
|
}
|
|
403
426
|
if (renderNodes.length > 0) {
|
|
404
|
-
const pngNodes = renderNodes.filter(
|
|
405
|
-
|
|
427
|
+
const pngNodes = renderNodes.filter(
|
|
428
|
+
(node) => !node.fileName.toLowerCase().endsWith(".svg")
|
|
429
|
+
);
|
|
430
|
+
const svgNodes = renderNodes.filter(
|
|
431
|
+
(node) => node.fileName.toLowerCase().endsWith(".svg")
|
|
432
|
+
);
|
|
406
433
|
if (pngNodes.length > 0) {
|
|
407
434
|
const pngUrls = await this.getNodeRenderUrls(
|
|
408
435
|
fileKey,
|
|
@@ -410,18 +437,36 @@ class FigmaService {
|
|
|
410
437
|
"png",
|
|
411
438
|
{ pngScale }
|
|
412
439
|
);
|
|
413
|
-
const pngDownloads = pngNodes.map(
|
|
414
|
-
|
|
415
|
-
|
|
440
|
+
const pngDownloads = pngNodes.map(
|
|
441
|
+
({
|
|
442
|
+
nodeId,
|
|
416
443
|
fileName,
|
|
417
|
-
resolvedPath,
|
|
418
|
-
imageUrl,
|
|
419
444
|
needsCropping,
|
|
420
445
|
cropTransform,
|
|
421
|
-
requiresImageDimensions
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
446
|
+
requiresImageDimensions
|
|
447
|
+
}) => {
|
|
448
|
+
const imageUrl = pngUrls[nodeId];
|
|
449
|
+
if (!imageUrl) {
|
|
450
|
+
Logger.log(
|
|
451
|
+
`Skipping PNG render with missing URL for nodeId: ${nodeId}`
|
|
452
|
+
);
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
return downloadAndProcessImage(
|
|
456
|
+
fileName,
|
|
457
|
+
resolvedPath,
|
|
458
|
+
imageUrl,
|
|
459
|
+
needsCropping,
|
|
460
|
+
cropTransform,
|
|
461
|
+
requiresImageDimensions,
|
|
462
|
+
returnBuffer
|
|
463
|
+
).then(
|
|
464
|
+
(result) => ({ ...result, nodeId })
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
).filter(
|
|
468
|
+
(promise) => promise !== null
|
|
469
|
+
);
|
|
425
470
|
if (pngDownloads.length > 0) {
|
|
426
471
|
downloadPromises.push(Promise.all(pngDownloads));
|
|
427
472
|
}
|
|
@@ -433,18 +478,36 @@ class FigmaService {
|
|
|
433
478
|
"svg",
|
|
434
479
|
{ svgOptions }
|
|
435
480
|
);
|
|
436
|
-
const svgDownloads = svgNodes.map(
|
|
437
|
-
|
|
438
|
-
|
|
481
|
+
const svgDownloads = svgNodes.map(
|
|
482
|
+
({
|
|
483
|
+
nodeId,
|
|
439
484
|
fileName,
|
|
440
|
-
resolvedPath,
|
|
441
|
-
imageUrl,
|
|
442
485
|
needsCropping,
|
|
443
486
|
cropTransform,
|
|
444
|
-
requiresImageDimensions
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
487
|
+
requiresImageDimensions
|
|
488
|
+
}) => {
|
|
489
|
+
const imageUrl = svgUrls[nodeId];
|
|
490
|
+
if (!imageUrl) {
|
|
491
|
+
Logger.log(
|
|
492
|
+
`Skipping SVG render with missing URL for nodeId: ${nodeId}`
|
|
493
|
+
);
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
return downloadAndProcessImage(
|
|
497
|
+
fileName,
|
|
498
|
+
resolvedPath,
|
|
499
|
+
imageUrl,
|
|
500
|
+
needsCropping,
|
|
501
|
+
cropTransform,
|
|
502
|
+
requiresImageDimensions,
|
|
503
|
+
returnBuffer
|
|
504
|
+
).then(
|
|
505
|
+
(result) => ({ ...result, nodeId })
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
).filter(
|
|
509
|
+
(promise) => promise !== null
|
|
510
|
+
);
|
|
448
511
|
if (svgDownloads.length > 0) {
|
|
449
512
|
downloadPromises.push(Promise.all(svgDownloads));
|
|
450
513
|
}
|
|
@@ -458,7 +521,9 @@ class FigmaService {
|
|
|
458
521
|
*/
|
|
459
522
|
async getRawFile(fileKey, depth) {
|
|
460
523
|
const endpoint = `/files/${fileKey}${depth ? `?depth=${depth}` : ""}`;
|
|
461
|
-
Logger.log(
|
|
524
|
+
Logger.log(
|
|
525
|
+
`Retrieving raw Figma file: ${fileKey} (depth: ${depth ?? "default"})`
|
|
526
|
+
);
|
|
462
527
|
const response = await this.request(endpoint);
|
|
463
528
|
writeLogs("figma-raw.json", response);
|
|
464
529
|
return response;
|
|
@@ -1630,6 +1695,59 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1630
1695
|
throw new Error(`Failed to download frame image: ${error instanceof Error ? error.message : String(error)}`);
|
|
1631
1696
|
}
|
|
1632
1697
|
}
|
|
1698
|
+
function getImageNodeInfo(metadata) {
|
|
1699
|
+
if (!metadata.images || metadata.images.length === 0) {
|
|
1700
|
+
return [];
|
|
1701
|
+
}
|
|
1702
|
+
const imageAssets = findImageAssets(metadata.nodes, metadata.globalVars);
|
|
1703
|
+
return imageAssets.map((asset) => ({
|
|
1704
|
+
nodeId: asset.id,
|
|
1705
|
+
name: asset.name
|
|
1706
|
+
}));
|
|
1707
|
+
}
|
|
1708
|
+
function enrichMetadataWithImages(metadata, imagePaths, options = {}) {
|
|
1709
|
+
const { useRelativePaths = true, localPath } = options;
|
|
1710
|
+
if (!metadata.images || metadata.images.length === 0) {
|
|
1711
|
+
return metadata;
|
|
1712
|
+
}
|
|
1713
|
+
const imageAssets = findImageAssets(metadata.nodes, metadata.globalVars);
|
|
1714
|
+
let downloadResults;
|
|
1715
|
+
if (Array.isArray(imagePaths)) {
|
|
1716
|
+
if (imagePaths.length !== metadata.images.length) {
|
|
1717
|
+
throw new Error(`Number of image paths (${imagePaths.length}) must match number of images (${metadata.images.length})`);
|
|
1718
|
+
}
|
|
1719
|
+
downloadResults = imagePaths.map((filePath, index) => ({
|
|
1720
|
+
filePath,
|
|
1721
|
+
finalDimensions: metadata.images[index].finalDimensions,
|
|
1722
|
+
wasCropped: metadata.images[index].wasCropped,
|
|
1723
|
+
cssVariables: metadata.images[index].cssVariables
|
|
1724
|
+
}));
|
|
1725
|
+
} else {
|
|
1726
|
+
downloadResults = imageAssets.map((asset) => {
|
|
1727
|
+
const filePath = imagePaths[asset.id];
|
|
1728
|
+
if (!filePath) {
|
|
1729
|
+
throw new Error(`No image path provided for node ID: ${asset.id}`);
|
|
1730
|
+
}
|
|
1731
|
+
const imageMetadata = metadata.images.find((img) => img.nodeId === asset.id);
|
|
1732
|
+
return {
|
|
1733
|
+
filePath,
|
|
1734
|
+
finalDimensions: imageMetadata?.finalDimensions || { width: 0, height: 0 },
|
|
1735
|
+
wasCropped: imageMetadata?.wasCropped || false,
|
|
1736
|
+
cssVariables: imageMetadata?.cssVariables
|
|
1737
|
+
};
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
const enrichedNodes = enrichNodesWithImages(
|
|
1741
|
+
metadata.nodes,
|
|
1742
|
+
imageAssets,
|
|
1743
|
+
downloadResults,
|
|
1744
|
+
useRelativePaths
|
|
1745
|
+
);
|
|
1746
|
+
return {
|
|
1747
|
+
...metadata,
|
|
1748
|
+
nodes: enrichedNodes
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1633
1751
|
function sanitizeFileName(name) {
|
|
1634
1752
|
return name.replace(/[^a-z0-9]/gi, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
1635
1753
|
}
|
|
@@ -1706,8 +1824,10 @@ exports.componentExtractor = componentExtractor;
|
|
|
1706
1824
|
exports.contentOnly = contentOnly;
|
|
1707
1825
|
exports.downloadFigmaFrameImage = downloadFigmaFrameImage;
|
|
1708
1826
|
exports.downloadFigmaImages = downloadFigmaImages;
|
|
1827
|
+
exports.enrichMetadataWithImages = enrichMetadataWithImages;
|
|
1709
1828
|
exports.extractFromDesign = extractFromDesign;
|
|
1710
1829
|
exports.getFigmaMetadata = getFigmaMetadata;
|
|
1830
|
+
exports.getImageNodeInfo = getImageNodeInfo;
|
|
1711
1831
|
exports.layoutAndText = layoutAndText;
|
|
1712
1832
|
exports.layoutExtractor = layoutExtractor;
|
|
1713
1833
|
exports.layoutOnly = layoutOnly;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getFigmaMetadata, downloadFigmaImages, downloadFigmaFrameImage, type FigmaMetadataOptions, type FigmaImageOptions, type FigmaFrameImageOptions, type FigmaImageNode, type FigmaMetadataResult, type FigmaImageResult, } from "./lib.js";
|
|
1
|
+
export { getFigmaMetadata, downloadFigmaImages, downloadFigmaFrameImage, enrichMetadataWithImages, getImageNodeInfo, type FigmaMetadataOptions, type FigmaImageOptions, type FigmaFrameImageOptions, type FigmaImageNode, type FigmaMetadataResult, type FigmaImageResult, } from "./lib.js";
|
|
2
2
|
export type { SimplifiedDesign } from "./extractors/types.js";
|
|
3
3
|
export type { ExtractorFn, TraversalContext, TraversalOptions, GlobalVars, StyleTypes, } from "./extractors/index.js";
|
|
4
4
|
export { extractFromDesign, simplifyRawFigmaObject, layoutExtractor, textExtractor, visualsExtractor, componentExtractor, allExtractors, layoutAndText, contentOnly, visualsOnly, layoutOnly, collapseSvgContainers, } from "./extractors/index.js";
|
package/dist/index.js
CHANGED
|
@@ -287,15 +287,20 @@ class FigmaService {
|
|
|
287
287
|
*/
|
|
288
288
|
filterValidImages(images) {
|
|
289
289
|
if (!images) return {};
|
|
290
|
-
return Object.fromEntries(
|
|
290
|
+
return Object.fromEntries(
|
|
291
|
+
Object.entries(images).filter(([, value]) => !!value)
|
|
292
|
+
);
|
|
291
293
|
}
|
|
292
294
|
async request(endpoint) {
|
|
293
295
|
try {
|
|
294
296
|
Logger.log(`Calling ${this.baseUrl}${endpoint}`);
|
|
295
297
|
const headers = this.getAuthHeaders();
|
|
296
|
-
return await fetchWithRetry(
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
return await fetchWithRetry(
|
|
299
|
+
`${this.baseUrl}${endpoint}`,
|
|
300
|
+
{
|
|
301
|
+
headers
|
|
302
|
+
}
|
|
303
|
+
);
|
|
299
304
|
} catch (error) {
|
|
300
305
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
301
306
|
throw new Error(
|
|
@@ -370,7 +375,9 @@ class FigmaService {
|
|
|
370
375
|
const sanitizedPath = path.normalize(localPath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
371
376
|
resolvedPath = path.resolve(sanitizedPath);
|
|
372
377
|
if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
|
|
373
|
-
throw new Error(
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Invalid path specified. Directory traversal is not allowed."
|
|
380
|
+
);
|
|
374
381
|
}
|
|
375
382
|
}
|
|
376
383
|
const downloadPromises = [];
|
|
@@ -382,25 +389,45 @@ class FigmaService {
|
|
|
382
389
|
);
|
|
383
390
|
if (imageFills.length > 0) {
|
|
384
391
|
const fillUrls = await this.getImageFillUrls(fileKey);
|
|
385
|
-
const fillDownloads = imageFills.map(
|
|
386
|
-
|
|
387
|
-
|
|
392
|
+
const fillDownloads = imageFills.map(
|
|
393
|
+
({
|
|
394
|
+
imageRef,
|
|
388
395
|
fileName,
|
|
389
|
-
resolvedPath,
|
|
390
|
-
imageUrl,
|
|
391
396
|
needsCropping,
|
|
392
397
|
cropTransform,
|
|
393
|
-
requiresImageDimensions
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
398
|
+
requiresImageDimensions
|
|
399
|
+
}) => {
|
|
400
|
+
const imageUrl = fillUrls[imageRef];
|
|
401
|
+
if (!imageUrl) {
|
|
402
|
+
Logger.log(
|
|
403
|
+
`Skipping image fill with missing URL for imageRef: ${imageRef}`
|
|
404
|
+
);
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
return downloadAndProcessImage(
|
|
408
|
+
fileName,
|
|
409
|
+
resolvedPath,
|
|
410
|
+
imageUrl,
|
|
411
|
+
needsCropping,
|
|
412
|
+
cropTransform,
|
|
413
|
+
requiresImageDimensions,
|
|
414
|
+
returnBuffer
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
).filter(
|
|
418
|
+
(promise) => promise !== null
|
|
419
|
+
);
|
|
397
420
|
if (fillDownloads.length > 0) {
|
|
398
421
|
downloadPromises.push(Promise.all(fillDownloads));
|
|
399
422
|
}
|
|
400
423
|
}
|
|
401
424
|
if (renderNodes.length > 0) {
|
|
402
|
-
const pngNodes = renderNodes.filter(
|
|
403
|
-
|
|
425
|
+
const pngNodes = renderNodes.filter(
|
|
426
|
+
(node) => !node.fileName.toLowerCase().endsWith(".svg")
|
|
427
|
+
);
|
|
428
|
+
const svgNodes = renderNodes.filter(
|
|
429
|
+
(node) => node.fileName.toLowerCase().endsWith(".svg")
|
|
430
|
+
);
|
|
404
431
|
if (pngNodes.length > 0) {
|
|
405
432
|
const pngUrls = await this.getNodeRenderUrls(
|
|
406
433
|
fileKey,
|
|
@@ -408,18 +435,36 @@ class FigmaService {
|
|
|
408
435
|
"png",
|
|
409
436
|
{ pngScale }
|
|
410
437
|
);
|
|
411
|
-
const pngDownloads = pngNodes.map(
|
|
412
|
-
|
|
413
|
-
|
|
438
|
+
const pngDownloads = pngNodes.map(
|
|
439
|
+
({
|
|
440
|
+
nodeId,
|
|
414
441
|
fileName,
|
|
415
|
-
resolvedPath,
|
|
416
|
-
imageUrl,
|
|
417
442
|
needsCropping,
|
|
418
443
|
cropTransform,
|
|
419
|
-
requiresImageDimensions
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
444
|
+
requiresImageDimensions
|
|
445
|
+
}) => {
|
|
446
|
+
const imageUrl = pngUrls[nodeId];
|
|
447
|
+
if (!imageUrl) {
|
|
448
|
+
Logger.log(
|
|
449
|
+
`Skipping PNG render with missing URL for nodeId: ${nodeId}`
|
|
450
|
+
);
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
return downloadAndProcessImage(
|
|
454
|
+
fileName,
|
|
455
|
+
resolvedPath,
|
|
456
|
+
imageUrl,
|
|
457
|
+
needsCropping,
|
|
458
|
+
cropTransform,
|
|
459
|
+
requiresImageDimensions,
|
|
460
|
+
returnBuffer
|
|
461
|
+
).then(
|
|
462
|
+
(result) => ({ ...result, nodeId })
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
).filter(
|
|
466
|
+
(promise) => promise !== null
|
|
467
|
+
);
|
|
423
468
|
if (pngDownloads.length > 0) {
|
|
424
469
|
downloadPromises.push(Promise.all(pngDownloads));
|
|
425
470
|
}
|
|
@@ -431,18 +476,36 @@ class FigmaService {
|
|
|
431
476
|
"svg",
|
|
432
477
|
{ svgOptions }
|
|
433
478
|
);
|
|
434
|
-
const svgDownloads = svgNodes.map(
|
|
435
|
-
|
|
436
|
-
|
|
479
|
+
const svgDownloads = svgNodes.map(
|
|
480
|
+
({
|
|
481
|
+
nodeId,
|
|
437
482
|
fileName,
|
|
438
|
-
resolvedPath,
|
|
439
|
-
imageUrl,
|
|
440
483
|
needsCropping,
|
|
441
484
|
cropTransform,
|
|
442
|
-
requiresImageDimensions
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
485
|
+
requiresImageDimensions
|
|
486
|
+
}) => {
|
|
487
|
+
const imageUrl = svgUrls[nodeId];
|
|
488
|
+
if (!imageUrl) {
|
|
489
|
+
Logger.log(
|
|
490
|
+
`Skipping SVG render with missing URL for nodeId: ${nodeId}`
|
|
491
|
+
);
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
return downloadAndProcessImage(
|
|
495
|
+
fileName,
|
|
496
|
+
resolvedPath,
|
|
497
|
+
imageUrl,
|
|
498
|
+
needsCropping,
|
|
499
|
+
cropTransform,
|
|
500
|
+
requiresImageDimensions,
|
|
501
|
+
returnBuffer
|
|
502
|
+
).then(
|
|
503
|
+
(result) => ({ ...result, nodeId })
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
).filter(
|
|
507
|
+
(promise) => promise !== null
|
|
508
|
+
);
|
|
446
509
|
if (svgDownloads.length > 0) {
|
|
447
510
|
downloadPromises.push(Promise.all(svgDownloads));
|
|
448
511
|
}
|
|
@@ -456,7 +519,9 @@ class FigmaService {
|
|
|
456
519
|
*/
|
|
457
520
|
async getRawFile(fileKey, depth) {
|
|
458
521
|
const endpoint = `/files/${fileKey}${depth ? `?depth=${depth}` : ""}`;
|
|
459
|
-
Logger.log(
|
|
522
|
+
Logger.log(
|
|
523
|
+
`Retrieving raw Figma file: ${fileKey} (depth: ${depth ?? "default"})`
|
|
524
|
+
);
|
|
460
525
|
const response = await this.request(endpoint);
|
|
461
526
|
writeLogs("figma-raw.json", response);
|
|
462
527
|
return response;
|
|
@@ -1628,6 +1693,59 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1628
1693
|
throw new Error(`Failed to download frame image: ${error instanceof Error ? error.message : String(error)}`);
|
|
1629
1694
|
}
|
|
1630
1695
|
}
|
|
1696
|
+
function getImageNodeInfo(metadata) {
|
|
1697
|
+
if (!metadata.images || metadata.images.length === 0) {
|
|
1698
|
+
return [];
|
|
1699
|
+
}
|
|
1700
|
+
const imageAssets = findImageAssets(metadata.nodes, metadata.globalVars);
|
|
1701
|
+
return imageAssets.map((asset) => ({
|
|
1702
|
+
nodeId: asset.id,
|
|
1703
|
+
name: asset.name
|
|
1704
|
+
}));
|
|
1705
|
+
}
|
|
1706
|
+
function enrichMetadataWithImages(metadata, imagePaths, options = {}) {
|
|
1707
|
+
const { useRelativePaths = true, localPath } = options;
|
|
1708
|
+
if (!metadata.images || metadata.images.length === 0) {
|
|
1709
|
+
return metadata;
|
|
1710
|
+
}
|
|
1711
|
+
const imageAssets = findImageAssets(metadata.nodes, metadata.globalVars);
|
|
1712
|
+
let downloadResults;
|
|
1713
|
+
if (Array.isArray(imagePaths)) {
|
|
1714
|
+
if (imagePaths.length !== metadata.images.length) {
|
|
1715
|
+
throw new Error(`Number of image paths (${imagePaths.length}) must match number of images (${metadata.images.length})`);
|
|
1716
|
+
}
|
|
1717
|
+
downloadResults = imagePaths.map((filePath, index) => ({
|
|
1718
|
+
filePath,
|
|
1719
|
+
finalDimensions: metadata.images[index].finalDimensions,
|
|
1720
|
+
wasCropped: metadata.images[index].wasCropped,
|
|
1721
|
+
cssVariables: metadata.images[index].cssVariables
|
|
1722
|
+
}));
|
|
1723
|
+
} else {
|
|
1724
|
+
downloadResults = imageAssets.map((asset) => {
|
|
1725
|
+
const filePath = imagePaths[asset.id];
|
|
1726
|
+
if (!filePath) {
|
|
1727
|
+
throw new Error(`No image path provided for node ID: ${asset.id}`);
|
|
1728
|
+
}
|
|
1729
|
+
const imageMetadata = metadata.images.find((img) => img.nodeId === asset.id);
|
|
1730
|
+
return {
|
|
1731
|
+
filePath,
|
|
1732
|
+
finalDimensions: imageMetadata?.finalDimensions || { width: 0, height: 0 },
|
|
1733
|
+
wasCropped: imageMetadata?.wasCropped || false,
|
|
1734
|
+
cssVariables: imageMetadata?.cssVariables
|
|
1735
|
+
};
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
const enrichedNodes = enrichNodesWithImages(
|
|
1739
|
+
metadata.nodes,
|
|
1740
|
+
imageAssets,
|
|
1741
|
+
downloadResults,
|
|
1742
|
+
useRelativePaths
|
|
1743
|
+
);
|
|
1744
|
+
return {
|
|
1745
|
+
...metadata,
|
|
1746
|
+
nodes: enrichedNodes
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1631
1749
|
function sanitizeFileName(name) {
|
|
1632
1750
|
return name.replace(/[^a-z0-9]/gi, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
1633
1751
|
}
|
|
@@ -1705,8 +1823,10 @@ export {
|
|
|
1705
1823
|
contentOnly,
|
|
1706
1824
|
downloadFigmaFrameImage,
|
|
1707
1825
|
downloadFigmaImages,
|
|
1826
|
+
enrichMetadataWithImages,
|
|
1708
1827
|
extractFromDesign,
|
|
1709
1828
|
getFigmaMetadata,
|
|
1829
|
+
getImageNodeInfo,
|
|
1710
1830
|
layoutAndText,
|
|
1711
1831
|
layoutExtractor,
|
|
1712
1832
|
layoutOnly,
|
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"README.md",
|
|
19
|
-
"example.js"
|
|
20
|
-
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "vite build && tsc --project tsconfig.declarations.json",
|
|
23
|
-
"typecheck": "tsc --noEmit",
|
|
24
|
-
"test": "jest",
|
|
25
|
-
"dev": "vite build --watch",
|
|
26
|
-
"lint": "eslint .",
|
|
27
|
-
"format": "prettier --write \"src/**/*.ts\"",
|
|
28
|
-
"prepack": "npm run build"
|
|
29
|
-
},
|
|
30
|
-
"engines": {
|
|
31
|
-
"node": ">=18.0.0"
|
|
32
|
-
},
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/mikmokpok/figma-context-extractor.git"
|
|
36
|
-
},
|
|
37
|
-
"keywords": [
|
|
38
|
-
"figma",
|
|
39
|
-
"design",
|
|
40
|
-
"metadata",
|
|
41
|
-
"extractor",
|
|
42
|
-
"typescript",
|
|
43
|
-
"api",
|
|
44
|
-
"images"
|
|
45
|
-
],
|
|
46
|
-
"author": "",
|
|
47
|
-
"license": "MIT",
|
|
48
|
-
"dependencies": {
|
|
49
|
-
"@figma/rest-api-spec": "^0.33.0",
|
|
50
|
-
"js-yaml": "^4.1.0",
|
|
51
|
-
"remeda": "^2.20.1",
|
|
52
|
-
"sharp": "^0.34.3",
|
|
53
|
-
"zod": "^3.24.2"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/jest": "^29.5.14",
|
|
57
|
-
"@types/js-yaml": "^4.0.9",
|
|
58
|
-
"@types/node": "^20.17.0",
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^8.24.0",
|
|
60
|
-
"@typescript-eslint/parser": "^8.24.0",
|
|
61
|
-
"eslint": "^9.20.1",
|
|
62
|
-
"eslint-config-prettier": "^10.0.1",
|
|
63
|
-
"jest": "^29.7.0",
|
|
64
|
-
"prettier": "^3.5.0",
|
|
65
|
-
"ts-jest": "^29.2.5",
|
|
66
|
-
"typescript": "^5.7.3",
|
|
67
|
-
"vite": "^6.4.1"
|
|
2
|
+
"name": "figma-metadata-extractor",
|
|
3
|
+
"version": "1.0.12",
|
|
4
|
+
"description": "Extract metadata and download images from Figma files. A standalone library for accessing Figma design data and downloading frame images programmatically.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
68
14
|
}
|
|
69
|
-
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"example.js"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "vite build && tsc --project tsconfig.declarations.json",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "jest",
|
|
25
|
+
"dev": "vite build --watch",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
28
|
+
"prepack": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/mikmokpok/figma-context-extractor.git"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"figma",
|
|
39
|
+
"design",
|
|
40
|
+
"metadata",
|
|
41
|
+
"extractor",
|
|
42
|
+
"typescript",
|
|
43
|
+
"api",
|
|
44
|
+
"images"
|
|
45
|
+
],
|
|
46
|
+
"author": "",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@figma/rest-api-spec": "^0.33.0",
|
|
50
|
+
"js-yaml": "^4.1.0",
|
|
51
|
+
"remeda": "^2.20.1",
|
|
52
|
+
"sharp": "^0.34.3",
|
|
53
|
+
"zod": "^3.24.2"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/jest": "^29.5.14",
|
|
57
|
+
"@types/js-yaml": "^4.0.9",
|
|
58
|
+
"@types/node": "^20.17.0",
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^8.24.0",
|
|
60
|
+
"@typescript-eslint/parser": "^8.24.0",
|
|
61
|
+
"eslint": "^9.20.1",
|
|
62
|
+
"eslint-config-prettier": "^10.0.1",
|
|
63
|
+
"jest": "^29.7.0",
|
|
64
|
+
"prettier": "^3.5.0",
|
|
65
|
+
"ts-jest": "^29.2.5",
|
|
66
|
+
"typescript": "^5.7.3",
|
|
67
|
+
"vite": "^6.4.1"
|
|
68
|
+
}
|
|
69
|
+
}
|