svger-cli 2.0.1 → 2.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/.svgerconfig.example.json +38 -0
- package/CHANGELOG.md +64 -0
- package/DEVELOPMENT.md +353 -0
- package/README.md +24 -5
- package/SECURITY.md +69 -0
- package/dist/builder.js +16 -16
- package/dist/clean.js +2 -2
- package/dist/cli.js +38 -38
- package/dist/config.js +11 -11
- package/dist/core/error-handler.d.ts +63 -0
- package/dist/core/error-handler.js +227 -0
- package/dist/core/framework-templates.d.ts +17 -0
- package/{src/core/framework-templates.ts → dist/core/framework-templates.js} +104 -139
- package/dist/core/logger.d.ts +22 -0
- package/dist/core/logger.js +85 -0
- package/dist/core/performance-engine.d.ts +67 -0
- package/dist/core/performance-engine.js +252 -0
- package/dist/core/plugin-manager.d.ts +56 -0
- package/dist/core/plugin-manager.js +191 -0
- package/dist/core/style-compiler.d.ts +88 -0
- package/dist/core/style-compiler.js +468 -0
- package/dist/core/template-manager.d.ts +64 -0
- package/{src/core/template-manager.ts → dist/core/template-manager.js} +172 -255
- package/dist/index.d.ts +153 -0
- package/{src/index.ts → dist/index.js} +32 -110
- package/dist/lock.js +7 -7
- package/dist/processors/svg-processor.d.ts +73 -0
- package/dist/processors/svg-processor.js +261 -0
- package/dist/services/config.d.ts +55 -0
- package/dist/services/config.js +211 -0
- package/dist/services/file-watcher.d.ts +54 -0
- package/dist/services/file-watcher.js +180 -0
- package/dist/services/svg-service.d.ts +81 -0
- package/dist/services/svg-service.js +395 -0
- package/dist/templates/ComponentTemplate.js +25 -25
- package/dist/types/index.d.ts +146 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/native.d.ts +104 -0
- package/dist/utils/native.js +340 -0
- package/dist/watch.d.ts +1 -1
- package/dist/watch.js +14 -14
- package/package.json +154 -14
- package/.svgconfig.json +0 -3
- package/CODE_OF_CONDUCT.md +0 -79
- package/CONTRIBUTING.md +0 -146
- package/TESTING.md +0 -143
- package/cli-framework.test.js +0 -16
- package/cli-test-angular/Arrowbenddownleft.component.ts +0 -27
- package/cli-test-angular/Vite.component.ts +0 -27
- package/cli-test-angular/index.ts +0 -25
- package/cli-test-output/Arrowbenddownleft.vue +0 -33
- package/cli-test-output/Vite.vue +0 -33
- package/cli-test-output/index.ts +0 -25
- package/cli-test-react/Arrowbenddownleft.tsx +0 -39
- package/cli-test-react/Vite.tsx +0 -39
- package/cli-test-react/index.ts +0 -25
- package/cli-test-svelte/Arrowbenddownleft.svelte +0 -22
- package/cli-test-svelte/Vite.svelte +0 -22
- package/cli-test-svelte/index.ts +0 -25
- package/frameworks.test.js +0 -170
- package/my-svgs/ArrowBendDownLeft.svg +0 -6
- package/my-svgs/vite.svg +0 -1
- package/src/builder.ts +0 -104
- package/src/clean.ts +0 -21
- package/src/cli.ts +0 -221
- package/src/config.ts +0 -81
- package/src/core/error-handler.ts +0 -303
- package/src/core/logger.ts +0 -104
- package/src/core/performance-engine.ts +0 -327
- package/src/core/plugin-manager.ts +0 -228
- package/src/core/style-compiler.ts +0 -605
- package/src/lock.ts +0 -74
- package/src/processors/svg-processor.ts +0 -288
- package/src/services/config.ts +0 -241
- package/src/services/file-watcher.ts +0 -218
- package/src/services/svg-service.ts +0 -468
- package/src/templates/ComponentTemplate.ts +0 -57
- package/src/types/index.ts +0 -169
- package/src/utils/native.ts +0 -352
- package/src/watch.ts +0 -88
- package/test-output-mulit/TestIcon-angular-module.component.ts +0 -26
- package/test-output-mulit/TestIcon-angular-standalone.component.ts +0 -27
- package/test-output-mulit/TestIcon-lit.ts +0 -35
- package/test-output-mulit/TestIcon-preact.tsx +0 -38
- package/test-output-mulit/TestIcon-react.tsx +0 -35
- package/test-output-mulit/TestIcon-solid.tsx +0 -27
- package/test-output-mulit/TestIcon-svelte.svelte +0 -22
- package/test-output-mulit/TestIcon-vanilla.ts +0 -37
- package/test-output-mulit/TestIcon-vue-composition.vue +0 -33
- package/test-output-mulit/TestIcon-vue-options.vue +0 -31
- package/tsconfig.json +0 -18
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import { logger } from '../core/logger.js';
|
|
2
|
-
import { SVGProcessor, svgProcessor } from '../processors/svg-processor.js';
|
|
3
|
-
import { ComponentGenerationOptions } from '../types/index.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Performance optimization engine for batch processing and parallel execution
|
|
7
|
-
*/
|
|
8
|
-
export class PerformanceEngine {
|
|
9
|
-
private static instance: PerformanceEngine;
|
|
10
|
-
private processingCache: Map<string, { result: any; timestamp: number }> = new Map();
|
|
11
|
-
private readonly cacheTimeout = 5 * 60 * 1000; // 5 minutes
|
|
12
|
-
|
|
13
|
-
private constructor() {}
|
|
14
|
-
|
|
15
|
-
public static getInstance(): PerformanceEngine {
|
|
16
|
-
if (!PerformanceEngine.instance) {
|
|
17
|
-
PerformanceEngine.instance = new PerformanceEngine();
|
|
18
|
-
}
|
|
19
|
-
return PerformanceEngine.instance;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Process multiple SVG files in parallel with optimized batching
|
|
24
|
-
*/
|
|
25
|
-
public async processBatch(
|
|
26
|
-
files: Array<{ path: string; outputDir: string; options?: Partial<ComponentGenerationOptions> }>,
|
|
27
|
-
config: { batchSize?: number; parallel?: boolean; maxConcurrency?: number } = {}
|
|
28
|
-
): Promise<Array<{ success: boolean; filePath: string; error?: Error; duration: number }>> {
|
|
29
|
-
const {
|
|
30
|
-
batchSize = 10,
|
|
31
|
-
parallel = true,
|
|
32
|
-
maxConcurrency = Math.min(4, require('os').cpus().length)
|
|
33
|
-
} = config;
|
|
34
|
-
|
|
35
|
-
logger.info(`Processing ${files.length} files with ${parallel ? 'parallel' : 'sequential'} execution`);
|
|
36
|
-
|
|
37
|
-
const startTime = Date.now();
|
|
38
|
-
const results: Array<{ success: boolean; filePath: string; error?: Error; duration: number }> = [];
|
|
39
|
-
|
|
40
|
-
if (!parallel) {
|
|
41
|
-
// Sequential processing for stability
|
|
42
|
-
for (const file of files) {
|
|
43
|
-
const result = await this.processSingleWithCaching(file);
|
|
44
|
-
results.push(result);
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
// Parallel processing with controlled concurrency
|
|
48
|
-
const batches = this.createBatches(files, batchSize);
|
|
49
|
-
|
|
50
|
-
for (const batch of batches) {
|
|
51
|
-
const semaphore = new Semaphore(maxConcurrency);
|
|
52
|
-
const batchPromises = batch.map(file =>
|
|
53
|
-
this.withSemaphore(semaphore, () => this.processSingleWithCaching(file))
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
const batchResults = await Promise.all(batchPromises);
|
|
57
|
-
results.push(...batchResults);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const totalDuration = Date.now() - startTime;
|
|
62
|
-
const successful = results.filter(r => r.success).length;
|
|
63
|
-
|
|
64
|
-
logger.info(`Batch processing complete: ${successful}/${files.length} successful in ${totalDuration}ms`);
|
|
65
|
-
|
|
66
|
-
return results;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Process single file with caching support
|
|
71
|
-
*/
|
|
72
|
-
private async processSingleWithCaching(
|
|
73
|
-
file: { path: string; outputDir: string; options?: Partial<ComponentGenerationOptions> }
|
|
74
|
-
): Promise<{ success: boolean; filePath: string; error?: Error; duration: number }> {
|
|
75
|
-
const startTime = Date.now();
|
|
76
|
-
const cacheKey = this.generateCacheKey(file.path, file.options || {});
|
|
77
|
-
|
|
78
|
-
// Check cache first
|
|
79
|
-
const cached = this.getCachedResult(cacheKey);
|
|
80
|
-
if (cached) {
|
|
81
|
-
logger.debug(`Cache hit for ${file.path}`);
|
|
82
|
-
return {
|
|
83
|
-
success: true,
|
|
84
|
-
filePath: file.path,
|
|
85
|
-
duration: Date.now() - startTime
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const result = await svgProcessor.processSVGFile(
|
|
91
|
-
file.path,
|
|
92
|
-
file.outputDir,
|
|
93
|
-
file.options
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// Cache successful results
|
|
97
|
-
if (result.success) {
|
|
98
|
-
this.setCachedResult(cacheKey, result);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
success: result.success,
|
|
103
|
-
filePath: file.path,
|
|
104
|
-
error: result.error,
|
|
105
|
-
duration: Date.now() - startTime
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
} catch (error) {
|
|
109
|
-
return {
|
|
110
|
-
success: false,
|
|
111
|
-
filePath: file.path,
|
|
112
|
-
error: error as Error,
|
|
113
|
-
duration: Date.now() - startTime
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Optimize SVG content with performance considerations
|
|
120
|
-
*/
|
|
121
|
-
public optimizeSVGContent(content: string, level: 'fast' | 'balanced' | 'maximum' = 'balanced'): string {
|
|
122
|
-
const startTime = performance.now();
|
|
123
|
-
let optimized = content;
|
|
124
|
-
|
|
125
|
-
switch (level) {
|
|
126
|
-
case 'fast':
|
|
127
|
-
// Basic optimizations only
|
|
128
|
-
optimized = this.applyFastOptimizations(content);
|
|
129
|
-
break;
|
|
130
|
-
|
|
131
|
-
case 'balanced':
|
|
132
|
-
// Standard optimizations with good performance/quality balance
|
|
133
|
-
optimized = this.applyBalancedOptimizations(content);
|
|
134
|
-
break;
|
|
135
|
-
|
|
136
|
-
case 'maximum':
|
|
137
|
-
// Comprehensive optimizations
|
|
138
|
-
optimized = this.applyMaximumOptimizations(content);
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const duration = performance.now() - startTime;
|
|
143
|
-
const compressionRatio = (1 - optimized.length / content.length) * 100;
|
|
144
|
-
|
|
145
|
-
logger.debug(`SVG optimization (${level}): ${compressionRatio.toFixed(1)}% reduction in ${duration.toFixed(2)}ms`);
|
|
146
|
-
|
|
147
|
-
return optimized;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Memory usage monitoring and optimization
|
|
152
|
-
*/
|
|
153
|
-
public monitorMemoryUsage(): {
|
|
154
|
-
heapUsed: number;
|
|
155
|
-
heapTotal: number;
|
|
156
|
-
external: number;
|
|
157
|
-
cacheSize: number;
|
|
158
|
-
recommendations: string[];
|
|
159
|
-
} {
|
|
160
|
-
const memUsage = process.memoryUsage();
|
|
161
|
-
const cacheSize = this.processingCache.size;
|
|
162
|
-
const recommendations: string[] = [];
|
|
163
|
-
|
|
164
|
-
// Memory usage analysis
|
|
165
|
-
const heapUsedMB = memUsage.heapUsed / 1024 / 1024;
|
|
166
|
-
const heapTotalMB = memUsage.heapTotal / 1024 / 1024;
|
|
167
|
-
|
|
168
|
-
if (heapUsedMB > 500) {
|
|
169
|
-
recommendations.push('Consider reducing batch size or enabling sequential processing');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (cacheSize > 1000) {
|
|
173
|
-
recommendations.push('Cache size is large, consider clearing old entries');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (memUsage.external > 100 * 1024 * 1024) {
|
|
177
|
-
recommendations.push('High external memory usage detected');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
heapUsed: heapUsedMB,
|
|
182
|
-
heapTotal: heapTotalMB,
|
|
183
|
-
external: memUsage.external / 1024 / 1024,
|
|
184
|
-
cacheSize,
|
|
185
|
-
recommendations
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Clear processing cache
|
|
191
|
-
*/
|
|
192
|
-
public clearCache(): void {
|
|
193
|
-
this.processingCache.clear();
|
|
194
|
-
logger.debug('Processing cache cleared');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Get performance metrics
|
|
199
|
-
*/
|
|
200
|
-
public getPerformanceMetrics(): {
|
|
201
|
-
cacheHitRate: number;
|
|
202
|
-
averageProcessingTime: number;
|
|
203
|
-
memoryUsage: any;
|
|
204
|
-
} {
|
|
205
|
-
// This would be implemented with proper metrics collection
|
|
206
|
-
return {
|
|
207
|
-
cacheHitRate: 0, // Placeholder
|
|
208
|
-
averageProcessingTime: 0, // Placeholder
|
|
209
|
-
memoryUsage: this.monitorMemoryUsage()
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Private helper methods
|
|
214
|
-
|
|
215
|
-
private createBatches<T>(items: T[], batchSize: number): T[][] {
|
|
216
|
-
const batches: T[][] = [];
|
|
217
|
-
for (let i = 0; i < items.length; i += batchSize) {
|
|
218
|
-
batches.push(items.slice(i, i + batchSize));
|
|
219
|
-
}
|
|
220
|
-
return batches;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
private async withSemaphore<T>(semaphore: Semaphore, task: () => Promise<T>): Promise<T> {
|
|
224
|
-
await semaphore.acquire();
|
|
225
|
-
try {
|
|
226
|
-
return await task();
|
|
227
|
-
} finally {
|
|
228
|
-
semaphore.release();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private generateCacheKey(filePath: string, options: Partial<ComponentGenerationOptions>): string {
|
|
233
|
-
// Create a hash of file path and options for caching
|
|
234
|
-
const key = JSON.stringify({ filePath, options });
|
|
235
|
-
return Buffer.from(key).toString('base64');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private getCachedResult(key: string): any | null {
|
|
239
|
-
const cached = this.processingCache.get(key);
|
|
240
|
-
if (!cached) return null;
|
|
241
|
-
|
|
242
|
-
// Check if cache entry is still valid
|
|
243
|
-
if (Date.now() - cached.timestamp > this.cacheTimeout) {
|
|
244
|
-
this.processingCache.delete(key);
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return cached.result;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private setCachedResult(key: string, result: any): void {
|
|
252
|
-
this.processingCache.set(key, {
|
|
253
|
-
result,
|
|
254
|
-
timestamp: Date.now()
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private applyFastOptimizations(content: string): string {
|
|
259
|
-
return content
|
|
260
|
-
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
261
|
-
.replace(/>\s+</g, '><') // Remove spaces between tags
|
|
262
|
-
.trim();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private applyBalancedOptimizations(content: string): string {
|
|
266
|
-
let optimized = this.applyFastOptimizations(content);
|
|
267
|
-
|
|
268
|
-
// Remove unnecessary attributes
|
|
269
|
-
optimized = optimized
|
|
270
|
-
.replace(/\s+id="[^"]*"/g, '') // Remove IDs (usually not needed in components)
|
|
271
|
-
.replace(/\s+class="[^"]*"/g, '') // Remove classes
|
|
272
|
-
.replace(/<!--[\s\S]*?-->/g, '') // Remove comments
|
|
273
|
-
.replace(/\s+xmlns="[^"]*"/g, ''); // Remove xmlns (React adds it)
|
|
274
|
-
|
|
275
|
-
return optimized;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private applyMaximumOptimizations(content: string): string {
|
|
279
|
-
let optimized = this.applyBalancedOptimizations(content);
|
|
280
|
-
|
|
281
|
-
// Advanced optimizations
|
|
282
|
-
optimized = optimized
|
|
283
|
-
.replace(/\s+style="[^"]*"/g, '') // Remove inline styles
|
|
284
|
-
.replace(/\s+data-[^=]*="[^"]*"/g, '') // Remove data attributes
|
|
285
|
-
.replace(/fill="none"/g, '') // Remove default fill
|
|
286
|
-
.replace(/stroke="none"/g, '') // Remove default stroke
|
|
287
|
-
.replace(/\s+version="[^"]*"/g, '') // Remove version
|
|
288
|
-
.replace(/\s+baseProfile="[^"]*"/g, ''); // Remove baseProfile
|
|
289
|
-
|
|
290
|
-
return optimized;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Semaphore for controlling concurrency
|
|
296
|
-
*/
|
|
297
|
-
class Semaphore {
|
|
298
|
-
private permits: number;
|
|
299
|
-
private waitQueue: Array<() => void> = [];
|
|
300
|
-
|
|
301
|
-
constructor(permits: number) {
|
|
302
|
-
this.permits = permits;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async acquire(): Promise<void> {
|
|
306
|
-
if (this.permits > 0) {
|
|
307
|
-
this.permits--;
|
|
308
|
-
return Promise.resolve();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return new Promise<void>((resolve) => {
|
|
312
|
-
this.waitQueue.push(resolve);
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
release(): void {
|
|
317
|
-
this.permits++;
|
|
318
|
-
if (this.waitQueue.length > 0) {
|
|
319
|
-
const resolve = this.waitQueue.shift()!;
|
|
320
|
-
this.permits--;
|
|
321
|
-
resolve();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Export singleton instance
|
|
327
|
-
export const performanceEngine = PerformanceEngine.getInstance();
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { Plugin, PluginConfig } from '../types/index.js';
|
|
2
|
-
import { logger } from '../core/logger.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Plugin management system for extending SVG processing capabilities
|
|
6
|
-
*/
|
|
7
|
-
export class PluginManager {
|
|
8
|
-
private static instance: PluginManager;
|
|
9
|
-
private plugins: Map<string, Plugin> = new Map();
|
|
10
|
-
private activePlugins: Set<string> = new Set();
|
|
11
|
-
|
|
12
|
-
private constructor() {
|
|
13
|
-
this.loadBuiltinPlugins();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public static getInstance(): PluginManager {
|
|
17
|
-
if (!PluginManager.instance) {
|
|
18
|
-
PluginManager.instance = new PluginManager();
|
|
19
|
-
}
|
|
20
|
-
return PluginManager.instance;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Load built-in plugins
|
|
25
|
-
*/
|
|
26
|
-
private loadBuiltinPlugins(): void {
|
|
27
|
-
// SVG Optimizer Plugin
|
|
28
|
-
this.registerPlugin({
|
|
29
|
-
name: 'svg-optimizer',
|
|
30
|
-
version: '1.0.0',
|
|
31
|
-
process: async (content: string, options?: any) => {
|
|
32
|
-
return this.optimizeSVG(content, options);
|
|
33
|
-
},
|
|
34
|
-
validate: (options?: any) => true
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Color Theme Plugin
|
|
38
|
-
this.registerPlugin({
|
|
39
|
-
name: 'color-theme',
|
|
40
|
-
version: '1.0.0',
|
|
41
|
-
process: async (content: string, options?: any) => {
|
|
42
|
-
return this.applyColorTheme(content, options);
|
|
43
|
-
},
|
|
44
|
-
validate: (options?: any) => {
|
|
45
|
-
return options && typeof options.theme === 'object';
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Size Normalizer Plugin
|
|
50
|
-
this.registerPlugin({
|
|
51
|
-
name: 'size-normalizer',
|
|
52
|
-
version: '1.0.0',
|
|
53
|
-
process: async (content: string, options?: any) => {
|
|
54
|
-
return this.normalizeSizes(content, options);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
logger.debug('Built-in plugins loaded');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Register a new plugin
|
|
63
|
-
*/
|
|
64
|
-
public registerPlugin(plugin: Plugin): void {
|
|
65
|
-
if (this.plugins.has(plugin.name)) {
|
|
66
|
-
logger.warn(`Plugin ${plugin.name} is already registered, overwriting`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
this.plugins.set(plugin.name, plugin);
|
|
70
|
-
logger.debug(`Plugin registered: ${plugin.name} v${plugin.version}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Enable a plugin
|
|
75
|
-
*/
|
|
76
|
-
public enablePlugin(name: string, config?: PluginConfig): void {
|
|
77
|
-
const plugin = this.plugins.get(name);
|
|
78
|
-
if (!plugin) {
|
|
79
|
-
throw new Error(`Plugin not found: ${name}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (plugin.validate && config?.options && !plugin.validate(config.options)) {
|
|
83
|
-
throw new Error(`Invalid options for plugin: ${name}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
this.activePlugins.add(name);
|
|
87
|
-
logger.info(`Plugin enabled: ${name}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Disable a plugin
|
|
92
|
-
*/
|
|
93
|
-
public disablePlugin(name: string): void {
|
|
94
|
-
this.activePlugins.delete(name);
|
|
95
|
-
logger.info(`Plugin disabled: ${name}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Process content through all active plugins
|
|
100
|
-
*/
|
|
101
|
-
public async processContent(
|
|
102
|
-
content: string,
|
|
103
|
-
pluginConfigs: PluginConfig[] = []
|
|
104
|
-
): Promise<string> {
|
|
105
|
-
let processedContent = content;
|
|
106
|
-
|
|
107
|
-
for (const config of pluginConfigs) {
|
|
108
|
-
const plugin = this.plugins.get(config.name);
|
|
109
|
-
|
|
110
|
-
if (!plugin) {
|
|
111
|
-
logger.warn(`Plugin not found: ${config.name}, skipping`);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!this.activePlugins.has(config.name)) {
|
|
116
|
-
logger.debug(`Plugin ${config.name} is disabled, skipping`);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
logger.debug(`Processing with plugin: ${config.name}`);
|
|
122
|
-
processedContent = await plugin.process(processedContent, config.options);
|
|
123
|
-
} catch (error) {
|
|
124
|
-
logger.error(`Plugin ${config.name} failed:`, error);
|
|
125
|
-
// Continue processing with other plugins
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return processedContent;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Get list of available plugins
|
|
134
|
-
*/
|
|
135
|
-
public getAvailablePlugins(): Array<{ name: string; version: string; enabled: boolean }> {
|
|
136
|
-
return Array.from(this.plugins.entries()).map(([name, plugin]) => ({
|
|
137
|
-
name,
|
|
138
|
-
version: plugin.version,
|
|
139
|
-
enabled: this.activePlugins.has(name)
|
|
140
|
-
}));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Built-in SVG optimizer
|
|
145
|
-
*/
|
|
146
|
-
private optimizeSVG(content: string, options: any = {}): string {
|
|
147
|
-
let optimized = content;
|
|
148
|
-
|
|
149
|
-
// Remove unnecessary whitespace
|
|
150
|
-
if (options.removeWhitespace !== false) {
|
|
151
|
-
optimized = optimized.replace(/>\s+</g, '><');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Remove empty groups
|
|
155
|
-
if (options.removeEmptyGroups !== false) {
|
|
156
|
-
optimized = optimized.replace(/<g[^>]*>\s*<\/g>/g, '');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Merge similar paths (basic implementation)
|
|
160
|
-
if (options.mergePaths === true) {
|
|
161
|
-
// This would require more sophisticated path parsing
|
|
162
|
-
logger.debug('Path merging not implemented yet');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Remove comments
|
|
166
|
-
if (options.removeComments !== false) {
|
|
167
|
-
optimized = optimized.replace(/<!--[\s\S]*?-->/g, '');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return optimized;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Built-in color theme applier
|
|
175
|
-
*/
|
|
176
|
-
private applyColorTheme(content: string, options: any = {}): string {
|
|
177
|
-
if (!options.theme) {
|
|
178
|
-
return content;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
let themed = content;
|
|
182
|
-
const theme = options.theme;
|
|
183
|
-
|
|
184
|
-
// Replace colors according to theme
|
|
185
|
-
for (const [originalColor, newColor] of Object.entries(theme)) {
|
|
186
|
-
const colorRegex = new RegExp(`(fill|stroke)=["']${originalColor}["']`, 'g');
|
|
187
|
-
themed = themed.replace(colorRegex, `$1="${newColor}"`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return themed;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Built-in size normalizer
|
|
195
|
-
*/
|
|
196
|
-
private normalizeSizes(content: string, options: any = {}): string {
|
|
197
|
-
const targetSize = options.size || 24;
|
|
198
|
-
|
|
199
|
-
// This is a basic implementation - would need more sophisticated sizing logic
|
|
200
|
-
let normalized = content;
|
|
201
|
-
|
|
202
|
-
// Normalize stroke-width relative to size
|
|
203
|
-
if (options.normalizeStrokes !== false) {
|
|
204
|
-
const strokeRegex = /stroke-width=["']([^"']+)["']/g;
|
|
205
|
-
normalized = normalized.replace(strokeRegex, (match, width) => {
|
|
206
|
-
const numericWidth = parseFloat(width);
|
|
207
|
-
if (!isNaN(numericWidth)) {
|
|
208
|
-
const normalizedWidth = (numericWidth / 24) * targetSize;
|
|
209
|
-
return `stroke-width="${normalizedWidth}"`;
|
|
210
|
-
}
|
|
211
|
-
return match;
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return normalized;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Clear all plugins (useful for testing)
|
|
220
|
-
*/
|
|
221
|
-
public clear(): void {
|
|
222
|
-
this.plugins.clear();
|
|
223
|
-
this.activePlugins.clear();
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Export singleton instance
|
|
228
|
-
export const pluginManager = PluginManager.getInstance();
|