svger-cli 4.0.1 → 4.0.3

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 (41) hide show
  1. package/CHANGELOG.md +230 -0
  2. package/README.md +27 -27
  3. package/dist/builder.d.ts +6 -3
  4. package/dist/builder.js +34 -24
  5. package/dist/cli.js +122 -10
  6. package/dist/config.d.ts +8 -2
  7. package/dist/config.js +17 -124
  8. package/dist/core/enhanced-plugin-manager.d.ts +1 -0
  9. package/dist/core/enhanced-plugin-manager.js +37 -11
  10. package/dist/core/framework-templates.js +4 -0
  11. package/dist/core/logger.js +8 -4
  12. package/dist/core/performance-engine.js +16 -3
  13. package/dist/core/style-compiler.js +6 -7
  14. package/dist/core/template-manager.js +18 -14
  15. package/dist/index.d.ts +1 -2
  16. package/dist/index.js +8 -2
  17. package/dist/integrations/jest-preset.js +30 -2
  18. package/dist/lock.js +1 -1
  19. package/dist/optimizers/basic-cleaner.js +4 -0
  20. package/dist/optimizers/path-parser.js +199 -115
  21. package/dist/optimizers/path-simplifier.js +27 -24
  22. package/dist/optimizers/remove-unused-defs.js +16 -0
  23. package/dist/optimizers/shape-conversion.js +22 -27
  24. package/dist/optimizers/style-optimizer.js +5 -0
  25. package/dist/optimizers/svg-tree-parser.js +4 -0
  26. package/dist/optimizers/transform-collapsing.js +11 -15
  27. package/dist/optimizers/transform-optimizer.js +20 -21
  28. package/dist/optimizers/types.js +64 -74
  29. package/dist/plugins/gradient-optimizer.js +4 -0
  30. package/dist/processors/svg-processor.js +28 -10
  31. package/dist/services/config.js +28 -11
  32. package/dist/services/file-watcher.js +8 -3
  33. package/dist/services/svg-service.d.ts +1 -1
  34. package/dist/services/svg-service.js +24 -11
  35. package/dist/utils/native.d.ts +0 -1
  36. package/dist/utils/native.js +6 -14
  37. package/dist/utils/visual-diff.js +7 -2
  38. package/dist/watch.js +4 -3
  39. package/docs/ERROR-HANDLING-STANDARD.md +111 -0
  40. package/docs/OPTIONAL-DEPENDENCIES.md +1 -1
  41. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,11 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import { CLI } from './utils/native.js';
2
+ import { CLI, FileSystem } from './utils/native.js';
3
3
  import { svgService } from './services/svg-service.js';
4
4
  import { configService } from './services/config.js';
5
5
  import { logger } from './core/logger.js';
6
6
  import { getPluginManager } from './core/enhanced-plugin-manager.js';
7
+ import { svgProcessor } from './processors/svg-processor.js';
7
8
  import { resolve } from 'path';
8
9
  import { pathToFileURL } from 'url';
10
+ import path from 'path';
11
+ import { readFileSync } from 'fs';
12
+ import { fileURLToPath } from 'url';
13
+ import { dirname, join } from 'path';
14
+ // Read version dynamically from package.json
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
18
+ const CLI_VERSION = packageJson.version;
9
19
  const program = new CLI();
