mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-19 08: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:
1
bun.lock
1
bun.lock
@@ -143,6 +143,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",
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"weather": {
|
||||
"type": "local",
|
||||
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||
}
|
||||
}
|
||||
"$schema": "https://opencode.ai/config.json"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.13.0"
|
||||
".": "0.14.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
configured_endpoints: 43
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-2e754dafcad0636137256cef499b2bcd72cf17de08f44ec03c3589b2a05341a2.yml
|
||||
openapi_spec_hash: 2d3cf84d3033068ce6c07386411527ef
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-0a4165f1eabf826d3092ea6b789aa527048278dcd4bd891f9e5ac890b9bcbb35.yml
|
||||
openapi_spec_hash: da60e4fc813eb0f9ac3ab5f112e26bf6
|
||||
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.0 (2025-09-14)
|
||||
|
||||
Full Changelog: [v0.13.0...v0.14.0](https://github.com/sst/opencode-sdk-go/compare/v0.13.0...v0.14.0)
|
||||
|
||||
### Features
|
||||
|
||||
- **api:** api update ([dad0bc3](https://github.com/sst/opencode-sdk-go/commit/dad0bc3da99f20a0d002a6b94e049fb70f8e6a77))
|
||||
|
||||
## 0.13.0 (2025-09-14)
|
||||
|
||||
Full Changelog: [v0.12.0...v0.13.0](https://github.com/sst/opencode-sdk-go/compare/v0.12.0...v0.13.0)
|
||||
|
||||
@@ -24,7 +24,7 @@ Or to pin the version:
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```sh
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.13.0'
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.14.0'
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
package internal
|
||||
|
||||
const PackageVersion = "0.13.0" // x-release-please-version
|
||||
const PackageVersion = "0.14.0" // x-release-please-version
|
||||
|
||||
@@ -8,12 +8,15 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||
"github.com/sst/opencode-sdk-go/internal/apiquery"
|
||||
"github.com/sst/opencode-sdk-go/internal/param"
|
||||
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||
"github.com/sst/opencode-sdk-go/option"
|
||||
"github.com/sst/opencode-sdk-go/shared"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// SessionPermissionService contains methods and other services that help with
|
||||
@@ -60,7 +63,7 @@ type Permission struct {
|
||||
Title string `json:"title,required"`
|
||||
Type string `json:"type,required"`
|
||||
CallID string `json:"callID"`
|
||||
Pattern string `json:"pattern"`
|
||||
Pattern PermissionPatternUnion `json:"pattern"`
|
||||
JSON permissionJSON `json:"-"`
|
||||
}
|
||||
|
||||
@@ -107,6 +110,30 @@ func (r permissionTimeJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Union satisfied by [shared.UnionString] or [PermissionPatternArray].
|
||||
type PermissionPatternUnion interface {
|
||||
ImplementsPermissionPatternUnion()
|
||||
}
|
||||
|
||||
func init() {
|
||||
apijson.RegisterUnion(
|
||||
reflect.TypeOf((*PermissionPatternUnion)(nil)).Elem(),
|
||||
"",
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.String,
|
||||
Type: reflect.TypeOf(shared.UnionString("")),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(PermissionPatternArray{}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type PermissionPatternArray []string
|
||||
|
||||
func (r PermissionPatternArray) ImplementsPermissionPatternUnion() {}
|
||||
|
||||
type SessionPermissionRespondParams struct {
|
||||
Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
|
||||
Directory param.Field[string] `query:"directory"`
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
package shared
|
||||
|
||||
type UnionString string
|
||||
|
||||
func (UnionString) ImplementsPermissionPatternUnion() {}
|
||||
|
||||
type UnionBool bool
|
||||
|
||||
func (UnionBool) ImplementsConfigProviderOptionsTimeoutUnion() {}
|
||||
|
||||
Reference in New Issue
Block a user