vectify 2.0.5 → 2.1.1

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 (50) hide show
  1. package/README.md +12 -13
  2. package/README.zh-CN.md +12 -13
  3. package/dist/{chunk-CIKTK6HI.mjs → chunk-FS34P27H.mjs} +8 -0
  4. package/dist/{chunk-GY4VNET5.mjs → chunk-QYE23M3E.mjs} +625 -65
  5. package/dist/cli.js +676 -117
  6. package/dist/cli.mjs +3 -3
  7. package/dist/{helpers-UPZEBRGK.mjs → helpers-ZOR3OD66.mjs} +1 -1
  8. package/dist/index.d.mts +33 -2
  9. package/dist/index.d.ts +33 -2
  10. package/dist/index.js +669 -110
  11. package/dist/index.mjs +2 -2
  12. package/dist/templates/angular/component.ts.hbs +11 -2
  13. package/dist/templates/angular/createIcon.ts.hbs +11 -2
  14. package/dist/templates/astro/component.astro.hbs +12 -2
  15. package/dist/templates/astro/createIcon.astro.hbs +12 -2
  16. package/dist/templates/lit/component.js.hbs +1 -0
  17. package/dist/templates/lit/component.ts.hbs +1 -0
  18. package/dist/templates/lit/createIcon.js.hbs +12 -2
  19. package/dist/templates/lit/createIcon.ts.hbs +12 -2
  20. package/dist/templates/preact/component.jsx.hbs +1 -1
  21. package/dist/templates/preact/component.tsx.hbs +1 -1
  22. package/dist/templates/preact/createIcon.jsx.hbs +12 -3
  23. package/dist/templates/preact/createIcon.tsx.hbs +13 -3
  24. package/dist/templates/qwik/component.jsx.hbs +1 -1
  25. package/dist/templates/qwik/component.tsx.hbs +1 -1
  26. package/dist/templates/qwik/createIcon.jsx.hbs +12 -3
  27. package/dist/templates/qwik/createIcon.tsx.hbs +13 -3
  28. package/dist/templates/react/component.jsx.hbs +1 -1
  29. package/dist/templates/react/component.tsx.hbs +1 -1
  30. package/dist/templates/react/createIcon.jsx.hbs +12 -3
  31. package/dist/templates/react/createIcon.tsx.hbs +13 -3
  32. package/dist/templates/solid/component.tsx.hbs +1 -1
  33. package/dist/templates/solid/createIcon.jsx.hbs +13 -3
  34. package/dist/templates/solid/createIcon.tsx.hbs +14 -3
  35. package/dist/templates/svelte/component.js.svelte.hbs +4 -1
  36. package/dist/templates/svelte/component.ts.svelte.hbs +4 -1
  37. package/dist/templates/svelte/icon.js.svelte.hbs +23 -2
  38. package/dist/templates/svelte/icon.ts.svelte.hbs +23 -2
  39. package/dist/templates/vanilla/component.ts.hbs +1 -1
  40. package/dist/templates/vanilla/createIcon.js.hbs +12 -3
  41. package/dist/templates/vanilla/createIcon.ts.hbs +13 -3
  42. package/dist/templates/vue/component.js.vue.hbs +5 -2
  43. package/dist/templates/vue/component.ts.vue.hbs +5 -2
  44. package/dist/templates/vue/icon.js.vue.hbs +26 -2
  45. package/dist/templates/vue/icon.ts.vue.hbs +26 -2
  46. package/dist/templates/vue2/component.js.vue.hbs +4 -2
  47. package/dist/templates/vue2/component.ts.vue.hbs +5 -2
  48. package/dist/templates/vue2/icon.js.vue.hbs +25 -2
  49. package/dist/templates/vue2/icon.ts.vue.hbs +25 -2
  50. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import {
2
+ __require,
2
3
  ensureDir,
3
4
  fileExists,
4
5
  findProjectRoot,
@@ -6,7 +7,7 @@ import {
6
7
  getSvgFiles,
7
8
  readFile,
8
9
  writeFile
9
- } from "./chunk-CIKTK6HI.mjs";
10
+ } from "./chunk-FS34P27H.mjs";
10
11
 
11
12
  // src/parsers/svg-parser.ts
12
13
  import * as cheerio from "cheerio";
@@ -28,6 +29,7 @@ function parseSvg(svgContent) {
28
29
  if (svgElement.length === 0) {
29
30
  throw new Error("Invalid SVG: No <svg> tag found");
30
31
  }
32
+ const viewBox = svgElement.attr("viewBox") || "0 0 24 24";
31
33
  const iconNodes = [];
32
34
  svgElement.children().each((_, element) => {
33
35
  const node = parseElement($, element);
@@ -35,7 +37,29 @@ function parseSvg(svgContent) {
35
37
  iconNodes.push(node);
36
38
  }
37
39
  });
38
- return iconNodes;
40
+ const isMultiColor = detectMultiColor(iconNodes);
41
+ return { iconNodes, viewBox, isMultiColor };
42
+ }
43
+ function detectMultiColor(nodes) {
44
+ const colors = /* @__PURE__ */ new Set();
45
+ function collectColors(node) {
46
+ const [, attrs, children] = node;
47
+ if (attrs.fill && attrs.fill !== "none" && attrs.fill !== "transparent") {
48
+ colors.add(String(attrs.fill).toLowerCase());
49
+ }
50
+ if (attrs.stroke && attrs.stroke !== "none" && attrs.stroke !== "transparent") {
51
+ colors.add(String(attrs.stroke).toLowerCase());
52
+ }
53
+ if (children && children.length > 0) {
54
+ children.forEach(collectColors);
55
+ }
56
+ }
57
+ nodes.forEach(collectColors);
58
+ const realColors = Array.from(colors).filter((color) => {
59
+ const c = color.toLowerCase();
60
+ return c !== "currentcolor";
61
+ });
62
+ return realColors.length >= 2;
39
63
  }