10
20
  /**
11
21
  * Load a plugin from npm package or local path
@@ -80,7 +90,7 @@ function listRegisteredPlugins() {
80
90
  program
81
91
  .name('svger-cli')
82
92
  .description('Custom SVG to Angular, React, Vue, Svelte, Solid, and other component converter')
83
- .version('4.0.0');
93
+ .version(CLI_VERSION);
84
94
  // -------- Build Command --------
85
95
  /**
86
96
  * Build all SVGs from a source folder to an output folder.
@@ -113,6 +123,39 @@ program
113
123
  }
114
124
  }
115
125
  const [src, out] = args;
126
+ // Validate required arguments
127
+ if (!src || !out) {
128
+ logger.error('Error: Both <src> and <out> paths are required');
129
+ process.exit(1);
130
+ }
131
+ // Validate framework type if provided — O(1) Set.has() instead of O(n) Array.includes()
132
+ const validFrameworks = new Set([
133
+ 'react',
134
+ 'react-native',
135
+ 'vue',
136
+ 'svelte',
137
+ 'angular',
138
+ 'solid',
139
+ 'preact',
140
+ 'lit',
141
+ 'vanilla',
142
+ ]);
143
+ if (opts.framework && !validFrameworks.has(opts.framework)) {
144
+ logger.error(`Error: Invalid framework "${opts.framework}". Valid options: ${[...validFrameworks].join(', ')}`);
145
+ process.exit(1);
146
+ }
147
+ // Validate optimization level if provided — O(1) Set.has()
148
+ const validOptLevels = new Set([
149
+ 'none',
150
+ 'basic',
151
+ 'balanced',
152
+ 'aggressive',
153
+ 'maximum',
154
+ ]);
155
+ if (opts.optimize && !validOptLevels.has(opts.optimize)) {
156
+ logger.error(`Error: Invalid optimization level "${opts.optimize}". Valid options: ${[...validOptLevels].join(', ')}`);
157
+ process.exit(1);
158
+ }
116
159
  // Build config from CLI options
117
160
  const buildConfig = { src, out };
118
161
  if (opts.framework) {
@@ -155,6 +198,11 @@ program
155
198
  .action(async (args) => {
156
199
  try {
157
200
  const [src, out] = args;
201
+ // Validate required arguments
202
+ if (!src || !out) {
203
+ logger.error('Error: Both <src> and <out> paths are required');
204
+ process.exit(1);
205
+ }
158
206
  await svgService.startWatching({ src, out });
159
207
  // Keep the process running
160
208
  process.on('SIGINT', () => {
@@ -262,12 +310,27 @@ program
262
310
  logger.error('Invalid format. Use key=value');
263
311
  process.exit(1);
264
312
  }
265
- const parsedValue = !isNaN(Number(value)) ? Number(value) : value;
313
+ // Parse value with O(1) lookup for known literals, then type detection
314
+ const literalValues = {
315
+ true: true,
316
+ false: false,
317
+ };
318
+ let parsedValue;
319
+ if (value in literalValues) {
320
+ parsedValue = literalValues[value];
321
+ }
322
+ else if (!isNaN(Number(value)) && value.trim() !== '') {
323
+ parsedValue = Number(value);
324
+ }
325
+ else {
326
+ parsedValue = value;
327
+ }
266
328
  return configService.setConfig(key, parsedValue);
267
329
  }
268
330
  if (opts.show)
269
331
  return configService.showConfig();
270
332
  logger.error('No option provided. Use --init, --set, or --show');
333
+ process.exit(1);
271
334
  }
272
335
  catch (error) {
273
336
  logger.error('Config operation failed:', error);
@@ -311,16 +374,65 @@ program
311
374
  .option('--in-place', 'Optimize files in-place (overwrite originals)')
312
375
  .action(async (args, opts) => {
313
376
  try {
314
- const [input, output = input] = args;
377
+ const [input, output = opts.inPlace ? input : args[1] || input] = args;
378
+ if (!input) {
379
+ logger.error('Error: Input path is required');
380
+ process.exit(1);
381
+ }
382
+ // Validate optimization level if provided — O(1) Set.has()
383
+ const validOptLevels = new Set([
384
+ 'basic',
385
+ 'balanced',
386
+ 'aggressive',
387
+ 'maximum',
388
+ ]);
315
389
  const level = opts.level || 'balanced';
390
+ if (!validOptLevels.has(level)) {
391
+ logger.error(`Error: Invalid optimization level "${level}". Valid options: ${[...validOptLevels].join(', ')}`);
392
+ process.exit(1);
393
+ }
394
+ const inputDir = path.resolve(input);
395
+ const outputDir = opts.inPlace ? inputDir : path.resolve(output);
396
+ // Validate input directory
397
+ if (!(await FileSystem.exists(inputDir))) {
398
+ logger.error(`Error: Input directory not found: ${inputDir}`);
399
+ process.exit(1);
400
+ }
401
+ // Set optimization level
402
+ svgService.setOptimizerLevel(level);
316
403
  logger.info(`Optimizing SVG files at ${level.toUpperCase()} level...`);
317
- logger.info(`Input: ${input}, Output: ${output}`);
318
- // Implementation would go through svg-processor
319
- // For now, show success message
320
- logger.success(`Optimization complete!`);
404
+ logger.info(`Input: ${inputDir}`);
405
+ logger.info(`Output: ${outputDir}`);
406
+ // Ensure output directory exists
407
+ await FileSystem.ensureDir(outputDir);
408
+ // Read all SVG files
409
+ const files = await FileSystem.readDir(inputDir);
410
+ const svgFiles = files.filter((file) => file.endsWith('.svg'));
411
+ if (svgFiles.length === 0) {
412
+ logger.warn('No SVG files found in input directory');
413
+ return;
414
+ }
415
+ let optimized = 0;
416
+ let failed = 0;
417
+ // Process each SVG file
418
+ for (const file of svgFiles) {
419
+ try {
420
+ const inputPath = path.join(inputDir, file);
421
+ const outputPath = path.join(outputDir, file);
422
+ const content = await FileSystem.readFile(inputPath, 'utf-8');
423
+ const optimizedContent = await svgProcessor.cleanSVGContent(content);
424
+ await FileSystem.writeFile(outputPath, optimizedContent, 'utf-8');
425
+ optimized++;
426
+ logger.info(`✓ Optimized: ${file}`);
427
+ }
428
+ catch (error) {
429
+ failed++;
430
+ logger.error(`✗ Failed: ${file} - ${error instanceof Error ? error.message : String(error)}`);
431
+ }
432
+ }
433
+ logger.success(`Optimization complete! ${optimized} optimized, ${failed} failed`);
321
434
  if (opts.validate) {
322
- logger.info('Running visual validation...');
323
- logger.success('Visual validation passed! ✅');
435
+ logger.warn('Visual validation not yet implemented for optimize command');
324
436
  }
325
437
  }
326
438
  catch (error) {
package/dist/config.d.ts CHANGED
@@ -1,28 +1,34 @@
1
1
  /**
2
2
  * Read the current svger-cli configuration.
3
3
  *
4
- * @returns {Record<string, any>} Configuration object. Returns an empty object if no config file exists.
4
+ * @deprecated Use `configService.readConfig()` instead.
5
+ * @returns {Record<string, any>} Configuration object.
5
6
  */
