svger-cli 2.0.0 → 2.0.2
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/dist/cli.js +0 -0
- package/dist/core/error-handler.d.ts +63 -0
- package/dist/core/error-handler.js +224 -0
- package/dist/core/framework-templates.d.ts +17 -0
- package/{src/core/framework-templates.ts → dist/core/framework-templates.js} +100 -137
- 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 +251 -0
- package/dist/core/plugin-manager.d.ts +56 -0
- package/dist/core/plugin-manager.js +189 -0
- package/dist/core/style-compiler.d.ts +88 -0
- package/dist/core/style-compiler.js +466 -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 +151 -0
- package/{src/index.ts → dist/index.js} +30 -108
- package/dist/processors/svg-processor.d.ts +67 -0
- package/dist/processors/svg-processor.js +225 -0
- package/dist/services/config.d.ts +55 -0
- package/dist/services/config.js +209 -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 +383 -0
- package/dist/types/index.d.ts +140 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/native.d.ts +74 -0
- package/dist/utils/native.js +305 -0
- package/package.json +9 -3
- 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/docs/ADR-SVG-INTRGRATION-METHODS-001.adr.md +0 -157
- package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +0 -550
- package/docs/FRAMEWORK-GUIDE.md +0 -768
- package/docs/IMPLEMENTATION-SUMMARY.md +0 -376
- package/docs/TDR-SVG-INTRGRATION-METHODS-001.tdr.md +0 -115
- 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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Professional logging service with configurable levels and formatted output
|
|
3
|
+
*/
|
|
4
|
+
export class LoggerService {
|
|
5
|
+
static instance;
|
|
6
|
+
logLevel = 'info';
|
|
7
|
+
enableColors = true;
|
|
8
|
+
constructor() { }
|
|
9
|
+
static getInstance() {
|
|
10
|
+
if (!LoggerService.instance) {
|
|
11
|
+
LoggerService.instance = new LoggerService();
|
|
12
|
+
}
|
|
13
|
+
return LoggerService.instance;
|
|
14
|
+
}
|
|
15
|
+
setLogLevel(level) {
|
|
16
|
+
this.logLevel = level;
|
|
17
|
+
}
|
|
18
|
+
setColors(enabled) {
|
|
19
|
+
this.enableColors = enabled;
|
|
20
|
+
}
|
|
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;
|
|
26
|
+
}
|
|
27
|
+
formatMessage(level, message) {
|
|
28
|
+
const timestamp = new Date().toISOString();
|
|
29
|
+
const prefix = this.getPrefix(level);
|
|
30
|
+
return `${timestamp} ${prefix} ${message}`;
|
|
31
|
+
}
|
|
32
|
+
getPrefix(level) {
|
|
33
|
+
if (!this.enableColors) {
|
|
34
|
+
return `[${level.toUpperCase()}]`;
|
|
35
|
+
}
|
|
36
|
+
const colors = {
|
|
37
|
+
debug: '\x1b[36m', // Cyan
|
|
38
|
+
info: '\x1b[34m', // Blue
|
|
39
|
+
warn: '\x1b[33m', // Yellow
|
|
40
|
+
error: '\x1b[31m', // Red
|
|
41
|
+
success: '\x1b[32m', // Green
|
|
42
|
+
};
|
|
43
|
+
const reset = '\x1b[0m';
|
|
44
|
+
const color = colors[level] || colors.info;
|
|
45
|
+
const icons = {
|
|
46
|
+
debug: '🔍',
|
|
47
|
+
info: 'ℹ️',
|
|
48
|
+
warn: '⚠️',
|
|
49
|
+
error: '❌',
|
|
50
|
+
success: '✅',
|
|
51
|
+
};
|
|
52
|
+
const icon = icons[level] || icons.info;
|
|
53
|
+
return `${color}${icon} [${level.toUpperCase()}]${reset}`;
|
|
54
|
+
}
|
|
55
|
+
debug(message, ...args) {
|
|
56
|
+
if (this.shouldLog('debug')) {
|
|
57
|
+
console.debug(this.formatMessage('debug', message), ...args);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
info(message, ...args) {
|
|
61
|
+
if (this.shouldLog('info')) {
|
|
62
|
+
console.info(this.formatMessage('info', message), ...args);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
warn(message, ...args) {
|
|
66
|
+
if (this.shouldLog('warn')) {
|
|
67
|
+
console.warn(this.formatMessage('warn', message), ...args);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
error(message, ...args) {
|
|
71
|
+
if (this.shouldLog('error')) {
|
|
72
|
+
console.error(this.formatMessage('error', message), ...args);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
success(message, ...args) {
|
|
76
|
+
if (this.shouldLog('info')) {
|
|
77
|
+
const timestamp = new Date().toISOString();
|
|
78
|
+
const prefix = this.getPrefix('info');
|
|
79
|
+
const successPrefix = this.enableColors ? '✅ [SUCCESS]' : '[SUCCESS]';
|
|
80
|
+
console.log(`${timestamp} ${successPrefix} ${message}`, ...args);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Export singleton instance
|
|
85
|
+
export const logger = LoggerService.getInstance();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ComponentGenerationOptions } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Performance optimization engine for batch processing and parallel execution
|
|
4
|
+
*/
|
|
5
|
+
export declare class PerformanceEngine {
|
|
6
|
+
private static instance;
|
|
7
|
+
private processingCache;
|
|
8
|
+
private readonly cacheTimeout;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): PerformanceEngine;
|
|
11
|
+
/**
|
|
12
|
+
* Process multiple SVG files in parallel with optimized batching
|
|
13
|
+
*/
|
|
14
|
+
processBatch(files: Array<{
|
|
15
|
+
path: string;
|
|
16
|
+
outputDir: string;
|
|
17
|
+
options?: Partial<ComponentGenerationOptions>;
|
|
18
|
+
}>, config?: {
|
|
19
|
+
batchSize?: number;
|
|
20
|
+
parallel?: boolean;
|
|
21
|
+
maxConcurrency?: number;
|
|
22
|
+
}): Promise<Array<{
|
|
23
|
+
success: boolean;
|
|
24
|
+
filePath: string;
|
|
25
|
+
error?: Error;
|
|
26
|
+
duration: number;
|
|
27
|
+
}>>;
|
|
28
|
+
/**
|
|
29
|
+
* Process single file with caching support
|
|
30
|
+
*/
|
|
31
|
+
private processSingleWithCaching;
|
|
32
|
+
/**
|
|
33
|
+
* Optimize SVG content with performance considerations
|
|
34
|
+
*/
|
|
35
|
+
optimizeSVGContent(content: string, level?: 'fast' | 'balanced' | 'maximum'): string;
|
|
36
|
+
/**
|
|
37
|
+
* Memory usage monitoring and optimization
|
|
38
|
+
*/
|
|
39
|
+
monitorMemoryUsage(): {
|
|
40
|
+
heapUsed: number;
|
|
41
|
+
heapTotal: number;
|
|
42
|
+
external: number;
|
|
43
|
+
cacheSize: number;
|
|
44
|
+
recommendations: string[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Clear processing cache
|
|
48
|
+
*/
|
|
49
|
+
clearCache(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get performance metrics
|
|
52
|
+
*/
|
|
53
|
+
getPerformanceMetrics(): {
|
|
54
|
+
cacheHitRate: number;
|
|
55
|
+
averageProcessingTime: number;
|
|
56
|
+
memoryUsage: any;
|
|
57
|
+
};
|
|
58
|
+
private createBatches;
|
|
59
|
+
private withSemaphore;
|
|
60
|
+
private generateCacheKey;
|
|
61
|
+
private getCachedResult;
|
|
62
|
+
private setCachedResult;
|
|
63
|
+
private applyFastOptimizations;
|
|
64
|
+
private applyBalancedOptimizations;
|
|
65
|
+
private applyMaximumOptimizations;
|
|
66
|
+
}
|
|
67
|
+
export declare const performanceEngine: PerformanceEngine;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
import { svgProcessor } from '../processors/svg-processor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Performance optimization engine for batch processing and parallel execution
|
|
5
|
+
*/
|
|
6
|
+
export class PerformanceEngine {
|
|
7
|
+
static instance;
|
|
8
|
+
processingCache = new Map();
|
|
9
|
+
cacheTimeout = 5 * 60 * 1000; // 5 minutes
|
|
10
|
+
constructor() { }
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!PerformanceEngine.instance) {
|
|
13
|
+
PerformanceEngine.instance = new PerformanceEngine();
|
|
14
|
+
}
|
|
15
|
+
return PerformanceEngine.instance;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Process multiple SVG files in parallel with optimized batching
|
|
19
|
+
*/
|
|
20
|
+
async processBatch(files, config = {}) {
|
|
21
|
+
const { batchSize = 10, parallel = true, maxConcurrency = Math.min(4, require('os').cpus().length) } = config;
|
|
22
|
+
logger.info(`Processing ${files.length} files with ${parallel ? 'parallel' : 'sequential'} execution`);
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
const results = [];
|
|
25
|
+
if (!parallel) {
|
|
26
|
+
// Sequential processing for stability
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const result = await this.processSingleWithCaching(file);
|
|
29
|
+
results.push(result);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Parallel processing with controlled concurrency
|
|
34
|
+
const batches = this.createBatches(files, batchSize);
|
|
35
|
+
for (const batch of batches) {
|
|
36
|
+
const semaphore = new Semaphore(maxConcurrency);
|
|
37
|
+
const batchPromises = batch.map(file => this.withSemaphore(semaphore, () => this.processSingleWithCaching(file)));
|
|
38
|
+
const batchResults = await Promise.all(batchPromises);
|
|
39
|
+
results.push(...batchResults);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const totalDuration = Date.now() - startTime;
|
|
43
|
+
const successful = results.filter(r => r.success).length;
|
|
44
|
+
logger.info(`Batch processing complete: ${successful}/${files.length} successful in ${totalDuration}ms`);
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Process single file with caching support
|
|
49
|
+
*/
|
|
50
|
+
async processSingleWithCaching(file) {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
const cacheKey = this.generateCacheKey(file.path, file.options || {});
|
|
53
|
+
// Check cache first
|
|
54
|
+
const cached = this.getCachedResult(cacheKey);
|
|
55
|
+
if (cached) {
|
|
56
|
+
logger.debug(`Cache hit for ${file.path}`);
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
filePath: file.path,
|
|
60
|
+
duration: Date.now() - startTime
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const result = await svgProcessor.processSVGFile(file.path, file.outputDir, file.options);
|
|
65
|
+
// Cache successful results
|
|
66
|
+
if (result.success) {
|
|
67
|
+
this.setCachedResult(cacheKey, result);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
success: result.success,
|
|
71
|
+
filePath: file.path,
|
|
72
|
+
error: result.error,
|
|
73
|
+
duration: Date.now() - startTime
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
filePath: file.path,
|
|
80
|
+
error: error,
|
|
81
|
+
duration: Date.now() - startTime
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Optimize SVG content with performance considerations
|
|
87
|
+
*/
|
|
88
|
+
optimizeSVGContent(content, level = 'balanced') {
|
|
89
|
+
const startTime = performance.now();
|
|
90
|
+
let optimized = content;
|
|
91
|
+
switch (level) {
|
|
92
|
+
case 'fast':
|
|
93
|
+
// Basic optimizations only
|
|
94
|
+
optimized = this.applyFastOptimizations(content);
|
|
95
|
+
break;
|
|
96
|
+
case 'balanced':
|
|
97
|
+
// Standard optimizations with good performance/quality balance
|
|
98
|
+
optimized = this.applyBalancedOptimizations(content);
|
|
99
|
+
break;
|
|
100
|
+
case 'maximum':
|
|
101
|
+
// Comprehensive optimizations
|
|
102
|
+
optimized = this.applyMaximumOptimizations(content);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
const duration = performance.now() - startTime;
|
|
106
|
+
const compressionRatio = (1 - optimized.length / content.length) * 100;
|
|
107
|
+
logger.debug(`SVG optimization (${level}): ${compressionRatio.toFixed(1)}% reduction in ${duration.toFixed(2)}ms`);
|
|
108
|
+
return optimized;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Memory usage monitoring and optimization
|
|
112
|
+
*/
|
|
113
|
+
monitorMemoryUsage() {
|
|
114
|
+
const memUsage = process.memoryUsage();
|
|
115
|
+
const cacheSize = this.processingCache.size;
|
|
116
|
+
const recommendations = [];
|
|
117
|
+
// Memory usage analysis
|
|
118
|
+
const heapUsedMB = memUsage.heapUsed / 1024 / 1024;
|
|
119
|
+
const heapTotalMB = memUsage.heapTotal / 1024 / 1024;
|
|
120
|
+
if (heapUsedMB > 500) {
|
|
121
|
+
recommendations.push('Consider reducing batch size or enabling sequential processing');
|
|
122
|
+
}
|
|
123
|
+
if (cacheSize > 1000) {
|
|
124
|
+
recommendations.push('Cache size is large, consider clearing old entries');
|
|
125
|
+
}
|
|
126
|
+
if (memUsage.external > 100 * 1024 * 1024) {
|
|
127
|
+
recommendations.push('High external memory usage detected');
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
heapUsed: heapUsedMB,
|
|
131
|
+
heapTotal: heapTotalMB,
|
|
132
|
+
external: memUsage.external / 1024 / 1024,
|
|
133
|
+
cacheSize,
|
|
134
|
+
recommendations
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Clear processing cache
|
|
139
|
+
*/
|
|
140
|
+
clearCache() {
|
|
141
|
+
this.processingCache.clear();
|
|
142
|
+
logger.debug('Processing cache cleared');
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get performance metrics
|
|
146
|
+
*/
|
|
147
|
+
getPerformanceMetrics() {
|
|
148
|
+
// This would be implemented with proper metrics collection
|
|
149
|
+
return {
|
|
150
|
+
cacheHitRate: 0, // Placeholder
|
|
151
|
+
averageProcessingTime: 0, // Placeholder
|
|
152
|
+
memoryUsage: this.monitorMemoryUsage()
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Private helper methods
|
|
156
|
+
createBatches(items, batchSize) {
|
|
157
|
+
const batches = [];
|
|
158
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
159
|
+
batches.push(items.slice(i, i + batchSize));
|
|
160
|
+
}
|
|
161
|
+
return batches;
|
|
162
|
+
}
|
|
163
|
+
async withSemaphore(semaphore, task) {
|
|
164
|
+
await semaphore.acquire();
|
|
165
|
+
try {
|
|
166
|
+
return await task();
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
semaphore.release();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
generateCacheKey(filePath, options) {
|
|
173
|
+
// Create a hash of file path and options for caching
|
|
174
|
+
const key = JSON.stringify({ filePath, options });
|
|
175
|
+
return Buffer.from(key).toString('base64');
|
|
176
|
+
}
|
|
177
|
+
getCachedResult(key) {
|
|
178
|
+
const cached = this.processingCache.get(key);
|
|
179
|
+
if (!cached)
|
|
180
|
+
return null;
|
|
181
|
+
// Check if cache entry is still valid
|
|
182
|
+
if (Date.now() - cached.timestamp > this.cacheTimeout) {
|
|
183
|
+
this.processingCache.delete(key);
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return cached.result;
|
|
187
|
+
}
|
|
188
|
+
setCachedResult(key, result) {
|
|
189
|
+
this.processingCache.set(key, {
|
|
190
|
+
result,
|
|
191
|
+
timestamp: Date.now()
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
applyFastOptimizations(content) {
|
|
195
|
+
return content
|
|
196
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
197
|
+
.replace(/>\s+</g, '><') // Remove spaces between tags
|
|
198
|
+
.trim();
|
|
199
|
+
}
|
|
200
|
+
applyBalancedOptimizations(content) {
|
|
201
|
+
let optimized = this.applyFastOptimizations(content);
|
|
202
|
+
// Remove unnecessary attributes
|
|
203
|
+
optimized = optimized
|
|
204
|
+
.replace(/\s+id="[^"]*"/g, '') // Remove IDs (usually not needed in components)
|
|
205
|
+
.replace(/\s+class="[^"]*"/g, '') // Remove classes
|
|
206
|
+
.replace(/<!--[\s\S]*?-->/g, '') // Remove comments
|
|
207
|
+
.replace(/\s+xmlns="[^"]*"/g, ''); // Remove xmlns (React adds it)
|
|
208
|
+
return optimized;
|
|
209
|
+
}
|
|
210
|
+
applyMaximumOptimizations(content) {
|
|
211
|
+
let optimized = this.applyBalancedOptimizations(content);
|
|
212
|
+
// Advanced optimizations
|
|
213
|
+
optimized = optimized
|
|
214
|
+
.replace(/\s+style="[^"]*"/g, '') // Remove inline styles
|
|
215
|
+
.replace(/\s+data-[^=]*="[^"]*"/g, '') // Remove data attributes
|
|
216
|
+
.replace(/fill="none"/g, '') // Remove default fill
|
|
217
|
+
.replace(/stroke="none"/g, '') // Remove default stroke
|
|
218
|
+
.replace(/\s+version="[^"]*"/g, '') // Remove version
|
|
219
|
+
.replace(/\s+baseProfile="[^"]*"/g, ''); // Remove baseProfile
|
|
220
|
+
return optimized;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Semaphore for controlling concurrency
|
|
225
|
+
*/
|
|
226
|
+
class Semaphore {
|
|
227
|
+
permits;
|
|
228
|
+
waitQueue = [];
|
|
229
|
+
constructor(permits) {
|
|
230
|
+
this.permits = permits;
|
|
231
|
+
}
|
|
232
|
+
async acquire() {
|
|
233
|
+
if (this.permits > 0) {
|
|
234
|
+
this.permits--;
|
|
235
|
+
return Promise.resolve();
|
|
236
|
+
}
|
|
237
|
+
return new Promise((resolve) => {
|
|
238
|
+
this.waitQueue.push(resolve);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
release() {
|
|
242
|
+
this.permits++;
|
|
243
|
+
if (this.waitQueue.length > 0) {
|
|
244
|
+
const resolve = this.waitQueue.shift();
|
|
245
|
+
this.permits--;
|
|
246
|
+
resolve();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Export singleton instance
|
|
251
|
+
export const performanceEngine = PerformanceEngine.getInstance();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Plugin, PluginConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin management system for extending SVG processing capabilities
|
|
4
|
+
*/
|
|
5
|
+
export declare class PluginManager {
|
|
6
|
+
private static instance;
|
|
7
|
+
private plugins;
|
|
8
|
+
private activePlugins;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): PluginManager;
|
|
11
|
+
/**
|
|
12
|
+
* Load built-in plugins
|
|
13
|
+
*/
|
|
14
|
+
private loadBuiltinPlugins;
|
|
15
|
+
/**
|
|
16
|
+
* Register a new plugin
|
|
17
|
+
*/
|
|
18
|
+
registerPlugin(plugin: Plugin): void;
|
|
19
|
+
/**
|
|
20
|
+
* Enable a plugin
|
|
21
|
+
*/
|
|
22
|
+
enablePlugin(name: string, config?: PluginConfig): void;
|
|
23
|
+
/**
|
|
24
|
+
* Disable a plugin
|
|
25
|
+
*/
|
|
26
|
+
disablePlugin(name: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Process content through all active plugins
|
|
29
|
+
*/
|
|
30
|
+
processContent(content: string, pluginConfigs?: PluginConfig[]): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Get list of available plugins
|
|
33
|
+
*/
|
|
34
|
+
getAvailablePlugins(): Array<{
|
|
35
|
+
name: string;
|
|
36
|
+
version: string;
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Built-in SVG optimizer
|
|
41
|
+
*/
|
|
42
|
+
private optimizeSVG;
|
|
43
|
+
/**
|
|
44
|
+
* Built-in color theme applier
|
|
45
|
+
*/
|
|
46
|
+
private applyColorTheme;
|
|
47
|
+
/**
|
|
48
|
+
* Built-in size normalizer
|
|
49
|
+
*/
|
|
50
|
+
private normalizeSizes;
|
|
51
|
+
/**
|
|
52
|
+
* Clear all plugins (useful for testing)
|
|
53
|
+
*/
|
|
54
|
+
clear(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare const pluginManager: PluginManager;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { logger } from '../core/logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin management system for extending SVG processing capabilities
|
|
4
|
+
*/
|
|
5
|
+
export class PluginManager {
|
|
6
|
+
static instance;
|
|
7
|
+
plugins = new Map();
|
|
8
|
+
activePlugins = new Set();
|
|
9
|
+
constructor() {
|
|
10
|
+
this.loadBuiltinPlugins();
|
|
11
|
+
}
|
|
12
|
+
static getInstance() {
|
|
13
|
+
if (!PluginManager.instance) {
|
|
14
|
+
PluginManager.instance = new PluginManager();
|
|
15
|
+
}
|
|
16
|
+
return PluginManager.instance;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Load built-in plugins
|
|
20
|
+
*/
|
|
21
|
+
loadBuiltinPlugins() {
|
|
22
|
+
// SVG Optimizer Plugin
|
|
23
|
+
this.registerPlugin({
|
|
24
|
+
name: 'svg-optimizer',
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
process: async (content, options) => {
|
|
27
|
+
return this.optimizeSVG(content, options);
|
|
28
|
+
},
|
|
29
|
+
validate: (options) => true
|
|
30
|
+
});
|
|
31
|
+
// Color Theme Plugin
|
|
32
|
+
this.registerPlugin({
|
|
33
|
+
name: 'color-theme',
|
|
34
|
+
version: '1.0.0',
|
|
35
|
+
process: async (content, options) => {
|
|
36
|
+
return this.applyColorTheme(content, options);
|
|
37
|
+
},
|
|
38
|
+
validate: (options) => {
|
|
39
|
+
return options && typeof options.theme === 'object';
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Size Normalizer Plugin
|
|
43
|
+
this.registerPlugin({
|
|
44
|
+
name: 'size-normalizer',
|
|
45
|
+
version: '1.0.0',
|
|
46
|
+
process: async (content, options) => {
|
|
47
|
+
return this.normalizeSizes(content, options);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
logger.debug('Built-in plugins loaded');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a new plugin
|
|
54
|
+
*/
|
|
55
|
+
registerPlugin(plugin) {
|
|
56
|
+
if (this.plugins.has(plugin.name)) {
|
|
57
|
+
logger.warn(`Plugin ${plugin.name} is already registered, overwriting`);
|
|
58
|
+
}
|
|
59
|
+
this.plugins.set(plugin.name, plugin);
|
|
60
|
+
logger.debug(`Plugin registered: ${plugin.name} v${plugin.version}`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Enable a plugin
|
|
64
|
+
*/
|
|
65
|
+
enablePlugin(name, config) {
|
|
66
|
+
const plugin = this.plugins.get(name);
|
|
67
|
+
if (!plugin) {
|
|
68
|
+
throw new Error(`Plugin not found: ${name}`);
|
|
69
|
+
}
|
|
70
|
+
if (plugin.validate && config?.options && !plugin.validate(config.options)) {
|
|
71
|
+
throw new Error(`Invalid options for plugin: ${name}`);
|
|
72
|
+
}
|
|
73
|
+
this.activePlugins.add(name);
|
|
74
|
+
logger.info(`Plugin enabled: ${name}`);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Disable a plugin
|
|
78
|
+
*/
|
|
79
|
+
disablePlugin(name) {
|
|
80
|
+
this.activePlugins.delete(name);
|
|
81
|
+
logger.info(`Plugin disabled: ${name}`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Process content through all active plugins
|
|
85
|
+
*/
|
|
86
|
+
async processContent(content, pluginConfigs = []) {
|
|
87
|
+
let processedContent = content;
|
|
88
|
+
for (const config of pluginConfigs) {
|
|
89
|
+
const plugin = this.plugins.get(config.name);
|
|
90
|
+
if (!plugin) {
|
|
91
|
+
logger.warn(`Plugin not found: ${config.name}, skipping`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!this.activePlugins.has(config.name)) {
|
|
95
|
+
logger.debug(`Plugin ${config.name} is disabled, skipping`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
logger.debug(`Processing with plugin: ${config.name}`);
|
|
100
|
+
processedContent = await plugin.process(processedContent, config.options);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger.error(`Plugin ${config.name} failed:`, error);
|
|
104
|
+
// Continue processing with other plugins
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return processedContent;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get list of available plugins
|
|
111
|
+
*/
|
|
112
|
+
getAvailablePlugins() {
|
|
113
|
+
return Array.from(this.plugins.entries()).map(([name, plugin]) => ({
|
|
114
|
+
name,
|
|
115
|
+
version: plugin.version,
|
|
116
|
+
enabled: this.activePlugins.has(name)
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Built-in SVG optimizer
|
|
121
|
+
*/
|
|
122
|
+
optimizeSVG(content, options = {}) {
|
|
123
|
+
let optimized = content;
|
|
124
|
+
// Remove unnecessary whitespace
|
|
125
|
+
if (options.removeWhitespace !== false) {
|
|
126
|
+
optimized = optimized.replace(/>\s+</g, '><');
|
|
127
|
+
}
|
|
128
|
+
// Remove empty groups
|
|
129
|
+
if (options.removeEmptyGroups !== false) {
|
|
130
|
+
optimized = optimized.replace(/<g[^>]*>\s*<\/g>/g, '');
|
|
131
|
+
}
|
|
132
|
+
// Merge similar paths (basic implementation)
|
|
133
|
+
if (options.mergePaths === true) {
|
|
134
|
+
// This would require more sophisticated path parsing
|
|
135
|
+
logger.debug('Path merging not implemented yet');
|
|
136
|
+
}
|
|
137
|
+
// Remove comments
|
|
138
|
+
if (options.removeComments !== false) {
|
|
139
|
+
optimized = optimized.replace(/<!--[\s\S]*?-->/g, '');
|
|
140
|
+
}
|
|
141
|
+
return optimized;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Built-in color theme applier
|
|
145
|
+
*/
|
|
146
|
+
applyColorTheme(content, options = {}) {
|
|
147
|
+
if (!options.theme) {
|
|
148
|
+
return content;
|
|
149
|
+
}
|
|
150
|
+
let themed = content;
|
|
151
|
+
const theme = options.theme;
|
|
152
|
+
// Replace colors according to theme
|
|
153
|
+
for (const [originalColor, newColor] of Object.entries(theme)) {
|
|
154
|
+
const colorRegex = new RegExp(`(fill|stroke)=["']${originalColor}["']`, 'g');
|
|
155
|
+
themed = themed.replace(colorRegex, `$1="${newColor}"`);
|
|
156
|
+
}
|
|
157
|
+
return themed;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Built-in size normalizer
|
|
161
|
+
*/
|
|
162
|
+
normalizeSizes(content, options = {}) {
|
|
163
|
+
const targetSize = options.size || 24;
|
|
164
|
+
// This is a basic implementation - would need more sophisticated sizing logic
|
|
165
|
+
let normalized = content;
|
|
166
|
+
// Normalize stroke-width relative to size
|
|
167
|
+
if (options.normalizeStrokes !== false) {
|
|
168
|
+
const strokeRegex = /stroke-width=["']([^"']+)["']/g;
|
|
169
|
+
normalized = normalized.replace(strokeRegex, (match, width) => {
|
|
170
|
+
const numericWidth = parseFloat(width);
|
|
171
|
+
if (!isNaN(numericWidth)) {
|
|
172
|
+
const normalizedWidth = (numericWidth / 24) * targetSize;
|
|
173
|
+
return `stroke-width="${normalizedWidth}"`;
|
|
174
|
+
}
|
|
175
|
+
return match;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return normalized;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Clear all plugins (useful for testing)
|
|
182
|
+
*/
|
|
183
|
+
clear() {
|
|
184
|
+
this.plugins.clear();
|
|
185
|
+
this.activePlugins.clear();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Export singleton instance
|
|
189
|
+
export const pluginManager = PluginManager.getInstance();
|