svger-cli 4.0.2 → 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.
@@ -45,78 +45,68 @@ export function getDefaultOptConfig(level) {
45
45
  shapeConversionThreshold: 5,
46
46
  optimizationLevel: level,
47
47
  };
48
- switch (level) {
49
- case OptLevel.NONE:
50
- return {
51
- ...baseConfig,
52
- removeMetadata: false,
53
- removeComments: false,
54
- normalizeWhitespace: false,
55
- removeUnnecessaryAttrs: false,
56
- shortenColors: false,
57
- removeEmptyContainers: false,
58
- removeDoctype: false,
59
- removeXMLProcInst: false,
60
- enableNumericOptimization: false,
61
- enableStyleOptimization: false,
62
- enableTransformOptimization: false,
63
- enableTransformCollapsing: false,
64
- enablePathOptimization: false,
65
- enablePathSimplification: false,
66
- };
67
- case OptLevel.BASIC:
68
- return baseConfig;
69
- case OptLevel.BALANCED:
70
- return {
71
- ...baseConfig,
72
- removeHiddenElements: true,
73
- floatPrecision: 3,
74
- enableNumericOptimization: true,
75
- enableStyleOptimization: true,
76
- enableTransformOptimization: false,
77
- enableTransformCollapsing: false,
78
- enablePathOptimization: false,
79
- };
80
- case OptLevel.AGGRESSIVE:
81
- return {
82
- ...baseConfig,
83
- removeHiddenElements: true,
84
- mergePaths: true,
85
- collapseGroups: true,
86
- floatPrecision: 2,
87
- pathTolerance: 0.7,
88
- sortAttrs: true,
89
- enableNumericOptimization: true,
90
- enableStyleOptimization: true,
91
- enableTransformOptimization: true,
92
- enableTransformCollapsing: true,
93
- enablePathOptimization: true,
94
- // DISABLED: Shape conversion conflicts with floatPrecision=2
95
- // Causes visual regressions (coordinates get rounded before conversion)
96
- shapeConversion: false,
97
- shapeConversionThreshold: 5,
98
- };
99
- case OptLevel.MAXIMUM:
100
- return {
101
- ...baseConfig,
102
- removeHiddenElements: true,
103
- mergePaths: true,
104
- collapseGroups: true,
105
- inlineStyles: true,
106
- floatPrecision: 1,
107
- pathTolerance: 0.9,
108
- sortAttrs: true,
109
- removeViewBox: false, // Keep viewBox even at maximum for compatibility
110
- enableNumericOptimization: true,
111
- enableStyleOptimization: true,
112
- enableTransformOptimization: true,
113
- enableTransformCollapsing: true,
114
- enablePathOptimization: true,
115
- enablePathSimplification: true,
116
- shapeConversion: true,
117
- shapeConversionThreshold: 0, // Convert even 1-byte savings
118
- };
119
- default:
120
- return baseConfig;
121
- }
48
+ // O(1) object lookup for optimization level → config overrides
49
+ const levelOverrides = {
50
+ [OptLevel.NONE]: {
51
+ removeMetadata: false,
52
+ removeComments: false,
53
+ normalizeWhitespace: false,
54
+ removeUnnecessaryAttrs: false,
55
+ shortenColors: false,
56
+ removeEmptyContainers: false,
57
+ removeDoctype: false,
58
+ removeXMLProcInst: false,
59
+ enableNumericOptimization: false,
60
+ enableStyleOptimization: false,
61
+ enableTransformOptimization: false,
62
+ enableTransformCollapsing: false,
63
+ enablePathOptimization: false,
64
+ enablePathSimplification: false,
65
+ },
66
+ [OptLevel.BASIC]: {},
67
+ [OptLevel.BALANCED]: {
68
+ removeHiddenElements: true,
69
+ floatPrecision: 3,
70
+ enableNumericOptimization: true,
71
+ enableStyleOptimization: true,
72
+ enableTransformOptimization: false,
73
+ enableTransformCollapsing: false,
74
+ enablePathOptimization: false,
75
+ },
76
+ [OptLevel.AGGRESSIVE]: {
77
+ removeHiddenElements: true,
78
+ mergePaths: true,
79
+ collapseGroups: true,
80
+ floatPrecision: 2,
81
+ pathTolerance: 0.7,
82
+ sortAttrs: true,
83
+ enableNumericOptimization: true,
84
+ enableStyleOptimization: true,
85
+ enableTransformOptimization: true,
86
+ enableTransformCollapsing: true,
87
+ enablePathOptimization: true,
88
+ shapeConversion: false,
89
+ shapeConversionThreshold: 5,
90
+ },
91
+ [OptLevel.MAXIMUM]: {
92
+ removeHiddenElements: true,
93
+ mergePaths: true,
94
+ collapseGroups: true,
95
+ inlineStyles: true,
96
+ floatPrecision: 1,
97
+ pathTolerance: 0.9,
98
+ sortAttrs: true,
99
+ removeViewBox: false,
100
+ enableNumericOptimization: true,
101
+ enableStyleOptimization: true,
102
+ enableTransformOptimization: true,
103
+ enableTransformCollapsing: true,
104
+ enablePathOptimization: true,
105
+ enablePathSimplification: true,
106
+ shapeConversion: true,
107
+ shapeConversionThreshold: 0,
108
+ },
109
+ };
110
+ const overrides = levelOverrides[level] ?? {};
111
+ return { ...baseConfig, ...overrides };
122
112
  }
