mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-25 11:44:22 +01:00
feat: improve file watcher with chokidar and better ignore patterns (#2621)
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"ai": "catalog:",
|
||||
"chokidar": "4.0.3",
|
||||
"decimal.js": "10.5.0",
|
||||
"diff": "8.0.2",
|
||||
"gray-matter": "4.0.3",
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Installation } from "../../installation"
|
||||
import { Config } from "../../config/config"
|
||||
import { Bus } from "../../bus"
|
||||
import { Log } from "../../util/log"
|
||||
import { FileWatcher } from "../../file/watch"
|
||||
import { FileWatcher } from "../../file/watcher"
|
||||
import { Ide } from "../../ide"
|
||||
|
||||
import { Flag } from "../../flag/flag"
|
||||
@@ -101,7 +101,6 @@ export const TuiCommand = cmd({
|
||||
}
|
||||
return undefined
|
||||
})()
|
||||
FileWatcher.init()
|
||||
const providers = await Provider.list()
|
||||
if (Object.keys(providers).length === 0) {
|
||||
return "needs_provider"
|
||||
@@ -181,6 +180,7 @@ export const TuiCommand = cmd({
|
||||
.then(() => Bus.publish(Ide.Event.Installed, { ide }))
|
||||
.catch(() => {})
|
||||
})()
|
||||
FileWatcher.init()
|
||||
|
||||
await proc.exited
|
||||
server.stop()
|
||||
|
||||
@@ -365,6 +365,11 @@ export namespace Config {
|
||||
.record(z.string(), Command)
|
||||
.optional()
|
||||
.describe("Command configuration, see https://opencode.ai/docs/commands"),
|
||||
watcher: z
|
||||
.object({
|
||||
ignore: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
plugin: z.string().array().optional(),
|
||||
snapshot: z.boolean().optional(),
|
||||
share: z
|
||||
|
||||
61
packages/opencode/src/file/ignore.ts
Normal file
61
packages/opencode/src/file/ignore.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export namespace FileIgnore {
|
||||
const DEFAULT_PATTERNS = [
|
||||
// Dependencies
|
||||
"**/node_modules/**",
|
||||
"**/bower_components/**",
|
||||
"**/.pnpm-store/**",
|
||||
"**/vendor/**",
|
||||
|
||||
// vcs
|
||||
"**/.git/**",
|
||||
|
||||
// Build outputs
|
||||
"**/dist/**",
|
||||
"**/build/**",
|
||||
"**/out/**",
|
||||
"**/.next/**",
|
||||
"**/target/**", // Rust
|
||||
"**/bin/**",
|
||||
"**/obj/**", // .NET
|
||||
|
||||
// Version control
|
||||
"**/.git/**",
|
||||
"**/.svn/**",
|
||||
"**/.hg/**",
|
||||
|
||||
// IDE/Editor
|
||||
"**/.vscode/**",
|
||||
"**/.idea/**",
|
||||
"**/*.swp",
|
||||
"**/*.swo",
|
||||
|
||||
// OS
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db",
|
||||
|
||||
// Logs & temp
|
||||
"**/logs/**",
|
||||
"**/tmp/**",
|
||||
"**/temp/**",
|
||||
"**/*.log",
|
||||
|
||||
// Coverage/test outputs
|
||||
"**/coverage/**",
|
||||
"**/.nyc_output/**",
|
||||
]
|
||||
|
||||
const GLOBS = DEFAULT_PATTERNS.map((p) => new Bun.Glob(p))
|
||||
|
||||
export function match(
|
||||
filepath: string,
|
||||
opts: {
|
||||
extra?: Bun.Glob[]
|
||||
},
|
||||
) {
|
||||
const extra = opts.extra || []
|
||||
for (const glob of [...GLOBS, ...extra]) {
|
||||
if (glob.match(filepath)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import z from "zod/v4"
|
||||
import { Bus } from "../bus"
|
||||
import fs from "fs"
|
||||
import { Log } from "../util/log"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
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("rename"), z.literal("change")]),
|
||||
}),
|
||||
),
|
||||
}
|
||||
const state = Instance.state(
|
||||
() => {
|
||||
if (Instance.project.vcs !== "git") return {}
|
||||
try {
|
||||
const watcher = fs.watch(Instance.directory, { recursive: true }, (event, file) => {
|
||||
log.info("change", { file, event })
|
||||
if (!file) return
|
||||
Bus.publish(Event.Updated, {
|
||||
file,
|
||||
event,
|
||||
})
|
||||
})
|
||||
return { watcher }
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
async (state) => {
|
||||
state.watcher?.close()
|
||||
},
|
||||
)
|
||||
|
||||
export function init() {
|
||||
if (Flag.OPENCODE_DISABLE_WATCHER || true) return
|
||||
state()
|
||||
}
|
||||
}
|
||||
61
packages/opencode/src/file/watcher.ts
Normal file
61
packages/opencode/src/file/watcher.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
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,
|
||||
awaitWriteFinish: true,
|
||||
ignored: (filepath) => {
|
||||
return FileIgnore.match(filepath, {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
export namespace Flag {
|
||||
export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE")
|
||||
export const OPENCODE_DISABLE_WATCHER = truthy("OPENCODE_DISABLE_WATCHER")
|
||||
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
|
||||
export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
|
||||
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
|
||||
@@ -10,6 +9,9 @@ export namespace Flag {
|
||||
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
|
||||
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
|
||||
|
||||
// Experimental
|
||||
export const OPENCODE_EXPERIMENTAL_WATCHER = truthy("OPENCODE_EXPERIMENTAL_WATCHER")
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
return value === "true" || value === "1"
|
||||
|
||||
Reference in New Issue
Block a user