import z from "zod" import { Bus } from "../bus" import { Instance } from "../project/instance" import { Log } from "../util/log" import { FileIgnore } from "./ignore" import { Config } from "../config/config" // @ts-ignore import { createWrapper } from "@parcel/watcher/wrapper" import { lazy } from "@/util/lazy" import type ParcelWatcher from "@parcel/watcher" 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 watcher = lazy(() => { const binding = require( `@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? "-glibc" : ""}`, ) return createWrapper(binding) as typeof import("@parcel/watcher") }) const state = Instance.state( async () => { if (Instance.project.vcs !== "git") return {} log.info("init") const cfg = await Config.get() const backend = (() => { if (process.platform === "win32") return "windows" if (process.platform === "darwin") return "fs-events" if (process.platform === "linux") return "inotify" })() if (!backend) { log.error("watcher backend not supported", { platform: process.platform }) return {} } log.info("watcher backend", { platform: process.platform, backend }) const subscribe: ParcelWatcher.SubscribeCallback = (err, evts) => { if (err) return for (const evt of evts) { if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" }) if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" }) if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" }) } } const subs = [] const cfgIgnores = cfg.watcher?.ignore ?? [] subs.push( await watcher().subscribe(Instance.directory, subscribe, { ignore: [...FileIgnore.PATTERNS, ...cfgIgnores], backend, }), ) const vcsDir = Instance.project.vcsDir if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) { subs.push( await watcher().subscribe(vcsDir, subscribe, { ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"], backend, }), ) } return { subs } }, async (state) => { if (!state.subs) return await Promise.all(state.subs.map((sub) => sub?.unsubscribe())) }, ) export function init() { state() } }