40
64
  function parseElement($, element) {
41
65
  if (element.type !== "tag") {
@@ -164,14 +188,15 @@ function getQwikTemplatePath(typescript, type) {
164
188
  function getAngularTemplatePath(type) {
165
189
  return `angular/${type}.ts.hbs`;
166
190
  }
167
- function generateAngularComponent(componentName, iconNode, typescript, keepColors = false) {
191
+ function generateAngularComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
168
192
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
169
193
  const templatePath = getAngularTemplatePath("component");
170
194
  return renderTemplate(templatePath, {
171
195
  componentName,
172
196
  formattedNodes,
173
197
  typescript,
174
- keepColors
198
+ keepColors,
199
+ viewBox
175
200
  });
176
201
  }
177
202
  function generateAngularBaseComponent(typescript) {
@@ -186,14 +211,15 @@ function generateAngularBaseComponent(typescript) {
186
211
  function getAstroTemplatePath(type) {
187
212
  return `astro/${type}.astro.hbs`;
188
213
  }
189
- function generateAstroComponent(componentName, iconNode, typescript, keepColors = false) {
214
+ function generateAstroComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
190
215
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
191
216
  const templatePath = getAstroTemplatePath("component");
192
217
  return renderTemplate(templatePath, {
193
218
  componentName,
194
219
  formattedNodes,
195
220
  typescript,
196
- keepColors
221
+ keepColors,
222
+ viewBox
197
223
  });
198
224
  }
199
225
  function generateAstroBaseComponent(typescript) {
@@ -209,14 +235,15 @@ function getLitTemplatePath(typescript, type) {
209
235
  const ext = typescript ? "ts" : "js";
210
236
  return `lit/${type}.${ext}.hbs`;
211
237
  }
212
- function generateLitComponent(componentName, iconNode, typescript, keepColors = false) {
238
+ function generateLitComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
213
239
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
214
240
  const templatePath = getLitTemplatePath(typescript, "component");
215
241
  return renderTemplate(templatePath, {
216
242
  componentName,
217
243
  formattedNodes,
218
244
  typescript,
219
- keepColors
245
+ keepColors,
246
+ viewBox
220
247
  });
221
248
  }
222
249
  function generateLitBaseComponent(typescript) {
@@ -228,14 +255,15 @@ function generateLitBaseComponent(typescript) {
228
255
  }
229
256
 
230
257
  // src/generators/react.ts
231
- function generateReactComponent(componentName, iconNode, typescript, keepColors = false) {
258
+ function generateReactComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
232
259
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
233
260
  const templatePath = getReactTemplatePath(typescript, "component");
234
261
  return renderTemplate(templatePath, {
235
262
  typescript,
236
263
  componentName,
237
264
  formattedNodes,
238
- keepColors
265
+ keepColors,
266
+ viewBox
239
267
  });
240
268
  }
241
269
  function generateCreateIcon(typescript) {
@@ -244,7 +272,7 @@ function generateCreateIcon(typescript) {
244
272
  }
245
273
 
246
274
  // src/generators/react-like.ts
247
- function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, framework) {
275
+ function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, viewBox, framework) {
248
276
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
249
277
  let templatePath;
250
278
  if (framework === "solid") {
@@ -260,7 +288,8 @@ function generateReactLikeComponent(componentName, iconNode, typescript, keepCol
260
288
  componentName,
261
289
  formattedNodes,
262
290
  typescript,
263
- keepColors
291
+ keepColors,
292
+ viewBox
264
293
  });
265
294
  }
266
295
  function generateReactLikeBaseComponent(typescript, framework) {
@@ -281,12 +310,14 @@ function generateReactLikeBaseComponent(typescript, framework) {
281
310
  }
282
311
 
283
312
  // src/generators/svelte.ts
284
- function generateSvelteComponent(_componentName, iconNode, typescript) {
313
+ function generateSvelteComponent(_componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
285
314
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
286
315
  const templatePath = getSvelteTemplatePath(typescript, "component");
287
316
  return renderTemplate(templatePath, {
288
317
  typescript,
289
- formattedNodes
318
+ formattedNodes,
319
+ keepColors,
320
+ viewBox
290
321
  });
291
322
  }
292
323
  function generateSvelteIcon(typescript) {
@@ -295,14 +326,15 @@ function generateSvelteIcon(typescript) {
295
326
  }
296
327
 
297
328
  // src/generators/vanilla.ts
298
- function generateVanillaComponent(componentName, iconNode, typescript, keepColors) {
329
+ function generateVanillaComponent(componentName, iconNode, typescript, keepColors, viewBox) {
299
330
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
300
331
  const templatePath = getVanillaTemplatePath(typescript, "component");
301
332
  return renderTemplate(templatePath, {
302
333
  componentName,
303
334
  formattedNodes,
304
335
  typescript,
305
- keepColors
336
+ keepColors,
337
+ viewBox
306
338
  });
307
339
  }
308
340
  function generateVanillaBaseComponent(typescript) {
@@ -314,13 +346,15 @@ function generateVanillaBaseComponent(typescript) {
314
346
  }
315
347
 
316
348
  // src/generators/vue.ts
317
- function generateVueComponent(componentName, iconNode, typescript) {
349
+ function generateVueComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
318
350
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
319
351
  const templatePath = getVueTemplatePath(typescript, "component");
320
352
  return renderTemplate(templatePath, {
321
353
  typescript,
322
354
  componentName,
323
- formattedNodes
355
+ formattedNodes,
356
+ keepColors,
357
+ viewBox
324
358
  });
325
359
  }
326
360
  function generateVueIcon(typescript) {
@@ -329,13 +363,15 @@ function generateVueIcon(typescript) {
329
363
  }
330
364
 
331
365
  // src/generators/vue2.ts
332
- function generateVue2Component(componentName, iconNode, typescript) {
366
+ function generateVue2Component(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
333
367
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
334
368
  const templatePath = getVue2TemplatePath(typescript, "component");
335
369
  return renderTemplate(templatePath, {
336
370
  typescript,
337
371
  componentName,
338
- formattedNodes
372
+ formattedNodes,
373
+ keepColors,
374
+ viewBox
339
375
  });
340
376
  }
341
377
  function generateVue2Icon(typescript) {
@@ -353,8 +389,8 @@ var ReactStrategy = class {
353
389
  this.getIndexExtension = (typescript) => {
354
390
  return typescript ? "ts" : "js";
355
391
  };
356
- this.generateComponent = (componentName, iconNode, typescript, keepColors = false) => {
357
- return generateReactComponent(componentName, iconNode, typescript, keepColors);
392
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
393
+ return generateReactComponent(componentName, iconNode, typescript, keepColors, viewBox);
358
394
  };
359
395
  this.generateBaseComponent = (typescript) => {
360
396
  return {
@@ -373,8 +409,8 @@ var VueStrategy = class {
373
409
  this.getIndexExtension = (typescript) => {
374
410
  return typescript ? "ts" : "js";
375
411
  };
376
- this.generateComponent = (componentName, iconNode, typescript) => {
377
- return generateVueComponent(componentName, iconNode, typescript);
412
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
413
+ return generateVueComponent(componentName, iconNode, typescript, keepColors, viewBox);
378
414
  };
379
415
  this.generateBaseComponent = (typescript) => {
380
416
  return {
@@ -393,8 +429,8 @@ var Vue2Strategy = class {
393
429
  this.getIndexExtension = (typescript) => {
394
430
  return typescript ? "ts" : "js";
395
431
  };
396
- this.generateComponent = (componentName, iconNode, typescript) => {
397
- return generateVue2Component(componentName, iconNode, typescript);
432
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
433
+ return generateVue2Component(componentName, iconNode, typescript, keepColors, viewBox);
398
434
  };
399
435
  this.generateBaseComponent = (typescript) => {
400
436
  return {
@@ -413,8 +449,8 @@ var SvelteStrategy = class {
413
449
  this.getIndexExtension = (typescript) => {
414
450
  return typescript ? "ts" : "js";
415
451
  };
416
- this.generateComponent = (componentName, iconNode, typescript) => {
417
- return generateSvelteComponent(componentName, iconNode, typescript);
452
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
453
+ return generateSvelteComponent(componentName, iconNode, typescript, keepColors, viewBox);
418
454
  };
419
455
  this.generateBaseComponent = (typescript) => {
420
456
  return {
@@ -433,8 +469,8 @@ var SolidStrategy = class {
433
469
  this.getIndexExtension = (typescript) => {
434
470
  return typescript ? "ts" : "js";
435
471
  };
436
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
437
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "solid");
472
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
473
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "solid");
438
474
  };
439
475
  this.generateBaseComponent = (typescript) => {
440
476
  return generateReactLikeBaseComponent(typescript, "solid");
@@ -450,8 +486,8 @@ var PreactStrategy = class {
450
486
  this.getIndexExtension = (typescript) => {
451
487
  return typescript ? "ts" : "js";
452
488
  };
453
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
454
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "preact");
489
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
490
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "preact");
455
491
  };
456
492
  this.generateBaseComponent = (typescript) => {
457
493
  return generateReactLikeBaseComponent(typescript, "preact");
@@ -467,8 +503,8 @@ var VanillaStrategy = class {
467
503
  this.getIndexExtension = (typescript) => {
468
504
  return typescript ? "ts" : "js";
469
505
  };
470
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
471
- return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false);
506
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
507
+ return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
472
508
  };
473
509
  this.generateBaseComponent = (typescript) => {
474
510
  return generateVanillaBaseComponent(typescript);
@@ -484,8 +520,8 @@ var LitStrategy = class {
484
520
  this.getIndexExtension = (typescript) => {
485
521
  return typescript ? "ts" : "js";
486
522
  };
487
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
488
- return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false);
523
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
524
+ return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
489
525
  };
490
526
  this.generateBaseComponent = (typescript) => {
491
527
  return generateLitBaseComponent(typescript);
@@ -501,8 +537,8 @@ var QwikStrategy = class {
501
537
  this.getIndexExtension = (typescript) => {
502
538
  return typescript ? "ts" : "js";
503
539
  };
504
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
505
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "qwik");
540
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
541
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "qwik");
506
542
  };
507
543
  this.generateBaseComponent = (typescript) => {
508
544
  return generateReactLikeBaseComponent(typescript, "qwik");
@@ -518,8 +554,8 @@ var AstroStrategy = class {
518
554
  this.getIndexExtension = (typescript) => {
519
555
  return typescript ? "ts" : "js";
520
556
  };
521
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
522
- return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false);
557
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
558
+ return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
523
559
  };
524
560
  this.generateBaseComponent = (typescript) => {
525
561
  return generateAstroBaseComponent(typescript);
@@ -535,8 +571,8 @@ var AngularStrategy = class {
535
571
  this.getIndexExtension = (_typescript) => {
536
572
  return "ts";
537
573
  };
538
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
539
- return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false);
574
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
575
+ return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
540
576
  };
541
577
  this.generateBaseComponent = (typescript) => {
542
578
  return generateAngularBaseComponent(typescript);
@@ -601,7 +637,6 @@ var DEFAULT_CONFIG = {
601
637
  output: "./src/icons",
602
638
  typescript: true,
603
639
  optimize: true,
604
- keepColors: false,
605
640
  prefix: "",
606
641
  suffix: "",
607
642
  configDir: ".",
@@ -664,7 +699,7 @@ async function loadConfig(configPath) {
664
699
  return mergedConfig;
665
700
  }
666
701
  async function findConfig() {
667
- const { fileExists: fileExists2 } = await import("./helpers-UPZEBRGK.mjs");
702
+ const { fileExists: fileExists2 } = await import("./helpers-ZOR3OD66.mjs");
668
703
  const configFiles = [
669
704
  "vectify.config.ts",
670
705
  "vectify.config.js"
@@ -681,6 +716,37 @@ async function findConfig() {
681
716
  // src/generators/index.ts
682
717
  import path4 from "path";
683
718
 
719
+ // src/cache/hash-utils.ts
720
+ import { createHash } from "crypto";
721
+ import { readFile as readFile2 } from "fs/promises";
722
+ function hashContent(content) {
723
+ return createHash("sha256").update(content).digest("hex");
724
+ }
725
+ async function hashFile(filePath) {
726
+ try {
727
+ const content = await readFile2(filePath, "utf-8");
728
+ return hashContent(content);
729
+ } catch (error) {
730
+ throw new Error(`Failed to hash file ${filePath}: ${error}`);
731
+ }
732
+ }
733
+ function hashConfig(config) {
734
+ const relevantConfig = {
735
+ framework: config.framework,
736
+ typescript: config.typescript,
737
+ keepColors: config.keepColors,
738
+ prefix: config.prefix,
739
+ suffix: config.suffix,
740
+ optimize: config.optimize,
741
+ svgoConfig: config.svgoConfig,
742
+ componentNameTransform: config.componentNameTransform?.toString()
743
+ };
744
+ return hashContent(JSON.stringify(relevantConfig));
745
+ }
746
+ function hashSvgoConfig(svgoConfig) {
747
+ return hashContent(JSON.stringify(svgoConfig || {}));
748
+ }
749
+
684
750
  // src/parsers/optimizer.ts
685
751
  import { optimize } from "svgo";
686
752
  var DEFAULT_SVGO_CONFIG = {
@@ -864,27 +930,130 @@ async function generateIcons(config, dryRun = false) {
864
930
  }
865
931
  return stats;
866
932
  }
867
- async function generateIconComponent(svgFile, config, dryRun = false) {
933
+ async function generateIconsIncremental(config, cacheManager, dryRun = false) {
934
+ const stats = {
935
+ success: 0,
936
+ failed: 0,
937
+ total: 0,
938
+ errors: []
939
+ };
940
+ try {
941
+ if (!dryRun) {
942
+ await ensureDir(config.output);
943
+ }
944
+ const svgFiles = await getSvgFiles(config.input);
945
+ stats.total = svgFiles.length;
946
+ if (svgFiles.length === 0) {
947
+ console.warn(`No SVG files found in ${config.input}`);
948
+ return stats;
949
+ }
950
+ if (cacheManager.hasConfigChanged(config)) {
951
+ console.log("\u26A0\uFE0F Configuration changed, rebuilding all icons...");
952
+ cacheManager.invalidateAll();
953
+ }
954
+ cacheManager.updateConfigHash(config);
955
+ await cacheManager.cleanStaleEntries(svgFiles);
956
+ if (config.generateOptions?.cleanOutput && !dryRun) {
957
+ await cleanOutputDirectory(svgFiles, config);
958
+ }
959
+ await generateBaseComponent(config, dryRun);
960
+ const strategy = getFrameworkStrategy(config.framework);
961
+ const typescript = config.typescript ?? true;
962
+ const fileExt = strategy.getComponentExtension(typescript);
963
+ const filesToGenerate = [];
964
+ const cachedFiles = [];
965
+ for (const svgFile of svgFiles) {
966
+ const fileName = path4.basename(svgFile);
967
+ const componentName = getComponentName(
968
+ fileName,
969
+ config.prefix,
970
+ config.suffix,
971
+ config.componentNameTransform
972
+ );
973
+ const componentPath = path4.join(config.output, `${componentName}.${fileExt}`);
974
+ const needsRegen = await cacheManager.needsRegeneration(
975
+ svgFile,
976
+ componentPath,
977
+ config
978
+ );
979
+ if (needsRegen) {
980
+ filesToGenerate.push(svgFile);
981
+ } else {
982
+ cachedFiles.push(svgFile);
983
+ }
984
+ }
985
+ if (cachedFiles.length > 0) {
986
+ console.log(`\u{1F4E6} Cache: ${cachedFiles.length} cached, ${filesToGenerate.length} to generate`);
987
+ }
988
+ for (const svgFile of filesToGenerate) {
989
+ try {
990
+ await generateIconComponent(svgFile, config, dryRun, cacheManager);
991
+ stats.success++;
992
+ } catch (error) {
993
+ stats.failed++;
994
+ stats.errors.push({
995
+ file: svgFile,
996
+ error: error.message
997
+ });
998
+ console.error(`Failed to generate ${svgFile}: ${error.message}`);
999
+ }
1000
+ }
1001
+ stats.success += cachedFiles.length;
1002
+ if (config.generateOptions?.index) {
1003
+ await generateIndexFile(svgFiles, config, dryRun);
1004
+ }
1005
+ if (config.generateOptions?.preview && !dryRun) {
1006
+ await generatePreviewHtml(svgFiles, config);
1007
+ }
1008
+ if (!dryRun) {
1009
+ await cacheManager.save();
1010
+ const cacheStats = cacheManager.getStats();
1011
+ if (cacheStats.total > 0) {
1012
+ const hitRate = cacheManager.getHitRate();
1013
+ const timeSaved = (cacheStats.timeSaved / 1e3).toFixed(1);
1014
+ console.log(`\u26A1 Cache saved ~${timeSaved}s (${hitRate.toFixed(1)}% hit rate)`);
1015
+ }
1016
+ }
1017
+ if (config.format && !dryRun) {
1018
+ const formatResult = await formatOutput(config.output, config.format);
1019
+ if (formatResult.success && formatResult.tool) {
1020
+ console.log(`Formatted with ${formatResult.tool}`);
1021
+ } else if (formatResult.error) {
1022
+ console.warn(formatResult.error);
1023
+ }
1024
+ }
1025
+ if (config.hooks?.onComplete) {
1026
+ await config.hooks.onComplete(stats);
1027
+ }
1028
+ } catch (error) {
1029
+ throw new Error(`Generation failed: ${error.message}`);
1030
+ }
1031
+ return stats;
1032
+ }
1033
+ async function generateIconComponent(svgFile, config, dryRun = false, cacheManager) {
868
1034
  let svgContent = await readFile(svgFile);
869
1035
  const fileName = path4.basename(svgFile);
870
1036
  if (config.hooks?.beforeParse) {
871
1037
  svgContent = await config.hooks.beforeParse(svgContent, fileName);
872
1038
  }
1039
+ const { isMultiColor: isMultiColorBeforeOptimization } = parseSvg(svgContent);
873
1040
  svgContent = await optimizeSvg(svgContent, config);
874
- const iconNode = parseSvg(svgContent);
1041
+ const { iconNodes, viewBox } = parseSvg(svgContent);
875
1042
  const componentName = getComponentName(
876
1043
  fileName,
877
1044
  config.prefix,
878
1045
  config.suffix,
879
- config.transform
1046
+ config.componentNameTransform
880
1047
  );
881
1048
  const strategy = getFrameworkStrategy(config.framework);
882
1049
  const typescript = config.typescript ?? true;
1050
+ const keepColors = config.keepColors !== void 0 ? config.keepColors : isMultiColorBeforeOptimization;
883
1051
  let code = strategy.generateComponent(
884
1052
  componentName,
885
- iconNode,
1053
+ iconNodes,
886
1054
  typescript,
887
- config.keepColors ?? false
1055
+ keepColors,
1056
+ viewBox
888
1057
  );
889
1058
  if (config.hooks?.afterGenerate) {
890
1059
  code = await config.hooks.afterGenerate(code, componentName);
@@ -895,6 +1064,16 @@ async function generateIconComponent(svgFile, config, dryRun = false) {
895
1064
  console.log(` ${componentName}.${fileExt}`);
896
1065
  } else {
897
1066
  await writeFile(outputPath, code);
1067
+ if (cacheManager) {
1068
+ const componentHash = hashContent(code);
1069
+ await cacheManager.updateEntry(
1070
+ svgFile,
1071
+ outputPath,
1072
+ componentName,
1073
+ componentHash,
1074
+ config
1075
+ );
1076
+ }
898
1077
  }
899
1078
  }
900
1079
  async function generateBaseComponent(config, dryRun = false) {
@@ -912,19 +1091,22 @@ async function generateIndexFile(svgFiles, config, dryRun = false) {
912
1091
  const typescript = config.typescript ?? true;
913
1092
  const strategy = getFrameworkStrategy(config.framework);
914
1093
  const ext = strategy.getIndexExtension(typescript);
1094
+ const componentExt = strategy.getComponentExtension(typescript);
915
1095
  const usesDefaultExport = ["vue", "vue2", "svelte", "react", "preact"].includes(config.framework);
1096
+ const needsExtension = ["vue", "vue2", "svelte"].includes(config.framework);
916
1097
  const exports = svgFiles.map((svgFile) => {
917
1098
  const fileName = path4.basename(svgFile);
918
1099
  const componentName = getComponentName(
919
1100
  fileName,
920
1101
  config.prefix,
921
1102
  config.suffix,
922
- config.transform
1103
+ config.componentNameTransform
923
1104
  );
1105
+ const importPath = needsExtension ? `./${componentName}.${componentExt}` : `./${componentName}`;
924
1106
  if (usesDefaultExport) {
925
- return `export { default as ${componentName} } from './${componentName}'`;
1107
+ return `export { default as ${componentName} } from '${importPath}'`;
926
1108
  } else {
927
- return `export { ${componentName} } from './${componentName}'`;
1109
+ return `export { ${componentName} } from '${importPath}'`;
928
1110
  }
929
1111
  }).join("\n");
930
1112
  const indexPath = path4.join(config.output, `index.${ext}`);
@@ -942,7 +1124,7 @@ async function generatePreviewHtml(svgFiles, config) {
942
1124
  fileName,
943
1125
  config.prefix,
944
1126
  config.suffix,
945
- config.transform
1127
+ config.componentNameTransform
946
1128
  );
947
1129
  });
948
1130
  const svgContents = await Promise.all(
@@ -1123,7 +1305,7 @@ async function cleanOutputDirectory(svgFiles, config) {
1123
1305
  fileName,
1124
1306
  config.prefix,
1125
1307
  config.suffix,
1126
- config.transform
1308
+ config.componentNameTransform
1127
1309
  );
1128
1310
  return `${componentName}.${fileExt}`;
1129
1311
  })
@@ -1158,6 +1340,255 @@ async function cleanOutputDirectory(svgFiles, config) {
1158
1340
  // src/commands/generate.ts
1159
1341
  import chalk from "chalk";
1160
1342
  import ora from "ora";
1343
+
1344
+ // src/cache/cache-manager.ts
1345
+ import { existsSync } from "fs";
1346
+ import { mkdir, readFile as readFile3, stat, writeFile as writeFile2 } from "fs/promises";
1347
+ import { dirname, join } from "path";
1348
+ var CACHE_VERSION = "1.0.0";
1349
+ var CacheManager = class {
1350
+ constructor(outputDir, cacheDir = ".vectify") {
1351
+ this.cache = {
1352
+ version: CACHE_VERSION,
1353
+ configHash: "",
1354
+ entries: {},
1355
+ baseComponentHash: ""
1356
+ };
1357
+ this.cacheFilePath = join(outputDir, cacheDir, "cache.json");
1358
+ this.stats = {
1359
+ hits: 0,
1360
+ misses: 0,
1361
+ total: 0,
1362
+ timeSaved: 0
1363
+ };
1364
+ this.isDirty = false;
1365
+ process.on("beforeExit", () => {
1366
+ if (this.isDirty) {
1367
+ this.saveSync();
1368
+ }
1369
+ });
1370
+ }
1371
+ /**
1372
+ * Load cache from disk
1373
+ */
1374
+ async load() {
1375
+ try {
1376
+ if (!existsSync(this.cacheFilePath)) {
1377
+ return;
1378
+ }
1379
+ const content = await readFile3(this.cacheFilePath, "utf-8");
1380
+ const loadedCache = JSON.parse(content);
1381
+ if (loadedCache.version !== CACHE_VERSION) {
1382
+ console.log("\u26A0\uFE0F Cache version mismatch, rebuilding cache...");
1383
+ return;
1384
+ }
1385
+ this.cache = loadedCache;
1386
+ } catch (error) {
1387
+ console.warn("\u26A0\uFE0F Failed to load cache, starting fresh:", error);
1388
+ this.cache = {
1389
+ version: CACHE_VERSION,
1390
+ configHash: "",
1391
+ entries: {},
1392
+ baseComponentHash: ""
1393
+ };
1394
+ }
1395
+ }
1396
+ /**
1397
+ * Save cache to disk (atomic write)
1398
+ */
1399
+ async save() {
1400
+ try {
1401
+ const cacheDir = dirname(this.cacheFilePath);
1402
+ await mkdir(cacheDir, { recursive: true });
1403
+ const tempPath = `${this.cacheFilePath}.tmp`;
1404
+ await writeFile2(tempPath, JSON.stringify(this.cache, null, 2), "utf-8");
1405
+ await writeFile2(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
1406
+ this.isDirty = false;
1407
+ } catch (error) {
1408
+ console.warn("\u26A0\uFE0F Failed to save cache:", error);
1409
+ }
1410
+ }
1411
+ /**
1412
+ * Synchronous save for process exit
1413
+ */
1414
+ saveSync() {
1415
+ try {
1416
+ const fs2 = __require("fs");
1417
+ const cacheDir = dirname(this.cacheFilePath);
1418
+ if (!fs2.existsSync(cacheDir)) {
1419
+ fs2.mkdirSync(cacheDir, { recursive: true });
1420
+ }
1421
+ fs2.writeFileSync(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
1422
+ this.isDirty = false;
1423
+ } catch {
1424
+ console.warn("\u26A0\uFE0F Failed to save cache on exit");
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Check if a file needs regeneration
1429
+ */
1430
+ async needsRegeneration(svgPath, componentPath, config) {
1431
+ this.stats.total++;
1432
+ if (!existsSync(componentPath)) {
1433
+ this.stats.misses++;
1434
+ return true;
1435
+ }
1436
+ const entry = this.cache.entries[svgPath];
1437
+ if (!entry) {
1438
+ this.stats.misses++;
1439
+ return true;
1440
+ }
1441
+ try {
1442
+ const stats = await stat(svgPath);
1443
+ const currentMtime = stats.mtimeMs;
1444
+ if (currentMtime === entry.svgMtime) {
1445
+ if (!this.isConfigMatching(entry, config)) {
1446
+ this.stats.misses++;
1447
+ return true;
1448
+ }
1449
+ this.stats.hits++;
1450
+ this.stats.timeSaved += 50;
1451
+ return false;
1452
+ }
1453
+ const currentHash = await hashFile(svgPath);
1454
+ if (currentHash === entry.svgHash) {
1455
+ entry.svgMtime = currentMtime;
1456
+ this.isDirty = true;
1457
+ this.stats.hits++;
1458
+ this.stats.timeSaved += 50;
1459
+ return false;
1460
+ }
1461
+ this.stats.misses++;
1462
+ return true;
1463
+ } catch {
1464
+ this.stats.misses++;
1465
+ return true;
1466
+ }
1467
+ }
1468
+ /**
1469
+ * Check if config matches cache entry
1470
+ */
1471
+ isConfigMatching(entry, config) {
1472
+ const snapshot = entry.configSnapshot;
1473
+ return snapshot.framework === config.framework && snapshot.typescript === config.typescript && snapshot.keepColors === config.keepColors && snapshot.prefix === (config.prefix || "") && snapshot.suffix === (config.suffix || "") && snapshot.optimize === config.optimize && snapshot.svgoConfigHash === hashSvgoConfig(config.svgoConfig);
1474
+ }
1475
+ /**
1476
+ * Update cache entry after successful generation
1477
+ */
1478
+ async updateEntry(svgPath, componentPath, componentName, componentHash, config) {
1479
+ try {
1480
+ const svgHash = await hashFile(svgPath);
1481
+ const stats = await stat(svgPath);
1482
+ this.cache.entries[svgPath] = {
1483
+ svgPath,
1484
+ svgHash,
1485
+ svgMtime: stats.mtimeMs,
1486
+ componentName,
1487
+ componentPath,
1488
+ componentHash,
1489
+ configSnapshot: {
1490
+ framework: config.framework,
1491
+ typescript: config.typescript ?? true,
1492
+ keepColors: config.keepColors ?? false,
1493
+ prefix: config.prefix || "",
1494
+ suffix: config.suffix || "",
1495
+ optimize: config.optimize ?? true,
1496
+ svgoConfigHash: hashSvgoConfig(config.svgoConfig)
1497
+ },
1498
+ generatedAt: Date.now()
1499
+ };
1500
+ this.isDirty = true;
1501
+ } catch (error) {
1502
+ console.warn(`\u26A0\uFE0F Failed to update cache entry for ${svgPath}:`, error);
1503
+ }
1504
+ }
1505
+ /**
1506
+ * Remove cache entry
1507
+ */
1508
+ removeEntry(svgPath) {
1509
+ if (this.cache.entries[svgPath]) {
1510
+ delete this.cache.entries[svgPath];
1511
+ this.isDirty = true;
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Update config hash
1516
+ */
1517
+ updateConfigHash(config) {
1518
+ const newHash = hashConfig(config);
1519
+ if (this.cache.configHash !== newHash) {
1520
+ this.cache.configHash = newHash;
1521
+ this.isDirty = true;
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Update base component hash
1526
+ */
1527
+ updateBaseComponentHash(hash) {
1528
+ if (this.cache.baseComponentHash !== hash) {
1529
+ this.cache.baseComponentHash = hash;
1530
+ this.isDirty = true;
1531
+ }
1532
+ }
1533
+ /**
1534
+ * Check if config has changed (invalidates all cache)
1535
+ */
1536
+ hasConfigChanged(config) {
1537
+ const currentHash = hashConfig(config);
1538
+ return this.cache.configHash !== "" && this.cache.configHash !== currentHash;
1539
+ }
1540
+ /**
1541
+ * Invalidate all cache entries
1542
+ */
1543
+ invalidateAll() {
1544
+ this.cache.entries = {};
1545
+ this.isDirty = true;
1546
+ }
1547
+ /**
1548
+ * Clean stale entries (files that no longer exist)
1549
+ */
1550
+ async cleanStaleEntries(existingSvgPaths) {
1551
+ const existingSet = new Set(existingSvgPaths);
1552
+ let cleaned = 0;
1553
+ for (const svgPath of Object.keys(this.cache.entries)) {
1554
+ if (!existingSet.has(svgPath)) {
1555
+ delete this.cache.entries[svgPath];
1556
+ cleaned++;
1557
+ this.isDirty = true;
1558
+ }
1559
+ }
1560
+ if (cleaned > 0) {
1561
+ console.log(`\u{1F9F9} Cleaned ${cleaned} stale cache entries`);
1562
+ }
1563
+ }
1564
+ /**
1565
+ * Get cache statistics
1566
+ */
1567
+ getStats() {
1568
+ return { ...this.stats };
1569
+ }
1570
+ /**
1571
+ * Reset statistics
1572
+ */
1573
+ resetStats() {
1574
+ this.stats = {
1575
+ hits: 0,
1576
+ misses: 0,
1577
+ total: 0,
1578
+ timeSaved: 0
1579
+ };
1580
+ }
1581
+ /**
1582
+ * Get cache hit rate
1583
+ */
1584
+ getHitRate() {
1585
+ if (this.stats.total === 0)
1586
+ return 0;
1587
+ return this.stats.hits / this.stats.total * 100;
1588
+ }
1589
+ };
1590
+
1591
+ // src/commands/generate.ts
1161
1592
  async function generate(options = {}) {
1162
1593
  const spinner = ora("Loading configuration...").start();
1163
1594
  try {
@@ -1179,9 +1610,19 @@ async function generate(options = {}) {
1179
1610
  ${chalk.bold("Files that would be generated:")}
1180
1611
  `);
1181
1612
  }
1613
+ const useIncremental = config.incremental?.enabled !== false && !options.force && !options.dryRun;
1614
+ let cacheManager = null;
1615
+ if (useIncremental) {
1616
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
1617
+ cacheManager = new CacheManager(config.output, cacheDir);
1618
+ await cacheManager.load();
1619
+ }
1620
+ if (options.force) {
1621
+ spinner.info(chalk.yellow("Force mode - ignoring cache, regenerating all icons"));
1622
+ }
1182
1623
  const actionText = options.dryRun ? "Analyzing icons..." : "Generating icon components...";
1183
1624
  spinner.start(actionText);
1184
- const stats = await generateIcons(config, options.dryRun);
1625
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager, options.dryRun) : await generateIcons(config, options.dryRun);
1185
1626
  if (stats.failed > 0) {
1186
1627
  spinner.warn(`${options.dryRun ? "Analyzed" : "Generated"} ${chalk.green(stats.success)} icons, ${chalk.red(stats.failed)} failed`);
1187
1628
  if (stats.errors.length > 0) {
@@ -1373,6 +1814,89 @@ import path6 from "path";
1373
1814
  import chalk3 from "chalk";
1374
1815
  import chokidar from "chokidar";
1375
1816
  import ora3 from "ora";
1817
+
1818
+ // src/cache/svg-validator.ts
1819
+ import { readFile as readFile4, stat as stat2 } from "fs/promises";
1820
+ import { load as load2 } from "cheerio";
1821
+ var MIN_SVG_SIZE = 20;
1822
+ var DRAWABLE_ELEMENTS = [
1823
+ "path",
1824
+ "circle",
1825
+ "rect",
1826
+ "ellipse",
1827
+ "line",
1828
+ "polyline",
1829
+ "polygon",
1830
+ "text",
1831
+ "image",
1832
+ "use"
1833
+ ];
1834
+ async function validateSVGFile(filePath) {
1835
+ try {
1836
+ const stats = await stat2(filePath);
1837
+ if (stats.size < MIN_SVG_SIZE) {
1838
+ return {
1839
+ isValid: false,
1840
+ isEmpty: true,
1841
+ reason: "File is too small to be a valid SVG"
1842
+ };
1843
+ }
1844
+ const content = await readFile4(filePath, "utf-8");
1845
+ if (!content.trim()) {
1846
+ return {
1847
+ isValid: false,
1848
+ isEmpty: true,
1849
+ reason: "File is empty"
1850
+ };
1851
+ }
1852
+ if (!content.includes("<svg")) {
1853
+ return {
1854
+ isValid: false,
1855
+ isEmpty: false,
1856
+ reason: "File does not contain <svg> tag"
1857
+ };
1858
+ }
1859
+ if (!hasDrawableContent(content)) {
1860
+ return {
1861
+ isValid: false,
1862
+ isEmpty: true,
1863
+ reason: "SVG has no drawable content"
1864
+ };
1865
+ }
1866
+ try {
1867
+ const $ = load2(content, { xmlMode: true });
1868
+ const svgElement = $("svg");
1869
+ if (svgElement.length === 0) {
1870
+ return {
1871
+ isValid: false,
1872
+ isEmpty: false,
1873
+ reason: "Failed to parse SVG element"
1874
+ };
1875
+ }
1876
+ return {
1877
+ isValid: true,
1878
+ isEmpty: false
1879
+ };
1880
+ } catch (parseError) {
1881
+ return {
1882
+ isValid: false,
1883
+ isEmpty: false,
1884
+ reason: `Failed to parse SVG: ${parseError}`
1885
+ };
1886
+ }
1887
+ } catch (error) {
1888
+ return {
1889
+ isValid: false,
1890
+ isEmpty: false,
1891
+ reason: `Failed to read file: ${error}`
1892
+ };
1893
+ }
1894
+ }
1895
+ function hasDrawableContent(content) {
1896
+ return DRAWABLE_ELEMENTS.some((element) => content.includes(`<${element}`));
1897
+ }
1898
+
1899
+ // src/commands/watch.ts
1376
1900
  async function watch(options = {}) {
1377
1901
  const spinner = ora3("Loading configuration...").start();
1378
1902
  try {
@@ -1388,16 +1912,24 @@ async function watch(options = {}) {
1388
1912
  }
1389
1913
  const config = await loadConfig(configPath);
1390
1914
  spinner.succeed(`Config loaded from ${chalk3.green(configPath)}`);
1915
+ const useIncremental = config.incremental?.enabled !== false;
1916
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
1917
+ const cacheManager = useIncremental ? new CacheManager(config.output, cacheDir) : null;
1918
+ if (cacheManager) {
1919
+ await cacheManager.load();
1920
+ }
1391
1921
  spinner.start("Generating icon components...");
1392
- const initialStats = await generateIcons(config);
1922
+ const initialStats = useIncremental && cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1393
1923
  spinner.succeed(`Generated ${chalk3.green(initialStats.success)} icon components`);
1394
1924
  const watchPath = path6.join(config.input, "**/*.svg");
1395
1925
  const debounce = config.watch?.debounce ?? 300;
1396
1926
  const ignore = config.watch?.ignore ?? ["**/node_modules/**", "**/.git/**"];
1927
+ const emptyFileRetryDelay = config.watch?.emptyFileRetryDelay ?? 2e3;
1397
1928
  console.log(chalk3.bold("\nWatching for changes..."));
1398
1929
  console.log(` ${chalk3.cyan(watchPath)}`);
1399
1930
  console.log(` ${chalk3.gray("Press Ctrl+C to stop")}
1400
1931
  `);
1932
+ const pendingChanges = /* @__PURE__ */ new Map();
1401
1933
  const debounceTimer = null;
1402
1934
  const watcher = chokidar.watch(watchPath, {
1403
1935
  ignored: ignore,
@@ -1405,12 +1937,12 @@ async function watch(options = {}) {
1405
1937
  ignoreInitial: true
1406
1938
  });
1407
1939
  watcher.on("add", (filePath) => {
1408
- handleChange("added", filePath, config, debounce, debounceTimer);
1940
+ handleChange("added", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1409
1941
  }).on("change", (filePath) => {
1410
- handleChange("changed", filePath, config, debounce, debounceTimer);
1942
+ handleChange("changed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1411
1943
  }).on("unlink", (filePath) => {
1412
1944
  console.log(chalk3.yellow(`SVG file removed: ${path6.basename(filePath)}`));
1413
- handleChange("removed", filePath, config, debounce, debounceTimer);
1945
+ handleChange("removed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1414
1946
  }).on("error", (error) => {
1415
1947
  console.error(chalk3.red(`Watcher error: ${error.message}`));
1416
1948
  });
@@ -1427,23 +1959,51 @@ ${chalk3.yellow("Stopping watch mode...")}`);
1427
1959
  throw error;
1428
1960
  }
1429
1961
  }
1430
- function handleChange(event, filePath, config, debounce, timer) {
1431
- const fileName = path6.basename(filePath);
1962
+ function handleChange(event, filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer) {
1963
+ pendingChanges.set(filePath, event);
1432
1964
  if (timer) {
1433
1965
  clearTimeout(timer);
1434
1966
  }
1435
1967
  timer = setTimeout(async () => {
1436
- const spinner = ora3(`Regenerating icons...`).start();
1968
+ const changes = Array.from(pendingChanges.entries());
1969
+ pendingChanges.clear();
1970
+ const validChanges = [];
1971
+ const invalidFiles = [];
1972
+ for (const [file, changeEvent] of changes) {
1973
+ if (changeEvent === "removed") {
1974
+ validChanges.push([file, changeEvent]);
1975
+ continue;
1976
+ }
1977
+ const validation = await validateSVGFile(file);
1978
+ if (!validation.isValid) {
1979
+ if (validation.isEmpty) {
1980
+ console.log(chalk3.yellow(`\u23F3 Waiting for content: ${path6.basename(file)}`));
1981
+ setTimeout(() => {
1982
+ handleChange(changeEvent, file, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer);
1983
+ }, emptyFileRetryDelay);
1984
+ } else {
1985
+ console.error(chalk3.red(`\u274C Invalid SVG: ${path6.basename(file)} - ${validation.reason}`));
1986
+ invalidFiles.push(file);
1987
+ }
1988
+ continue;
1989
+ }
1990
+ validChanges.push([file, changeEvent]);
1991
+ }
1992
+ if (validChanges.length === 0) {
1993
+ return;
1994
+ }
1995
+ const spinner = ora3(`Processing ${validChanges.length} change(s)...`).start();
1437
1996
  try {
1438
- const stats = await generateIcons(config);
1997
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1439
1998
  if (stats.failed > 0) {
1440
1999
  spinner.warn(
1441
2000
  `Regenerated ${chalk3.green(stats.success)} icons, ${chalk3.red(stats.failed)} failed`
1442
2001
  );
1443
2002
  } else {
1444
- spinner.succeed(`Regenerated ${chalk3.green(stats.success)} icon components`);
2003
+ spinner.succeed(`\u2713 Updated ${chalk3.green(stats.success)} icon components`);
1445
2004
  }
1446
- console.log(chalk3.gray(` Triggered by: ${fileName} (${event})
2005
+ const triggerFiles = validChanges.map(([file, evt]) => `${path6.basename(file)} (${evt})`).join(", ");
2006
+ console.log(chalk3.gray(` Triggered by: ${triggerFiles}
1447
2007
  `));
1448
2008
  } catch (error) {
1449
2009
  spinner.fail("Regeneration failed");