blob: 8aa8d3babe4731f942688c7f401e2e506632a0ac [file]
import { watch as watchDirectory } from "node:fs";
import { basename } from "node:path";
export interface ClientSettingsWatcher {
stop(): void;
}
type WatchListener = (
eventType: string,
filename: string | Buffer | null,
) => void;
interface ClientSettingsWatcherDependencies {
readonly watch?: (
workspaceRoot: string,
listener: WatchListener,
) => WatchHandle;
readonly debounceMs?: number;
readonly onError?: (error: unknown) => void;
}
const SETTINGS_FILE = "settings.json";
const DEFAULT_DEBOUNCE_MS = 300;
interface WatchHandle {
on(event: "error", listener: (error: Error) => void): unknown;
close(): void;
}
export function startClientSettingsWatcher(
workspaceRoot: string,
onChanged: () => void,
dependencies: ClientSettingsWatcherDependencies = {},
): ClientSettingsWatcher {
let timer: ReturnType<typeof setTimeout> | undefined;
let watcher: WatchHandle | undefined;
const stop = (): void => {
watcher?.close();
watcher = undefined;
if (timer) clearTimeout(timer);
timer = undefined;
};
const schedule = (): void => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
onChanged();
}, dependencies.debounceMs ?? DEFAULT_DEBOUNCE_MS);
};
try {
watcher = (dependencies.watch ?? watchDirectory)(
workspaceRoot,
(_eventType, filename) => {
if (!watcher) return;
if (filename && basename(filename.toString()) !== SETTINGS_FILE) return;
schedule();
},
);
watcher.on("error", (error) => {
dependencies.onError?.(error);
stop();
});
} catch (error) {
dependencies.onError?.(error);
}
return { stop };
}