watcher.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import z from "zod"
  2. import { Bus } from "../bus"
  3. import { Instance } from "../project/instance"
  4. import { Log } from "../util/log"
  5. import { FileIgnore } from "./ignore"
  6. import { Config } from "../config/config"
  7. // @ts-ignore
  8. import { createWrapper } from "@parcel/watcher/wrapper"
  9. import { lazy } from "@/util/lazy"
  10. import type ParcelWatcher from "@parcel/watcher"
  11. export namespace FileWatcher {
  12. const log = Log.create({ service: "file.watcher" })
  13. export const Event = {
  14. Updated: Bus.event(
  15. "file.watcher.updated",
  16. z.object({
  17. file: z.string(),
  18. event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]),
  19. }),
  20. ),
  21. }
  22. const watcher = lazy(() => {
  23. const binding = require(
  24. `@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? "-glibc" : ""}`,
  25. )
  26. return createWrapper(binding) as typeof import("@parcel/watcher")
  27. })
  28. const state = Instance.state(
  29. async () => {
  30. if (Instance.project.vcs !== "git") return {}
  31. log.info("init")
  32. const cfg = await Config.get()
  33. const backend = (() => {
  34. if (process.platform === "win32") return "windows"
  35. if (process.platform === "darwin") return "fs-events"
  36. if (process.platform === "linux") return "inotify"
  37. })()
  38. if (!backend) {
  39. log.error("watcher backend not supported", { platform: process.platform })
  40. return {}
  41. }
  42. log.info("watcher backend", { platform: process.platform, backend })
  43. const subscribe: ParcelWatcher.SubscribeCallback = (err, evts) => {
  44. if (err) return
  45. for (const evt of evts) {
  46. log.info("event", evt)
  47. if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" })
  48. if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" })
  49. if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
  50. }
  51. }
  52. const subs = []
  53. const cfgIgnores = cfg.watcher?.ignore ?? []
  54. subs.push(
  55. await watcher().subscribe(Instance.directory, subscribe, {
  56. ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
  57. backend,
  58. }),
  59. )
  60. const vcsDir = Instance.project.vcsDir
  61. if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
  62. subs.push(
  63. await watcher().subscribe(vcsDir, subscribe, {
  64. ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"],
  65. backend,
  66. }),
  67. )
  68. }
  69. return { subs }
  70. },
  71. async (state) => {
  72. if (!state.subs) return
  73. await Promise.all(state.subs.map((sub) => sub?.unsubscribe()))
  74. },
  75. )
  76. export function init() {
  77. // if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return
  78. state()
  79. }
  80. }