@@ -187,7 +187,7 @@ export class SVGProcessor {
187
187
  const baseName = path.basename(fileName, '.svg');
188
188
  // Object lookup map for naming conventions - O(1) performance
189
189
  const namingHandlers = {
190
- kebab: () => toPascalCase(baseName),
190
+ kebab: () => toKebabCase(baseName),
191
191
  camel: () => {
192
192
  const pascalName = toPascalCase(baseName);
193
193
  return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
@@ -340,13 +340,26 @@ export class SVGProcessor {
340
340
  error: error,
341
341
  };
342
342
  logger.error(`Failed to process ${svgFilePath}:`, error);
343
+ // Immediately remove failed jobs to prevent memory leaks
344
+ this.processingQueue.delete(jobId);
343
345
  return result;
344
346
  }
345
347
  finally {
346
- // Clean up completed jobs after some time
347
- setTimeout(() => {
348
- this.processingQueue.delete(jobId);
349
- }, 30000); // 30 seconds
348
+ // Clean up completed jobs after a short delay (allows stats queries)
349
+ if (job.status === 'completed') {
350
+ setTimeout(() => {
351
+ this.processingQueue.delete(jobId);
352
+ }, 30000); // 30 seconds
353
+ }
354
+ // Cap queue size as a safety net to prevent unbounded growth
355
+ if (this.processingQueue.size > 10000) {
356
+ const oldestEntries = Array.from(this.processingQueue.entries())
357
+ .filter(([, j]) => j.status === 'completed' || j.status === 'failed')
358
+ .slice(0, this.processingQueue.size - 5000);
359
+ for (const [key] of oldestEntries) {
360
+ this.processingQueue.delete(key);
361
+ }
362
+ }
350
363
  }
351
364
  }
352
365
  /**
@@ -354,7 +367,7 @@ export class SVGProcessor {
354
367
  */
355
368
  getProcessingStats() {
356
369
  const jobs = Array.from(this.processingQueue.values());
357
- // Single pass through jobs instead of 4 separate filters
370
+ // Direct property increment via object key O(1) per job, avoids switch branching
358
371
  const stats = {
359
372
  total: jobs.length,
360
373
  pending: 0,
@@ -363,20 +376,7 @@ export class SVGProcessor {
363
376
  failed: 0,
364
377
  };
365
378
  for (const job of jobs) {
366
- switch (job.status) {
367
- case 'pending':
368
- stats.pending++;
369
- break;
370
- case 'processing':
371
- stats.processing++;
372
- break;
373
- case 'completed':
374
- stats.completed++;
375
- break;
376
- case 'failed':
377
- stats.failed++;
378
- break;
379
- }
379
+ stats[job.status]++;
380
380
  }
381
381
  return stats;
382
382
  }
@@ -275,10 +275,14 @@ export class ConfigService {
275
275
  logger.info(`Migrated optimization level: ${oldOptimization} → ${migratedConfig.performance.optimization}`);
276
276
  }
277
277
  }
278
- // Save migrated config
279
- this.writeConfig(migratedConfig);
280
- logger.success('Configuration migrated to v4.0.0');
281
- return migratedConfig;
278
+ // Save migrated config merged with defaults to ensure all new keys exist
279
+ const mergedMigratedConfig = {
280
+ ...this.getDefaultConfig(),
281
+ ...migratedConfig,
282
+ };
283
+ this.writeConfig(mergedMigratedConfig);
284
+ logger.success(`Configuration migrated from ${currentVersion} to ${CURRENT_VERSION}`);
285
+ return mergedMigratedConfig;
282
286
  }
283
287
  /**
284
288
  * Validate configuration
@@ -25,8 +25,15 @@ export class SVGService {
25
25
  * Set optimizer level for SVG processing
26
26
  */
