diff --git a/bun.lock b/bun.lock index 58f0c941..cf8863f8 100644 --- a/bun.lock +++ b/bun.lock @@ -152,6 +152,7 @@ "@openauthjs/openauth": "0.4.3", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/sdk": "workspace:*", + "@parcel/watcher": "2.5.1", "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", @@ -181,6 +182,7 @@ "@ai-sdk/amazon-bedrock": "2.2.10", "@ai-sdk/google-vertex": "3.0.16", "@octokit/webhooks-types": "7.6.1", + "@parcel/watcher-win32-x64": "2.5.1", "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "1.0.7", "@types/bun": "catalog:", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index ea83d4e2..5db71235 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -20,6 +20,7 @@ "@ai-sdk/amazon-bedrock": "2.2.10", "@ai-sdk/google-vertex": "3.0.16", "@octokit/webhooks-types": "7.6.1", + "@parcel/watcher-win32-x64": "2.5.1", "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "1.0.7", "@types/bun": "catalog:", @@ -37,6 +38,7 @@ "@openauthjs/openauth": "0.4.3", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/sdk": "workspace:*", + "@parcel/watcher": "2.5.1", "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 02090c69..4c4bc782 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -1,4 +1,5 @@ #!/usr/bin/env bun +import path from "path" const dir = new URL("..", import.meta.url).pathname process.chdir(dir) 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` .cwd("../tui") .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({ compile: { target: `bun-${os}-${arch}` as any, diff --git a/packages/opencode/src/file/ignore.ts b/packages/opencode/src/file/ignore.ts index bfe5fa84..2e1d1428 100644 --- a/packages/opencode/src/file/ignore.ts +++ b/packages/opencode/src/file/ignore.ts @@ -45,6 +45,8 @@ export namespace FileIgnore { const FILE_GLOBS = FILES.map((p) => new Bun.Glob(p)) + export const PATTERNS = [...FILES, ...FOLDERS] + export function match( filepath: string, opts?: { diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 652cb785..f7827c67 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -1,11 +1,13 @@ 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" +// @ts-ignore +import { createWrapper } from "@parcel/watcher/wrapper" +import { lazy } from "@/util/lazy" export namespace FileWatcher { 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( 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, - }) + 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" }) + } }, - }) - 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 } + { + ignore: [...FileIgnore.PATTERNS, ...(cfg.watcher?.ignore ?? [])], + backend: "inotify", + }, + ) + return { sub } }, async (state) => { - state.watcher?.close() + state.sub?.unsubscribe() }, )