6
7
  export declare function readConfig(): Record<string, any>;
7
8
  /**
8
9
  * Write a configuration object to the config file.
9
10
  *
11
+ * @deprecated Use `configService.writeConfig(config)` instead.
10
12
  * @param {Record<string, any>} config - Configuration object to write.
11
13
  */
12
14
  export declare function writeConfig(config: Record<string, any>): void;
13
15
  /**
14
16
  * Initialize the svger-cli configuration with default values.
15
- * If a config file already exists, this function will not overwrite it.
17
+ *
18
+ * @deprecated Use `configService.initConfig()` instead.
16
19
  */
17
20
  export declare function initConfig(): Promise<void>;
18
21
  /**
19
22
  * Set a specific configuration key to a new value.
20
23
  *
24
+ * @deprecated Use `configService.setConfig(key, value)` instead.
21
25
  * @param {string} key - The config key to set.
22
26
  * @param {any} value - The value to assign to the key.
23
27
  */
24
28
  export declare function setConfig(key: string, value: any): void;
25
29
  /**
26
30
  * Display the current configuration in the console.
31
+ *
32
+ * @deprecated Use `configService.showConfig()` instead.
27
33
  */
28
34
  export declare function showConfig(): void;
package/dist/config.js CHANGED
@@ -1,158 +1,51 @@
1
- import path from 'path';
2
- import { FileSystem } from './utils/native.js';
3
- const CONFIG_FILE = '.svgconfig.json';
4
1
  /**
5
- * Get the absolute path to the configuration file.
2
+ * Legacy configuration module - delegates to ConfigService.
6
3
  *
7
- * @returns {string} Absolute path to .svgconfig.json
4
+ * @deprecated Use `configService` from `./services/config.js` instead.
5
+ * This module exists only for backward compatibility and will be removed in v5.0.0.
8
6
  */
9
- function getConfigPath() {
10
- return path.resolve(CONFIG_FILE);
11
- }
7
+ import { configService } from './services/config.js';
12
8
  /**
13
9
  * Read the current svger-cli configuration.
14
10
  *
15
- * @returns {Record<string, any>} Configuration object. Returns an empty object if no config file exists.
11
+ * @deprecated Use `configService.readConfig()` instead.
12
+ * @returns {Record<string, any>} Configuration object.
16
13
  */
