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.
- package/CODE_OF_CONDUCT.md +79 -0
- package/CONTRIBUTING.md +146 -0
- package/LICENSE +21 -0
- package/README.md +1862 -73
- package/TESTING.md +143 -0
- package/cli-framework.test.js +16 -0
- package/cli-test-angular/Arrowbenddownleft.component.ts +27 -0
- package/cli-test-angular/Vite.component.ts +27 -0
- package/cli-test-angular/index.ts +25 -0
- package/{my-icons/ArrowBendDownLeft.tsx → cli-test-output/Arrowbenddownleft.vue} +28 -12
- package/{my-icons/Vite.tsx → cli-test-output/Vite.vue} +28 -12
- package/cli-test-output/index.ts +25 -0
- package/cli-test-react/Arrowbenddownleft.tsx +39 -0
- package/cli-test-react/Vite.tsx +39 -0
- package/cli-test-react/index.ts +25 -0
- package/cli-test-svelte/Arrowbenddownleft.svelte +22 -0
- package/cli-test-svelte/Vite.svelte +22 -0
- package/cli-test-svelte/index.ts +25 -0
- package/dist/builder.js +12 -13
- package/dist/clean.js +3 -3
- package/dist/cli.js +139 -61
- package/dist/config.d.ts +1 -1
- package/dist/config.js +5 -7
- package/dist/lock.js +1 -1
- package/dist/templates/ComponentTemplate.d.ts +15 -0
- package/dist/templates/ComponentTemplate.js +15 -0
- package/dist/watch.d.ts +2 -1
- package/dist/watch.js +30 -33
- package/docs/ADR-SVG-INTRGRATION-METHODS-002.adr.md +550 -0
- package/docs/FRAMEWORK-GUIDE.md +768 -0
- package/docs/IMPLEMENTATION-SUMMARY.md +376 -0
- package/frameworks.test.js +170 -0
- package/package.json +7 -10
- package/src/builder.ts +12 -13
- package/src/clean.ts +3 -3
- package/src/cli.ts +148 -59
- package/src/config.ts +5 -6
- package/src/core/error-handler.ts +303 -0
- package/src/core/framework-templates.ts +428 -0
- package/src/core/logger.ts +104 -0
- package/src/core/performance-engine.ts +327 -0
- package/src/core/plugin-manager.ts +228 -0
- package/src/core/style-compiler.ts +605 -0
- package/src/core/template-manager.ts +619 -0
- package/src/index.ts +235 -0
- package/src/lock.ts +1 -1
- package/src/processors/svg-processor.ts +288 -0
- package/src/services/config.ts +241 -0
- package/src/services/file-watcher.ts +218 -0
- package/src/services/svg-service.ts +468 -0
- package/src/types/index.ts +169 -0
- package/src/utils/native.ts +352 -0
- package/src/watch.ts +36 -36
- package/test-output-mulit/TestIcon-angular-module.component.ts +26 -0
- package/test-output-mulit/TestIcon-angular-standalone.component.ts +27 -0
- package/test-output-mulit/TestIcon-lit.ts +35 -0
- package/test-output-mulit/TestIcon-preact.tsx +38 -0
- package/test-output-mulit/TestIcon-react.tsx +35 -0
- package/test-output-mulit/TestIcon-solid.tsx +27 -0
- package/test-output-mulit/TestIcon-svelte.svelte +22 -0
- package/test-output-mulit/TestIcon-vanilla.ts +37 -0
- package/test-output-mulit/TestIcon-vue-composition.vue +33 -0
- package/test-output-mulit/TestIcon-vue-options.vue +31 -0
- 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();
|