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
|
@@ -144,8 +144,9 @@ function shouldConvert(element, pathData, threshold) {
|
|
|
144
144
|
function convertShapeToPath(node, threshold) {
|
|
145
145
|
const tag = node.tag;
|
|
146
146
|
let pathData = null;
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
// O(1) object lookup for shape tag → path converter
|
|
148
|
+
const shapeConverters = {
|
|
149
|
+
rect: () => {
|
|
149
150
|
const x = parseFloat(node.attrs.get('x') || '0');
|
|
150
151
|
const y = parseFloat(node.attrs.get('y') || '0');
|
|
151
152
|
const width = parseFloat(node.attrs.get('width') || '0');
|
|
@@ -156,41 +157,35 @@ function convertShapeToPath(node, threshold) {
|
|
|
156
157
|
const ry = node.attrs.has('ry')
|
|
157
158
|
? parseFloat(node.attrs.get('ry'))
|
|
158
159
|
: undefined;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
case 'circle': {
|
|
160
|
+
return rectToPath(x, y, width, height, rx, ry);
|
|
161
|
+
},
|
|
162
|
+
circle: () => {
|
|
163
163
|
const cx = parseFloat(node.attrs.get('cx') || '0');
|
|
164
164
|
const cy = parseFloat(node.attrs.get('cy') || '0');
|
|
165
165
|
const r = parseFloat(node.attrs.get('r') || '0');
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
case 'ellipse': {
|
|
166
|
+
return circleToPath(cx, cy, r);
|
|
167
|
+
},
|
|
168
|
+
ellipse: () => {
|
|
170
169
|
const cx = parseFloat(node.attrs.get('cx') || '0');
|
|
171
170
|
const cy = parseFloat(node.attrs.get('cy') || '0');
|
|
172
171
|
const rx = parseFloat(node.attrs.get('rx') || '0');
|
|
173
172
|
const ry = parseFloat(node.attrs.get('ry') || '0');
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
case 'polygon': {
|
|
173
|
+
return ellipseToPath(cx, cy, rx, ry);
|
|
174
|
+
},
|
|
175
|
+
polygon: () => {
|
|
178
176
|
const points = node.attrs.get('points');
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
case 'polyline': {
|
|
177
|
+
return points ? polygonToPath(points) : null;
|
|
178
|
+
},
|
|
179
|
+
polyline: () => {
|
|
185
180
|
const points = node.attrs.get('points');
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return { converted: false, savings: 0 };
|
|
181
|
+
return points ? polylineToPath(points) : null;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const converter = shapeConverters[tag];
|
|
185
|
+
if (!converter) {
|
|
186
|
+
return { converted: false, savings: 0 };
|
|
193
187
|
}
|
|
188
|
+
pathData = converter();
|
|
194
189
|
// Check if conversion was possible
|
|
195
190
|
if (!pathData) {
|
|
196
191
|
return { converted: false, savings: 0 };
|
|
@@ -45,6 +45,11 @@ function parseCSSRules(cssText: string): Map<string, Map<string, string>> {
|
|
|
45
45
|
let match;
|
|
46
46
|
|
|
47
47
|
while ((match = ruleRegex.exec(cleaned)) !== null) {
|
|
48
|
+
// Prevent infinite loop if regex doesn't advance
|
|
49
|
+
if (match.index === ruleRegex.lastIndex) {
|
|
50
|
+
ruleRegex.lastIndex++;
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
const selector = match[1].trim();
|
|
49
54
|
const declarationsText = match[2].trim();
|
|
50
55
|
|
|
@@ -60,6 +60,10 @@ function parseAttributes(attrString) {
|
|
|
60
60
|
const attrRegex = /([a-zA-Z][a-zA-Z0-9-:]*)=["']([^"']*)["']/g;
|
|
61
61
|
let match;
|
|
62
62
|
while ((match = attrRegex.exec(attrString)) !== null) {
|
|
63
|
+
// Prevent infinite loop if regex doesn't advance
|
|
64
|
+
if (match.index === attrRegex.lastIndex) {
|
|
65
|
+
attrRegex.lastIndex++;
|
|
66
|
+
}
|
|
63
67
|
const [, name, value] = match;
|
|
64
68
|
attrs.set(name, value);
|
|
65
69
|
}
|
|
@@ -220,21 +220,17 @@ function applyTransformToPath(node, matrix) {
|
|
|
220
220
|
* Apply transform to shape coordinates
|
|
221
221
|
*/
|
|
222
222
|
function applyTransformToShape(node, matrix) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return applyTransformToPath(node, matrix);
|
|
235
|
-
default:
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
223
|
+
// O(1) object lookup for shape tag → transform handler
|
|
224
|
+
const shapeHandlers = {
|
|
225
|
+
rect: applyTransformToRect,
|
|
226
|
+
circle: applyTransformToCircle,
|
|
227
|
+
line: applyTransformToLine,
|
|
228
|
+
polygon: applyTransformToPoints,
|
|
229
|
+
polyline: applyTransformToPoints,
|
|
230
|
+
path: applyTransformToPath,
|
|
231
|
+
};
|
|
232
|
+
const handler = shapeHandlers[node.tag];
|
|
233
|
+
return handler ? handler(node, matrix) : false;
|
|
238
234
|
}
|
|
239
235
|
/**
|
|
240
236
|
* Collapse transforms by propagating down the tree
|
|
@@ -36,6 +36,10 @@ export function parseTransformList(transformStr) {
|
|
|
36
36
|
const regex = /(\w+)\s*\([^)]+\)/g;
|
|
37
37
|
let match;
|
|
38
38
|
while ((match = regex.exec(transformStr)) !== null) {
|
|
39
|
+
// Prevent infinite loop if regex doesn't advance
|
|
40
|
+
if (match.index === regex.lastIndex) {
|
|
41
|
+
regex.lastIndex++;
|
|
42
|
+
}
|
|
39
43
|
const cmd = parseTransformCommand(match[0]);
|
|
40
44
|
if (cmd) {
|
|
41
45
|
transforms.push(cmd);
|
|
@@ -63,32 +67,27 @@ function multiplyMatrices(m1, m2) {
|
|
|
63
67
|
*/
|
|
64
68
|
function transformToMatrix(transform) {
|
|
65
69
|
const { type, values } = transform;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
return IDENTITY_MATRIX;
|
|
72
|
-
case 'translate': {
|
|
70
|
+
// O(1) object lookup for transform type → matrix factory
|
|
71
|
+
const matrixFactories = {
|
|
72
|
+
matrix: () => (values.length === 6 ? values : IDENTITY_MATRIX),
|
|
73
|
+
translate: () => {
|
|
73
74
|
const tx = values[0] || 0;
|
|
74
75
|
const ty = values[1] || 0;
|
|
75
76
|
return [1, 0, 0, 1, tx, ty];
|
|
76
|
-
}
|
|
77
|
-
|
|
77
|
+
},
|
|
78
|
+
scale: () => {
|
|
78
79
|
const sx = values[0] || 1;
|
|
79
80
|
const sy = values[1] !== undefined ? values[1] : sx;
|
|
80
81
|
return [sx, 0, 0, sy, 0, 0];
|
|
81
|
-
}
|
|
82
|
-
|
|
82
|
+
},
|
|
83
|
+
rotate: () => {
|
|
83
84
|
const angle = values[0] || 0;
|
|
84
85
|
const rad = (angle * Math.PI) / 180;
|
|
85
86
|
const cos = Math.cos(rad);
|
|
86
87
|
const sin = Math.sin(rad);
|
|
87
|
-
// Rotate around point (cx, cy) if provided
|
|
88
88
|
if (values.length === 3) {
|
|
89
89
|
const cx = values[1];
|
|
90
90
|
const cy = values[2];
|
|
91
|
-
// translate(-cx, -cy) × rotate(angle) × translate(cx, cy)
|
|
92
91
|
return [
|
|
93
92
|
cos,
|
|
94
93
|
sin,
|
|
@@ -99,22 +98,22 @@ function transformToMatrix(transform) {
|
|
|
99
98
|
];
|
|
100
99
|
}
|
|
101
100
|
return [cos, sin, -sin, cos, 0, 0];
|
|
102
|
-
}
|
|
103
|
-
|
|
101
|
+
},
|
|
102
|
+
skewX: () => {
|
|
104
103
|
const angle = values[0] || 0;
|
|
105
104
|
const rad = (angle * Math.PI) / 180;
|
|
106
105
|
const tan = Math.tan(rad);
|
|
107
106
|
return [1, 0, tan, 1, 0, 0];
|
|
108
|
-
}
|
|
109
|
-
|
|
107
|
+
},
|
|
108
|
+
skewY: () => {
|
|
110
109
|
const angle = values[0] || 0;
|
|
111
110
|
const rad = (angle * Math.PI) / 180;
|
|
112
111
|
const tan = Math.tan(rad);
|
|
113
112
|
return [1, tan, 0, 1, 0, 0];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const factory = matrixFactories[type];
|
|
116
|
+
return factory ? factory() : IDENTITY_MATRIX;
|
|
118
117
|
}
|
|
119
118
|
/**
|
|
120
119
|
* Consolidate transform list into single matrix
|
package/dist/optimizers/types.js
CHANGED
|
@@ -45,78 +45,68 @@ export function getDefaultOptConfig(level) {
|
|
|
45
45
|
shapeConversionThreshold: 5,
|
|
46
46
|
optimizationLevel: level,
|
|
47
47
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
}
|
|
@@ -44,6 +44,10 @@ function removeDuplicateStops(gradientContent) {
|
|
|
44
44
|
const seen = new Set();
|
|
45
45
|
let match;
|
|
46
46
|
while ((match = stopRegex.exec(gradientContent)) !== null) {
|
|
47
|
+
// Prevent infinite loop if regex doesn't advance
|
|
48
|
+
if (match.index === stopRegex.lastIndex) {
|
|
49
|
+
stopRegex.lastIndex++;
|
|
50
|
+
}
|
|
47
51
|
const stopTag = match[0];
|
|
48
52
|
const normalized = stopTag.replace(/\s+/g, ' ').trim();
|
|
49
53
|
if (!seen.has(normalized)) {
|
|
@@ -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: () =>
|
|
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
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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,13 +367,18 @@ export class SVGProcessor {
|
|
|
354
367
|
*/
|
|
355
368
|
getProcessingStats() {
|
|
356
369
|
const jobs = Array.from(this.processingQueue.values());
|
|
357
|
-
|
|
370
|
+
// Direct property increment via object key — O(1) per job, avoids switch branching
|
|
371
|
+
const stats = {
|
|
358
372
|
total: jobs.length,
|
|
359
|
-
pending:
|
|
360
|
-
processing:
|
|
361
|
-
completed:
|
|
362
|
-
failed:
|
|
373
|
+
pending: 0,
|
|
374
|
+
processing: 0,
|
|
375
|
+
completed: 0,
|
|
376
|
+
failed: 0,
|
|
363
377
|
};
|
|
378
|
+
for (const job of jobs) {
|
|
379
|
+
stats[job.status]++;
|
|
380
|
+
}
|
|
381
|
+
return stats;
|
|
364
382
|
}
|
|
365
383
|
/**
|
|
366
384
|
* Clear processing queue
|
package/dist/services/config.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { FileSystem } from '../utils/native.js';
|
|
3
3
|
import { logger } from '../core/logger.js';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
// Get package version dynamically
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
|
|
11
|
+
const CURRENT_VERSION = packageJson.version;
|
|
4
12
|
/**
|
|
5
13
|
* Professional configuration management service
|
|
6
14
|
*/
|
|
@@ -21,7 +29,7 @@ export class ConfigService {
|
|
|
21
29
|
getDefaultConfig() {
|
|
22
30
|
return {
|
|
23
31
|
// Configuration Version (for migration compatibility)
|
|
24
|
-
version:
|
|
32
|
+
version: CURRENT_VERSION,
|
|
25
33
|
// Source & Output
|
|
26
34
|
source: './src/assets/svg',
|
|
27
35
|
output: './src/components/icons',
|
|
@@ -130,7 +138,7 @@ export class ConfigService {
|
|
|
130
138
|
return this.cachedConfig;
|
|
131
139
|
}
|
|
132
140
|
// Check if migration is needed
|
|
133
|
-
if (!configData.version || configData.version !==
|
|
141
|
+
if (!configData.version || configData.version !== CURRENT_VERSION) {
|
|
134
142
|
logger.info('Detected older configuration version, migrating...');
|
|
135
143
|
const migratedConfig = this.migrateConfig(configData);
|
|
136
144
|
this.cachedConfig = migratedConfig;
|
|
@@ -220,16 +228,21 @@ export class ConfigService {
|
|
|
220
228
|
* Migrate configuration from older versions to v4.0.0
|
|
221
229
|
*/
|
|
222
230
|
migrateConfig(config) {
|
|
231
|
+
// Validate config is not null/undefined
|
|
232
|
+
if (!config || typeof config !== 'object') {
|
|
233
|
+
logger.warn('Invalid config provided for migration, using defaults');
|
|
234
|
+
return this.getDefaultConfig();
|
|
235
|
+
}
|
|
223
236
|
const currentVersion = config.version || '3.0.0';
|
|
224
|
-
// No migration needed if already
|
|
225
|
-
if (currentVersion ===
|
|
226
|
-
return config;
|
|
237
|
+
// No migration needed if already current version
|
|
238
|
+
if (currentVersion === CURRENT_VERSION) {
|
|
239
|
+
return { ...this.getDefaultConfig(), ...config };
|
|
227
240
|
}
|
|
228
|
-
logger.info(`Migrating configuration from ${currentVersion} to
|
|
241
|
+
logger.info(`Migrating configuration from ${currentVersion} to ${CURRENT_VERSION}...`);
|
|
229
242
|
// Migration from v3.x to v4.0.0
|
|
230
243
|
const migratedConfig = { ...config };
|
|
231
244
|
// Add version field
|
|
232
|
-
migratedConfig.version =
|
|
245
|
+
migratedConfig.version = CURRENT_VERSION;
|
|
233
246
|
// Ensure plugins array exists (new in v4.0.0)
|
|
234
247
|
if (!migratedConfig.plugins) {
|
|
235
248
|
migratedConfig.plugins = [];
|
|
@@ -262,10 +275,14 @@ export class ConfigService {
|
|
|
262
275
|
logger.info(`Migrated optimization level: ${oldOptimization} → ${migratedConfig.performance.optimization}`);
|
|
263
276
|
}
|
|
264
277
|
}
|
|
265
|
-
// Save migrated config
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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;
|
|
269
286
|
}
|
|
270
287
|
/**
|
|
271
288
|
* Validate configuration
|
|
@@ -59,10 +59,15 @@ export class FileWatcherService {
|
|
|
59
59
|
if (this.debounceTimers.has(debounceKey)) {
|
|
60
60
|
clearTimeout(this.debounceTimers.get(debounceKey));
|
|
61
61
|
}
|
|
62
|
-
// Set new timer
|
|
62
|
+
// Set new timer with error handling
|
|
63
63
|
const timer = setTimeout(async () => {
|
|
64
64
|
this.debounceTimers.delete(debounceKey);
|
|
65
|
-
|
|
65
|
+
try {
|
|
66
|
+
await this.processFileEvent(watchId, eventType, filePath);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger.error(`Failed to process file event for ${filePath}:`, error);
|
|
70
|
+
}
|
|
66
71
|
}, this.debounceDelay);
|
|
67
72
|
this.debounceTimers.set(debounceKey, timer);
|
|
68
73
|
}
|
|
@@ -130,7 +135,7 @@ export class FileWatcherService {
|
|
|
130
135
|
watcher.close();
|
|
131
136
|
this.watchers.delete(watchId);
|
|
132
137
|
this.eventHandlers.delete(watchId);
|
|
133
|
-
// Clear
|
|
138
|
+
// Clear all pending debounce timers for this watcher to prevent orphaned timers
|
|
134
139
|
for (const [key, timer] of this.debounceTimers.entries()) {
|
|
135
140
|
if (key.startsWith(`${watchId}-`)) {
|
|
136
141
|
clearTimeout(timer);
|
|
@@ -25,8 +25,15 @@ export class SVGService {
|
|
|
25
25
|
* Set optimizer level for SVG processing
|
|
26
26
|
*/
|
|
27
27
|
setOptimizerLevel(level) {
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
@@ -117,15 +124,21 @@ export class SVGService {
|
|
|
117
124
|
});
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
|
-
// Log summary
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
// Log summary - single pass through results
|
|
128
|
+
const successfulResults = [];
|
|
129
|
+
const failedResults = [];
|
|
130
|
+
for (const result of results) {
|
|
131
|
+
if (result.success) {
|
|
132
|
+
successfulResults.push(result);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
failedResults.push(result);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
logger.info(`Build complete: ${successfulResults.length} successful, ${failedResults.length} failed`);
|
|
139
|
+
if (failedResults.length > 0) {
|
|
125
140
|
logger.warn('Some files failed to process:');
|
|
126
|
-
|
|
127
|
-
.filter(r => !r.success)
|
|
128
|
-
.forEach(r => {
|
|
141
|
+
failedResults.forEach(r => {
|
|
129
142
|
logger.warn(` - ${r.file}: ${r.error?.message}`);
|
|
130
143
|
});
|
|
131
144
|
}
|
|
@@ -210,7 +223,7 @@ export class SVGService {
|
|
|
210
223
|
},
|
|
211
224
|
unlink: async () => {
|
|
212
225
|
logger.info(`SVG removed: ${fileName}`);
|
|
213
|
-
await this.handleFileRemoval(event.filePath, outDir);
|
|
226
|
+
await this.handleFileRemoval(event.filePath, outDir, config);
|
|
214
227
|
},
|
|
215
228
|
};
|
|
216
229
|
const handler = eventHandlers[event.type];
|
package/dist/utils/native.d.ts
CHANGED
|
@@ -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>;
|