svger-cli 1.0.6 → 1.0.8

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 +8 -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,241 @@
1
+ import path from 'path';
2
+ import { FileSystem } from '../utils/native.js';
3
+ import { SVGConfig } from '../types/index.js';
4
+ import { logger } from '../core/logger.js';
5
+
6
+ /**
7
+ * Professional configuration management service
8
+ */
9
+ export class ConfigService {
10
+ private static instance: ConfigService;
11
+ private static readonly CONFIG_FILE = '.svgconfig.json';
12
+ private cachedConfig: SVGConfig | null = null;
13
+
14
+ private constructor() {}
15
+
16
+ public static getInstance(): ConfigService {
17
+ if (!ConfigService.instance) {
18
+ ConfigService.instance = new ConfigService();
19
+ }
20
+ return ConfigService.instance;
21
+ }
22
+
23
+ /**
24
+ * Get the default configuration
25
+ */
26
+ public getDefaultConfig(): SVGConfig {
27
+ return {
28
+ source: './src/assets/svg',
29
+ output: './src/components/icons',
30
+ framework: 'react',
31
+ typescript: true,
32
+ watch: false,
33
+ defaultWidth: 24,
34
+ defaultHeight: 24,
35
+ defaultFill: 'currentColor',
36
+ exclude: [],
37
+ styleRules: {
38
+ fill: 'inherit',
39
+ stroke: 'none',
40
+ },
41
+ plugins: [],
42
+ template: {
43
+ type: 'default'
44
+ },
45
+ frameworkOptions: {
46
+ forwardRef: true,
47
+ memo: false,
48
+ scriptSetup: true,
49
+ standalone: true
50
+ },
51
+ errorHandling: {
52
+ skipOnError: false,
53
+ logLevel: 'info',
54
+ maxRetries: 3
55
+ },
56
+ performance: {
57
+ batchSize: 10,
58
+ parallel: true,
59
+ timeout: 30000,
60
+ enableCache: true
61
+ }
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Get configuration file path
67
+ */
68
+ private getConfigPath(): string {
69
+ return path.resolve(ConfigService.CONFIG_FILE);
70
+ }
71
+
72
+ /**
73
+ * Read configuration from file with caching
74
+ */
75
+ public readConfig(): SVGConfig {
76
+ if (this.cachedConfig) {
77
+ return { ...this.cachedConfig };
78
+ }
79
+
80
+ try {
81
+ const configData = FileSystem.readJSONSync(this.getConfigPath());
82
+
83
+ if (Object.keys(configData).length === 0) {
84
+ logger.debug('No configuration found, using defaults');
85
+ this.cachedConfig = this.getDefaultConfig();
86
+ return this.cachedConfig;
87
+ }
88
+
89
+ // Merge with defaults to ensure all required properties exist
90
+ this.cachedConfig = {
91
+ ...this.getDefaultConfig(),
92
+ ...configData
93
+ };
94
+
95
+ logger.debug('Configuration loaded successfully');
96
+ return this.cachedConfig!;
97
+ } catch (error) {
98
+ logger.warn('Failed to read configuration, using defaults:', error);
99
+ this.cachedConfig = this.getDefaultConfig();
100
+ return this.cachedConfig;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Write configuration to file
106
+ */
107
+ public writeConfig(config: SVGConfig): void {
108
+ try {
109
+ FileSystem.writeJSONSync(this.getConfigPath(), config, { spaces: 2 });
110
+ this.cachedConfig = config; // Update cache
111
+ logger.success('Configuration saved successfully');
112
+ } catch (error) {
113
+ logger.error('Failed to write configuration:', error);
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Initialize configuration with defaults
120
+ */
121
+ public async initConfig(): Promise<void> {
122
+ const configPath = this.getConfigPath();
123
+
124
+ if (await FileSystem.exists(configPath)) {
125
+ logger.warn('Config file already exists:', configPath);
126
+ return;
127
+ }
128
+
129
+ const defaultConfig = this.getDefaultConfig();
130
+ this.writeConfig(defaultConfig);
131
+ logger.success('Configuration initialized with defaults');
132
+ }
133
+
134
+ /**
135
+ * Set a specific configuration value
136
+ */
137
+ public setConfig(key: string, value: any): void {
138
+ const config = this.readConfig();
139
+
140
+ // Support nested key paths like 'styleRules.fill'
141
+ const keys = key.split('.');
142
+ let current: any = config;
143
+
144
+ for (let i = 0; i < keys.length - 1; i++) {
145
+ const k = keys[i];
146
+ if (!(k in current)) {
147
+ current[k] = {};
148
+ }
149
+ current = current[k];
150
+ }
151
+
152
+ const finalKey = keys[keys.length - 1];
153
+ current[finalKey] = value;
154
+
155
+ this.writeConfig(config);
156
+ logger.success(`Configuration updated: ${key} = ${value}`);
157
+ }
158
+
159
+ /**
160
+ * Get a specific configuration value
161
+ */
162
+ public getConfig(key?: string): any {
163
+ const config = this.readConfig();
164
+
165
+ if (!key) {
166
+ return config;
167
+ }
168
+
169
+ // Support nested key paths
170
+ const keys = key.split('.');
171
+ let current: any = config;
172
+
173
+ for (const k of keys) {
174
+ if (!(k in current)) {
175
+ return undefined;
176
+ }
177
+ current = current[k];
178
+ }
179
+
180
+ return current;
181
+ }
182
+
183
+ /**
184
+ * Validate configuration
185
+ */
186
+ public validateConfig(config?: SVGConfig): { valid: boolean; errors: string[] } {
187
+ const configToValidate = config || this.readConfig();
188
+ const errors: string[] = [];
189
+
190
+ // Required string fields
191
+ const requiredStringFields = ['source', 'output', 'defaultFill'];
192
+ for (const field of requiredStringFields) {
193
+ if (!configToValidate[field as keyof SVGConfig] || typeof configToValidate[field as keyof SVGConfig] !== 'string') {
194
+ errors.push(`${field} must be a non-empty string`);
195
+ }
196
+ }
197
+
198
+ // Required numeric fields
199
+ const requiredNumericFields = ['defaultWidth', 'defaultHeight'];
200
+ for (const field of requiredNumericFields) {
201
+ const value = configToValidate[field as keyof SVGConfig];
202
+ if (typeof value !== 'number' || value <= 0) {
203
+ errors.push(`${field} must be a positive number`);
204
+ }
205
+ }
206
+
207
+ // Validate exclude array
208
+ if (configToValidate.exclude && !Array.isArray(configToValidate.exclude)) {
209
+ errors.push('exclude must be an array');
210
+ }
211
+
212
+ // Validate styleRules object
213
+ if (configToValidate.styleRules && typeof configToValidate.styleRules !== 'object') {
214
+ errors.push('styleRules must be an object');
215
+ }
216
+
217
+ return {
218
+ valid: errors.length === 0,
219
+ errors
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Clear cached configuration (useful for testing)
225
+ */
226
+ public clearCache(): void {
227
+ this.cachedConfig = null;
228
+ }
229
+
230
+ /**
231
+ * Display current configuration
232
+ */
233
+ public showConfig(): void {
234
+ const config = this.readConfig();
235
+ console.log('📄 Current Configuration:');
236
+ console.log(JSON.stringify(config, null, 2));
237
+ }
238
+ }
239
+
240
+ // Export singleton instance
241
+ export const configService = ConfigService.getInstance();
@@ -0,0 +1,218 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { FileWatchEvent, WatchOptions } from '../types/index.js';
4
+ import { FileSystem } from '../utils/native.js';
5
+ import { logger } from '../core/logger.js';
6
+
7
+ /**
8
+ * Professional file watching service with debouncing and event filtering
9
+ */
10
+ export class FileWatcherService {
11
+ private static instance: FileWatcherService;
12
+ private watchers: Map<string, fs.FSWatcher> = new Map();
13
+ private eventHandlers: Map<string, Function[]> = new Map();
14
+ private debounceTimers: Map<string, NodeJS.Timeout> = new Map();
15
+ private readonly debounceDelay = 300; // milliseconds
16
+
17
+ private constructor() {}
18
+
19
+ public static getInstance(): FileWatcherService {
20
+ if (!FileWatcherService.instance) {
21
+ FileWatcherService.instance = new FileWatcherService();
22
+ }
23
+ return FileWatcherService.instance;
24
+ }
25
+
26
+ /**
27
+ * Start watching a directory for SVG file changes
28
+ */
29
+ public async watchDirectory(
30
+ watchPath: string,
31
+ options: Partial<WatchOptions> = {}
32
+ ): Promise<string> {
33
+ const resolvedPath = path.resolve(watchPath);
34
+
35
+ if (!(await FileSystem.exists(resolvedPath))) {
36
+ throw new Error(`Watch path does not exist: ${resolvedPath}`);
37
+ }
38
+
39
+ const watchId = `watch-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
40
+
41
+ try {
42
+ const watcher = fs.watch(resolvedPath, {
43
+ persistent: true,
44
+ recursive: false
45
+ }, (eventType, filename) => {
46
+ if (filename) {
47
+ this.handleFileEvent(watchId, eventType, path.join(resolvedPath, filename));
48
+ }
49
+ });
50
+
51
+ this.watchers.set(watchId, watcher);
52
+ this.eventHandlers.set(watchId, []);
53
+
54
+ logger.info(`Started watching directory: ${resolvedPath}`);
55
+ return watchId;
56
+
57
+ } catch (error) {
58
+ logger.error(`Failed to start watching ${resolvedPath}:`, error);
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Handle file system events with debouncing
65
+ */
66
+ private handleFileEvent(watchId: string, eventType: string, filePath: string): void {
67
+ // Only process SVG files
68
+ if (!filePath.endsWith('.svg')) {
69
+ return;
70
+ }
71
+
72
+ const debounceKey = `${watchId}-${filePath}`;
73
+
74
+ // Clear existing timer
75
+ if (this.debounceTimers.has(debounceKey)) {
76
+ clearTimeout(this.debounceTimers.get(debounceKey)!);
77
+ }
78
+
79
+ // Set new timer
80
+ const timer = setTimeout(async () => {
81
+ this.debounceTimers.delete(debounceKey);
82
+ await this.processFileEvent(watchId, eventType, filePath);
83
+ }, this.debounceDelay);
84
+
85
+ this.debounceTimers.set(debounceKey, timer);
86
+ }
87
+
88
+ /**
89
+ * Process debounced file events
90
+ */
91
+ private async processFileEvent(watchId: string, eventType: string, filePath: string): Promise<void> {
92
+ try {
93
+ const exists = await FileSystem.exists(filePath);
94
+ let actualEventType: FileWatchEvent['type'];
95
+
96
+ // Determine actual event type
97
+ if (eventType === 'rename') {
98
+ actualEventType = exists ? 'add' : 'unlink';
99
+ } else {
100
+ actualEventType = 'change';
101
+ }
102
+
103
+ const event: FileWatchEvent = {
104
+ type: actualEventType,
105
+ filePath,
106
+ timestamp: Date.now()
107
+ };
108
+
109
+ logger.debug(`File event: ${actualEventType} - ${path.basename(filePath)}`);
110
+
111
+ // Emit event to registered handlers
112
+ this.emitEvent(watchId, event);
113
+
114
+ } catch (error) {
115
+ logger.error(`Error processing file event for ${filePath}:`, error);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Register an event handler for a specific watcher
121
+ */
122
+ public onFileEvent(watchId: string, handler: (event: FileWatchEvent) => void | Promise<void>): void {
123
+ const handlers = this.eventHandlers.get(watchId) || [];
124
+ handlers.push(handler);
125
+ this.eventHandlers.set(watchId, handlers);
126
+ }
127
+
128
+ /**
129
+ * Emit event to all registered handlers
130
+ */
131
+ private emitEvent(watchId: string, event: FileWatchEvent): void {
132
+ const handlers = this.eventHandlers.get(watchId) || [];
133
+
134
+ for (const handler of handlers) {
135
+ try {
136
+ const result = handler(event);
137
+ // Handle async handlers
138
+ if (result && typeof result.then === 'function') {
139
+ result.catch((error: any) => {
140
+ logger.error(`Error in file event handler:`, error);
141
+ });
142
+ }
143
+ } catch (error) {
144
+ logger.error(`Error in file event handler:`, error);
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Stop watching a specific directory
151
+ */
152
+ public stopWatching(watchId: string): void {
153
+ const watcher = this.watchers.get(watchId);
154
+ if (watcher) {
155
+ watcher.close();
156
+ this.watchers.delete(watchId);
157
+ this.eventHandlers.delete(watchId);
158
+
159
+ // Clear any pending debounce timers for this watcher
160
+ for (const [key, timer] of this.debounceTimers.entries()) {
161
+ if (key.startsWith(`${watchId}-`)) {
162
+ clearTimeout(timer);
163
+ this.debounceTimers.delete(key);
164
+ }
165
+ }
166
+
167
+ logger.info(`Stopped watching: ${watchId}`);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Stop all watchers
173
+ */
174
+ public stopAllWatchers(): void {
175
+ for (const watchId of this.watchers.keys()) {
176
+ this.stopWatching(watchId);
177
+ }
178
+ logger.info('Stopped all file watchers');
179
+ }
180
+
181
+ /**
182
+ * Get active watch statistics
183
+ */
184
+ public getWatchStats(): {
185
+ activeWatchers: number;
186
+ pendingEvents: number;
187
+ totalHandlers: number;
188
+ } {
189
+ let totalHandlers = 0;
190
+ for (const handlers of this.eventHandlers.values()) {
191
+ totalHandlers += handlers.length;
192
+ }
193
+
194
+ return {
195
+ activeWatchers: this.watchers.size,
196
+ pendingEvents: this.debounceTimers.size,
197
+ totalHandlers
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Cleanup on shutdown
203
+ */
204
+ public shutdown(): void {
205
+ this.stopAllWatchers();
206
+
207
+ // Clear all debounce timers
208
+ for (const timer of this.debounceTimers.values()) {
209
+ clearTimeout(timer);
210
+ }
211
+ this.debounceTimers.clear();
212
+
213
+ logger.info('File watcher service shutdown complete');
214
+ }
215
+ }
216
+
217
+ // Export singleton instance
218
+ export const fileWatcher = FileWatcherService.getInstance();