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
package/dist/index.js CHANGED
@@ -40,10 +40,10 @@ __export(helpers_exports, {
40
40
  getComponentName: () => getComponentName,
41
41
  getSvgFiles: () => getSvgFiles,
42
42
  mergeClasses: () => mergeClasses,
43
- readFile: () => readFile,
43
+ readFile: () => readFile3,
44
44
  toKebabCase: () => toKebabCase,
45
45
  toPascalCase: () => toPascalCase,
46
- writeFile: () => writeFile
46
+ writeFile: () => writeFile2
47
47
  });
48
48
  function toPascalCase(str) {
49
49
  return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^(.)/, (char) => char.toUpperCase()).replace(/[^a-z0-9]/gi, "");
@@ -93,11 +93,11 @@ async function fileExists(filePath) {
93
93
  return false;
94
94
  }
95
95
  }
96
- async function readFile(filePath) {
96
+ async function readFile3(filePath) {
97
97
  const fs2 = await import("fs/promises");
98
98
  return await fs2.readFile(filePath, "utf-8");
99
99
  }
100
- async function writeFile(filePath, content) {
100
+ async function writeFile2(filePath, content) {
101
101
  const fs2 = await import("fs/promises");
102
102
  await fs2.writeFile(filePath, content, "utf-8");
103
103
  }
@@ -151,8 +151,288 @@ module.exports = __toCommonJS(src_exports);
151
151
  var import_chalk = __toESM(require("chalk"));
152
152
  var import_ora = __toESM(require("ora"));
153
153
 
154
+ // src/cache/cache-manager.ts
155
+ var import_node_fs = require("fs");
156
+ var import_promises2 = require("fs/promises");
157
+ var import_node_path = require("path");
158
+
159
+ // src/cache/hash-utils.ts
160
+ var import_node_crypto = require("crypto");
161
+ var import_promises = require("fs/promises");
162
+ function hashContent(content) {
163
+ return (0, import_node_crypto.createHash)("sha256").update(content).digest("hex");
164
+ }
165
+ async function hashFile(filePath) {
166
+ try {
167
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
168
+ return hashContent(content);
169
+ } catch (error) {
170
+ throw new Error(`Failed to hash file ${filePath}: ${error}`);
171
+ }
172
+ }
173
+ function hashConfig(config) {
174
+ const relevantConfig = {
175
+ framework: config.framework,
176
+ typescript: config.typescript,
177
+ keepColors: config.keepColors,
178
+ prefix: config.prefix,
179
+ suffix: config.suffix,
180
+ optimize: config.optimize,
181
+ svgoConfig: config.svgoConfig,
182
+ componentNameTransform: config.componentNameTransform?.toString()
183
+ };
184
+ return hashContent(JSON.stringify(relevantConfig));
185
+ }
186
+ function hashSvgoConfig(svgoConfig) {
187
+ return hashContent(JSON.stringify(svgoConfig || {}));
188
+ }
189
+
190
+ // src/cache/cache-manager.ts
191
+ var CACHE_VERSION = "1.0.0";
192
+ var CacheManager = class {
193
+ constructor(outputDir, cacheDir = ".vectify") {
194
+ this.cache = {
195
+ version: CACHE_VERSION,
196
+ configHash: "",
197
+ entries: {},
198
+ baseComponentHash: ""
199
+ };
200
+ this.cacheFilePath = (0, import_node_path.join)(outputDir, cacheDir, "cache.json");
201
+ this.stats = {
202
+ hits: 0,
203
+ misses: 0,
204
+ total: 0,
205
+ timeSaved: 0
206
+ };
207
+ this.isDirty = false;
208
+ process.on("beforeExit", () => {
209
+ if (this.isDirty) {
210
+ this.saveSync();
211
+ }
212
+ });
213
+ }
214
+ /**
215
+ * Load cache from disk
216
+ */
217
+ async load() {
218
+ try {
219
+ if (!(0, import_node_fs.existsSync)(this.cacheFilePath)) {
220
+ return;
221
+ }
222
+ const content = await (0, import_promises2.readFile)(this.cacheFilePath, "utf-8");
223
+ const loadedCache = JSON.parse(content);
224
+ if (loadedCache.version !== CACHE_VERSION) {
225
+ console.log("\u26A0\uFE0F Cache version mismatch, rebuilding cache...");
226
+ return;
227
+ }
228
+ this.cache = loadedCache;
229
+ } catch (error) {
230
+ console.warn("\u26A0\uFE0F Failed to load cache, starting fresh:", error);
231
+ this.cache = {
232
+ version: CACHE_VERSION,
233
+ configHash: "",
234
+ entries: {},
235
+ baseComponentHash: ""
236
+ };
237
+ }
238
+ }
239
+ /**
240
+ * Save cache to disk (atomic write)
241
+ */
242
+ async save() {
243
+ try {
244
+ const cacheDir = (0, import_node_path.dirname)(this.cacheFilePath);
245
+ await (0, import_promises2.mkdir)(cacheDir, { recursive: true });
246
+ const tempPath = `${this.cacheFilePath}.tmp`;
247
+ await (0, import_promises2.writeFile)(tempPath, JSON.stringify(this.cache, null, 2), "utf-8");
248
+ await (0, import_promises2.writeFile)(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
249
+ this.isDirty = false;
250
+ } catch (error) {
251
+ console.warn("\u26A0\uFE0F Failed to save cache:", error);
252
+ }
253
+ }
254
+ /**
255
+ * Synchronous save for process exit
256
+ */
257
+ saveSync() {
258
+ try {
259
+ const fs2 = require("fs");
260
+ const cacheDir = (0, import_node_path.dirname)(this.cacheFilePath);
261
+ if (!fs2.existsSync(cacheDir)) {
262
+ fs2.mkdirSync(cacheDir, { recursive: true });
263
+ }
264
+ fs2.writeFileSync(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
265
+ this.isDirty = false;
266
+ } catch {
267
+ console.warn("\u26A0\uFE0F Failed to save cache on exit");
268
+ }
269
+ }
270
+ /**
271
+ * Check if a file needs regeneration
272
+ */
273
+ async needsRegeneration(svgPath, componentPath, config) {
274
+ this.stats.total++;
275
+ if (!(0, import_node_fs.existsSync)(componentPath)) {
276
+ this.stats.misses++;
277
+ return true;
278
+ }
279
+ const entry = this.cache.entries[svgPath];
280
+ if (!entry) {
281
+ this.stats.misses++;
282
+ return true;
283
+ }
284
+ try {
285
+ const stats = await (0, import_promises2.stat)(svgPath);
286
+ const currentMtime = stats.mtimeMs;
287
+ if (currentMtime === entry.svgMtime) {
288
+ if (!this.isConfigMatching(entry, config)) {
289
+ this.stats.misses++;
290
+ return true;
291
+ }
292
+ this.stats.hits++;
293
+ this.stats.timeSaved += 50;
294
+ return false;
295
+ }
296
+ const currentHash = await hashFile(svgPath);
297
+ if (currentHash === entry.svgHash) {
298
+ entry.svgMtime = currentMtime;
299
+ this.isDirty = true;
300
+ this.stats.hits++;
301
+ this.stats.timeSaved += 50;
302
+ return false;
303
+ }
304
+ this.stats.misses++;
305
+ return true;
306
+ } catch {
307
+ this.stats.misses++;
308
+ return true;
309
+ }
310
+ }
311
+ /**
312
+ * Check if config matches cache entry
313
+ */
314
+ isConfigMatching(entry, config) {
315
+ const snapshot = entry.configSnapshot;
316
+ 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);
317
+ }
318
+ /**
319
+ * Update cache entry after successful generation
320
+ */
321
+ async updateEntry(svgPath, componentPath, componentName, componentHash, config) {
322
+ try {
323
+ const svgHash = await hashFile(svgPath);
324
+ const stats = await (0, import_promises2.stat)(svgPath);
325
+ this.cache.entries[svgPath] = {
326
+ svgPath,
327
+ svgHash,
328
+ svgMtime: stats.mtimeMs,
329
+ componentName,
330
+ componentPath,
331
+ componentHash,
332
+ configSnapshot: {
333
+ framework: config.framework,
334
+ typescript: config.typescript ?? true,
335
+ keepColors: config.keepColors ?? false,
336
+ prefix: config.prefix || "",
337
+ suffix: config.suffix || "",
338
+ optimize: config.optimize ?? true,
339
+ svgoConfigHash: hashSvgoConfig(config.svgoConfig)
340
+ },
341
+ generatedAt: Date.now()
342
+ };
343
+ this.isDirty = true;
344
+ } catch (error) {
345
+ console.warn(`\u26A0\uFE0F Failed to update cache entry for ${svgPath}:`, error);
346
+ }
347
+ }
348
+ /**
349
+ * Remove cache entry
350
+ */
351
+ removeEntry(svgPath) {
352
+ if (this.cache.entries[svgPath]) {
353
+ delete this.cache.entries[svgPath];
354
+ this.isDirty = true;
355
+ }
356
+ }
357
+ /**
358
+ * Update config hash
359
+ */
360
+ updateConfigHash(config) {
361
+ const newHash = hashConfig(config);
362
+ if (this.cache.configHash !== newHash) {
363
+ this.cache.configHash = newHash;
364
+ this.isDirty = true;
365
+ }
366
+ }
367
+ /**
368
+ * Update base component hash
369
+ */
370
+ updateBaseComponentHash(hash) {
371
+ if (this.cache.baseComponentHash !== hash) {
372
+ this.cache.baseComponentHash = hash;
373
+ this.isDirty = true;
374
+ }
375
+ }
376
+ /**
377
+ * Check if config has changed (invalidates all cache)
378
+ */
379
+ hasConfigChanged(config) {
380
+ const currentHash = hashConfig(config);
381
+ return this.cache.configHash !== "" && this.cache.configHash !== currentHash;
382
+ }
383
+ /**
384
+ * Invalidate all cache entries
385
+ */
386
+ invalidateAll() {
387
+ this.cache.entries = {};
388
+ this.isDirty = true;
389
+ }
390
+ /**
391
+ * Clean stale entries (files that no longer exist)
392
+ */
393
+ async cleanStaleEntries(existingSvgPaths) {
394
+ const existingSet = new Set(existingSvgPaths);
395
+ let cleaned = 0;
396
+ for (const svgPath of Object.keys(this.cache.entries)) {
397
+ if (!existingSet.has(svgPath)) {
398
+ delete this.cache.entries[svgPath];
399
+ cleaned++;
400
+ this.isDirty = true;
401
+ }
402
+ }
403
+ if (cleaned > 0) {
404
+ console.log(`\u{1F9F9} Cleaned ${cleaned} stale cache entries`);
405
+ }
406
+ }
407
+ /**
408
+ * Get cache statistics
409
+ */
410
+ getStats() {
411
+ return { ...this.stats };
412
+ }
413
+ /**
414
+ * Reset statistics
415
+ */
416
+ resetStats() {
417
+ this.stats = {
418
+ hits: 0,
419
+ misses: 0,
420
+ total: 0,
421
+ timeSaved: 0
422
+ };
423
+ }
424
+ /**
425
+ * Get cache hit rate
426
+ */
427
+ getHitRate() {
428
+ if (this.stats.total === 0)
429
+ return 0;
430
+ return this.stats.hits / this.stats.total * 100;
431
+ }
432
+ };
433
+
154
434
  // src/config/loader.ts
155
- var import_node_path2 = __toESM(require("path"));
435
+ var import_node_path3 = __toESM(require("path"));
156
436
  var import_node_process = __toESM(require("process"));
157
437
  var import_jiti = require("jiti");
158
438
 
@@ -176,6 +456,7 @@ function parseSvg(svgContent) {
176
456
  if (svgElement.length === 0) {
177
457
  throw new Error("Invalid SVG: No <svg> tag found");
178
458
  }
459
+ const viewBox = svgElement.attr("viewBox") || "0 0 24 24";
179
460
  const iconNodes = [];
180
461
  svgElement.children().each((_, element) => {
181
462
  const node = parseElement($, element);
@@ -183,7 +464,29 @@ function parseSvg(svgContent) {
183
464
  iconNodes.push(node);
184
465
  }
185
466
  });
186
- return iconNodes;
467
+ const isMultiColor = detectMultiColor(iconNodes);
468
+ return { iconNodes, viewBox, isMultiColor };
469
+ }
470
+ function detectMultiColor(nodes) {
471
+ const colors = /* @__PURE__ */ new Set();
472
+ function collectColors(node) {
473
+ const [, attrs, children] = node;
474
+ if (attrs.fill && attrs.fill !== "none" && attrs.fill !== "transparent") {
475
+ colors.add(String(attrs.fill).toLowerCase());
476
+ }
477
+ if (attrs.stroke && attrs.stroke !== "none" && attrs.stroke !== "transparent") {
478
+ colors.add(String(attrs.stroke).toLowerCase());
479
+ }
480
+ if (children && children.length > 0) {
481
+ children.forEach(collectColors);
482
+ }
483
+ }
484
+ nodes.forEach(collectColors);
485
+ const realColors = Array.from(colors).filter((color) => {
486
+ const c = color.toLowerCase();
487
+ return c !== "currentcolor";
488
+ });
489
+ return realColors.length >= 2;
187
490
  }
188
491
  function parseElement($, element) {
189
492
  if (element.type !== "tag") {
@@ -254,22 +557,22 @@ function formatAttrs(attrs) {
254
557
  }
255
558
 
256
559
  // src/generators/templates/template-engine.ts
257
- var import_node_fs = __toESM(require("fs"));
258
- var import_node_path = __toESM(require("path"));
560
+ var import_node_fs2 = __toESM(require("fs"));
561
+ var import_node_path2 = __toESM(require("path"));
259
562
  var import_node_url = require("url");
260
563
  var import_handlebars = __toESM(require("handlebars"));
261
564
  var import_meta = {};
262
565
  function getTemplatesDir() {
263
566
  if (typeof __dirname !== "undefined") {
264
- return import_node_path.default.join(__dirname, "templates");
567
+ return import_node_path2.default.join(__dirname, "templates");
265
568
  }
266
569
  const currentFile = (0, import_node_url.fileURLToPath)(import_meta.url);
267
- return import_node_path.default.join(import_node_path.default.dirname(currentFile), "templates");
570
+ return import_node_path2.default.join(import_node_path2.default.dirname(currentFile), "templates");
268
571
  }
269
572
  function loadTemplate(templatePath) {
270
573
  const templatesDir = getTemplatesDir();
271
- const fullPath = import_node_path.default.join(templatesDir, templatePath);
272
- const templateContent = import_node_fs.default.readFileSync(fullPath, "utf-8");
574
+ const fullPath = import_node_path2.default.join(templatesDir, templatePath);
575
+ const templateContent = import_node_fs2.default.readFileSync(fullPath, "utf-8");
273
576
  return import_handlebars.default.compile(templateContent, { noEscape: true });
274
577
  }
275
578
  function renderTemplate(templatePath, data) {
@@ -313,14 +616,15 @@ function getQwikTemplatePath(typescript, type) {
313
616
  function getAngularTemplatePath(type) {
314
617
  return `angular/${type}.ts.hbs`;
315
618
  }
316
- function generateAngularComponent(componentName, iconNode, typescript, keepColors = false) {
619
+ function generateAngularComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
317
620
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
318
621
  const templatePath = getAngularTemplatePath("component");
319
622
  return renderTemplate(templatePath, {
320
623
  componentName,
321
624
  formattedNodes,
322
625
  typescript,
323
- keepColors
626
+ keepColors,
627
+ viewBox
324
628
  });
325
629
  }
326
630
  function generateAngularBaseComponent(typescript) {
@@ -335,14 +639,15 @@ function generateAngularBaseComponent(typescript) {
335
639
  function getAstroTemplatePath(type) {
336
640
  return `astro/${type}.astro.hbs`;
337
641
  }
338
- function generateAstroComponent(componentName, iconNode, typescript, keepColors = false) {
642
+ function generateAstroComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
339
643
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
340
644
  const templatePath = getAstroTemplatePath("component");
341
645
  return renderTemplate(templatePath, {
342
646
  componentName,
343
647
  formattedNodes,
344
648
  typescript,
345
- keepColors
649
+ keepColors,
650
+ viewBox
346
651
  });
347
652
  }
348
653
  function generateAstroBaseComponent(typescript) {
@@ -358,14 +663,15 @@ function getLitTemplatePath(typescript, type) {
358
663
  const ext = typescript ? "ts" : "js";
359
664
  return `lit/${type}.${ext}.hbs`;
360
665
  }
361
- function generateLitComponent(componentName, iconNode, typescript, keepColors = false) {
666
+ function generateLitComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
362
667
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
363
668
  const templatePath = getLitTemplatePath(typescript, "component");
364
669
  return renderTemplate(templatePath, {
365
670
  componentName,
366
671
  formattedNodes,
367
672
  typescript,
368
- keepColors
673
+ keepColors,
674
+ viewBox
369
675
  });
370
676
  }
371
677
  function generateLitBaseComponent(typescript) {
@@ -377,14 +683,15 @@ function generateLitBaseComponent(typescript) {
377
683
  }
378
684
 
379
685
  // src/generators/react.ts
380
- function generateReactComponent(componentName, iconNode, typescript, keepColors = false) {
686
+ function generateReactComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
381
687
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
382
688
  const templatePath = getReactTemplatePath(typescript, "component");
383
689
  return renderTemplate(templatePath, {
384
690
  typescript,
385
691
  componentName,
386
692
  formattedNodes,
387
- keepColors
693
+ keepColors,
694
+ viewBox
388
695
  });
389
696
  }
390
697
  function generateCreateIcon(typescript) {
@@ -393,7 +700,7 @@ function generateCreateIcon(typescript) {
393
700
  }
394
701
 
395
702
  // src/generators/react-like.ts
396
- function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, framework) {
703
+ function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, viewBox, framework) {
397
704
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
398
705
  let templatePath;
399
706
  if (framework === "solid") {
@@ -409,7 +716,8 @@ function generateReactLikeComponent(componentName, iconNode, typescript, keepCol
409
716
  componentName,
410
717
  formattedNodes,
411
718
  typescript,
412
- keepColors
719
+ keepColors,
720
+ viewBox
413
721
  });
414
722
  }
415
723
  function generateReactLikeBaseComponent(typescript, framework) {
@@ -430,12 +738,14 @@ function generateReactLikeBaseComponent(typescript, framework) {
430
738
  }
431
739
 
432
740
  // src/generators/svelte.ts
433
- function generateSvelteComponent(_componentName, iconNode, typescript) {
741
+ function generateSvelteComponent(_componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
434
742
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
435
743
  const templatePath = getSvelteTemplatePath(typescript, "component");
436
744
  return renderTemplate(templatePath, {
437
745
  typescript,
438
- formattedNodes
746
+ formattedNodes,
747
+ keepColors,
748
+ viewBox
439
749
  });
440
750
  }
441
751
  function generateSvelteIcon(typescript) {
@@ -444,14 +754,15 @@ function generateSvelteIcon(typescript) {
444
754
  }
445
755
 
446
756
  // src/generators/vanilla.ts
447
- function generateVanillaComponent(componentName, iconNode, typescript, keepColors) {
757
+ function generateVanillaComponent(componentName, iconNode, typescript, keepColors, viewBox) {
448
758
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
449
759
  const templatePath = getVanillaTemplatePath(typescript, "component");
450
760
  return renderTemplate(templatePath, {
451
761
  componentName,
452
762
  formattedNodes,
453
763
  typescript,
454
- keepColors
764
+ keepColors,
765
+ viewBox
455
766
  });
456
767
  }
457
768
  function generateVanillaBaseComponent(typescript) {
@@ -463,13 +774,15 @@ function generateVanillaBaseComponent(typescript) {
463
774
  }
464
775
 
465
776
  // src/generators/vue.ts
466
- function generateVueComponent(componentName, iconNode, typescript) {
777
+ function generateVueComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
467
778
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
468
779
  const templatePath = getVueTemplatePath(typescript, "component");
469
780
  return renderTemplate(templatePath, {
470
781
  typescript,
471
782
  componentName,
472
- formattedNodes
783
+ formattedNodes,
784
+ keepColors,
785
+ viewBox
473
786
  });
474
787
  }
475
788
  function generateVueIcon(typescript) {
@@ -478,13 +791,15 @@ function generateVueIcon(typescript) {
478
791
  }
479
792
 
480
793
  // src/generators/vue2.ts
481
- function generateVue2Component(componentName, iconNode, typescript) {
794
+ function generateVue2Component(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
482
795
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
483
796
  const templatePath = getVue2TemplatePath(typescript, "component");
484
797
  return renderTemplate(templatePath, {
485
798
  typescript,
486
799
  componentName,
487
- formattedNodes
800
+ formattedNodes,
801
+ keepColors,
802
+ viewBox
488
803
  });
489
804
  }
490
805
  function generateVue2Icon(typescript) {
@@ -502,8 +817,8 @@ var ReactStrategy = class {
502
817
  this.getIndexExtension = (typescript) => {
503
818
  return typescript ? "ts" : "js";
504
819
  };
505
- this.generateComponent = (componentName, iconNode, typescript, keepColors = false) => {
506
- return generateReactComponent(componentName, iconNode, typescript, keepColors);
820
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
821
+ return generateReactComponent(componentName, iconNode, typescript, keepColors, viewBox);
507
822
  };
508
823
  this.generateBaseComponent = (typescript) => {
509
824
  return {
@@ -522,8 +837,8 @@ var VueStrategy = class {
522
837
  this.getIndexExtension = (typescript) => {
523
838
  return typescript ? "ts" : "js";
524
839
  };
525
- this.generateComponent = (componentName, iconNode, typescript) => {
526
- return generateVueComponent(componentName, iconNode, typescript);
840
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
841
+ return generateVueComponent(componentName, iconNode, typescript, keepColors, viewBox);
527
842
  };
528
843
  this.generateBaseComponent = (typescript) => {
529
844
  return {
@@ -542,8 +857,8 @@ var Vue2Strategy = class {
542
857
  this.getIndexExtension = (typescript) => {
543
858
  return typescript ? "ts" : "js";
544
859
  };
545
- this.generateComponent = (componentName, iconNode, typescript) => {
546
- return generateVue2Component(componentName, iconNode, typescript);
860
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
861
+ return generateVue2Component(componentName, iconNode, typescript, keepColors, viewBox);
547
862
  };
548
863
  this.generateBaseComponent = (typescript) => {
549
864
  return {
@@ -562,8 +877,8 @@ var SvelteStrategy = class {
562
877
  this.getIndexExtension = (typescript) => {
563
878
  return typescript ? "ts" : "js";
564
879
  };
565
- this.generateComponent = (componentName, iconNode, typescript) => {
566
- return generateSvelteComponent(componentName, iconNode, typescript);
880
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
881
+ return generateSvelteComponent(componentName, iconNode, typescript, keepColors, viewBox);
567
882
  };
568
883
  this.generateBaseComponent = (typescript) => {
569
884
  return {
@@ -582,8 +897,8 @@ var SolidStrategy = class {
582
897
  this.getIndexExtension = (typescript) => {
583
898
  return typescript ? "ts" : "js";
584
899
  };
585
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
586
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "solid");
900
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
901
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "solid");
587
902
  };
588
903
  this.generateBaseComponent = (typescript) => {
589
904
  return generateReactLikeBaseComponent(typescript, "solid");
@@ -599,8 +914,8 @@ var PreactStrategy = class {
599
914
  this.getIndexExtension = (typescript) => {
600
915
  return typescript ? "ts" : "js";
601
916
  };
602
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
603
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "preact");
917
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
918
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "preact");
604
919
  };
605
920
  this.generateBaseComponent = (typescript) => {
606
921
  return generateReactLikeBaseComponent(typescript, "preact");
@@ -616,8 +931,8 @@ var VanillaStrategy = class {
616
931
  this.getIndexExtension = (typescript) => {
617
932
  return typescript ? "ts" : "js";
618
933
  };
619
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
620
- return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false);
934
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
935
+ return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
621
936
  };
622
937
  this.generateBaseComponent = (typescript) => {
623
938
  return generateVanillaBaseComponent(typescript);
@@ -633,8 +948,8 @@ var LitStrategy = class {
633
948
  this.getIndexExtension = (typescript) => {
634
949
  return typescript ? "ts" : "js";
635
950
  };
636
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
637
- return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false);
951
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
952
+ return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
638
953
  };
639
954
  this.generateBaseComponent = (typescript) => {
640
955
  return generateLitBaseComponent(typescript);
@@ -650,8 +965,8 @@ var QwikStrategy = class {
650
965
  this.getIndexExtension = (typescript) => {
651
966
  return typescript ? "ts" : "js";
652
967
  };
653
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
654
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "qwik");
968
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
969
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "qwik");
655
970
  };
656
971
  this.generateBaseComponent = (typescript) => {
657
972
  return generateReactLikeBaseComponent(typescript, "qwik");
@@ -667,8 +982,8 @@ var AstroStrategy = class {
667
982
  this.getIndexExtension = (typescript) => {
668
983
  return typescript ? "ts" : "js";
669
984
  };
670
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
671
- return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false);
985
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
986
+ return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
672
987
  };
673
988
  this.generateBaseComponent = (typescript) => {
674
989
  return generateAstroBaseComponent(typescript);
@@ -684,8 +999,8 @@ var AngularStrategy = class {
684
999
  this.getIndexExtension = (_typescript) => {
685
1000
  return "ts";
686
1001
  };
687
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
688
- return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false);
1002
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
1003
+ return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
689
1004
  };
690
1005
  this.generateBaseComponent = (typescript) => {
691
1006
  return generateAngularBaseComponent(typescript);
@@ -747,7 +1062,6 @@ var DEFAULT_CONFIG = {
747
1062
  output: "./src/icons",
748
1063
  typescript: true,
749
1064
  optimize: true,
750
- keepColors: false,
751
1065
  prefix: "",
752
1066
  suffix: "",
753
1067
  configDir: ".",
@@ -764,8 +1078,8 @@ var DEFAULT_CONFIG = {
764
1078
  }
765
1079
  };
766
1080
  async function loadConfig(configPath) {
767
- const absolutePath = import_node_path2.default.resolve(import_node_process.default.cwd(), configPath);
768
- const configDir = import_node_path2.default.dirname(absolutePath);
1081
+ const absolutePath = import_node_path3.default.resolve(import_node_process.default.cwd(), configPath);
1082
+ const configDir = import_node_path3.default.dirname(absolutePath);
769
1083
  const jiti = (0, import_jiti.createJiti)(configDir, {
770
1084
  interopDefault: true
771
1085
  });
@@ -792,12 +1106,12 @@ async function loadConfig(configPath) {
792
1106
  }
793
1107
  };
794
1108
  if (mergedConfig.configDir && mergedConfig.configDir !== ".") {
795
- const projectRoot = import_node_path2.default.resolve(configDir, mergedConfig.configDir);
796
- mergedConfig.input = import_node_path2.default.resolve(projectRoot, mergedConfig.input);
797
- mergedConfig.output = import_node_path2.default.resolve(projectRoot, mergedConfig.output);
1109
+ const projectRoot = import_node_path3.default.resolve(configDir, mergedConfig.configDir);
1110
+ mergedConfig.input = import_node_path3.default.resolve(projectRoot, mergedConfig.input);
1111
+ mergedConfig.output = import_node_path3.default.resolve(projectRoot, mergedConfig.output);
798
1112
  } else {
799
- mergedConfig.input = import_node_path2.default.resolve(configDir, mergedConfig.input);
800
- mergedConfig.output = import_node_path2.default.resolve(configDir, mergedConfig.output);
1113
+ mergedConfig.input = import_node_path3.default.resolve(configDir, mergedConfig.input);
1114
+ mergedConfig.output = import_node_path3.default.resolve(configDir, mergedConfig.output);
801
1115
  }
802
1116
  if (!mergedConfig.framework) {
803
1117
  const supported = frameworkRegistry.getSupportedFrameworks().join(", ");
@@ -816,7 +1130,7 @@ async function findConfig() {
816
1130
  "vectify.config.js"
817
1131
  ];
818
1132
  for (const file of configFiles) {
819
- const configPath = import_node_path2.default.resolve(import_node_process.default.cwd(), file);
1133
+ const configPath = import_node_path3.default.resolve(import_node_process.default.cwd(), file);
820
1134
  if (await fileExists2(configPath)) {
821
1135
  return configPath;
822
1136
  }
@@ -825,7 +1139,7 @@ async function findConfig() {
825
1139
  }
826
1140
 
827
1141
  // src/generators/index.ts
828
- var import_node_path4 = __toESM(require("path"));
1142
+ var import_node_path5 = __toESM(require("path"));
829
1143
 
830
1144
  // src/parsers/optimizer.ts
831
1145
  var import_svgo = require("svgo");
@@ -861,7 +1175,7 @@ async function optimizeSvg(svgContent, config) {
861
1175
 
862
1176
  // src/utils/formatter.ts
863
1177
  var import_node_child_process = require("child_process");
864
- var import_node_path3 = __toESM(require("path"));
1178
+ var import_node_path4 = __toESM(require("path"));
865
1179
  var import_node_process2 = __toESM(require("process"));
866
1180
  var import_node_util = require("util");
867
1181
  init_helpers();
@@ -916,7 +1230,7 @@ async function detectFormatter() {
916
1230
  for (const tool of priority) {
917
1231
  const patterns = FORMATTER_PATTERNS[tool];
918
1232
  for (const pattern of patterns) {
919
- const configPath = import_node_path3.default.join(cwd, pattern);
1233
+ const configPath = import_node_path4.default.join(cwd, pattern);
920
1234
  if (await fileExists(configPath)) {
921
1235
  return tool;
922
1236
  }
@@ -1012,90 +1326,206 @@ async function generateIcons(config, dryRun = false) {
1012
1326
  }
1013
1327
  return stats;
1014
1328
  }
1015
- async function generateIconComponent(svgFile, config, dryRun = false) {
1016
- let svgContent = await readFile(svgFile);
1017
- const fileName = import_node_path4.default.basename(svgFile);
1329
+ async function generateIconsIncremental(config, cacheManager, dryRun = false) {
1330
+ const stats = {
1331
+ success: 0,
1332
+ failed: 0,
1333
+ total: 0,
1334
+ errors: []
1335
+ };
1336
+ try {
1337
+ if (!dryRun) {
1338
+ await ensureDir(config.output);
1339
+ }
1340
+ const svgFiles = await getSvgFiles(config.input);
1341
+ stats.total = svgFiles.length;
1342
+ if (svgFiles.length === 0) {
1343
+ console.warn(`No SVG files found in ${config.input}`);
1344
+ return stats;
1345
+ }
1346
+ if (cacheManager.hasConfigChanged(config)) {
1347
+ console.log("\u26A0\uFE0F Configuration changed, rebuilding all icons...");
1348
+ cacheManager.invalidateAll();
1349
+ }
1350
+ cacheManager.updateConfigHash(config);
1351
+ await cacheManager.cleanStaleEntries(svgFiles);
1352
+ if (config.generateOptions?.cleanOutput && !dryRun) {
1353
+ await cleanOutputDirectory(svgFiles, config);
1354
+ }
1355
+ await generateBaseComponent(config, dryRun);
1356
+ const strategy = getFrameworkStrategy(config.framework);
1357
+ const typescript = config.typescript ?? true;
1358
+ const fileExt = strategy.getComponentExtension(typescript);
1359
+ const filesToGenerate = [];
1360
+ const cachedFiles = [];
1361
+ for (const svgFile of svgFiles) {
1362
+ const fileName = import_node_path5.default.basename(svgFile);
1363
+ const componentName = getComponentName(
1364
+ fileName,
1365
+ config.prefix,
1366
+ config.suffix,
1367
+ config.componentNameTransform
1368
+ );
1369
+ const componentPath = import_node_path5.default.join(config.output, `${componentName}.${fileExt}`);
1370
+ const needsRegen = await cacheManager.needsRegeneration(
1371
+ svgFile,
1372
+ componentPath,
1373
+ config
1374
+ );
1375
+ if (needsRegen) {
1376
+ filesToGenerate.push(svgFile);
1377
+ } else {
1378
+ cachedFiles.push(svgFile);
1379
+ }
1380
+ }
1381
+ if (cachedFiles.length > 0) {
1382
+ console.log(`\u{1F4E6} Cache: ${cachedFiles.length} cached, ${filesToGenerate.length} to generate`);
1383
+ }
1384
+ for (const svgFile of filesToGenerate) {
1385
+ try {
1386
+ await generateIconComponent(svgFile, config, dryRun, cacheManager);
1387
+ stats.success++;
1388
+ } catch (error) {
1389
+ stats.failed++;
1390
+ stats.errors.push({
1391
+ file: svgFile,
1392
+ error: error.message
1393
+ });
1394
+ console.error(`Failed to generate ${svgFile}: ${error.message}`);
1395
+ }
1396
+ }
1397
+ stats.success += cachedFiles.length;
1398
+ if (config.generateOptions?.index) {
1399
+ await generateIndexFile(svgFiles, config, dryRun);
1400
+ }
1401
+ if (config.generateOptions?.preview && !dryRun) {
1402
+ await generatePreviewHtml(svgFiles, config);
1403
+ }
1404
+ if (!dryRun) {
1405
+ await cacheManager.save();
1406
+ const cacheStats = cacheManager.getStats();
1407
+ if (cacheStats.total > 0) {
1408
+ const hitRate = cacheManager.getHitRate();
1409
+ const timeSaved = (cacheStats.timeSaved / 1e3).toFixed(1);
1410
+ console.log(`\u26A1 Cache saved ~${timeSaved}s (${hitRate.toFixed(1)}% hit rate)`);
1411
+ }
1412
+ }
1413
+ if (config.format && !dryRun) {
1414
+ const formatResult = await formatOutput(config.output, config.format);
1415
+ if (formatResult.success && formatResult.tool) {
1416
+ console.log(`Formatted with ${formatResult.tool}`);
1417
+ } else if (formatResult.error) {
1418
+ console.warn(formatResult.error);
1419
+ }
1420
+ }
1421
+ if (config.hooks?.onComplete) {
1422
+ await config.hooks.onComplete(stats);
1423
+ }
1424
+ } catch (error) {
1425
+ throw new Error(`Generation failed: ${error.message}`);
1426
+ }
1427
+ return stats;
1428
+ }
1429
+ async function generateIconComponent(svgFile, config, dryRun = false, cacheManager) {
1430
+ let svgContent = await readFile3(svgFile);
1431
+ const fileName = import_node_path5.default.basename(svgFile);
1018
1432
  if (config.hooks?.beforeParse) {
1019
1433
  svgContent = await config.hooks.beforeParse(svgContent, fileName);
1020
1434
  }
1435
+ const { isMultiColor: isMultiColorBeforeOptimization } = parseSvg(svgContent);
1021
1436
  svgContent = await optimizeSvg(svgContent, config);
1022
- const iconNode = parseSvg(svgContent);
1437
+ const { iconNodes, viewBox } = parseSvg(svgContent);
1023
1438
  const componentName = getComponentName(
1024
1439
  fileName,
1025
1440
  config.prefix,
1026
1441
  config.suffix,
1027
- config.transform
1442
+ config.componentNameTransform
1028
1443
  );
1029
1444
  const strategy = getFrameworkStrategy(config.framework);
1030
1445
  const typescript = config.typescript ?? true;
1446
+ const keepColors = config.keepColors !== void 0 ? config.keepColors : isMultiColorBeforeOptimization;
1031
1447
  let code = strategy.generateComponent(
1032
1448
  componentName,
1033
- iconNode,
1449
+ iconNodes,
1034
1450
  typescript,
1035
- config.keepColors ?? false
1451
+ keepColors,
1452
+ viewBox
1036
1453
  );
1037
1454
  if (config.hooks?.afterGenerate) {
1038
1455
  code = await config.hooks.afterGenerate(code, componentName);
1039
1456
  }
1040
1457
  const fileExt = strategy.getComponentExtension(typescript);
1041
- const outputPath = import_node_path4.default.join(config.output, `${componentName}.${fileExt}`);
1458
+ const outputPath = import_node_path5.default.join(config.output, `${componentName}.${fileExt}`);
1042
1459
  if (dryRun) {
1043
1460
  console.log(` ${componentName}.${fileExt}`);
1044
1461
  } else {
1045
- await writeFile(outputPath, code);
1462
+ await writeFile2(outputPath, code);
1463
+ if (cacheManager) {
1464
+ const componentHash = hashContent(code);
1465
+ await cacheManager.updateEntry(
1466
+ svgFile,
1467
+ outputPath,
1468
+ componentName,
1469
+ componentHash,
1470
+ config
1471
+ );
1472
+ }
1046
1473
  }
1047
1474
  }
1048
1475
  async function generateBaseComponent(config, dryRun = false) {
1049
1476
  const typescript = config.typescript ?? true;
1050
1477
  const strategy = getFrameworkStrategy(config.framework);
1051
1478
  const { code, fileName } = strategy.generateBaseComponent(typescript);
1052
- const outputPath = import_node_path4.default.join(config.output, fileName);
1479
+ const outputPath = import_node_path5.default.join(config.output, fileName);
1053
1480
  if (dryRun) {
1054
1481
  console.log(` ${fileName}`);
1055
1482
  } else {
1056
- await writeFile(outputPath, code);
1483
+ await writeFile2(outputPath, code);
1057
1484
  }
1058
1485
  }
1059
1486
  async function generateIndexFile(svgFiles, config, dryRun = false) {
1060
1487
  const typescript = config.typescript ?? true;
1061
1488
  const strategy = getFrameworkStrategy(config.framework);
1062
1489
  const ext = strategy.getIndexExtension(typescript);
1490
+ const componentExt = strategy.getComponentExtension(typescript);
1063
1491
  const usesDefaultExport = ["vue", "vue2", "svelte", "react", "preact"].includes(config.framework);
1492
+ const needsExtension = ["vue", "vue2", "svelte"].includes(config.framework);
1064
1493
  const exports2 = svgFiles.map((svgFile) => {
1065
- const fileName = import_node_path4.default.basename(svgFile);
1494
+ const fileName = import_node_path5.default.basename(svgFile);
1066
1495
  const componentName = getComponentName(
1067
1496
  fileName,
1068
1497
  config.prefix,
1069
1498
  config.suffix,
1070
- config.transform
1499
+ config.componentNameTransform
1071
1500
  );
1501
+ const importPath = needsExtension ? `./${componentName}.${componentExt}` : `./${componentName}`;
1072
1502
  if (usesDefaultExport) {
1073
- return `export { default as ${componentName} } from './${componentName}'`;
1503
+ return `export { default as ${componentName} } from '${importPath}'`;
1074
1504
  } else {
1075
- return `export { ${componentName} } from './${componentName}'`;
1505
+ return `export { ${componentName} } from '${importPath}'`;
1076
1506
  }
1077
1507
  }).join("\n");
1078
- const indexPath = import_node_path4.default.join(config.output, `index.${ext}`);
1508
+ const indexPath = import_node_path5.default.join(config.output, `index.${ext}`);
1079
1509
  if (dryRun) {
1080
1510
  console.log(` index.${ext}`);
1081
1511
  } else {
1082
- await writeFile(indexPath, `${exports2}
1512
+ await writeFile2(indexPath, `${exports2}
1083
1513
  `);
1084
1514
  }
1085
1515
  }
1086
1516
  async function generatePreviewHtml(svgFiles, config) {
1087
1517
  const componentNames = svgFiles.map((svgFile) => {
1088
- const fileName = import_node_path4.default.basename(svgFile);
1518
+ const fileName = import_node_path5.default.basename(svgFile);
1089
1519
  return getComponentName(
1090
1520
  fileName,
1091
1521
  config.prefix,
1092
1522
  config.suffix,
1093
- config.transform
1523
+ config.componentNameTransform
1094
1524
  );
1095
1525
  });
1096
1526
  const svgContents = await Promise.all(
1097
1527
  svgFiles.map(async (svgFile) => {
1098
- return await readFile(svgFile);
1528
+ return await readFile3(svgFile);
1099
1529
  })
1100
1530
  );
1101
1531
  const html = `<!DOCTYPE html>
@@ -1257,8 +1687,8 @@ async function generatePreviewHtml(svgFiles, config) {
1257
1687
  </script>
1258
1688
  </body>
1259
1689
  </html>`;
1260
- const previewPath = import_node_path4.default.join(config.output, "preview.html");
1261
- await writeFile(previewPath, html);
1690
+ const previewPath = import_node_path5.default.join(config.output, "preview.html");
1691
+ await writeFile2(previewPath, html);
1262
1692
  }
1263
1693
  async function cleanOutputDirectory(svgFiles, config) {
1264
1694
  const { readdir, unlink } = await import("fs/promises");
@@ -1266,12 +1696,12 @@ async function cleanOutputDirectory(svgFiles, config) {
1266
1696
  const fileExt = strategy.getComponentExtension(config.typescript ?? true);
1267
1697
  const expectedComponents = new Set(
1268
1698
  svgFiles.map((svgFile) => {
1269
- const fileName = import_node_path4.default.basename(svgFile, ".svg");
1699
+ const fileName = import_node_path5.default.basename(svgFile, ".svg");
1270
1700
  const componentName = getComponentName(
1271
1701
  fileName,
1272
1702
  config.prefix,
1273
1703
  config.suffix,
1274
- config.transform
1704
+ config.componentNameTransform
1275
1705
  );
1276
1706
  return `${componentName}.${fileExt}`;
1277
1707
  })
@@ -1293,7 +1723,7 @@ async function cleanOutputDirectory(svgFiles, config) {
1293
1723
  continue;
1294
1724
  }
1295
1725
  if (!expectedComponents.has(file)) {
1296
- const filePath = import_node_path4.default.join(config.output, file);
1726
+ const filePath = import_node_path5.default.join(config.output, file);
1297
1727
  await unlink(filePath);
1298
1728
  console.log(`Deleted orphaned component: ${file}`);
1299
1729
  }
@@ -1325,9 +1755,19 @@ async function generate(options = {}) {
1325
1755
  ${import_chalk.default.bold("Files that would be generated:")}
1326
1756
  `);
1327
1757
  }
1758
+ const useIncremental = config.incremental?.enabled !== false && !options.force && !options.dryRun;
1759
+ let cacheManager = null;
1760
+ if (useIncremental) {
1761
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
1762
+ cacheManager = new CacheManager(config.output, cacheDir);
1763
+ await cacheManager.load();
1764
+ }
1765
+ if (options.force) {
1766
+ spinner.info(import_chalk.default.yellow("Force mode - ignoring cache, regenerating all icons"));
1767
+ }
1328
1768
  const actionText = options.dryRun ? "Analyzing icons..." : "Generating icon components...";
1329
1769
  spinner.start(actionText);
1330
- const stats = await generateIcons(config, options.dryRun);
1770
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager, options.dryRun) : await generateIcons(config, options.dryRun);
1331
1771
  if (stats.failed > 0) {
1332
1772
  spinner.warn(`${options.dryRun ? "Analyzed" : "Generated"} ${import_chalk.default.green(stats.success)} icons, ${import_chalk.default.red(stats.failed)} failed`);
1333
1773
  if (stats.errors.length > 0) {
@@ -1360,7 +1800,7 @@ ${import_chalk.default.bold("Output:")} ${import_chalk.default.cyan(config.outpu
1360
1800
  }
1361
1801
 
1362
1802
  // src/commands/init.ts
1363
- var import_node_path5 = __toESM(require("path"));
1803
+ var import_node_path6 = __toESM(require("path"));
1364
1804
  var import_node_process3 = __toESM(require("process"));
1365
1805
  var import_chalk2 = __toESM(require("chalk"));
1366
1806
  var import_inquirer = __toESM(require("inquirer"));
@@ -1390,8 +1830,8 @@ Note: Project root detected at ${import_chalk2.default.cyan(projectRoot)}`));
1390
1830
  }
1391
1831
  }
1392
1832
  ]);
1393
- const configPath = import_node_path5.default.resolve(projectRoot, pathAnswers.configPath);
1394
- const configDir = import_node_path5.default.dirname(configPath);
1833
+ const configPath = import_node_path6.default.resolve(projectRoot, pathAnswers.configPath);
1834
+ const configDir = import_node_path6.default.dirname(configPath);
1395
1835
  if (!options.force && await fileExists(configPath)) {
1396
1836
  const { overwrite } = await import_inquirer.default.prompt([
1397
1837
  {
@@ -1465,18 +1905,18 @@ Note: Project root detected at ${import_chalk2.default.cyan(projectRoot)}`));
1465
1905
  default: ""
1466
1906
  }
1467
1907
  ]);
1468
- const inputPath = import_node_path5.default.resolve(projectRoot, answers.input);
1469
- const outputPath = import_node_path5.default.resolve(projectRoot, answers.output);
1908
+ const inputPath = import_node_path6.default.resolve(projectRoot, answers.input);
1909
+ const outputPath = import_node_path6.default.resolve(projectRoot, answers.output);
1470
1910
  const spinner = (0, import_ora2.default)("Setting up directories...").start();
1471
1911
  await ensureDir(inputPath);
1472
1912
  spinner.text = `Created input directory: ${import_chalk2.default.cyan(answers.input)}`;
1473
1913
  await ensureDir(outputPath);
1474
1914
  spinner.succeed(`Created output directory: ${import_chalk2.default.cyan(answers.output)}`);
1475
- const relativeConfigDir = import_node_path5.default.relative(configDir, projectRoot) || ".";
1915
+ const relativeConfigDir = import_node_path6.default.relative(configDir, projectRoot) || ".";
1476
1916
  const finalFramework = answers.vueVersion || answers.framework;
1477
1917
  const configContent = generateConfigContent({ ...answers, framework: finalFramework }, relativeConfigDir);
1478
1918
  spinner.start("Creating config file...");
1479
- await writeFile(configPath, configContent);
1919
+ await writeFile2(configPath, configContent);
1480
1920
  spinner.succeed(`Config file created at ${import_chalk2.default.green(configPath)}`);
1481
1921
  console.log(`
1482
1922
  ${import_chalk2.default.bold("Next steps:")}`);
@@ -1516,10 +1956,93 @@ export default defineConfig({
1516
1956
  }
1517
1957
 
1518
1958
  // src/commands/watch.ts
1519
- var import_node_path6 = __toESM(require("path"));
1959
+ var import_node_path7 = __toESM(require("path"));
1520
1960
  var import_chalk3 = __toESM(require("chalk"));
1521
1961
  var import_chokidar = __toESM(require("chokidar"));
1522
1962
  var import_ora3 = __toESM(require("ora"));
1963
+
1964
+ // src/cache/svg-validator.ts
1965
+ var import_promises3 = require("fs/promises");
1966
+ var import_cheerio = require("cheerio");
1967
+ var MIN_SVG_SIZE = 20;
1968
+ var DRAWABLE_ELEMENTS = [
1969
+ "path",
1970
+ "circle",
1971
+ "rect",
1972
+ "ellipse",
1973
+ "line",
1974
+ "polyline",
1975
+ "polygon",
1976
+ "text",
1977
+ "image",
1978
+ "use"
1979
+ ];
1980
+ async function validateSVGFile(filePath) {
1981
+ try {
1982
+ const stats = await (0, import_promises3.stat)(filePath);
1983
+ if (stats.size < MIN_SVG_SIZE) {
1984
+ return {
1985
+ isValid: false,
1986
+ isEmpty: true,
1987
+ reason: "File is too small to be a valid SVG"
1988
+ };
1989
+ }
1990
+ const content = await (0, import_promises3.readFile)(filePath, "utf-8");
1991
+ if (!content.trim()) {
1992
+ return {
1993
+ isValid: false,
1994
+ isEmpty: true,
1995
+ reason: "File is empty"
1996
+ };
1997
+ }
1998
+ if (!content.includes("<svg")) {
1999
+ return {
2000
+ isValid: false,
2001
+ isEmpty: false,
2002
+ reason: "File does not contain <svg> tag"
2003
+ };
2004
+ }
2005
+ if (!hasDrawableContent(content)) {
2006
+ return {
2007
+ isValid: false,
2008
+ isEmpty: true,
2009
+ reason: "SVG has no drawable content"
2010
+ };
2011
+ }
2012
+ try {
2013
+ const $ = (0, import_cheerio.load)(content, { xmlMode: true });
2014
+ const svgElement = $("svg");
2015
+ if (svgElement.length === 0) {
2016
+ return {
2017
+ isValid: false,
2018
+ isEmpty: false,
2019
+ reason: "Failed to parse SVG element"
2020
+ };
2021
+ }
2022
+ return {
2023
+ isValid: true,
2024
+ isEmpty: false
2025
+ };
2026
+ } catch (parseError) {
2027
+ return {
2028
+ isValid: false,
2029
+ isEmpty: false,
2030
+ reason: `Failed to parse SVG: ${parseError}`
2031
+ };
2032
+ }
2033
+ } catch (error) {
2034
+ return {
2035
+ isValid: false,
2036
+ isEmpty: false,
2037
+ reason: `Failed to read file: ${error}`
2038
+ };
2039
+ }
2040
+ }
2041
+ function hasDrawableContent(content) {
2042
+ return DRAWABLE_ELEMENTS.some((element) => content.includes(`<${element}`));
2043
+ }
2044
+
2045
+ // src/commands/watch.ts
1523
2046
  async function watch(options = {}) {
1524
2047
  const spinner = (0, import_ora3.default)("Loading configuration...").start();
1525
2048
  try {
@@ -1535,16 +2058,24 @@ async function watch(options = {}) {
1535
2058
  }
1536
2059
  const config = await loadConfig(configPath);
1537
2060
  spinner.succeed(`Config loaded from ${import_chalk3.default.green(configPath)}`);
2061
+ const useIncremental = config.incremental?.enabled !== false;
2062
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
2063
+ const cacheManager = useIncremental ? new CacheManager(config.output, cacheDir) : null;
2064
+ if (cacheManager) {
2065
+ await cacheManager.load();
2066
+ }
1538
2067
  spinner.start("Generating icon components...");
1539
- const initialStats = await generateIcons(config);
2068
+ const initialStats = useIncremental && cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1540
2069
  spinner.succeed(`Generated ${import_chalk3.default.green(initialStats.success)} icon components`);
1541
- const watchPath = import_node_path6.default.join(config.input, "**/*.svg");
2070
+ const watchPath = import_node_path7.default.join(config.input, "**/*.svg");
1542
2071
  const debounce = config.watch?.debounce ?? 300;
1543
2072
  const ignore = config.watch?.ignore ?? ["**/node_modules/**", "**/.git/**"];
2073
+ const emptyFileRetryDelay = config.watch?.emptyFileRetryDelay ?? 2e3;
1544
2074
  console.log(import_chalk3.default.bold("\nWatching for changes..."));
1545
2075
  console.log(` ${import_chalk3.default.cyan(watchPath)}`);
1546
2076
  console.log(` ${import_chalk3.default.gray("Press Ctrl+C to stop")}
1547
2077
  `);
2078
+ const pendingChanges = /* @__PURE__ */ new Map();
1548
2079
  const debounceTimer = null;
1549
2080
  const watcher = import_chokidar.default.watch(watchPath, {
1550
2081
  ignored: ignore,
@@ -1552,12 +2083,12 @@ async function watch(options = {}) {
1552
2083
  ignoreInitial: true
1553
2084
  });
1554
2085
  watcher.on("add", (filePath) => {
1555
- handleChange("added", filePath, config, debounce, debounceTimer);
2086
+ handleChange("added", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1556
2087
  }).on("change", (filePath) => {
1557
- handleChange("changed", filePath, config, debounce, debounceTimer);
2088
+ handleChange("changed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1558
2089
  }).on("unlink", (filePath) => {
1559
- console.log(import_chalk3.default.yellow(`SVG file removed: ${import_node_path6.default.basename(filePath)}`));
1560
- handleChange("removed", filePath, config, debounce, debounceTimer);
2090
+ console.log(import_chalk3.default.yellow(`SVG file removed: ${import_node_path7.default.basename(filePath)}`));
2091
+ handleChange("removed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1561
2092
  }).on("error", (error) => {
1562
2093
  console.error(import_chalk3.default.red(`Watcher error: ${error.message}`));
1563
2094
  });
@@ -1574,23 +2105,51 @@ ${import_chalk3.default.yellow("Stopping watch mode...")}`);
1574
2105
  throw error;
1575
2106
  }
1576
2107
  }
1577
- function handleChange(event, filePath, config, debounce, timer) {
1578
- const fileName = import_node_path6.default.basename(filePath);
2108
+ function handleChange(event, filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer) {
2109
+ pendingChanges.set(filePath, event);
1579
2110
  if (timer) {
1580
2111
  clearTimeout(timer);
1581
2112
  }
1582
2113
  timer = setTimeout(async () => {
1583
- const spinner = (0, import_ora3.default)(`Regenerating icons...`).start();
2114
+ const changes = Array.from(pendingChanges.entries());
2115
+ pendingChanges.clear();
2116
+ const validChanges = [];
2117
+ const invalidFiles = [];
2118
+ for (const [file, changeEvent] of changes) {
2119
+ if (changeEvent === "removed") {
2120
+ validChanges.push([file, changeEvent]);
2121
+ continue;
2122
+ }
2123
+ const validation = await validateSVGFile(file);
2124
+ if (!validation.isValid) {
2125
+ if (validation.isEmpty) {
2126
+ console.log(import_chalk3.default.yellow(`\u23F3 Waiting for content: ${import_node_path7.default.basename(file)}`));
2127
+ setTimeout(() => {
2128
+ handleChange(changeEvent, file, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer);
2129
+ }, emptyFileRetryDelay);
2130
+ } else {
2131
+ console.error(import_chalk3.default.red(`\u274C Invalid SVG: ${import_node_path7.default.basename(file)} - ${validation.reason}`));
2132
+ invalidFiles.push(file);
2133
+ }
2134
+ continue;
2135
+ }
2136
+ validChanges.push([file, changeEvent]);
2137
+ }
2138
+ if (validChanges.length === 0) {
2139
+ return;
2140
+ }
2141
+ const spinner = (0, import_ora3.default)(`Processing ${validChanges.length} change(s)...`).start();
1584
2142
  try {
1585
- const stats = await generateIcons(config);
2143
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1586
2144
  if (stats.failed > 0) {
1587
2145
  spinner.warn(
1588
2146
  `Regenerated ${import_chalk3.default.green(stats.success)} icons, ${import_chalk3.default.red(stats.failed)} failed`
1589
2147
  );
1590
2148
  } else {
1591
- spinner.succeed(`Regenerated ${import_chalk3.default.green(stats.success)} icon components`);
2149
+ spinner.succeed(`\u2713 Updated ${import_chalk3.default.green(stats.success)} icon components`);
1592
2150
  }
1593
- console.log(import_chalk3.default.gray(` Triggered by: ${fileName} (${event})
2151
+ const triggerFiles = validChanges.map(([file, evt]) => `${import_node_path7.default.basename(file)} (${evt})`).join(", ");
2152
+ console.log(import_chalk3.default.gray(` Triggered by: ${triggerFiles}
1594
2153
  `));
1595
2154
  } catch (error) {
1596
2155
  spinner.fail("Regeneration failed");