Add utility modules
Some checks failed
CI / test (push) Failing after 6s

This commit is contained in:
2026-01-30 01:01:10 +00:00
parent c90209b826
commit 208af3b907

141
src/utils/watcher.ts Normal file
View File

@@ -0,0 +1,141 @@
import chokidar, { FSWatcher, WatchOptions } from 'chokidar';
import * as path from 'path';
export interface WatcherOptions extends WatchOptions {
debounceMs?: number;
maxRetries?: number;
}
export interface FileChangeEvent {
type: 'add' | 'change' | 'unlink';
path: string;
timestamp: Date;
}
export type WatcherEventHandler = (event: FileChangeEvent) => void;
export class FileWatcher {
private watcher: FSWatcher | null = null;
private handlers: Map<string, Set<WatcherEventHandler>> = new Map();
private options: WatcherOptions;
private isRunning: boolean = false;
constructor(options: WatcherOptions = {}) {
this.options = {
debounceMs: options.debounceMs ?? 100,
maxRetries: options.maxRetries ?? 3,
persistent: options.persistent ?? true,
ignoreInitial: options.ignoreInitial ?? false,
awaitWriteFinish: options.awaitWriteFinish ?? {
stabilityThreshold: 100,
pollInterval: 50
},
...options
};
}
watch(paths: string | string[], patterns?: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const watchPaths = Array.isArray(paths) ? paths : [paths];
this.watcher = chokidar.watch(watchPaths, {
...this.options,
ignored: patterns
});
this.watcher
.on('ready', () => {
this.isRunning = true;
resolve();
})
.on('error', (error) => {
this.isRunning = false;
reject(error);
})
.on('add', (filePath) => {
this.emit('add', filePath);
})
.on('change', (filePath) => {
this.emit('change', filePath);
})
.on('unlink', (filePath) => {
this.emit('unlink', filePath);
});
});
}
on(eventType: string, handler: WatcherEventHandler): void {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, new Set());
}
this.handlers.get(eventType)!.add(handler);
}
off(eventType: string, handler: WatcherEventHandler): void {
const eventHandlers = this.handlers.get(eventType);
if (eventHandlers) {
eventHandlers.delete(handler);
}
}
private emit(eventType: string, filePath: string): void {
const event: FileChangeEvent = {
type: eventType as 'add' | 'change' | 'unlink',
path: path.resolve(filePath),
timestamp: new Date()
};
const handlers = this.handlers.get(eventType);
if (handlers) {
for (const handler of handlers) {
try {
handler(event);
} catch (error) {
console.error(`Error in watcher handler for ${eventType}:`, error);
}
}
}
const allHandlers = this.handlers.get('*');
if (allHandlers) {
for (const handler of allHandlers) {
try {
handler(event);
} catch (error) {
console.error('Error in wildcard watcher handler:', error);
}
}
}
}
getWatchedFiles(): string[] {
if (!this.watcher) {
return [];
}
return Object.values(this.watcher.getWatched()).flat();
}
add(filePath: string): void {
this.watcher?.add(filePath);
}
unwatch(filePath: string): void {
this.watcher?.unwatch(filePath);
}
close(): void {
if (this.watcher) {
this.watcher.close();
this.watcher = null;
this.isRunning = false;
}
}
isActive(): boolean {
return this.isRunning;
}
}
export function createFileWatcher(options?: WatcherOptions): FileWatcher {
return new FileWatcher(options);
}