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/cli.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
  }
@@ -133,8 +133,8 @@ var init_helpers = __esm({
133
133
  });
134
134
 
135
135
  // src/cli.ts
136
- var import_node_fs2 = require("fs");
137
- var import_node_path7 = require("path");
136
+ var import_node_fs3 = require("fs");
137
+ var import_node_path8 = require("path");
138
138
  var import_node_url2 = require("url");
139
139
  var import_chalk4 = __toESM(require("chalk"));
140
140
  var import_commander = require("commander");
@@ -143,8 +143,288 @@ var import_commander = require("commander");
143
143
  var import_chalk = __toESM(require("chalk"));
144
144
  var import_ora = __toESM(require("ora"));
145
145
 
146
+ // src/cache/cache-manager.ts
147
+ var import_node_fs = require("fs");
148
+ var import_promises2 = require("fs/promises");
149
+ var import_node_path = require("path");
150
+
151
+ // src/cache/hash-utils.ts
152
+ var import_node_crypto = require("crypto");
153
+ var import_promises = require("fs/promises");
154
+ function hashContent(content) {
155
+ return (0, import_node_crypto.createHash)("sha256").update(content).digest("hex");
156
+ }
157
+ async function hashFile(filePath) {
158
+ try {
159
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
160
+ return hashContent(content);
161
+ } catch (error) {
162
+ throw new Error(`Failed to hash file ${filePath}: ${error}`);
163
+ }
164
+ }
165
+ function hashConfig(config) {
166
+ const relevantConfig = {
167
+ framework: config.framework,
168
+ typescript: config.typescript,
169
+ keepColors: config.keepColors,
170
+ prefix: config.prefix,
171
+ suffix: config.suffix,
172
+ optimize: config.optimize,
173
+ svgoConfig: config.svgoConfig,
174
+ componentNameTransform: config.componentNameTransform?.toString()
175
+ };
176
+ return hashContent(JSON.stringify(relevantConfig));
177
+ }
178
+ function hashSvgoConfig(svgoConfig) {
179
+ return hashContent(JSON.stringify(svgoConfig || {}));
180
+ }
181
+
182
+ // src/cache/cache-manager.ts
183
+ var CACHE_VERSION = "1.0.0";
184
+ var CacheManager = class {
185
+ constructor(outputDir, cacheDir = ".vectify") {
186
+ this.cache = {
187
+ version: CACHE_VERSION,
188
+ configHash: "",
189
+ entries: {},
190
+ baseComponentHash: ""
191
+ };
192
+ this.cacheFilePath = (0, import_node_path.join)(outputDir, cacheDir, "cache.json");
193
+ this.stats = {
194
+ hits: 0,
195
+ misses: 0,
196
+ total: 0,
197
+ timeSaved: 0
198
+ };
199
+ this.isDirty = false;
200
+ process.on("beforeExit", () => {
201
+ if (this.isDirty) {
202
+ this.saveSync();
203
+ }
204
+ });
205
+ }
206
+ /**
207
+ * Load cache from disk
208
+ */
209
+ async load() {
210
+ try {
211
+ if (!(0, import_node_fs.existsSync)(this.cacheFilePath)) {
212
+ return;
213
+ }
214
+ const content = await (0, import_promises2.readFile)(this.cacheFilePath, "utf-8");
215
+ const loadedCache = JSON.parse(content);
216
+ if (loadedCache.version !== CACHE_VERSION) {
217
+ console.log("\u26A0\uFE0F Cache version mismatch, rebuilding cache...");
218
+ return;
219
+ }
220
+ this.cache = loadedCache;
221
+ } catch (error) {
222
+ console.warn("\u26A0\uFE0F Failed to load cache, starting fresh:", error);
223
+ this.cache = {
224
+ version: CACHE_VERSION,
225
+ configHash: "",
226
+ entries: {},
227
+ baseComponentHash: ""
228
+ };
229
+ }
230
+ }
231
+ /**
232
+ * Save cache to disk (atomic write)
233
+ */
234
+ async save() {
235
+ try {
236
+ const cacheDir = (0, import_node_path.dirname)(this.cacheFilePath);
237
+ await (0, import_promises2.mkdir)(cacheDir, { recursive: true });
238
+ const tempPath = `${this.cacheFilePath}.tmp`;
239
+ await (0, import_promises2.writeFile)(tempPath, JSON.stringify(this.cache, null, 2), "utf-8");
240
+ await (0, import_promises2.writeFile)(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
241
+ this.isDirty = false;
242
+ } catch (error) {
243
+ console.warn("\u26A0\uFE0F Failed to save cache:", error);
244
+ }
245
+ }
246
+ /**
247
+ * Synchronous save for process exit
248
+ */
249
+ saveSync() {
250
+ try {
251
+ const fs2 = require("fs");
252
+ const cacheDir = (0, import_node_path.dirname)(this.cacheFilePath);
253
+ if (!fs2.existsSync(cacheDir)) {
254
+ fs2.mkdirSync(cacheDir, { recursive: true });
255
+ }
256
+ fs2.writeFileSync(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
257
+ this.isDirty = false;
258
+ } catch {
259
+ console.warn("\u26A0\uFE0F Failed to save cache on exit");
260
+ }
261
+ }
262
+ /**
263
+ * Check if a file needs regeneration
264
+ */
265
+ async needsRegeneration(svgPath, componentPath, config) {
266
+ this.stats.total++;
267
+ if (!(0, import_node_fs.existsSync)(componentPath)) {
268
+ this.stats.misses++;
269
+ return true;
270
+ }
271
+ const entry = this.cache.entries[svgPath];
272
+ if (!entry) {
273
+ this.stats.misses++;
274
+ return true;
275
+ }
276
+ try {
277
+ const stats = await (0, import_promises2.stat)(svgPath);
278
+ const currentMtime = stats.mtimeMs;
279
+ if (currentMtime === entry.svgMtime) {
280
+ if (!this.isConfigMatching(entry, config)) {
281
+ this.stats.misses++;
282
+ return true;
283
+ }
284
+ this.stats.hits++;
285
+ this.stats.timeSaved += 50;
286
+ return false;
287
+ }
288
+ const currentHash = await hashFile(svgPath);
289
+ if (currentHash === entry.svgHash) {
290
+ entry.svgMtime = currentMtime;
291
+ this.isDirty = true;
292
+ this.stats.hits++;
293
+ this.stats.timeSaved += 50;
294
+ return false;
295
+ }
296
+ this.stats.misses++;
297
+ return true;
298
+ } catch {
299
+ this.stats.misses++;
300
+ return true;
301
+ }
302
+ }
303
+ /**
304
+ * Check if config matches cache entry
305
+ */
306
+ isConfigMatching(entry, config) {
307
+ const snapshot = entry.configSnapshot;
308
+ 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);
309
+ }
310
+ /**
311
+ * Update cache entry after successful generation
312
+ */
313
+ async updateEntry(svgPath, componentPath, componentName, componentHash, config) {
314
+ try {
315
+ const svgHash = await hashFile(svgPath);
316
+ const stats = await (0, import_promises2.stat)(svgPath);
317
+ this.cache.entries[svgPath] = {
318
+ svgPath,
319
+ svgHash,
320
+ svgMtime: stats.mtimeMs,
321
+ componentName,
322
+ componentPath,
323
+ componentHash,
324
+ configSnapshot: {
325
+ framework: config.framework,
326
+ typescript: config.typescript ?? true,
327
+ keepColors: config.keepColors ?? false,
328
+ prefix: config.prefix || "",
329
+ suffix: config.suffix || "",
330
+ optimize: config.optimize ?? true,
331
+ svgoConfigHash: hashSvgoConfig(config.svgoConfig)
332
+ },
333
+ generatedAt: Date.now()
334
+ };
335
+ this.isDirty = true;
336
+ } catch (error) {
337
+ console.warn(`\u26A0\uFE0F Failed to update cache entry for ${svgPath}:`, error);
338
+ }
339
+ }
340
+ /**
341
+ * Remove cache entry
342
+ */
343
+ removeEntry(svgPath) {
344
+ if (this.cache.entries[svgPath]) {
345
+ delete this.cache.entries[svgPath];
346
+ this.isDirty = true;
347
+ }
348
+ }
349
+ /**
350
+ * Update config hash
351
+ */
352
+ updateConfigHash(config) {
353
+ const newHash = hashConfig(config);
354
+ if (this.cache.configHash !== newHash) {
355
+ this.cache.configHash = newHash;
356
+ this.isDirty = true;
357
+ }
358
+ }
359
+ /**
360
+ * Update base component hash
361
+ */
362
+ updateBaseComponentHash(hash) {
363
+ if (this.cache.baseComponentHash !== hash) {
364
+ this.cache.baseComponentHash = hash;
365
+ this.isDirty = true;
366
+ }
367
+ }
368
+ /**
369
+ * Check if config has changed (invalidates all cache)
370
+ */
371
+ hasConfigChanged(config) {
372
+ const currentHash = hashConfig(config);
373
+ return this.cache.configHash !== "" && this.cache.configHash !== currentHash;
374
+ }
375
+ /**
376
+ * Invalidate all cache entries
377
+ */
378
+ invalidateAll() {
379
+ this.cache.entries = {};
380
+ this.isDirty = true;
381
+ }
382
+ /**
383
+ * Clean stale entries (files that no longer exist)
384
+ */
385
+ async cleanStaleEntries(existingSvgPaths) {
386
+ const existingSet = new Set(existingSvgPaths);
387
+ let cleaned = 0;
388
+ for (const svgPath of Object.keys(this.cache.entries)) {
389
+ if (!existingSet.has(svgPath)) {
390
+ delete this.cache.entries[svgPath];
391
+ cleaned++;
392
+ this.isDirty = true;
393
+ }
394
+ }
395
+ if (cleaned > 0) {
396
+ console.log(`\u{1F9F9} Cleaned ${cleaned} stale cache entries`);
397
+ }
398
+ }
399
+ /**
400
+ * Get cache statistics
401
+ */
402
+ getStats() {
403
+ return { ...this.stats };
404
+ }
405
+ /**
406
+ * Reset statistics
407
+ */
408
+ resetStats() {
409
+ this.stats = {
410
+ hits: 0,
411
+ misses: 0,
412
+ total: 0,
413
+ timeSaved: 0
414
+ };
415
+ }
416
+ /**
417
+ * Get cache hit rate
418
+ */
419
+ getHitRate() {
420
+ if (this.stats.total === 0)
421
+ return 0;
422
+ return this.stats.hits / this.stats.total * 100;
423
+ }
424
+ };
425
+
146
426
  // src/config/loader.ts
147
- var import_node_path2 = __toESM(require("path"));
427
+ var import_node_path3 = __toESM(require("path"));
148
428
  var import_node_process = __toESM(require("process"));
149
429
  var import_jiti = require("jiti");
150
430
 
@@ -168,6 +448,7 @@ function parseSvg(svgContent) {
168
448
  if (svgElement.length === 0) {
169
449
  throw new Error("Invalid SVG: No <svg> tag found");
170
450
  }
451
+ const viewBox = svgElement.attr("viewBox") || "0 0 24 24";
171
452
  const iconNodes = [];
172
453
  svgElement.children().each((_, element) => {
173
454
  const node = parseElement($, element);
@@ -175,7 +456,29 @@ function parseSvg(svgContent) {
175
456
  iconNodes.push(node);
176
457
  }
177
458
  });
178
- return iconNodes;
459
+ const isMultiColor = detectMultiColor(iconNodes);
460
+ return { iconNodes, viewBox, isMultiColor };
461
+ }
462
+ function detectMultiColor(nodes) {
463
+ const colors = /* @__PURE__ */ new Set();
464
+ function collectColors(node) {
465
+ const [, attrs, children] = node;
466
+ if (attrs.fill && attrs.fill !== "none" && attrs.fill !== "transparent") {
467
+ colors.add(String(attrs.fill).toLowerCase());
468
+ }
469
+ if (attrs.stroke && attrs.stroke !== "none" && attrs.stroke !== "transparent") {
470
+ colors.add(String(attrs.stroke).toLowerCase());
471
+ }
472
+ if (children && children.length > 0) {
473
+ children.forEach(collectColors);
474
+ }
475
+ }
476
+ nodes.forEach(collectColors);
477
+ const realColors = Array.from(colors).filter((color) => {
478
+ const c = color.toLowerCase();
479
+ return c !== "currentcolor";
480
+ });
481
+ return realColors.length >= 2;
179
482
  }
180
483
  function parseElement($, element) {
181
484
  if (element.type !== "tag") {
@@ -246,22 +549,22 @@ function formatAttrs(attrs) {
246
549
  }
247
550
 
248
551
  // src/generators/templates/template-engine.ts
249
- var import_node_fs = __toESM(require("fs"));
250
- var import_node_path = __toESM(require("path"));
552
+ var import_node_fs2 = __toESM(require("fs"));
553
+ var import_node_path2 = __toESM(require("path"));
251
554
  var import_node_url = require("url");
252
555
  var import_handlebars = __toESM(require("handlebars"));
253
556
  var import_meta = {};
254
557
  function getTemplatesDir() {
255
558
  if (typeof __dirname !== "undefined") {
256
- return import_node_path.default.join(__dirname, "templates");
559
+ return import_node_path2.default.join(__dirname, "templates");
257
560
  }
258
561
  const currentFile = (0, import_node_url.fileURLToPath)(import_meta.url);
259
- return import_node_path.default.join(import_node_path.default.dirname(currentFile), "templates");
562
+ return import_node_path2.default.join(import_node_path2.default.dirname(currentFile), "templates");
260
563
  }
261
564
  function loadTemplate(templatePath) {
262
565
  const templatesDir = getTemplatesDir();
263
- const fullPath = import_node_path.default.join(templatesDir, templatePath);
264
- const templateContent = import_node_fs.default.readFileSync(fullPath, "utf-8");
566
+ const fullPath = import_node_path2.default.join(templatesDir, templatePath);
567
+ const templateContent = import_node_fs2.default.readFileSync(fullPath, "utf-8");
265
568
  return import_handlebars.default.compile(templateContent, { noEscape: true });
266
569
  }
267
570
  function renderTemplate(templatePath, data) {
@@ -305,14 +608,15 @@ function getQwikTemplatePath(typescript, type) {
305
608
  function getAngularTemplatePath(type) {
306
609
  return `angular/${type}.ts.hbs`;
307
610
  }
308
- function generateAngularComponent(componentName, iconNode, typescript, keepColors = false) {
611
+ function generateAngularComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
309
612
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
310
613
  const templatePath = getAngularTemplatePath("component");
311
614
  return renderTemplate(templatePath, {
312
615
  componentName,
313
616
  formattedNodes,
314
617
  typescript,
315
- keepColors
618
+ keepColors,
619
+ viewBox
316
620
  });
317
621
  }
318
622
  function generateAngularBaseComponent(typescript) {
@@ -327,14 +631,15 @@ function generateAngularBaseComponent(typescript) {
327
631
  function getAstroTemplatePath(type) {
328
632
  return `astro/${type}.astro.hbs`;
329
633
  }
330
- function generateAstroComponent(componentName, iconNode, typescript, keepColors = false) {
634
+ function generateAstroComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
331
635
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
332
636
  const templatePath = getAstroTemplatePath("component");
333
637
  return renderTemplate(templatePath, {
334
638
  componentName,
335
639
  formattedNodes,
336
640
  typescript,
337
- keepColors
641
+ keepColors,
642
+ viewBox
338
643
  });
339
644
  }
340
645
  function generateAstroBaseComponent(typescript) {
@@ -350,14 +655,15 @@ function getLitTemplatePath(typescript, type) {
350
655
  const ext = typescript ? "ts" : "js";
351
656
  return `lit/${type}.${ext}.hbs`;
352
657
  }
353
- function generateLitComponent(componentName, iconNode, typescript, keepColors = false) {
658
+ function generateLitComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
354
659
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
355
660
  const templatePath = getLitTemplatePath(typescript, "component");
356
661
  return renderTemplate(templatePath, {
357
662
  componentName,
358
663
  formattedNodes,
359
664
  typescript,
360
- keepColors
665
+ keepColors,
666
+ viewBox
361
667
  });
362
668
  }
363
669
  function generateLitBaseComponent(typescript) {
@@ -369,14 +675,15 @@ function generateLitBaseComponent(typescript) {
369
675
  }
370
676
 
371
677
  // src/generators/react.ts
372
- function generateReactComponent(componentName, iconNode, typescript, keepColors = false) {
678
+ function generateReactComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
373
679
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
374
680
  const templatePath = getReactTemplatePath(typescript, "component");
375
681
  return renderTemplate(templatePath, {
376
682
  typescript,
377
683
  componentName,
378
684
  formattedNodes,
379
- keepColors
685
+ keepColors,
686
+ viewBox
380
687
  });
381
688
  }
382
689
  function generateCreateIcon(typescript) {
@@ -385,7 +692,7 @@ function generateCreateIcon(typescript) {
385
692
  }
386
693
 
387
694
  // src/generators/react-like.ts
388
- function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, framework) {
695
+ function generateReactLikeComponent(componentName, iconNode, typescript, keepColors, viewBox, framework) {
389
696
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
390
697
  let templatePath;
391
698
  if (framework === "solid") {
@@ -401,7 +708,8 @@ function generateReactLikeComponent(componentName, iconNode, typescript, keepCol
401
708
  componentName,
402
709
  formattedNodes,
403
710
  typescript,
404
- keepColors
711
+ keepColors,
712
+ viewBox
405
713
  });
406
714
  }
407
715
  function generateReactLikeBaseComponent(typescript, framework) {
@@ -422,12 +730,14 @@ function generateReactLikeBaseComponent(typescript, framework) {
422
730
  }
423
731
 
424
732
  // src/generators/svelte.ts
425
- function generateSvelteComponent(_componentName, iconNode, typescript) {
733
+ function generateSvelteComponent(_componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
426
734
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
427
735
  const templatePath = getSvelteTemplatePath(typescript, "component");
428
736
  return renderTemplate(templatePath, {
429
737
  typescript,
430
- formattedNodes
738
+ formattedNodes,
739
+ keepColors,
740
+ viewBox
431
741
  });
432
742
  }
433
743
  function generateSvelteIcon(typescript) {
@@ -436,14 +746,15 @@ function generateSvelteIcon(typescript) {
436
746
  }
437
747
 
438
748
  // src/generators/vanilla.ts
439
- function generateVanillaComponent(componentName, iconNode, typescript, keepColors) {
749
+ function generateVanillaComponent(componentName, iconNode, typescript, keepColors, viewBox) {
440
750
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 2)).join(",\n");
441
751
  const templatePath = getVanillaTemplatePath(typescript, "component");
442
752
  return renderTemplate(templatePath, {
443
753
  componentName,
444
754
  formattedNodes,
445
755
  typescript,
446
- keepColors
756
+ keepColors,
757
+ viewBox
447
758
  });
448
759
  }
449
760
  function generateVanillaBaseComponent(typescript) {
@@ -455,13 +766,15 @@ function generateVanillaBaseComponent(typescript) {
455
766
  }
456
767
 
457
768
  // src/generators/vue.ts
458
- function generateVueComponent(componentName, iconNode, typescript) {
769
+ function generateVueComponent(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
459
770
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
460
771
  const templatePath = getVueTemplatePath(typescript, "component");
461
772
  return renderTemplate(templatePath, {
462
773
  typescript,
463
774
  componentName,
464
- formattedNodes
775
+ formattedNodes,
776
+ keepColors,
777
+ viewBox
465
778
  });
466
779
  }
467
780
  function generateVueIcon(typescript) {
@@ -470,13 +783,15 @@ function generateVueIcon(typescript) {
470
783
  }
471
784
 
472
785
  // src/generators/vue2.ts
473
- function generateVue2Component(componentName, iconNode, typescript) {
786
+ function generateVue2Component(componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") {
474
787
  const formattedNodes = iconNode.map((node) => formatIconNode(node, 4)).join(",\n");
475
788
  const templatePath = getVue2TemplatePath(typescript, "component");
476
789
  return renderTemplate(templatePath, {
477
790
  typescript,
478
791
  componentName,
479
- formattedNodes
792
+ formattedNodes,
793
+ keepColors,
794
+ viewBox
480
795
  });
481
796
  }
482
797
  function generateVue2Icon(typescript) {
@@ -494,8 +809,8 @@ var ReactStrategy = class {
494
809
  this.getIndexExtension = (typescript) => {
495
810
  return typescript ? "ts" : "js";
496
811
  };
497
- this.generateComponent = (componentName, iconNode, typescript, keepColors = false) => {
498
- return generateReactComponent(componentName, iconNode, typescript, keepColors);
812
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
813
+ return generateReactComponent(componentName, iconNode, typescript, keepColors, viewBox);
499
814
  };
500
815
  this.generateBaseComponent = (typescript) => {
501
816
  return {
@@ -514,8 +829,8 @@ var VueStrategy = class {
514
829
  this.getIndexExtension = (typescript) => {
515
830
  return typescript ? "ts" : "js";
516
831
  };
517
- this.generateComponent = (componentName, iconNode, typescript) => {
518
- return generateVueComponent(componentName, iconNode, typescript);
832
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
833
+ return generateVueComponent(componentName, iconNode, typescript, keepColors, viewBox);
519
834
  };
520
835
  this.generateBaseComponent = (typescript) => {
521
836
  return {
@@ -534,8 +849,8 @@ var Vue2Strategy = class {
534
849
  this.getIndexExtension = (typescript) => {
535
850
  return typescript ? "ts" : "js";
536
851
  };
537
- this.generateComponent = (componentName, iconNode, typescript) => {
538
- return generateVue2Component(componentName, iconNode, typescript);
852
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
853
+ return generateVue2Component(componentName, iconNode, typescript, keepColors, viewBox);
539
854
  };
540
855
  this.generateBaseComponent = (typescript) => {
541
856
  return {
@@ -554,8 +869,8 @@ var SvelteStrategy = class {
554
869
  this.getIndexExtension = (typescript) => {
555
870
  return typescript ? "ts" : "js";
556
871
  };
557
- this.generateComponent = (componentName, iconNode, typescript) => {
558
- return generateSvelteComponent(componentName, iconNode, typescript);
872
+ this.generateComponent = (componentName, iconNode, typescript, keepColors = false, viewBox = "0 0 24 24") => {
873
+ return generateSvelteComponent(componentName, iconNode, typescript, keepColors, viewBox);
559
874
  };
560
875
  this.generateBaseComponent = (typescript) => {
561
876
  return {
@@ -574,8 +889,8 @@ var SolidStrategy = class {
574
889
  this.getIndexExtension = (typescript) => {
575
890
  return typescript ? "ts" : "js";
576
891
  };
577
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
578
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "solid");
892
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
893
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "solid");
579
894
  };
580
895
  this.generateBaseComponent = (typescript) => {
581
896
  return generateReactLikeBaseComponent(typescript, "solid");
@@ -591,8 +906,8 @@ var PreactStrategy = class {
591
906
  this.getIndexExtension = (typescript) => {
592
907
  return typescript ? "ts" : "js";
593
908
  };
594
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
595
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "preact");
909
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
910
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "preact");
596
911
  };
597
912
  this.generateBaseComponent = (typescript) => {
598
913
  return generateReactLikeBaseComponent(typescript, "preact");
@@ -608,8 +923,8 @@ var VanillaStrategy = class {
608
923
  this.getIndexExtension = (typescript) => {
609
924
  return typescript ? "ts" : "js";
610
925
  };
611
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
612
- return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false);
926
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
927
+ return generateVanillaComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
613
928
  };
614
929
  this.generateBaseComponent = (typescript) => {
615
930
  return generateVanillaBaseComponent(typescript);
@@ -625,8 +940,8 @@ var LitStrategy = class {
625
940
  this.getIndexExtension = (typescript) => {
626
941
  return typescript ? "ts" : "js";
627
942
  };
628
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
629
- return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false);
943
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
944
+ return generateLitComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
630
945
  };
631
946
  this.generateBaseComponent = (typescript) => {
632
947
  return generateLitBaseComponent(typescript);
@@ -642,8 +957,8 @@ var QwikStrategy = class {
642
957
  this.getIndexExtension = (typescript) => {
643
958
  return typescript ? "ts" : "js";
644
959
  };
645
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
646
- return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, "qwik");
960
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
961
+ return generateReactLikeComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24", "qwik");
647
962
  };
648
963
  this.generateBaseComponent = (typescript) => {
649
964
  return generateReactLikeBaseComponent(typescript, "qwik");
@@ -659,8 +974,8 @@ var AstroStrategy = class {
659
974
  this.getIndexExtension = (typescript) => {
660
975
  return typescript ? "ts" : "js";
661
976
  };
662
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
663
- return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false);
977
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
978
+ return generateAstroComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
664
979
  };
665
980
  this.generateBaseComponent = (typescript) => {
666
981
  return generateAstroBaseComponent(typescript);
@@ -676,8 +991,8 @@ var AngularStrategy = class {
676
991
  this.getIndexExtension = (_typescript) => {
677
992
  return "ts";
678
993
  };
679
- this.generateComponent = (componentName, iconNode, typescript, keepColors) => {
680
- return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false);
994
+ this.generateComponent = (componentName, iconNode, typescript, keepColors, viewBox) => {
995
+ return generateAngularComponent(componentName, iconNode, typescript, keepColors ?? false, viewBox ?? "0 0 24 24");
681
996
  };
682
997
  this.generateBaseComponent = (typescript) => {
683
998
  return generateAngularBaseComponent(typescript);
@@ -739,7 +1054,6 @@ var DEFAULT_CONFIG = {
739
1054
  output: "./src/icons",
740
1055
  typescript: true,
741
1056
  optimize: true,
742
- keepColors: false,
743
1057
  prefix: "",
744
1058
  suffix: "",
745
1059
  configDir: ".",
@@ -756,8 +1070,8 @@ var DEFAULT_CONFIG = {
756
1070
  }
757
1071
  };
758
1072
  async function loadConfig(configPath) {
759
- const absolutePath = import_node_path2.default.resolve(import_node_process.default.cwd(), configPath);
760
- const configDir = import_node_path2.default.dirname(absolutePath);
1073
+ const absolutePath = import_node_path3.default.resolve(import_node_process.default.cwd(), configPath);
1074
+ const configDir = import_node_path3.default.dirname(absolutePath);
761
1075
  const jiti = (0, import_jiti.createJiti)(configDir, {
762
1076
  interopDefault: true
763
1077
  });
@@ -784,12 +1098,12 @@ async function loadConfig(configPath) {
784
1098
  }
785
1099
  };
786
1100
  if (mergedConfig.configDir && mergedConfig.configDir !== ".") {
787
- const projectRoot = import_node_path2.default.resolve(configDir, mergedConfig.configDir);
788
- mergedConfig.input = import_node_path2.default.resolve(projectRoot, mergedConfig.input);
789
- mergedConfig.output = import_node_path2.default.resolve(projectRoot, mergedConfig.output);
1101
+ const projectRoot = import_node_path3.default.resolve(configDir, mergedConfig.configDir);
1102
+ mergedConfig.input = import_node_path3.default.resolve(projectRoot, mergedConfig.input);
1103
+ mergedConfig.output = import_node_path3.default.resolve(projectRoot, mergedConfig.output);
790
1104
  } else {
791
- mergedConfig.input = import_node_path2.default.resolve(configDir, mergedConfig.input);
792
- mergedConfig.output = import_node_path2.default.resolve(configDir, mergedConfig.output);
1105
+ mergedConfig.input = import_node_path3.default.resolve(configDir, mergedConfig.input);
1106
+ mergedConfig.output = import_node_path3.default.resolve(configDir, mergedConfig.output);
793
1107
  }
794
1108
  if (!mergedConfig.framework) {
795
1109
  const supported = frameworkRegistry.getSupportedFrameworks().join(", ");
@@ -808,7 +1122,7 @@ async function findConfig() {
808
1122
  "vectify.config.js"
809
1123
  ];
810
1124
  for (const file of configFiles) {
811
- const configPath = import_node_path2.default.resolve(import_node_process.default.cwd(), file);
1125
+ const configPath = import_node_path3.default.resolve(import_node_process.default.cwd(), file);
812
1126
  if (await fileExists2(configPath)) {
813
1127
  return configPath;
814
1128
  }
@@ -817,7 +1131,7 @@ async function findConfig() {
817
1131
  }
818
1132
 
819
1133
  // src/generators/index.ts
820
- var import_node_path4 = __toESM(require("path"));
1134
+ var import_node_path5 = __toESM(require("path"));
821
1135
 
822
1136
  // src/parsers/optimizer.ts
823
1137
  var import_svgo = require("svgo");
@@ -853,7 +1167,7 @@ async function optimizeSvg(svgContent, config) {
853
1167
 
854
1168
  // src/utils/formatter.ts
855
1169
  var import_node_child_process = require("child_process");
856
- var import_node_path3 = __toESM(require("path"));
1170
+ var import_node_path4 = __toESM(require("path"));
857
1171
  var import_node_process2 = __toESM(require("process"));
858
1172
  var import_node_util = require("util");
859
1173
  init_helpers();
@@ -908,7 +1222,7 @@ async function detectFormatter() {
908
1222
  for (const tool of priority) {
909
1223
  const patterns = FORMATTER_PATTERNS[tool];
910
1224
  for (const pattern of patterns) {
911
- const configPath = import_node_path3.default.join(cwd, pattern);
1225
+ const configPath = import_node_path4.default.join(cwd, pattern);
912
1226
  if (await fileExists(configPath)) {
913
1227
  return tool;
914
1228
  }
@@ -1004,90 +1318,206 @@ async function generateIcons(config, dryRun = false) {
1004
1318
  }
1005
1319
  return stats;
1006
1320
  }
1007
- async function generateIconComponent(svgFile, config, dryRun = false) {
1008
- let svgContent = await readFile(svgFile);
1009
- const fileName = import_node_path4.default.basename(svgFile);
1321
+ async function generateIconsIncremental(config, cacheManager, dryRun = false) {
1322
+ const stats = {
1323
+ success: 0,
1324
+ failed: 0,
1325
+ total: 0,
1326
+ errors: []
1327
+ };
1328
+ try {
1329
+ if (!dryRun) {
1330
+ await ensureDir(config.output);
1331
+ }
1332
+ const svgFiles = await getSvgFiles(config.input);
1333
+ stats.total = svgFiles.length;
1334
+ if (svgFiles.length === 0) {
1335
+ console.warn(`No SVG files found in ${config.input}`);
1336
+ return stats;
1337
+ }
1338
+ if (cacheManager.hasConfigChanged(config)) {
1339
+ console.log("\u26A0\uFE0F Configuration changed, rebuilding all icons...");
1340
+ cacheManager.invalidateAll();
1341
+ }
1342
+ cacheManager.updateConfigHash(config);
1343
+ await cacheManager.cleanStaleEntries(svgFiles);
1344
+ if (config.generateOptions?.cleanOutput && !dryRun) {
1345
+ await cleanOutputDirectory(svgFiles, config);
1346
+ }
1347
+ await generateBaseComponent(config, dryRun);
1348
+ const strategy = getFrameworkStrategy(config.framework);
1349
+ const typescript = config.typescript ?? true;
1350
+ const fileExt = strategy.getComponentExtension(typescript);
1351
+ const filesToGenerate = [];
1352
+ const cachedFiles = [];
1353
+ for (const svgFile of svgFiles) {
1354
+ const fileName = import_node_path5.default.basename(svgFile);
1355
+ const componentName = getComponentName(
1356
+ fileName,
1357
+ config.prefix,
1358
+ config.suffix,
1359
+ config.componentNameTransform
1360
+ );
1361
+ const componentPath = import_node_path5.default.join(config.output, `${componentName}.${fileExt}`);
1362
+ const needsRegen = await cacheManager.needsRegeneration(
1363
+ svgFile,
1364
+ componentPath,
1365
+ config
1366
+ );
1367
+ if (needsRegen) {
1368
+ filesToGenerate.push(svgFile);
1369
+ } else {
1370
+ cachedFiles.push(svgFile);
1371
+ }
1372
+ }
1373
+ if (cachedFiles.length > 0) {
1374
+ console.log(`\u{1F4E6} Cache: ${cachedFiles.length} cached, ${filesToGenerate.length} to generate`);
1375
+ }
1376
+ for (const svgFile of filesToGenerate) {
1377
+ try {
1378
+ await generateIconComponent(svgFile, config, dryRun, cacheManager);
1379
+ stats.success++;
1380
+ } catch (error) {
1381
+ stats.failed++;
1382
+ stats.errors.push({
1383
+ file: svgFile,
1384
+ error: error.message
1385
+ });
1386
+ console.error(`Failed to generate ${svgFile}: ${error.message}`);
1387
+ }
1388
+ }
1389
+ stats.success += cachedFiles.length;
1390
+ if (config.generateOptions?.index) {
1391
+ await generateIndexFile(svgFiles, config, dryRun);
1392
+ }
1393
+ if (config.generateOptions?.preview && !dryRun) {
1394
+ await generatePreviewHtml(svgFiles, config);
1395
+ }
1396
+ if (!dryRun) {
1397
+ await cacheManager.save();
1398
+ const cacheStats = cacheManager.getStats();
1399
+ if (cacheStats.total > 0) {
1400
+ const hitRate = cacheManager.getHitRate();
1401
+ const timeSaved = (cacheStats.timeSaved / 1e3).toFixed(1);
1402
+ console.log(`\u26A1 Cache saved ~${timeSaved}s (${hitRate.toFixed(1)}% hit rate)`);
1403
+ }
1404
+ }
1405
+ if (config.format && !dryRun) {
1406
+ const formatResult = await formatOutput(config.output, config.format);
1407
+ if (formatResult.success && formatResult.tool) {
1408
+ console.log(`Formatted with ${formatResult.tool}`);
1409
+ } else if (formatResult.error) {
1410
+ console.warn(formatResult.error);
1411
+ }
1412
+ }
1413
+ if (config.hooks?.onComplete) {
1414
+ await config.hooks.onComplete(stats);
1415
+ }
1416
+ } catch (error) {
1417
+ throw new Error(`Generation failed: ${error.message}`);
1418
+ }
1419
+ return stats;
1420
+ }
1421
+ async function generateIconComponent(svgFile, config, dryRun = false, cacheManager) {
1422
+ let svgContent = await readFile3(svgFile);
1423
+ const fileName = import_node_path5.default.basename(svgFile);
1010
1424
  if (config.hooks?.beforeParse) {
1011
1425
  svgContent = await config.hooks.beforeParse(svgContent, fileName);
1012
1426
  }
1427
+ const { isMultiColor: isMultiColorBeforeOptimization } = parseSvg(svgContent);
1013
1428
  svgContent = await optimizeSvg(svgContent, config);
1014
- const iconNode = parseSvg(svgContent);
1429
+ const { iconNodes, viewBox } = parseSvg(svgContent);
1015
1430
  const componentName = getComponentName(
1016
1431
  fileName,
1017
1432
  config.prefix,
1018
1433
  config.suffix,
1019
- config.transform
1434
+ config.componentNameTransform
1020
1435
  );
1021
1436
  const strategy = getFrameworkStrategy(config.framework);
1022
1437
  const typescript = config.typescript ?? true;
1438
+ const keepColors = config.keepColors !== void 0 ? config.keepColors : isMultiColorBeforeOptimization;
1023
1439
  let code = strategy.generateComponent(
1024
1440
  componentName,
1025
- iconNode,
1441
+ iconNodes,
1026
1442
  typescript,
1027
- config.keepColors ?? false
1443
+ keepColors,
1444
+ viewBox
1028
1445
  );
1029
1446
  if (config.hooks?.afterGenerate) {
1030
1447
  code = await config.hooks.afterGenerate(code, componentName);
1031
1448
  }
1032
1449
  const fileExt = strategy.getComponentExtension(typescript);
1033
- const outputPath = import_node_path4.default.join(config.output, `${componentName}.${fileExt}`);
1450
+ const outputPath = import_node_path5.default.join(config.output, `${componentName}.${fileExt}`);
1034
1451
  if (dryRun) {
1035
1452
  console.log(` ${componentName}.${fileExt}`);
1036
1453
  } else {
1037
- await writeFile(outputPath, code);
1454
+ await writeFile2(outputPath, code);
1455
+ if (cacheManager) {
1456
+ const componentHash = hashContent(code);
1457
+ await cacheManager.updateEntry(
1458
+ svgFile,
1459
+ outputPath,
1460
+ componentName,
1461
+ componentHash,
1462
+ config
1463
+ );
1464
+ }
1038
1465
  }
1039
1466
  }
1040
1467
  async function generateBaseComponent(config, dryRun = false) {
1041
1468
  const typescript = config.typescript ?? true;
1042
1469
  const strategy = getFrameworkStrategy(config.framework);
1043
1470
  const { code, fileName } = strategy.generateBaseComponent(typescript);
1044
- const outputPath = import_node_path4.default.join(config.output, fileName);
1471
+ const outputPath = import_node_path5.default.join(config.output, fileName);
1045
1472
  if (dryRun) {
1046
1473
  console.log(` ${fileName}`);
1047
1474
  } else {
1048
- await writeFile(outputPath, code);
1475
+ await writeFile2(outputPath, code);
1049
1476
  }
1050
1477
  }
1051
1478
  async function generateIndexFile(svgFiles, config, dryRun = false) {
1052
1479
  const typescript = config.typescript ?? true;
1053
1480
  const strategy = getFrameworkStrategy(config.framework);
1054
1481
  const ext = strategy.getIndexExtension(typescript);
1482
+ const componentExt = strategy.getComponentExtension(typescript);
1055
1483
  const usesDefaultExport = ["vue", "vue2", "svelte", "react", "preact"].includes(config.framework);
1484
+ const needsExtension = ["vue", "vue2", "svelte"].includes(config.framework);
1056
1485
  const exports2 = svgFiles.map((svgFile) => {
1057
- const fileName = import_node_path4.default.basename(svgFile);
1486
+ const fileName = import_node_path5.default.basename(svgFile);
1058
1487
  const componentName = getComponentName(
1059
1488
  fileName,
1060
1489
  config.prefix,
1061
1490
  config.suffix,
1062
- config.transform
1491
+ config.componentNameTransform
1063
1492
  );
1493
+ const importPath = needsExtension ? `./${componentName}.${componentExt}` : `./${componentName}`;
1064
1494
  if (usesDefaultExport) {
1065
- return `export { default as ${componentName} } from './${componentName}'`;
1495
+ return `export { default as ${componentName} } from '${importPath}'`;
1066
1496
  } else {
1067
- return `export { ${componentName} } from './${componentName}'`;
1497
+ return `export { ${componentName} } from '${importPath}'`;
1068
1498
  }
1069
1499
  }).join("\n");
1070
- const indexPath = import_node_path4.default.join(config.output, `index.${ext}`);
1500
+ const indexPath = import_node_path5.default.join(config.output, `index.${ext}`);
1071
1501
  if (dryRun) {
1072
1502
  console.log(` index.${ext}`);
1073
1503
  } else {
1074
- await writeFile(indexPath, `${exports2}
1504
+ await writeFile2(indexPath, `${exports2}
1075
1505
  `);
1076
1506
  }
1077
1507
  }
1078
1508
  async function generatePreviewHtml(svgFiles, config) {
1079
1509
  const componentNames = svgFiles.map((svgFile) => {
1080
- const fileName = import_node_path4.default.basename(svgFile);
1510
+ const fileName = import_node_path5.default.basename(svgFile);
1081
1511
  return getComponentName(
1082
1512
  fileName,
1083
1513
  config.prefix,
1084
1514
  config.suffix,
1085
- config.transform
1515
+ config.componentNameTransform
1086
1516
  );
1087
1517
  });
1088
1518
  const svgContents = await Promise.all(
1089
1519
  svgFiles.map(async (svgFile) => {
1090
- return await readFile(svgFile);
1520
+ return await readFile3(svgFile);
1091
1521
  })
1092
1522
  );
1093
1523
  const html = `<!DOCTYPE html>
@@ -1249,8 +1679,8 @@ async function generatePreviewHtml(svgFiles, config) {
1249
1679
  </script>
1250
1680
  </body>
1251
1681
  </html>`;
1252
- const previewPath = import_node_path4.default.join(config.output, "preview.html");
1253
- await writeFile(previewPath, html);
1682
+ const previewPath = import_node_path5.default.join(config.output, "preview.html");
1683
+ await writeFile2(previewPath, html);
1254
1684
  }
1255
1685
  async function cleanOutputDirectory(svgFiles, config) {
1256
1686
  const { readdir, unlink } = await import("fs/promises");
@@ -1258,12 +1688,12 @@ async function cleanOutputDirectory(svgFiles, config) {
1258
1688
  const fileExt = strategy.getComponentExtension(config.typescript ?? true);
1259
1689
  const expectedComponents = new Set(
1260
1690
  svgFiles.map((svgFile) => {
1261
- const fileName = import_node_path4.default.basename(svgFile, ".svg");
1691
+ const fileName = import_node_path5.default.basename(svgFile, ".svg");
1262
1692
  const componentName = getComponentName(
1263
1693
  fileName,
1264
1694
  config.prefix,
1265
1695
  config.suffix,
1266
- config.transform
1696
+ config.componentNameTransform
1267
1697
  );
1268
1698
  return `${componentName}.${fileExt}`;
1269
1699
  })
@@ -1285,7 +1715,7 @@ async function cleanOutputDirectory(svgFiles, config) {
1285
1715
  continue;
1286
1716
  }
1287
1717
  if (!expectedComponents.has(file)) {
1288
- const filePath = import_node_path4.default.join(config.output, file);
1718
+ const filePath = import_node_path5.default.join(config.output, file);
1289
1719
  await unlink(filePath);
1290
1720
  console.log(`Deleted orphaned component: ${file}`);
1291
1721
  }
@@ -1317,9 +1747,19 @@ async function generate(options = {}) {
1317
1747
  ${import_chalk.default.bold("Files that would be generated:")}
1318
1748
  `);
1319
1749
  }
1750
+ const useIncremental = config.incremental?.enabled !== false && !options.force && !options.dryRun;
1751
+ let cacheManager = null;
1752
+ if (useIncremental) {
1753
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
1754
+ cacheManager = new CacheManager(config.output, cacheDir);
1755
+ await cacheManager.load();
1756
+ }
1757
+ if (options.force) {
1758
+ spinner.info(import_chalk.default.yellow("Force mode - ignoring cache, regenerating all icons"));
1759
+ }
1320
1760
  const actionText = options.dryRun ? "Analyzing icons..." : "Generating icon components...";
1321
1761
  spinner.start(actionText);
1322
- const stats = await generateIcons(config, options.dryRun);
1762
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager, options.dryRun) : await generateIcons(config, options.dryRun);
1323
1763
  if (stats.failed > 0) {
1324
1764
  spinner.warn(`${options.dryRun ? "Analyzed" : "Generated"} ${import_chalk.default.green(stats.success)} icons, ${import_chalk.default.red(stats.failed)} failed`);
1325
1765
  if (stats.errors.length > 0) {
@@ -1352,7 +1792,7 @@ ${import_chalk.default.bold("Output:")} ${import_chalk.default.cyan(config.outpu
1352
1792
  }
1353
1793
 
1354
1794
  // src/commands/init.ts
1355
- var import_node_path5 = __toESM(require("path"));
1795
+ var import_node_path6 = __toESM(require("path"));
1356
1796
  var import_node_process3 = __toESM(require("process"));
1357
1797
  var import_chalk2 = __toESM(require("chalk"));
1358
1798
  var import_inquirer = __toESM(require("inquirer"));
@@ -1382,8 +1822,8 @@ Note: Project root detected at ${import_chalk2.default.cyan(projectRoot)}`));
1382
1822
  }
1383
1823
  }
1384
1824
  ]);
1385
- const configPath = import_node_path5.default.resolve(projectRoot, pathAnswers.configPath);
1386
- const configDir = import_node_path5.default.dirname(configPath);
1825
+ const configPath = import_node_path6.default.resolve(projectRoot, pathAnswers.configPath);
1826
+ const configDir = import_node_path6.default.dirname(configPath);
1387
1827
  if (!options.force && await fileExists(configPath)) {
1388
1828
  const { overwrite } = await import_inquirer.default.prompt([
1389
1829
  {
@@ -1457,18 +1897,18 @@ Note: Project root detected at ${import_chalk2.default.cyan(projectRoot)}`));
1457
1897
  default: ""
1458
1898
  }
1459
1899
  ]);
1460
- const inputPath = import_node_path5.default.resolve(projectRoot, answers.input);
1461
- const outputPath = import_node_path5.default.resolve(projectRoot, answers.output);
1900
+ const inputPath = import_node_path6.default.resolve(projectRoot, answers.input);
1901
+ const outputPath = import_node_path6.default.resolve(projectRoot, answers.output);
1462
1902
  const spinner = (0, import_ora2.default)("Setting up directories...").start();
1463
1903
  await ensureDir(inputPath);
1464
1904
  spinner.text = `Created input directory: ${import_chalk2.default.cyan(answers.input)}`;
1465
1905
  await ensureDir(outputPath);
1466
1906
  spinner.succeed(`Created output directory: ${import_chalk2.default.cyan(answers.output)}`);
1467
- const relativeConfigDir = import_node_path5.default.relative(configDir, projectRoot) || ".";
1907
+ const relativeConfigDir = import_node_path6.default.relative(configDir, projectRoot) || ".";
1468
1908
  const finalFramework = answers.vueVersion || answers.framework;
1469
1909
  const configContent = generateConfigContent({ ...answers, framework: finalFramework }, relativeConfigDir);
1470
1910
  spinner.start("Creating config file...");
1471
- await writeFile(configPath, configContent);
1911
+ await writeFile2(configPath, configContent);
1472
1912
  spinner.succeed(`Config file created at ${import_chalk2.default.green(configPath)}`);
1473
1913
  console.log(`
1474
1914
  ${import_chalk2.default.bold("Next steps:")}`);
@@ -1508,10 +1948,93 @@ export default defineConfig({
1508
1948
  }
1509
1949
 
1510
1950
  // src/commands/watch.ts
1511
- var import_node_path6 = __toESM(require("path"));
1951
+ var import_node_path7 = __toESM(require("path"));
1512
1952
  var import_chalk3 = __toESM(require("chalk"));
1513
1953
  var import_chokidar = __toESM(require("chokidar"));
1514
1954
  var import_ora3 = __toESM(require("ora"));
1955
+
1956
+ // src/cache/svg-validator.ts
1957
+ var import_promises3 = require("fs/promises");
1958
+ var import_cheerio = require("cheerio");
1959
+ var MIN_SVG_SIZE = 20;
1960
+ var DRAWABLE_ELEMENTS = [
1961
+ "path",
1962
+ "circle",
1963
+ "rect",
1964
+ "ellipse",
1965
+ "line",
1966
+ "polyline",
1967
+ "polygon",
1968
+ "text",
1969
+ "image",
1970
+ "use"
1971
+ ];
1972
+ async function validateSVGFile(filePath) {
1973
+ try {
1974
+ const stats = await (0, import_promises3.stat)(filePath);
1975
+ if (stats.size < MIN_SVG_SIZE) {
1976
+ return {
1977
+ isValid: false,
1978
+ isEmpty: true,
1979
+ reason: "File is too small to be a valid SVG"
1980
+ };
1981
+ }
1982
+ const content = await (0, import_promises3.readFile)(filePath, "utf-8");
1983
+ if (!content.trim()) {
1984
+ return {
1985
+ isValid: false,
1986
+ isEmpty: true,
1987
+ reason: "File is empty"
1988
+ };
1989
+ }
1990
+ if (!content.includes("<svg")) {
1991
+ return {
1992
+ isValid: false,
1993
+ isEmpty: false,
1994
+ reason: "File does not contain <svg> tag"
1995
+ };
1996
+ }
1997
+ if (!hasDrawableContent(content)) {
1998
+ return {
1999
+ isValid: false,
2000
+ isEmpty: true,
2001
+ reason: "SVG has no drawable content"
2002
+ };
2003
+ }
2004
+ try {
2005
+ const $ = (0, import_cheerio.load)(content, { xmlMode: true });
2006
+ const svgElement = $("svg");
2007
+ if (svgElement.length === 0) {
2008
+ return {
2009
+ isValid: false,
2010
+ isEmpty: false,
2011
+ reason: "Failed to parse SVG element"
2012
+ };
2013
+ }
2014
+ return {
2015
+ isValid: true,
2016
+ isEmpty: false
2017
+ };
2018
+ } catch (parseError) {
2019
+ return {
2020
+ isValid: false,
2021
+ isEmpty: false,
2022
+ reason: `Failed to parse SVG: ${parseError}`
2023
+ };
2024
+ }
2025
+ } catch (error) {
2026
+ return {
2027
+ isValid: false,
2028
+ isEmpty: false,
2029
+ reason: `Failed to read file: ${error}`
2030
+ };
2031
+ }
2032
+ }
2033
+ function hasDrawableContent(content) {
2034
+ return DRAWABLE_ELEMENTS.some((element) => content.includes(`<${element}`));
2035
+ }
2036
+
2037
+ // src/commands/watch.ts
1515
2038
  async function watch(options = {}) {
1516
2039
  const spinner = (0, import_ora3.default)("Loading configuration...").start();
1517
2040
  try {
@@ -1527,16 +2050,24 @@ async function watch(options = {}) {
1527
2050
  }
1528
2051
  const config = await loadConfig(configPath);
1529
2052
  spinner.succeed(`Config loaded from ${import_chalk3.default.green(configPath)}`);
2053
+ const useIncremental = config.incremental?.enabled !== false;
2054
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
2055
+ const cacheManager = useIncremental ? new CacheManager(config.output, cacheDir) : null;
2056
+ if (cacheManager) {
2057
+ await cacheManager.load();
2058
+ }
1530
2059
  spinner.start("Generating icon components...");
1531
- const initialStats = await generateIcons(config);
2060
+ const initialStats = useIncremental && cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1532
2061
  spinner.succeed(`Generated ${import_chalk3.default.green(initialStats.success)} icon components`);
1533
- const watchPath = import_node_path6.default.join(config.input, "**/*.svg");
2062
+ const watchPath = import_node_path7.default.join(config.input, "**/*.svg");
1534
2063
  const debounce = config.watch?.debounce ?? 300;
1535
2064
  const ignore = config.watch?.ignore ?? ["**/node_modules/**", "**/.git/**"];
2065
+ const emptyFileRetryDelay = config.watch?.emptyFileRetryDelay ?? 2e3;
1536
2066
  console.log(import_chalk3.default.bold("\nWatching for changes..."));
1537
2067
  console.log(` ${import_chalk3.default.cyan(watchPath)}`);
1538
2068
  console.log(` ${import_chalk3.default.gray("Press Ctrl+C to stop")}
1539
2069
  `);
2070
+ const pendingChanges = /* @__PURE__ */ new Map();
1540
2071
  const debounceTimer = null;
1541
2072
  const watcher = import_chokidar.default.watch(watchPath, {
1542
2073
  ignored: ignore,
@@ -1544,12 +2075,12 @@ async function watch(options = {}) {
1544
2075
  ignoreInitial: true
1545
2076
  });
1546
2077
  watcher.on("add", (filePath) => {
1547
- handleChange("added", filePath, config, debounce, debounceTimer);
2078
+ handleChange("added", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1548
2079
  }).on("change", (filePath) => {
1549
- handleChange("changed", filePath, config, debounce, debounceTimer);
2080
+ handleChange("changed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1550
2081
  }).on("unlink", (filePath) => {
1551
- console.log(import_chalk3.default.yellow(`SVG file removed: ${import_node_path6.default.basename(filePath)}`));
1552
- handleChange("removed", filePath, config, debounce, debounceTimer);
2082
+ console.log(import_chalk3.default.yellow(`SVG file removed: ${import_node_path7.default.basename(filePath)}`));
2083
+ handleChange("removed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1553
2084
  }).on("error", (error) => {
1554
2085
  console.error(import_chalk3.default.red(`Watcher error: ${error.message}`));
1555
2086
  });
@@ -1566,23 +2097,51 @@ ${import_chalk3.default.yellow("Stopping watch mode...")}`);
1566
2097
  throw error;
1567
2098
  }
1568
2099
  }
1569
- function handleChange(event, filePath, config, debounce, timer) {
1570
- const fileName = import_node_path6.default.basename(filePath);
2100
+ function handleChange(event, filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer) {
2101
+ pendingChanges.set(filePath, event);
1571
2102
  if (timer) {
1572
2103
  clearTimeout(timer);
1573
2104
  }
1574
2105
  timer = setTimeout(async () => {
1575
- const spinner = (0, import_ora3.default)(`Regenerating icons...`).start();
2106
+ const changes = Array.from(pendingChanges.entries());
2107
+ pendingChanges.clear();
2108
+ const validChanges = [];
2109
+ const invalidFiles = [];
2110
+ for (const [file, changeEvent] of changes) {
2111
+ if (changeEvent === "removed") {
2112
+ validChanges.push([file, changeEvent]);
2113
+ continue;
2114
+ }
2115
+ const validation = await validateSVGFile(file);
2116
+ if (!validation.isValid) {
2117
+ if (validation.isEmpty) {
2118
+ console.log(import_chalk3.default.yellow(`\u23F3 Waiting for content: ${import_node_path7.default.basename(file)}`));
2119
+ setTimeout(() => {
2120
+ handleChange(changeEvent, file, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer);
2121
+ }, emptyFileRetryDelay);
2122
+ } else {
2123
+ console.error(import_chalk3.default.red(`\u274C Invalid SVG: ${import_node_path7.default.basename(file)} - ${validation.reason}`));
2124
+ invalidFiles.push(file);
2125
+ }
2126
+ continue;
2127
+ }
2128
+ validChanges.push([file, changeEvent]);
2129
+ }
2130
+ if (validChanges.length === 0) {
2131
+ return;
2132
+ }
2133
+ const spinner = (0, import_ora3.default)(`Processing ${validChanges.length} change(s)...`).start();
1576
2134
  try {
1577
- const stats = await generateIcons(config);
2135
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1578
2136
  if (stats.failed > 0) {
1579
2137
  spinner.warn(
1580
2138
  `Regenerated ${import_chalk3.default.green(stats.success)} icons, ${import_chalk3.default.red(stats.failed)} failed`
1581
2139
  );
1582
2140
  } else {
1583
- spinner.succeed(`Regenerated ${import_chalk3.default.green(stats.success)} icon components`);
2141
+ spinner.succeed(`\u2713 Updated ${import_chalk3.default.green(stats.success)} icon components`);
1584
2142
  }
1585
- console.log(import_chalk3.default.gray(` Triggered by: ${fileName} (${event})
2143
+ const triggerFiles = validChanges.map(([file, evt]) => `${import_node_path7.default.basename(file)} (${evt})`).join(", ");
2144
+ console.log(import_chalk3.default.gray(` Triggered by: ${triggerFiles}
1586
2145
  `));
1587
2146
  } catch (error) {
1588
2147
  spinner.fail("Regeneration failed");
@@ -1596,13 +2155,13 @@ var import_meta2 = {};
1596
2155
  function getPackageJson() {
1597
2156
  let pkgPath;
1598
2157
  if (typeof __dirname !== "undefined") {
1599
- pkgPath = (0, import_node_path7.join)(__dirname, "../package.json");
2158
+ pkgPath = (0, import_node_path8.join)(__dirname, "../package.json");
1600
2159
  } else {
1601
2160
  const __filename = (0, import_node_url2.fileURLToPath)(import_meta2.url);
1602
- const __dirname2 = (0, import_node_path7.dirname)(__filename);
1603
- pkgPath = (0, import_node_path7.join)(__dirname2, "../package.json");
2161
+ const __dirname2 = (0, import_node_path8.dirname)(__filename);
2162
+ pkgPath = (0, import_node_path8.join)(__dirname2, "../package.json");
1604
2163
  }
1605
- return JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
2164
+ return JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
1606
2165
  }
1607
2166
  var packageJson = getPackageJson();
1608
2167
  var program = new import_commander.Command();
@@ -1615,7 +2174,7 @@ program.command("init").description("Initialize a new configuration file").optio
1615
2174
  process.exit(1);
1616
2175
  }
1617
2176
  });
1618
- program.command("generate").description("Generate icon components from SVG files").option("-c, --config <path>", "Path to config file").option("--dry-run", "Preview what will be generated without writing files").action(async (options) => {
2177
+ program.command("generate").description("Generate icon components from SVG files").option("-c, --config <path>", "Path to config file").option("--dry-run", "Preview what will be generated without writing files").option("--force", "Force full regeneration, ignoring cache").action(async (options) => {
1619
2178
  try {
1620
2179
  await generate(options);
1621
2180
  } catch (error) {