svger-cli 1.0.6 → 1.0.7

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.
Files changed (64) hide show
  1. package/CODE_OF_CONDUCT.md +79 -0
  2. package/CONTRIBUTING.md +146 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1862 -73
  5. package/TESTING.md +143 -0
  6. package/cli-framework.test.js +16 -0
  7. package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
  8. package/cli-test-angular/Vite.component.ts +27 -0
  9. package/cli-test-angular/index.ts +25 -0
  10. package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
  11. package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
  12. package/cli-test-output/index.ts +25 -0
  13. package/cli-test-react/Arrowbenddownleft.tsx +39 -0
  14. package/cli-test-react/Vite.tsx +39 -0
  15. package/cli-test-react/index.ts +25 -0
  16. package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
  17. package/cli-test-svelte/Vite.svelte +22 -0
  18. package/cli-test-svelte/index.ts +25 -0
  19. package/dist/builder.js +12 -13
  20. package/dist/clean.js +3 -3
  21. package/dist/cli.js +139 -61
  22. package/dist/config.d.ts +1 -1
  23. package/dist/config.js +5 -7
  24. package/dist/lock.js +1 -1
  25. package/dist/templates/ComponentTemplate.d.ts +15 -0
  26. package/dist/templates/ComponentTemplate.js +15 -0
  27. package/dist/watch.d.ts +2 -1
  28. package/dist/watch.js +30 -33
  29. package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
  30. package/docs/FRAMEWORK-GUIDE.md +768 -0
  31. package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
  32. package/frameworks.test.js +170 -0
  33. package/package.json +7 -10
  34. package/src/builder.ts +12 -13
  35. package/src/clean.ts +3 -3
  36. package/src/cli.ts +148 -59
  37. package/src/config.ts +5 -6
  38. package/src/core/error-handler.ts +303 -0
  39. package/src/core/framework-templates.ts +428 -0
  40. package/src/core/logger.ts +104 -0
  41. package/src/core/performance-engine.ts +327 -0
  42. package/src/core/plugin-manager.ts +228 -0
  43. package/src/core/style-compiler.ts +605 -0
  44. package/src/core/template-manager.ts +619 -0
  45. package/src/index.ts +235 -0
  46. package/src/lock.ts +1 -1
  47. package/src/processors/svg-processor.ts +288 -0
  48. package/src/services/config.ts +241 -0
  49. package/src/services/file-watcher.ts +218 -0
  50. package/src/services/svg-service.ts +468 -0
  51. package/src/types/index.ts +169 -0
  52. package/src/utils/native.ts +352 -0
  53. package/src/watch.ts +36 -36
  54. package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
  55. package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
  56. package/test-output-mulit/TestIcon-lit.ts +35 -0
  57. package/test-output-mulit/TestIcon-preact.tsx +38 -0
  58. package/test-output-mulit/TestIcon-react.tsx +35 -0
  59. package/test-output-mulit/TestIcon-solid.tsx +27 -0
  60. package/test-output-mulit/TestIcon-svelte.svelte +22 -0
  61. package/test-output-mulit/TestIcon-vanilla.ts +37 -0
  62. package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
  63. package/test-output-mulit/TestIcon-vue-options.vue +31 -0
  64. package/tsconfig.json +11 -9
@@ -0,0 +1,327 @@
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();
@@ -0,0 +1,228 @@
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();