figma-metadata-extractor 1.0.13 → 1.0.14
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/__vite-browser-external-Dyvby5gX.cjs +1 -0
- package/dist/__vite-browser-external-l0sNRNKZ.js +1 -0
- package/dist/index.cjs +136 -115
- package/dist/index.js +136 -115
- package/dist/utils/common.d.ts +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/dist/index.cjs
CHANGED
|
@@ -265,6 +265,122 @@ function formatHeadersForCurl(headers) {
|
|
|
265
265
|
}
|
|
266
266
|
return headerArgs;
|
|
267
267
|
}
|
|
268
|
+
async function downloadFigmaImage(fileName, localPath, imageUrl, returnBuffer = false) {
|
|
269
|
+
const { pipeline } = await Promise.resolve().then(() => require("./__vite-browser-external-Dyvby5gX.cjs"));
|
|
270
|
+
const { Readable } = await Promise.resolve().then(() => require("./__vite-browser-external-Dyvby5gX.cjs"));
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch(imageUrl, {
|
|
273
|
+
method: "GET"
|
|
274
|
+
});
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Failed to download image: ${response.status} ${response.statusText}`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (!response.body) {
|
|
281
|
+
throw new Error("Response body is empty");
|
|
282
|
+
}
|
|
283
|
+
if (returnBuffer) {
|
|
284
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
285
|
+
if (arrayBuffer.byteLength === 0) {
|
|
286
|
+
throw new Error("Downloaded image buffer is empty");
|
|
287
|
+
}
|
|
288
|
+
return arrayBuffer;
|
|
289
|
+
}
|
|
290
|
+
if (!fs.existsSync(localPath)) {
|
|
291
|
+
fs.mkdirSync(localPath, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
const fullPath = path.join(localPath, fileName);
|
|
294
|
+
const fileStream = fs.createWriteStream(fullPath);
|
|
295
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
296
|
+
const stats = fs.statSync(fullPath);
|
|
297
|
+
if (stats.size === 0) {
|
|
298
|
+
fs.unlinkSync(fullPath);
|
|
299
|
+
throw new Error("Downloaded file is empty (0 bytes)");
|
|
300
|
+
}
|
|
301
|
+
return fullPath;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
304
|
+
throw new Error(`Error downloading image: ${errorMessage}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function generateVarId(prefix = "var") {
|
|
308
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
309
|
+
let result = "";
|
|
310
|
+
for (let i = 0; i < 6; i++) {
|
|
311
|
+
const randomIndex = Math.floor(Math.random() * chars.length);
|
|
312
|
+
result += chars[randomIndex];
|
|
313
|
+
}
|
|
314
|
+
return `${prefix}_${result}`;
|
|
315
|
+
}
|
|
316
|
+
function generateCSSShorthand(values, {
|
|
317
|
+
ignoreZero = true,
|
|
318
|
+
suffix = "px"
|
|
319
|
+
} = {}) {
|
|
320
|
+
const { top, right, bottom, left } = values;
|
|
321
|
+
if (ignoreZero && top === 0 && right === 0 && bottom === 0 && left === 0) {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
if (top === right && right === bottom && bottom === left) {
|
|
325
|
+
return `${top}${suffix}`;
|
|
326
|
+
}
|
|
327
|
+
if (right === left) {
|
|
328
|
+
if (top === bottom) {
|
|
329
|
+
return `${top}${suffix} ${right}${suffix}`;
|
|
330
|
+
}
|
|
331
|
+
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix}`;
|
|
332
|
+
}
|
|
333
|
+
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix} ${left}${suffix}`;
|
|
334
|
+
}
|
|
335
|
+
function isVisible(element) {
|
|
336
|
+
return element.visible ?? true;
|
|
337
|
+
}
|
|
338
|
+
function pixelRound(num) {
|
|
339
|
+
if (isNaN(num)) {
|
|
340
|
+
throw new TypeError(`Input must be a valid number`);
|
|
341
|
+
}
|
|
342
|
+
return Number(Number(num).toFixed(2));
|
|
343
|
+
}
|
|
344
|
+
async function runWithConcurrency(tasks, limit) {
|
|
345
|
+
const results = new Array(tasks.length);
|
|
346
|
+
return new Promise((resolve, reject) => {
|
|
347
|
+
if (tasks.length === 0) {
|
|
348
|
+
resolve([]);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
let completed = 0;
|
|
352
|
+
let launched = 0;
|
|
353
|
+
let failed = false;
|
|
354
|
+
const next = () => {
|
|
355
|
+
if (failed) return;
|
|
356
|
+
if (completed === tasks.length) {
|
|
357
|
+
resolve(results);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
while (launched < tasks.length && launched - completed < limit) {
|
|
361
|
+
const index = launched++;
|
|
362
|
+
tasks[index]().then((result) => {
|
|
363
|
+
results[index] = result;
|
|
364
|
+
completed++;
|
|
365
|
+
next();
|
|
366
|
+
}).catch((err) => {
|
|
367
|
+
failed = true;
|
|
368
|
+
reject(err);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
next();
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const common = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
376
|
+
__proto__: null,
|
|
377
|
+
downloadFigmaImage,
|
|
378
|
+
generateCSSShorthand,
|
|
379
|
+
generateVarId,
|
|
380
|
+
isVisible,
|
|
381
|
+
pixelRound,
|
|
382
|
+
runWithConcurrency
|
|
383
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
268
384
|
class FigmaService {
|
|
269
385
|
apiKey;
|
|
270
386
|
oauthToken;
|
|
@@ -372,6 +488,7 @@ class FigmaService {
|
|
|
372
488
|
async downloadImages(fileKey, localPath, items, options = {}) {
|
|
373
489
|
if (items.length === 0) return [];
|
|
374
490
|
const { pngScale = 2, svgOptions, returnBuffer = false } = options;
|
|
491
|
+
const CONCURRENCY_LIMIT = 10;
|
|
375
492
|
let resolvedPath = "";
|
|
376
493
|
if (!returnBuffer) {
|
|
377
494
|
const sanitizedPath = path.normalize(localPath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
@@ -391,7 +508,7 @@ class FigmaService {
|
|
|
391
508
|
);
|
|
392
509
|
if (imageFills.length > 0) {
|
|
393
510
|
const fillUrls = await this.getImageFillUrls(fileKey);
|
|
394
|
-
const
|
|
511
|
+
const fillTasks = imageFills.map(
|
|
395
512
|
({
|
|
396
513
|
imageRef,
|
|
397
514
|
fileName,
|
|
@@ -406,7 +523,7 @@ class FigmaService {
|
|
|
406
523
|
);
|
|
407
524
|
return null;
|
|
408
525
|
}
|
|
409
|
-
return downloadAndProcessImage(
|
|
526
|
+
return () => downloadAndProcessImage(
|
|
410
527
|
fileName,
|
|
411
528
|
resolvedPath,
|
|
412
529
|
imageUrl,
|
|
@@ -417,10 +534,10 @@ class FigmaService {
|
|
|
417
534
|
);
|
|
418
535
|
}
|
|
419
536
|
).filter(
|
|
420
|
-
(
|
|
537
|
+
(task) => task !== null
|
|
421
538
|
);
|
|
422
|
-
if (
|
|
423
|
-
downloadPromises.push(
|
|
539
|
+
if (fillTasks.length > 0) {
|
|
540
|
+
downloadPromises.push(runWithConcurrency(fillTasks, CONCURRENCY_LIMIT));
|
|
424
541
|
}
|
|
425
542
|
}
|
|
426
543
|
if (renderNodes.length > 0) {
|
|
@@ -437,7 +554,7 @@ class FigmaService {
|
|
|
437
554
|
"png",
|
|
438
555
|
{ pngScale }
|
|
439
556
|
);
|
|
440
|
-
const
|
|
557
|
+
const pngTasks = pngNodes.map(
|
|
441
558
|
({
|
|
442
559
|
nodeId,
|
|
443
560
|
fileName,
|
|
@@ -452,7 +569,7 @@ class FigmaService {
|
|
|
452
569
|
);
|
|
453
570
|
return null;
|
|
454
571
|
}
|
|
455
|
-
return downloadAndProcessImage(
|
|
572
|
+
return () => downloadAndProcessImage(
|
|
456
573
|
fileName,
|
|
457
574
|
resolvedPath,
|
|
458
575
|
imageUrl,
|
|
@@ -465,10 +582,12 @@ class FigmaService {
|
|
|
465
582
|
);
|
|
466
583
|
}
|
|
467
584
|
).filter(
|
|
468
|
-
(
|
|
585
|
+
(task) => task !== null
|
|
469
586
|
);
|
|
470
|
-
if (
|
|
471
|
-
downloadPromises.push(
|
|
587
|
+
if (pngTasks.length > 0) {
|
|
588
|
+
downloadPromises.push(
|
|
589
|
+
runWithConcurrency(pngTasks, CONCURRENCY_LIMIT)
|
|
590
|
+
);
|
|
472
591
|
}
|
|
473
592
|
}
|
|
474
593
|
if (svgNodes.length > 0) {
|
|
@@ -478,7 +597,7 @@ class FigmaService {
|
|
|
478
597
|
"svg",
|
|
479
598
|
{ svgOptions }
|
|
480
599
|
);
|
|
481
|
-
const
|
|
600
|
+
const svgTasks = svgNodes.map(
|
|
482
601
|
({
|
|
483
602
|
nodeId,
|
|
484
603
|
fileName,
|
|
@@ -493,7 +612,7 @@ class FigmaService {
|
|
|
493
612
|
);
|
|
494
613
|
return null;
|
|
495
614
|
}
|
|
496
|
-
return downloadAndProcessImage(
|
|
615
|
+
return () => downloadAndProcessImage(
|
|
497
616
|
fileName,
|
|
498
617
|
resolvedPath,
|
|
499
618
|
imageUrl,
|
|
@@ -506,10 +625,12 @@ class FigmaService {
|
|
|
506
625
|
);
|
|
507
626
|
}
|
|
508
627
|
).filter(
|
|
509
|
-
(
|
|
628
|
+
(task) => task !== null
|
|
510
629
|
);
|
|
511
|
-
if (
|
|
512
|
-
downloadPromises.push(
|
|
630
|
+
if (svgTasks.length > 0) {
|
|
631
|
+
downloadPromises.push(
|
|
632
|
+
runWithConcurrency(svgTasks, CONCURRENCY_LIMIT)
|
|
633
|
+
);
|
|
513
634
|
}
|
|
514
635
|
}
|
|
515
636
|
}
|
|
@@ -541,106 +662,6 @@ class FigmaService {
|
|
|
541
662
|
return response;
|
|
542
663
|
}
|
|
543
664
|
}
|
|
544
|
-
async function downloadFigmaImage(fileName, localPath, imageUrl, returnBuffer = false) {
|
|
545
|
-
try {
|
|
546
|
-
const response = await fetch(imageUrl, {
|
|
547
|
-
method: "GET"
|
|
548
|
-
});
|
|
549
|
-
if (!response.ok) {
|
|
550
|
-
throw new Error(`Failed to download image: ${response.statusText}`);
|
|
551
|
-
}
|
|
552
|
-
if (returnBuffer) {
|
|
553
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
554
|
-
return arrayBuffer;
|
|
555
|
-
}
|
|
556
|
-
if (!fs.existsSync(localPath)) {
|
|
557
|
-
fs.mkdirSync(localPath, { recursive: true });
|
|
558
|
-
}
|
|
559
|
-
const fullPath = path.join(localPath, fileName);
|
|
560
|
-
const reader = response.body?.getReader();
|
|
561
|
-
if (!reader) {
|
|
562
|
-
throw new Error("Failed to get response body");
|
|
563
|
-
}
|
|
564
|
-
const writer = fs.createWriteStream(fullPath);
|
|
565
|
-
return new Promise((resolve, reject) => {
|
|
566
|
-
const processStream = async () => {
|
|
567
|
-
try {
|
|
568
|
-
while (true) {
|
|
569
|
-
const { done, value } = await reader.read();
|
|
570
|
-
if (done) {
|
|
571
|
-
writer.end();
|
|
572
|
-
break;
|
|
573
|
-
}
|
|
574
|
-
writer.write(value);
|
|
575
|
-
}
|
|
576
|
-
} catch (err) {
|
|
577
|
-
writer.end();
|
|
578
|
-
fs.unlink(fullPath, () => {
|
|
579
|
-
});
|
|
580
|
-
reject(err);
|
|
581
|
-
}
|
|
582
|
-
};
|
|
583
|
-
writer.on("finish", () => {
|
|
584
|
-
resolve(fullPath);
|
|
585
|
-
});
|
|
586
|
-
writer.on("error", (err) => {
|
|
587
|
-
reader.cancel();
|
|
588
|
-
fs.unlink(fullPath, () => {
|
|
589
|
-
});
|
|
590
|
-
reject(new Error(`Failed to write image: ${err.message}`));
|
|
591
|
-
});
|
|
592
|
-
processStream();
|
|
593
|
-
});
|
|
594
|
-
} catch (error) {
|
|
595
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
596
|
-
throw new Error(`Error downloading image: ${errorMessage}`);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
function generateVarId(prefix = "var") {
|
|
600
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
601
|
-
let result = "";
|
|
602
|
-
for (let i = 0; i < 6; i++) {
|
|
603
|
-
const randomIndex = Math.floor(Math.random() * chars.length);
|
|
604
|
-
result += chars[randomIndex];
|
|
605
|
-
}
|
|
606
|
-
return `${prefix}_${result}`;
|
|
607
|
-
}
|
|
608
|
-
function generateCSSShorthand(values, {
|
|
609
|
-
ignoreZero = true,
|
|
610
|
-
suffix = "px"
|
|
611
|
-
} = {}) {
|
|
612
|
-
const { top, right, bottom, left } = values;
|
|
613
|
-
if (ignoreZero && top === 0 && right === 0 && bottom === 0 && left === 0) {
|
|
614
|
-
return void 0;
|
|
615
|
-
}
|
|
616
|
-
if (top === right && right === bottom && bottom === left) {
|
|
617
|
-
return `${top}${suffix}`;
|
|
618
|
-
}
|
|
619
|
-
if (right === left) {
|
|
620
|
-
if (top === bottom) {
|
|
621
|
-
return `${top}${suffix} ${right}${suffix}`;
|
|
622
|
-
}
|
|
623
|
-
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix}`;
|
|
624
|
-
}
|
|
625
|
-
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix} ${left}${suffix}`;
|
|
626
|
-
}
|
|
627
|
-
function isVisible(element) {
|
|
628
|
-
return element.visible ?? true;
|
|
629
|
-
}
|
|
630
|
-
function pixelRound(num) {
|
|
631
|
-
if (isNaN(num)) {
|
|
632
|
-
throw new TypeError(`Input must be a valid number`);
|
|
633
|
-
}
|
|
634
|
-
return Number(Number(num).toFixed(2));
|
|
635
|
-
}
|
|
636
|
-
const common = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
637
|
-
__proto__: null,
|
|
638
|
-
downloadFigmaImage,
|
|
639
|
-
generateCSSShorthand,
|
|
640
|
-
generateVarId,
|
|
641
|
-
isVisible,
|
|
642
|
-
pixelRound
|
|
643
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
644
665
|
function hasValue(key, obj, typeGuard) {
|
|
645
666
|
const isObject = typeof obj === "object" && obj !== null;
|
|
646
667
|
if (!isObject || !(key in obj)) return false;
|
package/dist/index.js
CHANGED
|
@@ -263,6 +263,122 @@ function formatHeadersForCurl(headers) {
|
|
|
263
263
|
}
|
|
264
264
|
return headerArgs;
|
|
265
265
|
}
|
|
266
|
+
async function downloadFigmaImage(fileName, localPath, imageUrl, returnBuffer = false) {
|
|
267
|
+
const { pipeline } = await import("./__vite-browser-external-l0sNRNKZ.js");
|
|
268
|
+
const { Readable } = await import("./__vite-browser-external-l0sNRNKZ.js");
|
|
269
|
+
try {
|
|
270
|
+
const response = await fetch(imageUrl, {
|
|
271
|
+
method: "GET"
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Failed to download image: ${response.status} ${response.statusText}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (!response.body) {
|
|
279
|
+
throw new Error("Response body is empty");
|
|
280
|
+
}
|
|
281
|
+
if (returnBuffer) {
|
|
282
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
283
|
+
if (arrayBuffer.byteLength === 0) {
|
|
284
|
+
throw new Error("Downloaded image buffer is empty");
|
|
285
|
+
}
|
|
286
|
+
return arrayBuffer;
|
|
287
|
+
}
|
|
288
|
+
if (!fs.existsSync(localPath)) {
|
|
289
|
+
fs.mkdirSync(localPath, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
const fullPath = path.join(localPath, fileName);
|
|
292
|
+
const fileStream = fs.createWriteStream(fullPath);
|
|
293
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
294
|
+
const stats = fs.statSync(fullPath);
|
|
295
|
+
if (stats.size === 0) {
|
|
296
|
+
fs.unlinkSync(fullPath);
|
|
297
|
+
throw new Error("Downloaded file is empty (0 bytes)");
|
|
298
|
+
}
|
|
299
|
+
return fullPath;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
302
|
+
throw new Error(`Error downloading image: ${errorMessage}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function generateVarId(prefix = "var") {
|
|
306
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
307
|
+
let result = "";
|
|
308
|
+
for (let i = 0; i < 6; i++) {
|
|
309
|
+
const randomIndex = Math.floor(Math.random() * chars.length);
|
|
310
|
+
result += chars[randomIndex];
|
|
311
|
+
}
|
|
312
|
+
return `${prefix}_${result}`;
|
|
313
|
+
}
|
|
314
|
+
function generateCSSShorthand(values, {
|
|
315
|
+
ignoreZero = true,
|
|
316
|
+
suffix = "px"
|
|
317
|
+
} = {}) {
|
|
318
|
+
const { top, right, bottom, left } = values;
|
|
319
|
+
if (ignoreZero && top === 0 && right === 0 && bottom === 0 && left === 0) {
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
if (top === right && right === bottom && bottom === left) {
|
|
323
|
+
return `${top}${suffix}`;
|
|
324
|
+
}
|
|
325
|
+
if (right === left) {
|
|
326
|
+
if (top === bottom) {
|
|
327
|
+
return `${top}${suffix} ${right}${suffix}`;
|
|
328
|
+
}
|
|
329
|
+
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix}`;
|
|
330
|
+
}
|
|
331
|
+
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix} ${left}${suffix}`;
|
|
332
|
+
}
|
|
333
|
+
function isVisible(element) {
|
|
334
|
+
return element.visible ?? true;
|
|
335
|
+
}
|
|
336
|
+
function pixelRound(num) {
|
|
337
|
+
if (isNaN(num)) {
|
|
338
|
+
throw new TypeError(`Input must be a valid number`);
|
|
339
|
+
}
|
|
340
|
+
return Number(Number(num).toFixed(2));
|
|
341
|
+
}
|
|
342
|
+
async function runWithConcurrency(tasks, limit) {
|
|
343
|
+
const results = new Array(tasks.length);
|
|
344
|
+
return new Promise((resolve, reject) => {
|
|
345
|
+
if (tasks.length === 0) {
|
|
346
|
+
resolve([]);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
let completed = 0;
|
|
350
|
+
let launched = 0;
|
|
351
|
+
let failed = false;
|
|
352
|
+
const next = () => {
|
|
353
|
+
if (failed) return;
|
|
354
|
+
if (completed === tasks.length) {
|
|
355
|
+
resolve(results);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
while (launched < tasks.length && launched - completed < limit) {
|
|
359
|
+
const index = launched++;
|
|
360
|
+
tasks[index]().then((result) => {
|
|
361
|
+
results[index] = result;
|
|
362
|
+
completed++;
|
|
363
|
+
next();
|
|
364
|
+
}).catch((err) => {
|
|
365
|
+
failed = true;
|
|
366
|
+
reject(err);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
next();
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const common = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
374
|
+
__proto__: null,
|
|
375
|
+
downloadFigmaImage,
|
|
376
|
+
generateCSSShorthand,
|
|
377
|
+
generateVarId,
|
|
378
|
+
isVisible,
|
|
379
|
+
pixelRound,
|
|
380
|
+
runWithConcurrency
|
|
381
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
266
382
|
class FigmaService {
|
|
267
383
|
apiKey;
|
|
268
384
|
oauthToken;
|
|
@@ -370,6 +486,7 @@ class FigmaService {
|
|
|
370
486
|
async downloadImages(fileKey, localPath, items, options = {}) {
|
|
371
487
|
if (items.length === 0) return [];
|
|
372
488
|
const { pngScale = 2, svgOptions, returnBuffer = false } = options;
|
|
489
|
+
const CONCURRENCY_LIMIT = 10;
|
|
373
490
|
let resolvedPath = "";
|
|
374
491
|
if (!returnBuffer) {
|
|
375
492
|
const sanitizedPath = path.normalize(localPath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
@@ -389,7 +506,7 @@ class FigmaService {
|
|
|
389
506
|
);
|
|
390
507
|
if (imageFills.length > 0) {
|
|
391
508
|
const fillUrls = await this.getImageFillUrls(fileKey);
|
|
392
|
-
const
|
|
509
|
+
const fillTasks = imageFills.map(
|
|
393
510
|
({
|
|
394
511
|
imageRef,
|
|
395
512
|
fileName,
|
|
@@ -404,7 +521,7 @@ class FigmaService {
|
|
|
404
521
|
);
|
|
405
522
|
return null;
|
|
406
523
|
}
|
|
407
|
-
return downloadAndProcessImage(
|
|
524
|
+
return () => downloadAndProcessImage(
|
|
408
525
|
fileName,
|
|
409
526
|
resolvedPath,
|
|
410
527
|
imageUrl,
|
|
@@ -415,10 +532,10 @@ class FigmaService {
|
|
|
415
532
|
);
|
|
416
533
|
}
|
|
417
534
|
).filter(
|
|
418
|
-
(
|
|
535
|
+
(task) => task !== null
|
|
419
536
|
);
|
|
420
|
-
if (
|
|
421
|
-
downloadPromises.push(
|
|
537
|
+
if (fillTasks.length > 0) {
|
|
538
|
+
downloadPromises.push(runWithConcurrency(fillTasks, CONCURRENCY_LIMIT));
|
|
422
539
|
}
|
|
423
540
|
}
|
|
424
541
|
if (renderNodes.length > 0) {
|
|
@@ -435,7 +552,7 @@ class FigmaService {
|
|
|
435
552
|
"png",
|
|
436
553
|
{ pngScale }
|
|
437
554
|
);
|
|
438
|
-
const
|
|
555
|
+
const pngTasks = pngNodes.map(
|
|
439
556
|
({
|
|
440
557
|
nodeId,
|
|
441
558
|
fileName,
|
|
@@ -450,7 +567,7 @@ class FigmaService {
|
|
|
450
567
|
);
|
|
451
568
|
return null;
|
|
452
569
|
}
|
|
453
|
-
return downloadAndProcessImage(
|
|
570
|
+
return () => downloadAndProcessImage(
|
|
454
571
|
fileName,
|
|
455
572
|
resolvedPath,
|
|
456
573
|
imageUrl,
|
|
@@ -463,10 +580,12 @@ class FigmaService {
|
|
|
463
580
|
);
|
|
464
581
|
}
|
|
465
582
|
).filter(
|
|
466
|
-
(
|
|
583
|
+
(task) => task !== null
|
|
467
584
|
);
|
|
468
|
-
if (
|
|
469
|
-
downloadPromises.push(
|
|
585
|
+
if (pngTasks.length > 0) {
|
|
586
|
+
downloadPromises.push(
|
|
587
|
+
runWithConcurrency(pngTasks, CONCURRENCY_LIMIT)
|
|
588
|
+
);
|
|
470
589
|
}
|
|
471
590
|
}
|
|
472
591
|
if (svgNodes.length > 0) {
|
|
@@ -476,7 +595,7 @@ class FigmaService {
|
|
|
476
595
|
"svg",
|
|
477
596
|
{ svgOptions }
|
|
478
597
|
);
|
|
479
|
-
const
|
|
598
|
+
const svgTasks = svgNodes.map(
|
|
480
599
|
({
|
|
481
600
|
nodeId,
|
|
482
601
|
fileName,
|
|
@@ -491,7 +610,7 @@ class FigmaService {
|
|
|
491
610
|
);
|
|
492
611
|
return null;
|
|
493
612
|
}
|
|
494
|
-
return downloadAndProcessImage(
|
|
613
|
+
return () => downloadAndProcessImage(
|
|
495
614
|
fileName,
|
|
496
615
|
resolvedPath,
|
|
497
616
|
imageUrl,
|
|
@@ -504,10 +623,12 @@ class FigmaService {
|
|
|
504
623
|
);
|
|
505
624
|
}
|
|
506
625
|
).filter(
|
|
507
|
-
(
|
|
626
|
+
(task) => task !== null
|
|
508
627
|
);
|
|
509
|
-
if (
|
|
510
|
-
downloadPromises.push(
|
|
628
|
+
if (svgTasks.length > 0) {
|
|
629
|
+
downloadPromises.push(
|
|
630
|
+
runWithConcurrency(svgTasks, CONCURRENCY_LIMIT)
|
|
631
|
+
);
|
|
511
632
|
}
|
|
512
633
|
}
|
|
513
634
|
}
|
|
@@ -539,106 +660,6 @@ class FigmaService {
|
|
|
539
660
|
return response;
|
|
540
661
|
}
|
|
541
662
|
}
|
|
542
|
-
async function downloadFigmaImage(fileName, localPath, imageUrl, returnBuffer = false) {
|
|
543
|
-
try {
|
|
544
|
-
const response = await fetch(imageUrl, {
|
|
545
|
-
method: "GET"
|
|
546
|
-
});
|
|
547
|
-
if (!response.ok) {
|
|
548
|
-
throw new Error(`Failed to download image: ${response.statusText}`);
|
|
549
|
-
}
|
|
550
|
-
if (returnBuffer) {
|
|
551
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
552
|
-
return arrayBuffer;
|
|
553
|
-
}
|
|
554
|
-
if (!fs.existsSync(localPath)) {
|
|
555
|
-
fs.mkdirSync(localPath, { recursive: true });
|
|
556
|
-
}
|
|
557
|
-
const fullPath = path.join(localPath, fileName);
|
|
558
|
-
const reader = response.body?.getReader();
|
|
559
|
-
if (!reader) {
|
|
560
|
-
throw new Error("Failed to get response body");
|
|
561
|
-
}
|
|
562
|
-
const writer = fs.createWriteStream(fullPath);
|
|
563
|
-
return new Promise((resolve, reject) => {
|
|
564
|
-
const processStream = async () => {
|
|
565
|
-
try {
|
|
566
|
-
while (true) {
|
|
567
|
-
const { done, value } = await reader.read();
|
|
568
|
-
if (done) {
|
|
569
|
-
writer.end();
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
writer.write(value);
|
|
573
|
-
}
|
|
574
|
-
} catch (err) {
|
|
575
|
-
writer.end();
|
|
576
|
-
fs.unlink(fullPath, () => {
|
|
577
|
-
});
|
|
578
|
-
reject(err);
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
writer.on("finish", () => {
|
|
582
|
-
resolve(fullPath);
|
|
583
|
-
});
|
|
584
|
-
writer.on("error", (err) => {
|
|
585
|
-
reader.cancel();
|
|
586
|
-
fs.unlink(fullPath, () => {
|
|
587
|
-
});
|
|
588
|
-
reject(new Error(`Failed to write image: ${err.message}`));
|
|
589
|
-
});
|
|
590
|
-
processStream();
|
|
591
|
-
});
|
|
592
|
-
} catch (error) {
|
|
593
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
594
|
-
throw new Error(`Error downloading image: ${errorMessage}`);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
function generateVarId(prefix = "var") {
|
|
598
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
599
|
-
let result = "";
|
|
600
|
-
for (let i = 0; i < 6; i++) {
|
|
601
|
-
const randomIndex = Math.floor(Math.random() * chars.length);
|
|
602
|
-
result += chars[randomIndex];
|
|
603
|
-
}
|
|
604
|
-
return `${prefix}_${result}`;
|
|
605
|
-
}
|
|
606
|
-
function generateCSSShorthand(values, {
|
|
607
|
-
ignoreZero = true,
|
|
608
|
-
suffix = "px"
|
|
609
|
-
} = {}) {
|
|
610
|
-
const { top, right, bottom, left } = values;
|
|
611
|
-
if (ignoreZero && top === 0 && right === 0 && bottom === 0 && left === 0) {
|
|
612
|
-
return void 0;
|
|
613
|
-
}
|
|
614
|
-
if (top === right && right === bottom && bottom === left) {
|
|
615
|
-
return `${top}${suffix}`;
|
|
616
|
-
}
|
|
617
|
-
if (right === left) {
|
|
618
|
-
if (top === bottom) {
|
|
619
|
-
return `${top}${suffix} ${right}${suffix}`;
|
|
620
|
-
}
|
|
621
|
-
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix}`;
|
|
622
|
-
}
|
|
623
|
-
return `${top}${suffix} ${right}${suffix} ${bottom}${suffix} ${left}${suffix}`;
|
|
624
|
-
}
|
|
625
|
-
function isVisible(element) {
|
|
626
|
-
return element.visible ?? true;
|
|
627
|
-
}
|
|
628
|
-
function pixelRound(num) {
|
|
629
|
-
if (isNaN(num)) {
|
|
630
|
-
throw new TypeError(`Input must be a valid number`);
|
|
631
|
-
}
|
|
632
|
-
return Number(Number(num).toFixed(2));
|
|
633
|
-
}
|
|
634
|
-
const common = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
635
|
-
__proto__: null,
|
|
636
|
-
downloadFigmaImage,
|
|
637
|
-
generateCSSShorthand,
|
|
638
|
-
generateVarId,
|
|
639
|
-
isVisible,
|
|
640
|
-
pixelRound
|
|
641
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
642
663
|
function hasValue(key, obj, typeGuard) {
|
|
643
664
|
const isObject = typeof obj === "object" && obj !== null;
|
|
644
665
|
if (!isObject || !(key in obj)) return false;
|
package/dist/utils/common.d.ts
CHANGED
|
@@ -68,3 +68,10 @@ export declare function isVisible(element: {
|
|
|
68
68
|
* @throws TypeError If the input is not a valid number
|
|
69
69
|
*/
|
|
70
70
|
export declare function pixelRound(num: number): number;
|
|
71
|
+
/**
|
|
72
|
+
* Run a list of async tasks with a concurrency limit
|
|
73
|
+
* @param tasks - Array of functions that return a Promise
|
|
74
|
+
* @param limit - Maximum number of concurrent tasks
|
|
75
|
+
* @returns Promise resolving to array of results
|
|
76
|
+
*/
|
|
77
|
+
export declare function runWithConcurrency<T>(tasks: Array<() => Promise<T>>, limit: number): Promise<T[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "figma-metadata-extractor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Extract metadata and download images from Figma files. A standalone library for accessing Figma design data and downloading frame images programmatically.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|