figma-metadata-extractor 1.0.11 → 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.
Files changed (3) hide show
  1. package/dist/index.cjs +100 -35
  2. package/dist/index.js +100 -35
  3. 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(Object.entries(images).filter(([, value]) => !!value));
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(`${this.baseUrl}${endpoint}`, {
299
- headers
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("Invalid path specified. Directory traversal is not allowed.");
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(({ imageRef, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
388
- const imageUrl = fillUrls[imageRef];
389
- return imageUrl ? downloadAndProcessImage(
394
+ const fillDownloads = imageFills.map(
395
+ ({
396
+ imageRef,
390
397
  fileName,
391
- resolvedPath,
392
- imageUrl,
393
398
  needsCropping,
394
399
  cropTransform,
395
- requiresImageDimensions,
396
- returnBuffer
397
- ) : null;
398
- }).filter((promise) => promise !== null);
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((node) => !node.fileName.toLowerCase().endsWith(".svg"));
405
- const svgNodes = renderNodes.filter((node) => node.fileName.toLowerCase().endsWith(".svg"));
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(({ nodeId, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
414
- const imageUrl = pngUrls[nodeId];
415
- return imageUrl ? downloadAndProcessImage(
440
+ const pngDownloads = pngNodes.map(
441
+ ({
442
+ nodeId,
416
443
  fileName,
417
- resolvedPath,
418
- imageUrl,
419
444
  needsCropping,
420
445
  cropTransform,
421
- requiresImageDimensions,
422
- returnBuffer
423
- ).then((result) => ({ ...result, nodeId })) : null;
424
- }).filter((promise) => promise !== null);
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(({ nodeId, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
437
- const imageUrl = svgUrls[nodeId];
438
- return imageUrl ? downloadAndProcessImage(
481
+ const svgDownloads = svgNodes.map(
482
+ ({
483
+ nodeId,
439
484
  fileName,
440
- resolvedPath,
441
- imageUrl,
442
485
  needsCropping,
443
486
  cropTransform,
444
- requiresImageDimensions,
445
- returnBuffer
446
- ).then((result) => ({ ...result, nodeId })) : null;
447
- }).filter((promise) => promise !== null);
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(`Retrieving raw Figma file: ${fileKey} (depth: ${depth ?? "default"})`);
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;
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(Object.entries(images).filter(([, value]) => !!value));
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(`${this.baseUrl}${endpoint}`, {
297
- headers
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("Invalid path specified. Directory traversal is not allowed.");
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(({ imageRef, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
386
- const imageUrl = fillUrls[imageRef];
387
- return imageUrl ? downloadAndProcessImage(
392
+ const fillDownloads = imageFills.map(
393
+ ({
394
+ imageRef,
388
395
  fileName,
389
- resolvedPath,
390
- imageUrl,
391
396
  needsCropping,
392
397
  cropTransform,
393
- requiresImageDimensions,
394
- returnBuffer
395
- ) : null;
396
- }).filter((promise) => promise !== null);
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((node) => !node.fileName.toLowerCase().endsWith(".svg"));
403
- const svgNodes = renderNodes.filter((node) => node.fileName.toLowerCase().endsWith(".svg"));
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(({ nodeId, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
412
- const imageUrl = pngUrls[nodeId];
413
- return imageUrl ? downloadAndProcessImage(
438
+ const pngDownloads = pngNodes.map(
439
+ ({
440
+ nodeId,
414
441
  fileName,
415
- resolvedPath,
416
- imageUrl,
417
442
  needsCropping,
418
443
  cropTransform,
419
- requiresImageDimensions,
420
- returnBuffer
421
- ).then((result) => ({ ...result, nodeId })) : null;
422
- }).filter((promise) => promise !== null);
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(({ nodeId, fileName, needsCropping, cropTransform, requiresImageDimensions }) => {
435
- const imageUrl = svgUrls[nodeId];
436
- return imageUrl ? downloadAndProcessImage(
479
+ const svgDownloads = svgNodes.map(
480
+ ({
481
+ nodeId,
437
482
  fileName,
438
- resolvedPath,
439
- imageUrl,
440
483
  needsCropping,
441
484
  cropTransform,
442
- requiresImageDimensions,
443
- returnBuffer
444
- ).then((result) => ({ ...result, nodeId })) : null;
445
- }).filter((promise) => promise !== null);
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(`Retrieving raw Figma file: ${fileKey} (depth: ${depth ?? "default"})`);
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;
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
1
  {
2
- "name": "figma-metadata-extractor",
3
- "version": "1.0.11",
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"
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
+ }