17
14
  export function readConfig() {
18
- return FileSystem.readJSONSync(getConfigPath());
15
+ return configService.readConfig();
19
16
  }
20
17
  /**
21
18
  * Write a configuration object to the config file.
22
19
  *
20
+ * @deprecated Use `configService.writeConfig(config)` instead.
23
21
  * @param {Record<string, any>} config - Configuration object to write.
24
22
  */
25
23
  export function writeConfig(config) {
26
- FileSystem.writeJSONSync(getConfigPath(), config, { spaces: 2 });
24
+ configService.writeConfig(config);
27
25
  }
28
26
  /**
29
27
  * Initialize the svger-cli configuration with default values.
30
- * If a config file already exists, this function will not overwrite it.
28
+ *
29
+ * @deprecated Use `configService.initConfig()` instead.
31
30
  */
32
31
  export async function initConfig() {
33
- if (await FileSystem.exists(getConfigPath())) {
34
- console.log('⚠️ Config file already exists:', getConfigPath());
35
- return;
36
- }
37
- const defaultConfig = {
38
- // Configuration Version (for migration compatibility)
39
- version: '4.0.0',
40
- // Source & Output
41
- source: './src/assets/svg',
42
- output: './src/components/icons',
43
- // Framework Configuration
44
- framework: 'react',
45
- typescript: true,
46
- componentType: 'functional',
47
- // Processing Options
48
- watch: false,
49
- parallel: true,
50
- batchSize: 10,
51
- maxConcurrency: 4,
52
- cache: true,
53
- // Default Properties
54
- defaultWidth: 24,
55
- defaultHeight: 24,
56
- defaultFill: 'currentColor',
57
- defaultStroke: 'none',
58
- defaultStrokeWidth: 1,
59
- // Styling Configuration
60
- styleRules: {
61
- fill: 'inherit',
62
- stroke: 'none',
63
- },
64
- responsive: {
65
- breakpoints: ['sm', 'md', 'lg', 'xl'],
66
- values: {
67
- width: ['16px', '20px', '24px', '32px'],
68
- height: ['16px', '20px', '24px', '32px'],
69
- },
70
- },
71
- theme: {
72
- mode: 'auto',
73
- variables: {
74
- primary: 'currentColor',
75
- secondary: '#6b7280',
76
- accent: '#3b82f6',
77
- },
78
- },
79
- animations: [],
80
- // v4.0.0: Plugin System
81
- plugins: [],
82
- // Advanced Options
83
- exclude: [],
84
- include: [],
85
- // Error Handling
86
- errorHandling: {
87
- strategy: 'continue',
88
- maxRetries: 3,
89
- timeout: 30000,
90
- },
91
- // Performance Settings
92
- performance: {
93
- optimization: 'balanced',
94
- memoryLimit: 512,
95
- cacheTimeout: 3600000,
96
- },
97
- // Output Customization
98
- outputConfig: {
99
- naming: 'pascal',
100
- extension: 'tsx',
101
- directory: './src/components/icons',
102
- },
103
- // Framework-specific configurations
104
- react: {
105
- componentType: 'functional',
106
- forwardRef: true,
107
- memo: false,
108
- propsInterface: 'SVGProps',
109
- styledComponents: false,
110
- cssModules: false,
111
- },
112
- vue: {
113
- api: 'composition',
114
- setup: true,
115
- typescript: true,
116
- scoped: true,
117
- cssVariables: true,
118
- },
119
- angular: {
120
- standalone: true,
121
- signals: true,
122
- changeDetection: 'OnPush',
123
- encapsulation: 'Emulated',
124
- },
125
- // Legacy support (deprecated)
126
- template: {
127
- type: 'default',
128
- },
129
- frameworkOptions: {
130
- forwardRef: true,
131
- memo: false,
132
- scriptSetup: true,
133
- standalone: true,
134
- },
135
- };
136
- writeConfig(defaultConfig);
137
- console.log('✅ Config file created:', getConfigPath());
32
+ return configService.initConfig();
138
33
  }
