vectify 2.1.0 → 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.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ __require,
2
3
  ensureDir,
3
4
  fileExists,
4
5
  findProjectRoot,
@@ -6,7 +7,7 @@ import {
6
7
  getSvgFiles,
7
8
  readFile,
8
9
  writeFile
9
- } from "./chunk-CIKTK6HI.mjs";
10
+ } from "./chunk-FS34P27H.mjs";
10
11
 
11
12
  // src/parsers/svg-parser.ts
12
13
  import * as cheerio from "cheerio";
@@ -698,7 +699,7 @@ async function loadConfig(configPath) {
698
699
  return mergedConfig;
699
700
  }
700
701
  async function findConfig() {
701
- const { fileExists: fileExists2 } = await import("./helpers-UPZEBRGK.mjs");
702
+ const { fileExists: fileExists2 } = await import("./helpers-ZOR3OD66.mjs");
702
703
  const configFiles = [
703
704
  "vectify.config.ts",
704
705
  "vectify.config.js"
@@ -715,6 +716,37 @@ async function findConfig() {
715
716
  // src/generators/index.ts
716
717
  import path4 from "path";
717
718
 
719
+ // src/cache/hash-utils.ts
720
+ import { createHash } from "crypto";
721
+ import { readFile as readFile2 } from "fs/promises";
722
+ function hashContent(content) {
723
+ return createHash("sha256").update(content).digest("hex");
724
+ }
725
+ async function hashFile(filePath) {
726
+ try {
727
+ const content = await readFile2(filePath, "utf-8");
728
+ return hashContent(content);
729
+ } catch (error) {
730
+ throw new Error(`Failed to hash file ${filePath}: ${error}`);
731
+ }
732
+ }
733
+ function hashConfig(config) {
734
+ const relevantConfig = {
735
+ framework: config.framework,
736
+ typescript: config.typescript,
737
+ keepColors: config.keepColors,
738
+ prefix: config.prefix,
739
+ suffix: config.suffix,
740
+ optimize: config.optimize,
741
+ svgoConfig: config.svgoConfig,
742
+ componentNameTransform: config.componentNameTransform?.toString()
743
+ };
744
+ return hashContent(JSON.stringify(relevantConfig));
745
+ }
746
+ function hashSvgoConfig(svgoConfig) {
747
+ return hashContent(JSON.stringify(svgoConfig || {}));
748
+ }
749
+
718
750
  // src/parsers/optimizer.ts
719
751
  import { optimize } from "svgo";
720
752
  var DEFAULT_SVGO_CONFIG = {
@@ -898,7 +930,107 @@ async function generateIcons(config, dryRun = false) {
898
930
  }
899
931
  return stats;
900
932
  }
901
- async function generateIconComponent(svgFile, config, dryRun = false) {
933
+ async function generateIconsIncremental(config, cacheManager, dryRun = false) {
934
+ const stats = {
935
+ success: 0,
936
+ failed: 0,
937
+ total: 0,
938
+ errors: []
939
+ };
940
+ try {
941
+ if (!dryRun) {
942
+ await ensureDir(config.output);
943
+ }
944
+ const svgFiles = await getSvgFiles(config.input);
945
+ stats.total = svgFiles.length;
946
+ if (svgFiles.length === 0) {
947
+ console.warn(`No SVG files found in ${config.input}`);
948
+ return stats;
949
+ }
950
+ if (cacheManager.hasConfigChanged(config)) {
951
+ console.log("\u26A0\uFE0F Configuration changed, rebuilding all icons...");
952
+ cacheManager.invalidateAll();
953
+ }
954
+ cacheManager.updateConfigHash(config);
955
+ await cacheManager.cleanStaleEntries(svgFiles);
956
+ if (config.generateOptions?.cleanOutput && !dryRun) {
957
+ await cleanOutputDirectory(svgFiles, config);
958
+ }
959
+ await generateBaseComponent(config, dryRun);
960
+ const strategy = getFrameworkStrategy(config.framework);
961
+ const typescript = config.typescript ?? true;
962
+ const fileExt = strategy.getComponentExtension(typescript);
963
+ const filesToGenerate = [];
964
+ const cachedFiles = [];
965
+ for (const svgFile of svgFiles) {
966
+ const fileName = path4.basename(svgFile);
967
+ const componentName = getComponentName(
968
+ fileName,
969
+ config.prefix,
970
+ config.suffix,
971
+ config.componentNameTransform
972
+ );
973
+ const componentPath = path4.join(config.output, `${componentName}.${fileExt}`);
974
+ const needsRegen = await cacheManager.needsRegeneration(
975
+ svgFile,
976
+ componentPath,
977
+ config
978
+ );
979
+ if (needsRegen) {
980
+ filesToGenerate.push(svgFile);
981
+ } else {
982
+ cachedFiles.push(svgFile);
983
+ }
984
+ }
985
+ if (cachedFiles.length > 0) {
986
+ console.log(`\u{1F4E6} Cache: ${cachedFiles.length} cached, ${filesToGenerate.length} to generate`);
987
+ }
988
+ for (const svgFile of filesToGenerate) {
989
+ try {
990
+ await generateIconComponent(svgFile, config, dryRun, cacheManager);
991
+ stats.success++;
992
+ } catch (error) {
993
+ stats.failed++;
994
+ stats.errors.push({
995
+ file: svgFile,
996
+ error: error.message
997
+ });
998
+ console.error(`Failed to generate ${svgFile}: ${error.message}`);
999
+ }
1000
+ }
1001
+ stats.success += cachedFiles.length;
1002
+ if (config.generateOptions?.index) {
1003
+ await generateIndexFile(svgFiles, config, dryRun);
1004
+ }
1005
+ if (config.generateOptions?.preview && !dryRun) {
1006
+ await generatePreviewHtml(svgFiles, config);
1007
+ }
1008
+ if (!dryRun) {
1009
+ await cacheManager.save();
1010
+ const cacheStats = cacheManager.getStats();
1011
+ if (cacheStats.total > 0) {
1012
+ const hitRate = cacheManager.getHitRate();
1013
+ const timeSaved = (cacheStats.timeSaved / 1e3).toFixed(1);
1014
+ console.log(`\u26A1 Cache saved ~${timeSaved}s (${hitRate.toFixed(1)}% hit rate)`);
1015
+ }
1016
+ }
1017
+ if (config.format && !dryRun) {
1018
+ const formatResult = await formatOutput(config.output, config.format);
1019
+ if (formatResult.success && formatResult.tool) {
1020
+ console.log(`Formatted with ${formatResult.tool}`);
1021
+ } else if (formatResult.error) {
1022
+ console.warn(formatResult.error);
1023
+ }
1024
+ }
1025
+ if (config.hooks?.onComplete) {
1026
+ await config.hooks.onComplete(stats);
1027
+ }
1028
+ } catch (error) {
1029
+ throw new Error(`Generation failed: ${error.message}`);
1030
+ }
1031
+ return stats;
1032
+ }
1033
+ async function generateIconComponent(svgFile, config, dryRun = false, cacheManager) {
902
1034
  let svgContent = await readFile(svgFile);
903
1035
  const fileName = path4.basename(svgFile);
904
1036
  if (config.hooks?.beforeParse) {
@@ -911,7 +1043,7 @@ async function generateIconComponent(svgFile, config, dryRun = false) {
911
1043
  fileName,
912
1044
  config.prefix,
913
1045
  config.suffix,
914
- config.transform
1046
+ config.componentNameTransform
915
1047
  );
916
1048
  const strategy = getFrameworkStrategy(config.framework);
917
1049
  const typescript = config.typescript ?? true;
@@ -932,6 +1064,16 @@ async function generateIconComponent(svgFile, config, dryRun = false) {
932
1064
  console.log(` ${componentName}.${fileExt}`);
933
1065
  } else {
934
1066
  await writeFile(outputPath, code);
1067
+ if (cacheManager) {
1068
+ const componentHash = hashContent(code);
1069
+ await cacheManager.updateEntry(
1070
+ svgFile,
1071
+ outputPath,
1072
+ componentName,
1073
+ componentHash,
1074
+ config
1075
+ );
1076
+ }
935
1077
  }
936
1078
  }
937
1079
  async function generateBaseComponent(config, dryRun = false) {
@@ -958,7 +1100,7 @@ async function generateIndexFile(svgFiles, config, dryRun = false) {
958
1100
  fileName,
959
1101
  config.prefix,
960
1102
  config.suffix,
961
- config.transform
1103
+ config.componentNameTransform
962
1104
  );
963
1105
  const importPath = needsExtension ? `./${componentName}.${componentExt}` : `./${componentName}`;
964
1106
  if (usesDefaultExport) {
@@ -982,7 +1124,7 @@ async function generatePreviewHtml(svgFiles, config) {
982
1124
  fileName,
983
1125
  config.prefix,
984
1126
  config.suffix,
985
- config.transform
1127
+ config.componentNameTransform
986
1128
  );
987
1129
  });
988
1130
  const svgContents = await Promise.all(
@@ -1163,7 +1305,7 @@ async function cleanOutputDirectory(svgFiles, config) {
1163
1305
  fileName,
1164
1306
  config.prefix,
1165
1307
  config.suffix,
1166
- config.transform
1308
+ config.componentNameTransform
1167
1309
  );
1168
1310
  return `${componentName}.${fileExt}`;
1169
1311
  })
@@ -1198,6 +1340,255 @@ async function cleanOutputDirectory(svgFiles, config) {
1198
1340
  // src/commands/generate.ts
1199
1341
  import chalk from "chalk";
1200
1342
  import ora from "ora";
1343
+
1344
+ // src/cache/cache-manager.ts
1345
+ import { existsSync } from "fs";
1346
+ import { mkdir, readFile as readFile3, stat, writeFile as writeFile2 } from "fs/promises";
1347
+ import { dirname, join } from "path";
1348
+ var CACHE_VERSION = "1.0.0";
1349
+ var CacheManager = class {
1350
+ constructor(outputDir, cacheDir = ".vectify") {
1351
+ this.cache = {
1352
+ version: CACHE_VERSION,
1353
+ configHash: "",
1354
+ entries: {},
1355
+ baseComponentHash: ""
1356
+ };
1357
+ this.cacheFilePath = join(outputDir, cacheDir, "cache.json");
1358
+ this.stats = {
1359
+ hits: 0,
1360
+ misses: 0,
1361
+ total: 0,
1362
+ timeSaved: 0
1363
+ };
1364
+ this.isDirty = false;
1365
+ process.on("beforeExit", () => {
1366
+ if (this.isDirty) {
1367
+ this.saveSync();
1368
+ }
1369
+ });
1370
+ }
1371
+ /**
1372
+ * Load cache from disk
1373
+ */
1374
+ async load() {
1375
+ try {
1376
+ if (!existsSync(this.cacheFilePath)) {
1377
+ return;
1378
+ }
1379
+ const content = await readFile3(this.cacheFilePath, "utf-8");
1380
+ const loadedCache = JSON.parse(content);
1381
+ if (loadedCache.version !== CACHE_VERSION) {
1382
+ console.log("\u26A0\uFE0F Cache version mismatch, rebuilding cache...");
1383
+ return;
1384
+ }
1385
+ this.cache = loadedCache;
1386
+ } catch (error) {
1387
+ console.warn("\u26A0\uFE0F Failed to load cache, starting fresh:", error);
1388
+ this.cache = {
1389
+ version: CACHE_VERSION,
1390
+ configHash: "",
1391
+ entries: {},
1392
+ baseComponentHash: ""
1393
+ };
1394
+ }
1395
+ }
1396
+ /**
1397
+ * Save cache to disk (atomic write)
1398
+ */
1399
+ async save() {
1400
+ try {
1401
+ const cacheDir = dirname(this.cacheFilePath);
1402
+ await mkdir(cacheDir, { recursive: true });
1403
+ const tempPath = `${this.cacheFilePath}.tmp`;
1404
+ await writeFile2(tempPath, JSON.stringify(this.cache, null, 2), "utf-8");
1405
+ await writeFile2(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
1406
+ this.isDirty = false;
1407
+ } catch (error) {
1408
+ console.warn("\u26A0\uFE0F Failed to save cache:", error);
1409
+ }
1410
+ }
1411
+ /**
1412
+ * Synchronous save for process exit
1413
+ */
1414
+ saveSync() {
1415
+ try {
1416
+ const fs2 = __require("fs");
1417
+ const cacheDir = dirname(this.cacheFilePath);
1418
+ if (!fs2.existsSync(cacheDir)) {
1419
+ fs2.mkdirSync(cacheDir, { recursive: true });
1420
+ }
1421
+ fs2.writeFileSync(this.cacheFilePath, JSON.stringify(this.cache, null, 2), "utf-8");
1422
+ this.isDirty = false;
1423
+ } catch {
1424
+ console.warn("\u26A0\uFE0F Failed to save cache on exit");
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Check if a file needs regeneration
1429
+ */
1430
+ async needsRegeneration(svgPath, componentPath, config) {
1431
+ this.stats.total++;
1432
+ if (!existsSync(componentPath)) {
1433
+ this.stats.misses++;
1434
+ return true;
1435
+ }
1436
+ const entry = this.cache.entries[svgPath];
1437
+ if (!entry) {
1438
+ this.stats.misses++;
1439
+ return true;
1440
+ }
1441
+ try {
1442
+ const stats = await stat(svgPath);
1443
+ const currentMtime = stats.mtimeMs;
1444
+ if (currentMtime === entry.svgMtime) {
1445
+ if (!this.isConfigMatching(entry, config)) {
1446
+ this.stats.misses++;
1447
+ return true;
1448
+ }
1449
+ this.stats.hits++;
1450
+ this.stats.timeSaved += 50;
1451
+ return false;
1452
+ }
1453
+ const currentHash = await hashFile(svgPath);
1454
+ if (currentHash === entry.svgHash) {
1455
+ entry.svgMtime = currentMtime;
1456
+ this.isDirty = true;
1457
+ this.stats.hits++;
1458
+ this.stats.timeSaved += 50;
1459
+ return false;
1460
+ }
1461
+ this.stats.misses++;
1462
+ return true;
1463
+ } catch {
1464
+ this.stats.misses++;
1465
+ return true;
1466
+ }
1467
+ }
1468
+ /**
1469
+ * Check if config matches cache entry
1470
+ */
1471
+ isConfigMatching(entry, config) {
1472
+ const snapshot = entry.configSnapshot;
1473
+ return snapshot.framework === config.framework && snapshot.typescript === config.typescript && snapshot.keepColors === config.keepColors && snapshot.prefix === (config.prefix || "") && snapshot.suffix === (config.suffix || "") && snapshot.optimize === config.optimize && snapshot.svgoConfigHash === hashSvgoConfig(config.svgoConfig);
1474
+ }
1475
+ /**
1476
+ * Update cache entry after successful generation
1477
+ */
1478
+ async updateEntry(svgPath, componentPath, componentName, componentHash, config) {
1479
+ try {
1480
+ const svgHash = await hashFile(svgPath);
1481
+ const stats = await stat(svgPath);
1482
+ this.cache.entries[svgPath] = {
1483
+ svgPath,
1484
+ svgHash,
1485
+ svgMtime: stats.mtimeMs,
1486
+ componentName,
1487
+ componentPath,
1488
+ componentHash,
1489
+ configSnapshot: {
1490
+ framework: config.framework,
1491
+ typescript: config.typescript ?? true,
1492
+ keepColors: config.keepColors ?? false,
1493
+ prefix: config.prefix || "",
1494
+ suffix: config.suffix || "",
1495
+ optimize: config.optimize ?? true,
1496
+ svgoConfigHash: hashSvgoConfig(config.svgoConfig)
1497
+ },
1498
+ generatedAt: Date.now()
1499
+ };
1500
+ this.isDirty = true;
1501
+ } catch (error) {
1502
+ console.warn(`\u26A0\uFE0F Failed to update cache entry for ${svgPath}:`, error);
1503
+ }
1504
+ }
1505
+ /**
1506
+ * Remove cache entry
1507
+ */
1508
+ removeEntry(svgPath) {
1509
+ if (this.cache.entries[svgPath]) {
1510
+ delete this.cache.entries[svgPath];
1511
+ this.isDirty = true;
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Update config hash
1516
+ */
1517
+ updateConfigHash(config) {
1518
+ const newHash = hashConfig(config);
1519
+ if (this.cache.configHash !== newHash) {
1520
+ this.cache.configHash = newHash;
1521
+ this.isDirty = true;
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Update base component hash
1526
+ */
1527
+ updateBaseComponentHash(hash) {
1528
+ if (this.cache.baseComponentHash !== hash) {
1529
+ this.cache.baseComponentHash = hash;
1530
+ this.isDirty = true;
1531
+ }
1532
+ }
1533
+ /**
1534
+ * Check if config has changed (invalidates all cache)
1535
+ */
1536
+ hasConfigChanged(config) {
1537
+ const currentHash = hashConfig(config);
1538
+ return this.cache.configHash !== "" && this.cache.configHash !== currentHash;
1539
+ }
1540
+ /**
1541
+ * Invalidate all cache entries
1542
+ */
1543
+ invalidateAll() {
1544
+ this.cache.entries = {};
1545
+ this.isDirty = true;
1546
+ }
1547
+ /**
1548
+ * Clean stale entries (files that no longer exist)
1549
+ */
1550
+ async cleanStaleEntries(existingSvgPaths) {
1551
+ const existingSet = new Set(existingSvgPaths);
1552
+ let cleaned = 0;
1553
+ for (const svgPath of Object.keys(this.cache.entries)) {
1554
+ if (!existingSet.has(svgPath)) {
1555
+ delete this.cache.entries[svgPath];
1556
+ cleaned++;
1557
+ this.isDirty = true;
1558
+ }
1559
+ }
1560
+ if (cleaned > 0) {
1561
+ console.log(`\u{1F9F9} Cleaned ${cleaned} stale cache entries`);
1562
+ }
1563
+ }
1564
+ /**
1565
+ * Get cache statistics
1566
+ */
1567
+ getStats() {
1568
+ return { ...this.stats };
1569
+ }
1570
+ /**
1571
+ * Reset statistics
1572
+ */
1573
+ resetStats() {
1574
+ this.stats = {
1575
+ hits: 0,
1576
+ misses: 0,
1577
+ total: 0,
1578
+ timeSaved: 0
1579
+ };
1580
+ }
1581
+ /**
1582
+ * Get cache hit rate
1583
+ */
1584
+ getHitRate() {
1585
+ if (this.stats.total === 0)
1586
+ return 0;
1587
+ return this.stats.hits / this.stats.total * 100;
1588
+ }
1589
+ };
1590
+
1591
+ // src/commands/generate.ts
1201
1592
  async function generate(options = {}) {
1202
1593
  const spinner = ora("Loading configuration...").start();
1203
1594
  try {
@@ -1219,9 +1610,19 @@ async function generate(options = {}) {
1219
1610
  ${chalk.bold("Files that would be generated:")}
1220
1611
  `);
1221
1612
  }
1613
+ const useIncremental = config.incremental?.enabled !== false && !options.force && !options.dryRun;
1614
+ let cacheManager = null;
1615
+ if (useIncremental) {
1616
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
1617
+ cacheManager = new CacheManager(config.output, cacheDir);
1618
+ await cacheManager.load();
1619
+ }
1620
+ if (options.force) {
1621
+ spinner.info(chalk.yellow("Force mode - ignoring cache, regenerating all icons"));
1622
+ }
1222
1623
  const actionText = options.dryRun ? "Analyzing icons..." : "Generating icon components...";
1223
1624
  spinner.start(actionText);
1224
- const stats = await generateIcons(config, options.dryRun);
1625
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager, options.dryRun) : await generateIcons(config, options.dryRun);
1225
1626
  if (stats.failed > 0) {
1226
1627
  spinner.warn(`${options.dryRun ? "Analyzed" : "Generated"} ${chalk.green(stats.success)} icons, ${chalk.red(stats.failed)} failed`);
1227
1628
  if (stats.errors.length > 0) {
@@ -1413,6 +1814,89 @@ import path6 from "path";
1413
1814
  import chalk3 from "chalk";
1414
1815
  import chokidar from "chokidar";
1415
1816
  import ora3 from "ora";
1817
+
1818
+ // src/cache/svg-validator.ts
1819
+ import { readFile as readFile4, stat as stat2 } from "fs/promises";
1820
+ import { load as load2 } from "cheerio";
1821
+ var MIN_SVG_SIZE = 20;
1822
+ var DRAWABLE_ELEMENTS = [
1823
+ "path",
1824
+ "circle",
1825
+ "rect",
1826
+ "ellipse",
1827
+ "line",
1828
+ "polyline",
1829
+ "polygon",
1830
+ "text",
1831
+ "image",
1832
+ "use"
1833
+ ];
1834
+ async function validateSVGFile(filePath) {
1835
+ try {
1836
+ const stats = await stat2(filePath);
1837
+ if (stats.size < MIN_SVG_SIZE) {
1838
+ return {
1839
+ isValid: false,
1840
+ isEmpty: true,
1841
+ reason: "File is too small to be a valid SVG"
1842
+ };
1843
+ }
1844
+ const content = await readFile4(filePath, "utf-8");
1845
+ if (!content.trim()) {
1846
+ return {
1847
+ isValid: false,
1848
+ isEmpty: true,
1849
+ reason: "File is empty"
1850
+ };
1851
+ }
1852
+ if (!content.includes("<svg")) {
1853
+ return {
1854
+ isValid: false,
1855
+ isEmpty: false,
1856
+ reason: "File does not contain <svg> tag"
1857
+ };
1858
+ }
1859
+ if (!hasDrawableContent(content)) {
1860
+ return {
1861
+ isValid: false,
1862
+ isEmpty: true,
1863
+ reason: "SVG has no drawable content"
1864
+ };
1865
+ }
1866
+ try {
1867
+ const $ = load2(content, { xmlMode: true });
1868
+ const svgElement = $("svg");
1869
+ if (svgElement.length === 0) {
1870
+ return {
1871
+ isValid: false,
1872
+ isEmpty: false,
1873
+ reason: "Failed to parse SVG element"
1874
+ };
1875
+ }
1876
+ return {
1877
+ isValid: true,
1878
+ isEmpty: false
1879
+ };
1880
+ } catch (parseError) {
1881
+ return {
1882
+ isValid: false,
1883
+ isEmpty: false,
1884
+ reason: `Failed to parse SVG: ${parseError}`
1885
+ };
1886
+ }
1887
+ } catch (error) {
1888
+ return {
1889
+ isValid: false,
1890
+ isEmpty: false,
1891
+ reason: `Failed to read file: ${error}`
1892
+ };
1893
+ }
1894
+ }
1895
+ function hasDrawableContent(content) {
1896
+ return DRAWABLE_ELEMENTS.some((element) => content.includes(`<${element}`));
1897
+ }
1898
+
1899
+ // src/commands/watch.ts
1416
1900
  async function watch(options = {}) {
1417
1901
  const spinner = ora3("Loading configuration...").start();
1418
1902
  try {
@@ -1428,16 +1912,24 @@ async function watch(options = {}) {
1428
1912
  }
1429
1913
  const config = await loadConfig(configPath);
1430
1914
  spinner.succeed(`Config loaded from ${chalk3.green(configPath)}`);
1915
+ const useIncremental = config.incremental?.enabled !== false;
1916
+ const cacheDir = config.incremental?.cacheDir || ".vectify";
1917
+ const cacheManager = useIncremental ? new CacheManager(config.output, cacheDir) : null;
1918
+ if (cacheManager) {
1919
+ await cacheManager.load();
1920
+ }
1431
1921
  spinner.start("Generating icon components...");
1432
- const initialStats = await generateIcons(config);
1922
+ const initialStats = useIncremental && cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1433
1923
  spinner.succeed(`Generated ${chalk3.green(initialStats.success)} icon components`);
1434
1924
  const watchPath = path6.join(config.input, "**/*.svg");
1435
1925
  const debounce = config.watch?.debounce ?? 300;
1436
1926
  const ignore = config.watch?.ignore ?? ["**/node_modules/**", "**/.git/**"];
1927
+ const emptyFileRetryDelay = config.watch?.emptyFileRetryDelay ?? 2e3;
1437
1928
  console.log(chalk3.bold("\nWatching for changes..."));
1438
1929
  console.log(` ${chalk3.cyan(watchPath)}`);
1439
1930
  console.log(` ${chalk3.gray("Press Ctrl+C to stop")}
1440
1931
  `);
1932
+ const pendingChanges = /* @__PURE__ */ new Map();
1441
1933
  const debounceTimer = null;
1442
1934
  const watcher = chokidar.watch(watchPath, {
1443
1935
  ignored: ignore,
@@ -1445,12 +1937,12 @@ async function watch(options = {}) {
1445
1937
  ignoreInitial: true
1446
1938
  });
1447
1939
  watcher.on("add", (filePath) => {
1448
- handleChange("added", filePath, config, debounce, debounceTimer);
1940
+ handleChange("added", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1449
1941
  }).on("change", (filePath) => {
1450
- handleChange("changed", filePath, config, debounce, debounceTimer);
1942
+ handleChange("changed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1451
1943
  }).on("unlink", (filePath) => {
1452
1944
  console.log(chalk3.yellow(`SVG file removed: ${path6.basename(filePath)}`));
1453
- handleChange("removed", filePath, config, debounce, debounceTimer);
1945
+ handleChange("removed", filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, debounceTimer);
1454
1946
  }).on("error", (error) => {
1455
1947
  console.error(chalk3.red(`Watcher error: ${error.message}`));
1456
1948
  });
@@ -1467,23 +1959,51 @@ ${chalk3.yellow("Stopping watch mode...")}`);
1467
1959
  throw error;
1468
1960
  }
1469
1961
  }
1470
- function handleChange(event, filePath, config, debounce, timer) {
1471
- const fileName = path6.basename(filePath);
1962
+ function handleChange(event, filePath, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer) {
1963
+ pendingChanges.set(filePath, event);
1472
1964
  if (timer) {
1473
1965
  clearTimeout(timer);
1474
1966
  }
1475
1967
  timer = setTimeout(async () => {
1476
- const spinner = ora3(`Regenerating icons...`).start();
1968
+ const changes = Array.from(pendingChanges.entries());
1969
+ pendingChanges.clear();
1970
+ const validChanges = [];
1971
+ const invalidFiles = [];
1972
+ for (const [file, changeEvent] of changes) {
1973
+ if (changeEvent === "removed") {
1974
+ validChanges.push([file, changeEvent]);
1975
+ continue;
1976
+ }
1977
+ const validation = await validateSVGFile(file);
1978
+ if (!validation.isValid) {
1979
+ if (validation.isEmpty) {
1980
+ console.log(chalk3.yellow(`\u23F3 Waiting for content: ${path6.basename(file)}`));
1981
+ setTimeout(() => {
1982
+ handleChange(changeEvent, file, config, cacheManager, debounce, emptyFileRetryDelay, pendingChanges, timer);
1983
+ }, emptyFileRetryDelay);
1984
+ } else {
1985
+ console.error(chalk3.red(`\u274C Invalid SVG: ${path6.basename(file)} - ${validation.reason}`));
1986
+ invalidFiles.push(file);
1987
+ }
1988
+ continue;
1989
+ }
1990
+ validChanges.push([file, changeEvent]);
1991
+ }
1992
+ if (validChanges.length === 0) {
1993
+ return;
1994
+ }
1995
+ const spinner = ora3(`Processing ${validChanges.length} change(s)...`).start();
1477
1996
  try {
1478
- const stats = await generateIcons(config);
1997
+ const stats = cacheManager ? await generateIconsIncremental(config, cacheManager) : await generateIcons(config);
1479
1998
  if (stats.failed > 0) {
1480
1999
  spinner.warn(
1481
2000
  `Regenerated ${chalk3.green(stats.success)} icons, ${chalk3.red(stats.failed)} failed`
1482
2001
  );
1483
2002
  } else {
1484
- spinner.succeed(`Regenerated ${chalk3.green(stats.success)} icon components`);
2003
+ spinner.succeed(`\u2713 Updated ${chalk3.green(stats.success)} icon components`);
1485
2004
  }
1486
- console.log(chalk3.gray(` Triggered by: ${fileName} (${event})
2005
+ const triggerFiles = validChanges.map(([file, evt]) => `${path6.basename(file)} (${evt})`).join(", ");
2006
+ console.log(chalk3.gray(` Triggered by: ${triggerFiles}
1487
2007
  `));
1488
2008
  } catch (error) {
1489
2009
  spinner.fail("Regeneration failed");