27
27
  setOptimizerLevel(level) {
28
- const validLevels = ['none', 'basic', 'balanced', 'aggressive', 'maximum'];
29
- if (!validLevels.includes(level.toLowerCase())) {
28
+ // O(1) Set lookup instead of O(n) Array.includes()
29
+ const validLevels = new Set([
30
+ 'none',
31
+ 'basic',
32
+ 'balanced',
33
+ 'aggressive',
34
+ 'maximum',
35
+ ]);
36
+ if (!validLevels.has(level.toLowerCase())) {
30
37
  logger.warn(`Invalid optimization level "${level}". Using "basic" instead.`);
31
38
  svgProcessor.setOptimizationLevel(OptLevel.BASIC);
32
39
  return;
@@ -45,7 +45,6 @@ export declare class FileSystem {
45
45
  private static _readdir;
46
46
  private static _stat;
47
47
  private static _mkdir;
48
- private static _rmdir;
49
48
  private static _unlink;
50
49
  static exists(path: string): Promise<boolean>;
51
50
  static readFile(path: string, encoding?: BufferEncoding): Promise<string>;
@@ -62,7 +62,6 @@ export class FileSystem {
62
62
  static _readdir = promisify(fs.readdir);
63
63
  static _stat = promisify(fs.stat);
64
64
  static _mkdir = promisify(fs.mkdir);
65
- static _rmdir = promisify(fs.rmdir);
66
65
  static _unlink = promisify(fs.unlink);
67
66
  static async exists(path) {
68
67
  try {
@@ -94,18 +93,7 @@ export class FileSystem {
94
93
  }
95
94
  static async removeDir(dirPath) {
96
95
  try {
97
- const files = await this._readdir(dirPath);
98
- for (const file of files) {
99
- const filePath = `${dirPath}/${file}`;
100
- const stats = await this._stat(filePath);
101
- if (stats.isDirectory()) {
102
- await this.removeDir(filePath);
103
- }
104
- else {
105
- await this._unlink(filePath);
106
- }
107
- }
108
- await this._rmdir(dirPath);
96
+ await fs.promises.rm(dirPath, { recursive: true, force: true });
109
97
  }
110
98
  catch (error) {
111
99
  if (error.code !== 'ENOENT') {
@@ -311,10 +299,14 @@ export class FileWatcher {
311
299
  this.emit(eventType, `${path}/${filename}`);
312
300
  }
313
301
  });
302
+ watcher.on('error', error => {
303
+ this.emit('error', error);
304
+ });
314
305
  this.watchers.push(watcher);
315
306
  }
316
307
  catch (error) {
317
- console.error(`Failed to watch ${path}:`, error);
308
+ // Emit error event so consumers can handle it
309
+ this.emit('error', error);
318
310
  }
319
311
  return this;
320
312
  }
package/dist/watch.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import { generateSVG } from './builder.js';
3
3
  import { isLocked } from './lock.js';
4
- import { FileSystem, FileWatcher } from './utils/native.js';
4
+ import { FileSystem, FileWatcher, toPascalCase } from './utils/native.js';
5
5
  /**
6
6
  * Watches a source folder for changes to SVG files and automatically
7
7
  * rebuilds React components when SVGs are added, modified, or deleted.
@@ -59,8 +59,9 @@ export async function watchSVGs(config) {
59
59
  await generateSVG({ svgFile: filePath, outDir });
60
60
  }
61
61
  else {
62
- // File was deleted
63
- const componentName = path.basename(filePath, '.svg');
62
+ // File was deleted — use PascalCase to match the generated component filename
63
+ const baseName = path.basename(filePath, '.svg');
64
+ const componentName = toPascalCase(baseName);
64
65
  const outFile = path.join(outDir, `${componentName}.tsx`);
65
66
  if (await FileSystem.exists(outFile)) {
66
67
  await FileSystem.unlink(outFile);
@@ -1,7 +1,7 @@
1
1
  # Error Handling Standards
2
2
 
3
3
  ## Purpose
4
- This document establishes standardized error handling patterns for SVGER-CLI v4.0.1+ to ensure consistency, proper error propagation, and appropriate user feedback.
4
+ This document establishes standardized error handling patterns for SVGER-CLI v4.0.3+ to ensure consistency, proper error propagation, and appropriate user feedback.
5
5
 
6
6
  ## Standard Patterns
7
7
 
@@ -158,4 +158,4 @@ If you encounter any issues with optional dependencies:
158
158
 
159
159
  ---
160
160
 
161
- **Last Updated**: January 28, 2026 (v4.0.1)
161
+ **Last Updated**: January 28, 2026 (v4.0.3)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svger-cli",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "description": "Enterprise-grade SVG to component converter with advanced plugin system, visual diff testing, and official framework integrations. Supporting React, React Native, Vue, Angular, Svelte, Solid, Lit, Preact & Vanilla. Features TypeScript, HMR, optimization pipeline, and extensible architecture.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",