Browse Source

core: replace chokidar with @parcel/watcher for better performance and cross-platform support

Dax Raad 4 tháng trước cách đây
mục cha
commit
f3b71007d2

+ 2 - 0
bun.lock

@@ -152,6 +152,7 @@
         "@openauthjs/openauth": "0.4.3",
         "@openauthjs/openauth": "0.4.3",
         "@opencode-ai/plugin": "workspace:*",
         "@opencode-ai/plugin": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
+        "@parcel/watcher": "2.5.1",
         "@standard-schema/spec": "1.0.0",
         "@standard-schema/spec": "1.0.0",
         "@zip.js/zip.js": "2.7.62",
         "@zip.js/zip.js": "2.7.62",
         "ai": "catalog:",
         "ai": "catalog:",
@@ -181,6 +182,7 @@
         "@ai-sdk/amazon-bedrock": "2.2.10",
         "@ai-sdk/amazon-bedrock": "2.2.10",
         "@ai-sdk/google-vertex": "3.0.16",
         "@ai-sdk/google-vertex": "3.0.16",
         "@octokit/webhooks-types": "7.6.1",
         "@octokit/webhooks-types": "7.6.1",
+        "@parcel/watcher-win32-x64": "2.5.1",
         "@standard-schema/spec": "1.0.0",
         "@standard-schema/spec": "1.0.0",
         "@tsconfig/bun": "1.0.7",
         "@tsconfig/bun": "1.0.7",
         "@types/bun": "catalog:",
         "@types/bun": "catalog:",

+ 2 - 0
packages/opencode/package.json

@@ -20,6 +20,7 @@
     "@ai-sdk/amazon-bedrock": "2.2.10",
     "@ai-sdk/amazon-bedrock": "2.2.10",
     "@ai-sdk/google-vertex": "3.0.16",
     "@ai-sdk/google-vertex": "3.0.16",
     "@octokit/webhooks-types": "7.6.1",
     "@octokit/webhooks-types": "7.6.1",
+    "@parcel/watcher-win32-x64": "2.5.1",
     "@standard-schema/spec": "1.0.0",
     "@standard-schema/spec": "1.0.0",
     "@tsconfig/bun": "1.0.7",
     "@tsconfig/bun": "1.0.7",
     "@types/bun": "catalog:",
     "@types/bun": "catalog:",
@@ -37,6 +38,7 @@
     "@openauthjs/openauth": "0.4.3",
     "@openauthjs/openauth": "0.4.3",
     "@opencode-ai/plugin": "workspace:*",
     "@opencode-ai/plugin": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
+    "@parcel/watcher": "2.5.1",
     "@standard-schema/spec": "1.0.0",
     "@standard-schema/spec": "1.0.0",
     "@zip.js/zip.js": "2.7.62",
     "@zip.js/zip.js": "2.7.62",
     "ai": "catalog:",
     "ai": "catalog:",

+ 7 - 0
packages/opencode/script/build.ts

@@ -1,4 +1,5 @@
 #!/usr/bin/env bun
 #!/usr/bin/env bun
+import path from "path"
 const dir = new URL("..", import.meta.url).pathname
 const dir = new URL("..", import.meta.url).pathname
 process.chdir(dir)
 process.chdir(dir)
 import { $ } from "bun"
 import { $ } from "bun"
@@ -32,6 +33,12 @@ for (const [os, arch] of targets) {
   await $`CGO_ENABLED=0 GOOS=${os} GOARCH=${GOARCH[arch]} go build -ldflags="-s -w -X main.Version=${version}" -o ../opencode/dist/${name}/bin/tui ../tui/cmd/opencode/main.go`
   await $`CGO_ENABLED=0 GOOS=${os} GOARCH=${GOARCH[arch]} go build -ldflags="-s -w -X main.Version=${version}" -o ../opencode/dist/${name}/bin/tui ../tui/cmd/opencode/main.go`
     .cwd("../tui")
     .cwd("../tui")
     .quiet()
     .quiet()
+
+  const watcher = `@parcel/watcher-${os === "windows" ? "win32" : os}-${arch.replace("-baseline", "")}${os === "linux" ? "-glibc" : ""}`
+  await $`mkdir -p ../../node_modules/${watcher}`
+  await $`npm pack npm pack ${watcher}`.cwd(path.join(dir, "../../node_modules")).quiet()
+  await $`tar -xf ../../node_modules/${watcher.replace("@parcel/", "parcel-")}-*.tgz -C ../../node_modules/${watcher} --strip-components=1`
+
   await Bun.build({
   await Bun.build({
     compile: {
     compile: {
       target: `bun-${os}-${arch}` as any,
       target: `bun-${os}-${arch}` as any,

+ 2 - 0
packages/opencode/src/file/ignore.ts

@@ -45,6 +45,8 @@ export namespace FileIgnore {
 
 
   const FILE_GLOBS = FILES.map((p) => new Bun.Glob(p))
   const FILE_GLOBS = FILES.map((p) => new Bun.Glob(p))
 
 
+  export const PATTERNS = [...FILES, ...FOLDERS]
+
   export function match(
   export function match(
     filepath: string,
     filepath: string,
     opts?: {
     opts?: {

+ 27 - 24
packages/opencode/src/file/watcher.ts

@@ -1,11 +1,13 @@
 import z from "zod/v4"
 import z from "zod/v4"
 import { Bus } from "../bus"
 import { Bus } from "../bus"
-import chokidar from "chokidar"
 import { Flag } from "../flag/flag"
 import { Flag } from "../flag/flag"
 import { Instance } from "../project/instance"
 import { Instance } from "../project/instance"
 import { Log } from "../util/log"
 import { Log } from "../util/log"
 import { FileIgnore } from "./ignore"
 import { FileIgnore } from "./ignore"
 import { Config } from "../config/config"
 import { Config } from "../config/config"
+// @ts-ignore
+import { createWrapper } from "@parcel/watcher/wrapper"
+import { lazy } from "@/util/lazy"
 
 
 export namespace FileWatcher {
 export namespace FileWatcher {
   const log = Log.create({ service: "file.watcher" })
   const log = Log.create({ service: "file.watcher" })
@@ -20,37 +22,38 @@ export namespace FileWatcher {
     ),
     ),
   }
   }
 
 
+  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(
   const state = Instance.state(
     async () => {
     async () => {
       if (Instance.project.vcs !== "git") return {}
       if (Instance.project.vcs !== "git") return {}
       log.info("init")
       log.info("init")
       const cfg = await Config.get()
       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,
-          })
+      const sub = await watcher().subscribe(
+        Instance.directory,
+        (err, evts) => {
+          if (err) return
+          for (const evt of evts) {
+            log.info("event", evt)
+            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" })
+          }
+        },
+        {
+          ignore: [...FileIgnore.PATTERNS, ...(cfg.watcher?.ignore ?? [])],
+          backend: "inotify",
         },
         },
-      })
-      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 }
+      )
+      return { sub }
     },
     },
     async (state) => {
     async (state) => {
-      state.watcher?.close()
+      state.sub?.unsubscribe()
     },
     },
   )
   )