139
34
  /**
140
35
  * Set a specific configuration key to a new value.
141
36
  *
37
+ * @deprecated Use `configService.setConfig(key, value)` instead.
142
38
  * @param {string} key - The config key to set.
143
39
  * @param {any} value - The value to assign to the key.
144
40
  */
145
41
  export function setConfig(key, value) {
146
- const config = readConfig();
147
- config[key] = value;
148
- writeConfig(config);
149
- console.log(`✅ Set config ${key}=${value}`);
42
+ configService.setConfig(key, value);
150
43
  }
151
44
  /**
152
45
  * Display the current configuration in the console.
46
+ *
47
+ * @deprecated Use `configService.showConfig()` instead.
153
48
  */
154
49
  export function showConfig() {
155
- const config = readConfig();
156
- console.log('📄 Current Config:');
157
- console.log(JSON.stringify(config, null, 2));
50
+ configService.showConfig();
158
51
  }
@@ -21,6 +21,7 @@ interface PluginMetrics {
21
21
  export declare class EnhancedPluginManager {
22
22
  private plugins;
23
23
  private executionMetrics;
24
+ private readonly MAX_METRICS_SIZE;
24
25
  private enableVisualValidation;
25
26
  constructor();
26
27
  /**
@@ -11,6 +11,7 @@ import { compareVisually } from '../utils/visual-diff.js';
11
11
  export class EnhancedPluginManager {
12
12
  plugins = new Map();
13
13
  executionMetrics = [];
14
+ MAX_METRICS_SIZE = 1000; // Cap at 1000 entries to prevent memory leak
14
15
  enableVisualValidation = true;
15
16
  constructor() {
16
17
  logger.debug('EnhancedPluginManager initialized');
@@ -20,13 +21,15 @@ export class EnhancedPluginManager {
20
21
  */
21
22
  registerPlugin(plugin) {
22
23
  if (this.plugins.has(plugin.name)) {
23
- logger.warn(`Plugin "${plugin.name}" is already registered. Skipping duplicate.`);
24
- return;
24
+ const errorMsg = `Plugin "${plugin.name}" is already registered. Cannot register duplicate plugins with the same name.`;
25
+ logger.error(errorMsg);
26
+ throw new Error(errorMsg);
25
27
  }
26
28
  // Validate plugin structure
27
29
  if (!this.validatePlugin(plugin)) {
28
- logger.error(`Plugin "${plugin.name}" failed validation. Skipping.`);
29
- return;
30
+ const errorMsg = `Plugin "${plugin.name}" failed validation. Cannot register invalid plugin.`;
31
+ logger.error(errorMsg);
32
+ throw new Error(errorMsg);
30
33
  }
31
34
  // Initialize plugin if it has init method
32
35
  if (plugin.init) {
@@ -134,11 +137,20 @@ export class EnhancedPluginManager {
134
137
  const maxDiff = plugin.validation.maxDiffPercent ?? 5;
135
138
  validationPassed = visualDiff <= maxDiff;
136
139
  if (!validationPassed) {
137
- logger.warn(`Plugin "${plugin.name}" exceeded visual diff threshold: ${visualDiff.toFixed(4)}% > ${maxDiff}%`);
138
- }
139
- else {
140
- logger.debug(`Plugin "${plugin.name}" visual validation passed: ${visualDiff.toFixed(4)}% <= ${maxDiff}%`);
140
+ const errorMsg = `Plugin "${plugin.name}" exceeded visual diff threshold: ${visualDiff.toFixed(4)}% > ${maxDiff}%`;
141
+ logger.error(errorMsg);
142
+ // Return original content and stop execution on validation failure
143
+ return {
144
+ content: originalContent,
145
+ skipRemaining: true,
146
+ metadata: {
147
+ visualDiff,
148
+ validationPassed: false,
149
+ error: errorMsg,
150
+ },
151
+ };
141
152
  }
153
+ logger.debug(`Plugin "${plugin.name}" visual validation passed: ${visualDiff.toFixed(4)}% <= ${maxDiff}%`);
142
154
  }
143
155
  catch (error) {
144
156
  logger.warn(`Plugin "${plugin.name}" visual validation failed:`, error.message);
@@ -153,6 +165,10 @@ export class EnhancedPluginManager {
153
165
  visualDiff: visualDiff > 0 ? visualDiff : undefined,
154
166
  validationPassed,
155
167
  });
168
+ // Implement circular buffer to prevent memory leak in long-running watch mode
169
+ if (this.executionMetrics.length > this.MAX_METRICS_SIZE) {
170
+ this.executionMetrics.shift(); // Remove oldest entry
171
+ }
156
172
  logger.debug(`Plugin "${plugin.name}" completed in ${executionTime.toFixed(2)}ms` +
157
173
  (visualDiff > 0 ? ` (visual diff: ${visualDiff.toFixed(4)}%)` : ''));
158
174
  return result;
@@ -224,10 +240,20 @@ export class EnhancedPluginManager {
224
240
  * Get summary of execution metrics
225
241
  */
226
242
  getMetricsSummary() {
243
+ // Single pass through metrics instead of multiple filters
244
+ let totalExecutionTime = 0;
245
+ let validationsPassed = 0;
246
+ let validationsFailed = 0;
247
+ for (const metric of this.executionMetrics) {
248
+ totalExecutionTime += metric.executionTime;
249
+ if (metric.validationPassed) {
250
+ validationsPassed++;
251
+ }
252
+ else {
253
+ validationsFailed++;
254
+ }
255
+ }
227
256
  const totalExecutions = this.executionMetrics.length;
228
- const totalExecutionTime = this.executionMetrics.reduce((sum, m) => sum + m.executionTime, 0);
229
- const validationsPassed = this.executionMetrics.filter(m => m.validationPassed).length;
230
- const validationsFailed = this.executionMetrics.filter(m => !m.validationPassed).length;
231
257
  return {
232
258
  totalPlugins: this.plugins.size,
233
259
  totalExecutions,
@@ -46,6 +46,10 @@ export class FrameworkTemplateEngine {
46
46
  const attrRegex = /(\w+(?:-\w+)*)="([^"]*)"/g;
47
47
  let match;
48
48
  while ((match = attrRegex.exec(attributesString)) !== null) {
49
+ // Prevent infinite loop if regex doesn't advance
50
+ if (match.index === attrRegex.lastIndex) {
51
+ attrRegex.lastIndex++;
52
+ }
49
53
  attributes[match[1]] = match[2];
50
54
  }
51
55
  return { attributes, innerContent };
@@ -19,10 +19,14 @@ export class LoggerService {
19
19
  this.enableColors = enabled;
20
20
  }
21
21
  shouldLog(level) {
22
- const levels = ['debug', 'info', 'warn', 'error'];
23
- const currentIndex = levels.indexOf(this.logLevel);
24
- const messageIndex = levels.indexOf(level);
25
- return messageIndex >= currentIndex;
22
+ // O(1) Map lookup instead of O(n) indexOf on array
23
+ const levelPriority = {
24
+ debug: 0,
25
+ info: 1,
26
+ warn: 2,
27
+ error: 3,
28
+ };
29
+ return (levelPriority[level] ?? 0) >= (levelPriority[this.logLevel] ?? 0);
26
30
  }
27
31
  formatMessage(level, message) {
28
32
  const timestamp = new Date().toISOString();
@@ -1,4 +1,5 @@
1
1
  import os from 'os';
2
+ import fs from 'fs';
2
3
  import { logger } from '../core/logger.js';
3
4
  import { svgProcessor } from '../processors/svg-processor.js';
4
5
  /**
@@ -41,7 +42,12 @@ export class PerformanceEngine {
41
42
  }
42
43
  }
43
44
  const totalDuration = Date.now() - startTime;
44
- const successful = results.filter(r => r.success).length;
45
+ // Single-pass counter instead of results.filter().length O(n) → O(n) but avoids intermediate array allocation
46
+ let successful = 0;
47
+ for (let i = 0; i < results.length; i++) {
48
+ if (results[i].success)
49
+ successful++;
50
+ }
45
51
  logger.info(`Batch processing complete: ${successful}/${files.length} successful in ${totalDuration}ms`);
46
52
  return results;
47
53
  }
@@ -166,8 +172,15 @@ export class PerformanceEngine {
166
172
  }
167
173
  }
168
174
  generateCacheKey(filePath, options) {
169
- // Create a hash of file path and options for caching
170
- const key = JSON.stringify({ filePath, options });
175
+ // Include file modification time to invalidate cache when file changes
176
+ let mtimeMs = 0;
177
+ try {
178
+ mtimeMs = fs.statSync(filePath).mtimeMs;
179
+ }
180
+ catch {
181
+ // File may not exist yet; use 0 so caching still works
182
+ }
183
+ const key = JSON.stringify({ filePath, mtimeMs, options });
171
184
  return Buffer.from(key).toString('base64');
172
185
  }
173
186
  getCachedResult(key) {
@@ -217,13 +217,12 @@ function getAnimationStyles(animationType: string): React.CSSProperties {
217
217
  }
218
218
 
219
219
  function getThemeStyles(theme: _theme): React.CSSProperties {
220
- if (theme === 'dark') {
221
- return { filter: 'invert(1) hue-rotate(180deg)' };
222
- }
223
- if (theme === 'auto') {
224
- return { filter: 'var(--svger-theme-filter, none)' };
225
- }
226
- return {};
220
+ // O(1) object lookup instead of if-else chain
221
+ const themeStyleMap: Record<string, React.CSSProperties> = {
222
+ dark: { filter: 'invert(1) hue-rotate(180deg)' },
223
+ auto: { filter: 'var(--svger-theme-filter, none)' },
224
+ };
225
+ return themeStyleMap[theme as string] || {};
227
226
  }
228
227
 
229
228
  ${componentName}.displayName = "${componentName}";
@@ -424,23 +424,27 @@ const ${componentName} = React.forwardRef<SVGSVGElement, ${componentName}Props>(
424
424
  baseStyles.color = colorMap[variant];
425
425
  }
426
426
 
427
- // Apply theme
428
- if (theme === 'dark') {
429
- baseStyles.filter = 'invert(1)';
430
- } else if (theme === 'auto') {
431
- baseStyles.filter = 'var(--svger-theme-filter, none)';
427
+ // Apply theme — O(1) object lookup instead of if-else chain
428
+ const themeFilters: Record<string, string> = {
429
+ dark: 'invert(1)',
430
+ auto: 'var(--svger-theme-filter, none)',
431
+ };
432
+ if (theme && themeFilters[theme]) {
433
+ baseStyles.filter = themeFilters[theme];
432
434
  }
433
435
 
434
- // Apply animation
436
+ // Apply animation — O(1) object lookup instead of if-else chain
435
437
  if (animate) {
436
- if (animate === true || animate === 'spin') {
437
- baseStyles.animation = 'svger-spin 2s linear infinite';
438
- } else if (animate === 'pulse') {
439
- baseStyles.animation = 'svger-pulse 2s ease-in-out infinite';
440
- } else if (animate === 'bounce') {
441
- baseStyles.animation = 'svger-bounce 1s infinite';
442
- } else if (animate === 'fade') {
443
- baseStyles.animation = 'svger-fade 2s ease-in-out infinite alternate';
438
+ const animationMap: Record<string, string> = {
439
+ spin: 'svger-spin 2s linear infinite',
440
+ pulse: 'svger-pulse 2s ease-in-out infinite',
441
+ bounce: 'svger-bounce 1s infinite',
442
+ fade: 'svger-fade 2s ease-in-out infinite alternate',
443
+ };
444
+ const animKey = animate === true ? 'spin' : animate;
445
+ const anim = animationMap[animKey as string];
446
+ if (anim) {
447
+ baseStyles.animation = anim;
444
448
  }
445
449
  }
446
450
 
package/dist/index.d.ts CHANGED
@@ -4,7 +4,6 @@
4
4
  * A high-performance, framework-agnostic SVG processing toolkit with enterprise-grade
5
5
  * architecture and comprehensive styling capabilities.
6
6
  *
7
- * @version 2.0.0
8
7
  * @author SVGER-CLI Development Team
9
8
  * @license MIT
10
9
  */
@@ -148,7 +147,7 @@ export declare const SVGER: {
148
147
  /**
149
148
  * Package Version Information
150
149
  */
151
- export declare const VERSION = "2.0.0";
150
+ export declare const VERSION: string;
152
151
  export declare const PACKAGE_NAME = "svger-cli";
153
152
  /**
154
153
  * Webpack Plugin - Official webpack integration for SVGER-CLI