Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.3 kB · 191 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192import chokidar, { type FSWatcher } from 'chokidar'import { isAbsolute, join } from 'path'import { registerCleanup } from '../cleanupRegistry.js'import { logForDebugging } from '../debug.js'import { errorMessage } from '../errors.js'import { executeCwdChangedHooks, executeFileChangedHooks, type HookOutsideReplResult,} from '../hooks.js'import { clearCwdEnvFiles } from '../sessionEnvironment.js'import { getHooksConfigFromSnapshot } from './hooksConfigSnapshot.js'
let watcher: FSWatcher | null = nulllet currentCwd: stringlet dynamicWatchPaths: string[] = []let dynamicWatchPathsSorted: string[] = []let initialized = falselet hasEnvHooks = falselet notifyCallback: ((text: string, isError: boolean) => void) | null = null
export function setEnvHookNotifier( cb: ((text: string, isError: boolean) => void) | null,): void { notifyCallback = cb}
export function initializeFileChangedWatcher(cwd: string): void { if (initialized) return initialized = true currentCwd = cwd
const config = getHooksConfigFromSnapshot() hasEnvHooks = (config?.CwdChanged?.length ?? 0) > 0 || (config?.FileChanged?.length ?? 0) > 0
if (hasEnvHooks) { registerCleanup(async () => dispose()) }
const paths = resolveWatchPaths(config) if (paths.length === 0) return
startWatching(paths)}
function resolveWatchPaths( config?: ReturnType<typeof getHooksConfigFromSnapshot>,): string[] { const matchers = (config ?? getHooksConfigFromSnapshot())?.FileChanged ?? []
// Matcher field: filenames to watch in cwd, pipe-separated (e.g. ".envrc|.env") const staticPaths: string[] = [] for (const m of matchers) { if (!m.matcher) continue for (const name of m.matcher.split('|').map(s => s.trim())) { if (!name) continue staticPaths.push(isAbsolute(name) ? name : join(currentCwd, name)) } }
// Combine static matcher paths with dynamic paths from hook output return [...new Set([...staticPaths, ...dynamicWatchPaths])]}
function startWatching(paths: string[]): void { logForDebugging(`FileChanged: watching ${paths.length} paths`) watcher = chokidar.watch(paths, { persistent: true, ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 200 }, ignorePermissionErrors: true, }) watcher.on('change', p => handleFileEvent(p, 'change')) watcher.on('add', p => handleFileEvent(p, 'add')) watcher.on('unlink', p => handleFileEvent(p, 'unlink'))}
function handleFileEvent( path: string, event: 'change' | 'add' | 'unlink',): void { logForDebugging(`FileChanged: ${event} ${path}`) void executeFileChangedHooks(path, event) .then(({ results, watchPaths, systemMessages }) => { if (watchPaths.length > 0) { updateWatchPaths(watchPaths) } for (const msg of systemMessages) { notifyCallback?.(msg, false) } for (const r of results) { if (!r.succeeded && r.output) { notifyCallback?.(r.output, true) } } }) .catch(e => { const msg = errorMessage(e) logForDebugging(`FileChanged hook failed: ${msg}`, { level: 'error', }) notifyCallback?.(msg, true) })}
export function updateWatchPaths(paths: string[]): void { if (!initialized) return const sorted = paths.slice().sort() if ( sorted.length === dynamicWatchPathsSorted.length && sorted.every((p, i) => p === dynamicWatchPathsSorted[i]) ) { return } dynamicWatchPaths = paths dynamicWatchPathsSorted = sorted restartWatching()}
function restartWatching(): void { if (watcher) { void watcher.close() watcher = null } const paths = resolveWatchPaths() if (paths.length > 0) { startWatching(paths) }}
export async function onCwdChangedForHooks( oldCwd: string, newCwd: string,): Promise<void> { if (oldCwd === newCwd) return
// Re-evaluate from the current snapshot so mid-session hook changes are picked up const config = getHooksConfigFromSnapshot() const currentHasEnvHooks = (config?.CwdChanged?.length ?? 0) > 0 || (config?.FileChanged?.length ?? 0) > 0 if (!currentHasEnvHooks) return currentCwd = newCwd
await clearCwdEnvFiles() const hookResult = await executeCwdChangedHooks(oldCwd, newCwd).catch(e => { const msg = errorMessage(e) logForDebugging(`CwdChanged hook failed: ${msg}`, { level: 'error', }) notifyCallback?.(msg, true) return { results: [] as HookOutsideReplResult[], watchPaths: [] as string[], systemMessages: [] as string[], } }) dynamicWatchPaths = hookResult.watchPaths dynamicWatchPathsSorted = hookResult.watchPaths.slice().sort() for (const msg of hookResult.systemMessages) { notifyCallback?.(msg, false) } for (const r of hookResult.results) { if (!r.succeeded && r.output) { notifyCallback?.(r.output, true) } }
// Re-resolve matcher paths against the new cwd if (initialized) { restartWatching() }}
function dispose(): void { if (watcher) { void watcher.close() watcher = null } dynamicWatchPaths = [] dynamicWatchPathsSorted = [] initialized = false hasEnvHooks = false notifyCallback = null}
export function resetFileChangedWatcherForTesting(): void { dispose()}