(opts: FileWatcherOptions)
| 30 | * Returns a stop() function that cancels the debounce timer and closes the watcher. |
| 31 | */ |
| 32 | export function startFileWatcher(opts: FileWatcherOptions): () => void { |
| 33 | const { rootPath, debounceMs = 2000, extraExtensions, onReady, onChanged } = opts; |
| 34 | const trackedExtensions = new Set( |
| 35 | getSupportedExtensions(extraExtensions).map((extension) => extension.toLowerCase()) |
| 36 | ); |
| 37 | let debounceTimer: ReturnType<typeof setTimeout> | undefined; |
| 38 | |
| 39 | const trigger = (filePath: string) => { |
| 40 | if (!isTrackedSourcePath(filePath, trackedExtensions)) return; |
| 41 | if (debounceTimer !== undefined) clearTimeout(debounceTimer); |
| 42 | debounceTimer = setTimeout(() => { |
| 43 | debounceTimer = undefined; |
| 44 | onChanged(); |
| 45 | }, debounceMs); |
| 46 | }; |
| 47 | |
| 48 | const watcher = chokidar.watch(rootPath, { |
| 49 | ignored: [...EXCLUDED_GLOB_PATTERNS], |
| 50 | persistent: true, |
| 51 | ignoreInitial: true, |
| 52 | awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 } |
| 53 | }); |
| 54 | |
| 55 | watcher |
| 56 | .on('ready', () => onReady?.()) |
| 57 | .on('add', trigger) |
| 58 | .on('change', trigger) |
| 59 | .on('unlink', trigger) |
| 60 | .on('error', (err: unknown) => console.error('[file-watcher] error:', err)); |
| 61 | |
| 62 | return () => { |
| 63 | if (debounceTimer !== undefined) clearTimeout(debounceTimer); |
| 64 | void watcher.close(); |
| 65 | }; |
| 66 | } |
no test coverage detected