import z from "zod/v4" import { Bus } from "../bus" import chokidar from "chokidar" import { Flag } from "../flag/flag" import { Instance } from "../project/instance" import { Log } from "../util/log" import { FileIgnore } from "./ignore" import { Config } from "../config/config" export namespace FileWatcher { const log = Log.create({ service: "file.watcher" }) export const Event = { Updated: Bus.event( "file.watcher.updated", z.object({ file: z.string(), event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]), }), ), } const state = Instance.state( async () => { if (Instance.project.vcs !== "git") return {} log.info("init") const cfg = await Config.get() const ignore = (cfg.watcher?.ignore ?? []).map((v) => new Bun.Glob(v)) const watcher = chokidar.watch(Instance.directory, { ignoreInitial: true, ignored: (filepath) => { return FileIgnore.match(filepath, { whitelist: [new Bun.Glob("**/.git/{index,logs/HEAD}")], extra: ignore, }) }, }) watcher.on("change", (file) => { Bus.publish(Event.Updated, { file, event: "change" }) }) watcher.on("add", (file) => { Bus.publish(Event.Updated, { file, event: "add" }) }) watcher.on("unlink", (file) => { Bus.publish(Event.Updated, { file, event: "unlink" }) }) watcher.on("ready", () => { log.info("ready") }) return { watcher } }, async (state) => { state.watcher?.close() }, ) export function init() { if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return state() } }