figma-metadata-extractor 1.0.11 → 1.0.13
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 +195 -66
- package/dist/index.js +195 -66
- package/dist/lib.d.ts +6 -4
- 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;
|
|
@@ -1472,14 +1537,25 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1472
1537
|
);
|
|
1473
1538
|
let rawApiResponse;
|
|
1474
1539
|
if (nodeId) {
|
|
1475
|
-
rawApiResponse = await figmaService.getRawNode(
|
|
1540
|
+
rawApiResponse = await figmaService.getRawNode(
|
|
1541
|
+
fileKey,
|
|
1542
|
+
nodeId,
|
|
1543
|
+
depth || void 0
|
|
1544
|
+
);
|
|
1476
1545
|
} else {
|
|
1477
|
-
rawApiResponse = await figmaService.getRawFile(
|
|
1546
|
+
rawApiResponse = await figmaService.getRawFile(
|
|
1547
|
+
fileKey,
|
|
1548
|
+
depth || void 0
|
|
1549
|
+
);
|
|
1478
1550
|
}
|
|
1479
|
-
const simplifiedDesign = simplifyRawFigmaObject(
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1551
|
+
const simplifiedDesign = simplifyRawFigmaObject(
|
|
1552
|
+
rawApiResponse,
|
|
1553
|
+
allExtractors,
|
|
1554
|
+
{
|
|
1555
|
+
maxDepth: depth || void 0,
|
|
1556
|
+
afterChildren: collapseSvgContainers
|
|
1557
|
+
}
|
|
1558
|
+
);
|
|
1483
1559
|
Logger.log(
|
|
1484
1560
|
`Successfully extracted data: ${simplifiedDesign.nodes.length} nodes, ${Object.keys(simplifiedDesign.globalVars?.styles || {}).length} styles`
|
|
1485
1561
|
);
|
|
@@ -1491,7 +1567,9 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1491
1567
|
};
|
|
1492
1568
|
if (downloadImages) {
|
|
1493
1569
|
if (!returnBuffer && !localPath) {
|
|
1494
|
-
throw new Error(
|
|
1570
|
+
throw new Error(
|
|
1571
|
+
"localPath is required when downloadImages is true and returnBuffer is false"
|
|
1572
|
+
);
|
|
1495
1573
|
}
|
|
1496
1574
|
Logger.log("Discovering and downloading image assets...");
|
|
1497
1575
|
const imageAssets = findImageAssets(nodes, globalVars);
|
|
@@ -1512,10 +1590,20 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1512
1590
|
);
|
|
1513
1591
|
if (returnBuffer) {
|
|
1514
1592
|
result.images = downloadResults;
|
|
1515
|
-
Logger.log(
|
|
1593
|
+
Logger.log(
|
|
1594
|
+
`Successfully downloaded ${downloadResults.length} images as buffers`
|
|
1595
|
+
);
|
|
1516
1596
|
} else {
|
|
1517
|
-
result.nodes = enrichNodesWithImages(
|
|
1518
|
-
|
|
1597
|
+
result.nodes = enrichNodesWithImages(
|
|
1598
|
+
nodes,
|
|
1599
|
+
imageAssets,
|
|
1600
|
+
downloadResults,
|
|
1601
|
+
useRelativePaths,
|
|
1602
|
+
localPath
|
|
1603
|
+
);
|
|
1604
|
+
Logger.log(
|
|
1605
|
+
`Successfully downloaded and enriched ${downloadResults.length} images`
|
|
1606
|
+
);
|
|
1519
1607
|
}
|
|
1520
1608
|
}
|
|
1521
1609
|
}
|
|
@@ -1533,7 +1621,15 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1533
1621
|
}
|
|
1534
1622
|
}
|
|
1535
1623
|
async function downloadFigmaImages(figmaUrl, nodes, options) {
|
|
1536
|
-
const {
|
|
1624
|
+
const {
|
|
1625
|
+
apiKey,
|
|
1626
|
+
oauthToken,
|
|
1627
|
+
useOAuth = false,
|
|
1628
|
+
pngScale = 2,
|
|
1629
|
+
localPath,
|
|
1630
|
+
enableLogging = false,
|
|
1631
|
+
returnBuffer = false
|
|
1632
|
+
} = options;
|
|
1537
1633
|
Logger.enableLogging = enableLogging;
|
|
1538
1634
|
if (!apiKey && !oauthToken) {
|
|
1539
1635
|
throw new Error("Either apiKey or oauthToken is required");
|
|
@@ -1556,14 +1652,21 @@ async function downloadFigmaImages(figmaUrl, nodes, options) {
|
|
|
1556
1652
|
...node,
|
|
1557
1653
|
nodeId: node.nodeId.replace(/-/g, ":")
|
|
1558
1654
|
}));
|
|
1559
|
-
const results = await figmaService.downloadImages(
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1655
|
+
const results = await figmaService.downloadImages(
|
|
1656
|
+
fileKey,
|
|
1657
|
+
localPath || "",
|
|
1658
|
+
processedNodes,
|
|
1659
|
+
{
|
|
1660
|
+
pngScale,
|
|
1661
|
+
returnBuffer
|
|
1662
|
+
}
|
|
1663
|
+
);
|
|
1563
1664
|
return results;
|
|
1564
1665
|
} catch (error) {
|
|
1565
1666
|
Logger.error(`Error downloading images from ${fileKey}:`, error);
|
|
1566
|
-
throw new Error(
|
|
1667
|
+
throw new Error(
|
|
1668
|
+
`Failed to download images: ${error instanceof Error ? error.message : String(error)}`
|
|
1669
|
+
);
|
|
1567
1670
|
}
|
|
1568
1671
|
}
|
|
1569
1672
|
async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
@@ -1583,7 +1686,9 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1583
1686
|
throw new Error("Either apiKey or oauthToken is required");
|
|
1584
1687
|
}
|
|
1585
1688
|
if (!returnBuffer && (!localPath || !fileName)) {
|
|
1586
|
-
throw new Error(
|
|
1689
|
+
throw new Error(
|
|
1690
|
+
"localPath and fileName are required when returnBuffer is false"
|
|
1691
|
+
);
|
|
1587
1692
|
}
|
|
1588
1693
|
const urlMatch = figmaUrl.match(/figma\.com\/(file|design)\/([a-zA-Z0-9]+)/);
|
|
1589
1694
|
if (!urlMatch) {
|
|
@@ -1592,13 +1697,17 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1592
1697
|
const fileKey = urlMatch[2];
|
|
1593
1698
|
const nodeIdMatch = figmaUrl.match(/node-id=([^&]+)/);
|
|
1594
1699
|
if (!nodeIdMatch) {
|
|
1595
|
-
throw new Error(
|
|
1700
|
+
throw new Error(
|
|
1701
|
+
"No frame node-id found in URL. Please provide a Figma URL with a node-id parameter (e.g., ?node-id=123-456)"
|
|
1702
|
+
);
|
|
1596
1703
|
}
|
|
1597
1704
|
const nodeId = nodeIdMatch[1].replace(/-/g, ":");
|
|
1598
1705
|
if (fileName) {
|
|
1599
1706
|
const expectedExtension = `.${format}`;
|
|
1600
1707
|
if (!fileName.toLowerCase().endsWith(expectedExtension)) {
|
|
1601
|
-
throw new Error(
|
|
1708
|
+
throw new Error(
|
|
1709
|
+
`Filename must end with ${expectedExtension} for ${format} format`
|
|
1710
|
+
);
|
|
1602
1711
|
}
|
|
1603
1712
|
}
|
|
1604
1713
|
const figmaService = new FigmaService({
|
|
@@ -1607,27 +1716,38 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1607
1716
|
useOAuth: useOAuth && !!oauthToken
|
|
1608
1717
|
});
|
|
1609
1718
|
try {
|
|
1610
|
-
Logger.log(
|
|
1719
|
+
Logger.log(
|
|
1720
|
+
`Downloading ${format.toUpperCase()} image for frame ${nodeId} from file ${fileKey}`
|
|
1721
|
+
);
|
|
1611
1722
|
const imageNode = {
|
|
1612
1723
|
nodeId,
|
|
1613
1724
|
fileName: fileName || `temp.${format}`
|
|
1614
1725
|
};
|
|
1615
|
-
const results = await figmaService.downloadImages(
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1726
|
+
const results = await figmaService.downloadImages(
|
|
1727
|
+
fileKey,
|
|
1728
|
+
localPath || "",
|
|
1729
|
+
[imageNode],
|
|
1730
|
+
{
|
|
1731
|
+
pngScale: format === "png" ? pngScale : void 0,
|
|
1732
|
+
returnBuffer
|
|
1733
|
+
}
|
|
1734
|
+
);
|
|
1619
1735
|
if (results.length === 0) {
|
|
1620
1736
|
throw new Error(`Failed to download image for frame ${nodeId}`);
|
|
1621
1737
|
}
|
|
1622
1738
|
if (returnBuffer) {
|
|
1623
1739
|
Logger.log(`Successfully downloaded frame image as buffer`);
|
|
1624
1740
|
} else {
|
|
1625
|
-
Logger.log(
|
|
1741
|
+
Logger.log(
|
|
1742
|
+
`Successfully downloaded frame image to: ${results[0].filePath}`
|
|
1743
|
+
);
|
|
1626
1744
|
}
|
|
1627
1745
|
return results[0];
|
|
1628
1746
|
} catch (error) {
|
|
1629
1747
|
Logger.error(`Error downloading frame image from ${fileKey}:`, error);
|
|
1630
|
-
throw new Error(
|
|
1748
|
+
throw new Error(
|
|
1749
|
+
`Failed to download frame image: ${error instanceof Error ? error.message : String(error)}`
|
|
1750
|
+
);
|
|
1631
1751
|
}
|
|
1632
1752
|
}
|
|
1633
1753
|
function getImageNodeInfo(metadata) {
|
|
@@ -1649,7 +1769,9 @@ function enrichMetadataWithImages(metadata, imagePaths, options = {}) {
|
|
|
1649
1769
|
let downloadResults;
|
|
1650
1770
|
if (Array.isArray(imagePaths)) {
|
|
1651
1771
|
if (imagePaths.length !== metadata.images.length) {
|
|
1652
|
-
throw new Error(
|
|
1772
|
+
throw new Error(
|
|
1773
|
+
`Number of image paths (${imagePaths.length}) must match number of images (${metadata.images.length})`
|
|
1774
|
+
);
|
|
1653
1775
|
}
|
|
1654
1776
|
downloadResults = imagePaths.map((filePath, index) => ({
|
|
1655
1777
|
filePath,
|
|
@@ -1661,16 +1783,23 @@ function enrichMetadataWithImages(metadata, imagePaths, options = {}) {
|
|
|
1661
1783
|
downloadResults = imageAssets.map((asset) => {
|
|
1662
1784
|
const filePath = imagePaths[asset.id];
|
|
1663
1785
|
if (!filePath) {
|
|
1664
|
-
|
|
1786
|
+
return null;
|
|
1665
1787
|
}
|
|
1666
|
-
const imageMetadata = metadata.images.find(
|
|
1788
|
+
const imageMetadata = metadata.images.find(
|
|
1789
|
+
(img) => img.nodeId === asset.id
|
|
1790
|
+
);
|
|
1667
1791
|
return {
|
|
1668
1792
|
filePath,
|
|
1669
|
-
finalDimensions: imageMetadata?.finalDimensions || {
|
|
1793
|
+
finalDimensions: imageMetadata?.finalDimensions || {
|
|
1794
|
+
width: 0,
|
|
1795
|
+
height: 0
|
|
1796
|
+
},
|
|
1670
1797
|
wasCropped: imageMetadata?.wasCropped || false,
|
|
1671
1798
|
cssVariables: imageMetadata?.cssVariables
|
|
1672
1799
|
};
|
|
1673
|
-
})
|
|
1800
|
+
}).filter(
|
|
1801
|
+
(result) => result !== null
|
|
1802
|
+
);
|
|
1674
1803
|
}
|
|
1675
1804
|
const enrichedNodes = enrichNodesWithImages(
|
|
1676
1805
|
metadata.nodes,
|
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;
|
|
@@ -1470,14 +1535,25 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1470
1535
|
);
|
|
1471
1536
|
let rawApiResponse;
|
|
1472
1537
|
if (nodeId) {
|
|
1473
|
-
rawApiResponse = await figmaService.getRawNode(
|
|
1538
|
+
rawApiResponse = await figmaService.getRawNode(
|
|
1539
|
+
fileKey,
|
|
1540
|
+
nodeId,
|
|
1541
|
+
depth || void 0
|
|
1542
|
+
);
|
|
1474
1543
|
} else {
|
|
1475
|
-
rawApiResponse = await figmaService.getRawFile(
|
|
1544
|
+
rawApiResponse = await figmaService.getRawFile(
|
|
1545
|
+
fileKey,
|
|
1546
|
+
depth || void 0
|
|
1547
|
+
);
|
|
1476
1548
|
}
|
|
1477
|
-
const simplifiedDesign = simplifyRawFigmaObject(
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1549
|
+
const simplifiedDesign = simplifyRawFigmaObject(
|
|
1550
|
+
rawApiResponse,
|
|
1551
|
+
allExtractors,
|
|
1552
|
+
{
|
|
1553
|
+
maxDepth: depth || void 0,
|
|
1554
|
+
afterChildren: collapseSvgContainers
|
|
1555
|
+
}
|
|
1556
|
+
);
|
|
1481
1557
|
Logger.log(
|
|
1482
1558
|
`Successfully extracted data: ${simplifiedDesign.nodes.length} nodes, ${Object.keys(simplifiedDesign.globalVars?.styles || {}).length} styles`
|
|
1483
1559
|
);
|
|
@@ -1489,7 +1565,9 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1489
1565
|
};
|
|
1490
1566
|
if (downloadImages) {
|
|
1491
1567
|
if (!returnBuffer && !localPath) {
|
|
1492
|
-
throw new Error(
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
"localPath is required when downloadImages is true and returnBuffer is false"
|
|
1570
|
+
);
|
|
1493
1571
|
}
|
|
1494
1572
|
Logger.log("Discovering and downloading image assets...");
|
|
1495
1573
|
const imageAssets = findImageAssets(nodes, globalVars);
|
|
@@ -1510,10 +1588,20 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1510
1588
|
);
|
|
1511
1589
|
if (returnBuffer) {
|
|
1512
1590
|
result.images = downloadResults;
|
|
1513
|
-
Logger.log(
|
|
1591
|
+
Logger.log(
|
|
1592
|
+
`Successfully downloaded ${downloadResults.length} images as buffers`
|
|
1593
|
+
);
|
|
1514
1594
|
} else {
|
|
1515
|
-
result.nodes = enrichNodesWithImages(
|
|
1516
|
-
|
|
1595
|
+
result.nodes = enrichNodesWithImages(
|
|
1596
|
+
nodes,
|
|
1597
|
+
imageAssets,
|
|
1598
|
+
downloadResults,
|
|
1599
|
+
useRelativePaths,
|
|
1600
|
+
localPath
|
|
1601
|
+
);
|
|
1602
|
+
Logger.log(
|
|
1603
|
+
`Successfully downloaded and enriched ${downloadResults.length} images`
|
|
1604
|
+
);
|
|
1517
1605
|
}
|
|
1518
1606
|
}
|
|
1519
1607
|
}
|
|
@@ -1531,7 +1619,15 @@ async function getFigmaMetadata(figmaUrl, options = {}) {
|
|
|
1531
1619
|
}
|
|
1532
1620
|
}
|
|
1533
1621
|
async function downloadFigmaImages(figmaUrl, nodes, options) {
|
|
1534
|
-
const {
|
|
1622
|
+
const {
|
|
1623
|
+
apiKey,
|
|
1624
|
+
oauthToken,
|
|
1625
|
+
useOAuth = false,
|
|
1626
|
+
pngScale = 2,
|
|
1627
|
+
localPath,
|
|
1628
|
+
enableLogging = false,
|
|
1629
|
+
returnBuffer = false
|
|
1630
|
+
} = options;
|
|
1535
1631
|
Logger.enableLogging = enableLogging;
|
|
1536
1632
|
if (!apiKey && !oauthToken) {
|
|
1537
1633
|
throw new Error("Either apiKey or oauthToken is required");
|
|
@@ -1554,14 +1650,21 @@ async function downloadFigmaImages(figmaUrl, nodes, options) {
|
|
|
1554
1650
|
...node,
|
|
1555
1651
|
nodeId: node.nodeId.replace(/-/g, ":")
|
|
1556
1652
|
}));
|
|
1557
|
-
const results = await figmaService.downloadImages(
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1653
|
+
const results = await figmaService.downloadImages(
|
|
1654
|
+
fileKey,
|
|
1655
|
+
localPath || "",
|
|
1656
|
+
processedNodes,
|
|
1657
|
+
{
|
|
1658
|
+
pngScale,
|
|
1659
|
+
returnBuffer
|
|
1660
|
+
}
|
|
1661
|
+
);
|
|
1561
1662
|
return results;
|
|
1562
1663
|
} catch (error) {
|
|
1563
1664
|
Logger.error(`Error downloading images from ${fileKey}:`, error);
|
|
1564
|
-
throw new Error(
|
|
1665
|
+
throw new Error(
|
|
1666
|
+
`Failed to download images: ${error instanceof Error ? error.message : String(error)}`
|
|
1667
|
+
);
|
|
1565
1668
|
}
|
|
1566
1669
|
}
|
|
1567
1670
|
async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
@@ -1581,7 +1684,9 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1581
1684
|
throw new Error("Either apiKey or oauthToken is required");
|
|
1582
1685
|
}
|
|
1583
1686
|
if (!returnBuffer && (!localPath || !fileName)) {
|
|
1584
|
-
throw new Error(
|
|
1687
|
+
throw new Error(
|
|
1688
|
+
"localPath and fileName are required when returnBuffer is false"
|
|
1689
|
+
);
|
|
1585
1690
|
}
|
|
1586
1691
|
const urlMatch = figmaUrl.match(/figma\.com\/(file|design)\/([a-zA-Z0-9]+)/);
|
|
1587
1692
|
if (!urlMatch) {
|
|
@@ -1590,13 +1695,17 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1590
1695
|
const fileKey = urlMatch[2];
|
|
1591
1696
|
const nodeIdMatch = figmaUrl.match(/node-id=([^&]+)/);
|
|
1592
1697
|
if (!nodeIdMatch) {
|
|
1593
|
-
throw new Error(
|
|
1698
|
+
throw new Error(
|
|
1699
|
+
"No frame node-id found in URL. Please provide a Figma URL with a node-id parameter (e.g., ?node-id=123-456)"
|
|
1700
|
+
);
|
|
1594
1701
|
}
|
|
1595
1702
|
const nodeId = nodeIdMatch[1].replace(/-/g, ":");
|
|
1596
1703
|
if (fileName) {
|
|
1597
1704
|
const expectedExtension = `.${format}`;
|
|
1598
1705
|
if (!fileName.toLowerCase().endsWith(expectedExtension)) {
|
|
1599
|
-
throw new Error(
|
|
1706
|
+
throw new Error(
|
|
1707
|
+
`Filename must end with ${expectedExtension} for ${format} format`
|
|
1708
|
+
);
|
|
1600
1709
|
}
|
|
1601
1710
|
}
|
|
1602
1711
|
const figmaService = new FigmaService({
|
|
@@ -1605,27 +1714,38 @@ async function downloadFigmaFrameImage(figmaUrl, options) {
|
|
|
1605
1714
|
useOAuth: useOAuth && !!oauthToken
|
|
1606
1715
|
});
|
|
1607
1716
|
try {
|
|
1608
|
-
Logger.log(
|
|
1717
|
+
Logger.log(
|
|
1718
|
+
`Downloading ${format.toUpperCase()} image for frame ${nodeId} from file ${fileKey}`
|
|
1719
|
+
);
|
|
1609
1720
|
const imageNode = {
|
|
1610
1721
|
nodeId,
|
|
1611
1722
|
fileName: fileName || `temp.${format}`
|
|
1612
1723
|
};
|
|
1613
|
-
const results = await figmaService.downloadImages(
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1724
|
+
const results = await figmaService.downloadImages(
|
|
1725
|
+
fileKey,
|
|
1726
|
+
localPath || "",
|
|
1727
|
+
[imageNode],
|
|
1728
|
+
{
|
|
1729
|
+
pngScale: format === "png" ? pngScale : void 0,
|
|
1730
|
+
returnBuffer
|
|
1731
|
+
}
|
|
1732
|
+
);
|
|
1617
1733
|
if (results.length === 0) {
|
|
1618
1734
|
throw new Error(`Failed to download image for frame ${nodeId}`);
|
|
1619
1735
|
}
|
|
1620
1736
|
if (returnBuffer) {
|
|
1621
1737
|
Logger.log(`Successfully downloaded frame image as buffer`);
|
|
1622
1738
|
} else {
|
|
1623
|
-
Logger.log(
|
|
1739
|
+
Logger.log(
|
|
1740
|
+
`Successfully downloaded frame image to: ${results[0].filePath}`
|
|
1741
|
+
);
|
|
1624
1742
|
}
|
|
1625
1743
|
return results[0];
|
|
1626
1744
|
} catch (error) {
|
|
1627
1745
|
Logger.error(`Error downloading frame image from ${fileKey}:`, error);
|
|
1628
|
-
throw new Error(
|
|
1746
|
+
throw new Error(
|
|
1747
|
+
`Failed to download frame image: ${error instanceof Error ? error.message : String(error)}`
|
|
1748
|
+
);
|
|
1629
1749
|
}
|
|
1630
1750
|
}
|
|
1631
1751
|
function getImageNodeInfo(metadata) {
|
|
@@ -1647,7 +1767,9 @@ function enrichMetadataWithImages(metadata, imagePaths, options = {}) {
|
|
|
1647
1767
|
let downloadResults;
|
|
1648
1768
|
if (Array.isArray(imagePaths)) {
|
|
1649
1769
|
if (imagePaths.length !== metadata.images.length) {
|
|
1650
|
-
throw new Error(
|
|
1770
|
+
throw new Error(
|
|
1771
|
+
`Number of image paths (${imagePaths.length}) must match number of images (${metadata.images.length})`
|
|
1772
|
+
);
|
|
1651
1773
|
}
|
|
1652
1774
|
downloadResults = imagePaths.map((filePath, index) => ({
|
|
1653
1775
|
filePath,
|
|
@@ -1659,16 +1781,23 @@ function enrichMetadataWithImages(metadata, imagePaths, options = {}) {
|
|
|
1659
1781
|
downloadResults = imageAssets.map((asset) => {
|
|
1660
1782
|
const filePath = imagePaths[asset.id];
|
|
1661
1783
|
if (!filePath) {
|
|
1662
|
-
|
|
1784
|
+
return null;
|
|
1663
1785
|
}
|
|
1664
|
-
const imageMetadata = metadata.images.find(
|
|
1786
|
+
const imageMetadata = metadata.images.find(
|
|
1787
|
+
(img) => img.nodeId === asset.id
|
|
1788
|
+
);
|
|
1665
1789
|
return {
|
|
1666
1790
|
filePath,
|
|
1667
|
-
finalDimensions: imageMetadata?.finalDimensions || {
|
|
1791
|
+
finalDimensions: imageMetadata?.finalDimensions || {
|
|
1792
|
+
width: 0,
|
|
1793
|
+
height: 0
|
|
1794
|
+
},
|
|
1668
1795
|
wasCropped: imageMetadata?.wasCropped || false,
|
|
1669
1796
|
cssVariables: imageMetadata?.cssVariables
|
|
1670
1797
|
};
|
|
1671
|
-
})
|
|
1798
|
+
}).filter(
|
|
1799
|
+
(result) => result !== null
|
|
1800
|
+
);
|
|
1672
1801
|
}
|
|
1673
1802
|
const enrichedNodes = enrichNodesWithImages(
|
|
1674
1803
|
metadata.nodes,
|
package/dist/lib.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface FigmaMetadataOptions {
|
|
|
14
14
|
/** Local path for downloaded images (required if downloadImages is true) */
|
|
15
15
|
localPath?: string;
|
|
16
16
|
/** Image format for downloads (defaults to 'png') */
|
|
17
|
-
imageFormat?:
|
|
17
|
+
imageFormat?: "png" | "svg";
|
|
18
18
|
/** Export scale for PNG images (defaults to 2) */
|
|
19
19
|
pngScale?: number;
|
|
20
20
|
/**
|
|
@@ -85,7 +85,7 @@ export interface FigmaFrameImageOptions {
|
|
|
85
85
|
/** The filename for the downloaded image (must end with .png or .svg, optional if returnBuffer is true) */
|
|
86
86
|
fileName?: string;
|
|
87
87
|
/** Image format to download (defaults to 'png') */
|
|
88
|
-
format?:
|
|
88
|
+
format?: "png" | "svg";
|
|
89
89
|
/** Enable JSON debug log files (defaults to false) */
|
|
90
90
|
enableLogging?: boolean;
|
|
91
91
|
/** Return image as ArrayBuffer instead of saving to disk (defaults to false) */
|
|
@@ -99,10 +99,10 @@ export interface FigmaFrameImageOptions {
|
|
|
99
99
|
* @returns Promise resolving to the extracted metadata
|
|
100
100
|
*/
|
|
101
101
|
export declare function getFigmaMetadata(figmaUrl: string, options: FigmaMetadataOptions & {
|
|
102
|
-
outputFormat:
|
|
102
|
+
outputFormat: "json";
|
|
103
103
|
}): Promise<string>;
|
|
104
104
|
export declare function getFigmaMetadata(figmaUrl: string, options: FigmaMetadataOptions & {
|
|
105
|
-
outputFormat:
|
|
105
|
+
outputFormat: "yaml";
|
|
106
106
|
}): Promise<string>;
|
|
107
107
|
export declare function getFigmaMetadata(figmaUrl: string, options?: FigmaMetadataOptions): Promise<FigmaMetadataResult>;
|
|
108
108
|
/**
|
|
@@ -155,6 +155,7 @@ export declare function getImageNodeInfo(metadata: FigmaMetadataResult): Array<{
|
|
|
155
155
|
* Enrich metadata with saved image file paths
|
|
156
156
|
*
|
|
157
157
|
* Use this function after saving images from buffers to disk to add file path information to the metadata.
|
|
158
|
+
* When using the object format, missing image paths for node IDs are skipped rather than throwing errors.
|
|
158
159
|
*
|
|
159
160
|
* @param metadata - The metadata result from getFigmaMetadata
|
|
160
161
|
* @param imagePaths - Array of file paths (ordered) OR object mapping node IDs to paths/URLs
|
|
@@ -167,6 +168,7 @@ export declare function getImageNodeInfo(metadata: FigmaMetadataResult): Array<{
|
|
|
167
168
|
* const enriched = enrichMetadataWithImages(metadata, ['/path/to/img1.png', '/path/to/img2.png']);
|
|
168
169
|
*
|
|
169
170
|
* // Object format (keyed by node ID) - useful after uploading to CDN
|
|
171
|
+
* // Missing node IDs are automatically skipped
|
|
170
172
|
* const enriched = enrichMetadataWithImages(metadata, {
|
|
171
173
|
* '123:456': 'https://cdn.example.com/icon.png',
|
|
172
174
|
* '789:012': 'https://cdn.example.com/logo.png'
|
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.13",
|
|
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
|
+
}
|