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.
- package/CHANGELOG.md +230 -0
- package/README.md +27 -27
- package/dist/builder.d.ts +6 -3
- package/dist/builder.js +34 -24
- package/dist/cli.js +122 -10
- package/dist/config.d.ts +8 -2
- package/dist/config.js +17 -124
- package/dist/core/enhanced-plugin-manager.d.ts +1 -0
- package/dist/core/enhanced-plugin-manager.js +37 -11
- package/dist/core/framework-templates.js +4 -0
- package/dist/core/logger.js +8 -4
- package/dist/core/performance-engine.js +16 -3
- package/dist/core/style-compiler.js +6 -7
- package/dist/core/template-manager.js +18 -14
- package/dist/index.d.ts +1 -2
- package/dist/index.js +8 -2
- package/dist/integrations/jest-preset.js +30 -2
- package/dist/lock.js +1 -1
- package/dist/optimizers/basic-cleaner.js +4 -0
- package/dist/optimizers/path-parser.js +199 -115
- package/dist/optimizers/path-simplifier.js +27 -24
- package/dist/optimizers/remove-unused-defs.js +16 -0
- package/dist/optimizers/shape-conversion.js +22 -27
- package/dist/optimizers/style-optimizer.js +5 -0
- package/dist/optimizers/svg-tree-parser.js +4 -0
- package/dist/optimizers/transform-collapsing.js +11 -15
- package/dist/optimizers/transform-optimizer.js +20 -21
- package/dist/optimizers/types.js +64 -74
- package/dist/plugins/gradient-optimizer.js +4 -0
- package/dist/processors/svg-processor.js +28 -10
- package/dist/services/config.js +28 -11
- package/dist/services/file-watcher.js +8 -3
- package/dist/services/svg-service.d.ts +1 -1
- package/dist/services/svg-service.js +24 -11
- package/dist/utils/native.d.ts +0 -1
- package/dist/utils/native.js +6 -14
- package/dist/utils/visual-diff.js +7 -2
- package/dist/watch.js +4 -3
- package/docs/ERROR-HANDLING-STANDARD.md +111 -0
- package/docs/OPTIONAL-DEPENDENCIES.md +1 -1
- 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(
|
|
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
|
-
|
|
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: ${
|
|
318
|
-
|
|
319
|
-
//
|
|
320
|
-
|
|
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.
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2
|
+
* Legacy configuration module - delegates to ConfigService.
|
|
6
3
|
*
|
|
7
|
-
* @
|
|
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
|
-
|
|
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
|
-
* @
|
|
11
|
+
* @deprecated Use `configService.readConfig()` instead.
|
|
12
|
+
* @returns {Record<string, any>} Configuration object.
|
|
16
13
|
*/
|
|
17
14
|
export function readConfig() {
|
|
18
|
-
return
|
|
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
|
-
|
|
24
|
+
configService.writeConfig(config);
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
29
27
|
* Initialize the svger-cli configuration with default values.
|
|
30
|
-
*
|
|
28
|
+
*
|
|
29
|
+
* @deprecated Use `configService.initConfig()` instead.
|
|
31
30
|
*/
|
|
32
31
|
export async function initConfig() {
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
console.log('📄 Current Config:');
|
|
157
|
-
console.log(JSON.stringify(config, null, 2));
|
|
50
|
+
configService.showConfig();
|
|
158
51
|
}
|
|
@@ -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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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 };
|
package/dist/core/logger.js
CHANGED
|
@@ -19,10 +19,14 @@ export class LoggerService {
|
|
|
19
19
|
this.enableColors = enabled;
|
|
20
20
|
}
|
|
21
21
|
shouldLog(level) {
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
170
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
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
|