Skip to content

Commit 61c4815

Browse files
authored
refactor: unwrap FileWatcher namespace + self-reexport (redo) (#23000)
1 parent 01bb54a commit 61c4815

1 file changed

Lines changed: 121 additions & 121 deletions

File tree

packages/opencode/src/file/watcher.ts

Lines changed: 121 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -19,145 +19,145 @@ import { Log } from "../util"
1919

2020
declare const OPENCODE_LIBC: string | undefined
2121

22-
export namespace FileWatcher {
23-
const log = Log.create({ service: "file.watcher" })
24-
const SUBSCRIBE_TIMEOUT_MS = 10_000
25-
26-
export const Event = {
27-
Updated: BusEvent.define(
28-
"file.watcher.updated",
29-
z.object({
30-
file: z.string(),
31-
event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]),
32-
}),
33-
),
22+
const log = Log.create({ service: "file.watcher" })
23+
const SUBSCRIBE_TIMEOUT_MS = 10_000
24+
25+
export const Event = {
26+
Updated: BusEvent.define(
27+
"file.watcher.updated",
28+
z.object({
29+
file: z.string(),
30+
event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]),
31+
}),
32+
),
33+
}
34+
35+
const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
36+
try {
37+
const binding = require(
38+
`@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`,
39+
)
40+
return createWrapper(binding) as typeof import("@parcel/watcher")
41+
} catch (error) {
42+
log.error("failed to load watcher binding", { error })
43+
return
3444
}
45+
})
3546

36-
const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
37-
try {
38-
const binding = require(
39-
`@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`,
40-
)
41-
return createWrapper(binding) as typeof import("@parcel/watcher")
42-
} catch (error) {
43-
log.error("failed to load watcher binding", { error })
44-
return
45-
}
46-
})
47+
function getBackend() {
48+
if (process.platform === "win32") return "windows"
49+
if (process.platform === "darwin") return "fs-events"
50+
if (process.platform === "linux") return "inotify"
51+
}
4752

48-
function getBackend() {
49-
if (process.platform === "win32") return "windows"
50-
if (process.platform === "darwin") return "fs-events"
51-
if (process.platform === "linux") return "inotify"
52-
}
53+
function protecteds(dir: string) {
54+
return Protected.paths().filter((item) => {
55+
const rel = path.relative(dir, item)
56+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel)
57+
})
58+
}
5359

54-
function protecteds(dir: string) {
55-
return Protected.paths().filter((item) => {
56-
const rel = path.relative(dir, item)
57-
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel)
58-
})
59-
}
60+
export const hasNativeBinding = () => !!watcher()
6061

61-
export const hasNativeBinding = () => !!watcher()
62+
export interface Interface {
63+
readonly init: () => Effect.Effect<void>
64+
}
6265

63-
export interface Interface {
64-
readonly init: () => Effect.Effect<void>
65-
}
66+
export class Service extends Context.Service<Service, Interface>()("@opencode/FileWatcher") {}
6667

67-
export class Service extends Context.Service<Service, Interface>()("@opencode/FileWatcher") {}
68+
export const layer = Layer.effect(
69+
Service,
70+
Effect.gen(function* () {
71+
const config = yield* Config.Service
72+
const git = yield* Git.Service
6873

69-
export const layer = Layer.effect(
70-
Service,
71-
Effect.gen(function* () {
72-
const config = yield* Config.Service
73-
const git = yield* Git.Service
74+
const state = yield* InstanceState.make(
75+
Effect.fn("FileWatcher.state")(
76+
function* () {
77+
if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return
7478

75-
const state = yield* InstanceState.make(
76-
Effect.fn("FileWatcher.state")(
77-
function* () {
78-
if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return
79+
log.info("init", { directory: Instance.directory })
7980

80-
log.info("init", { directory: Instance.directory })
81+
const backend = getBackend()
82+
if (!backend) {
83+
log.error("watcher backend not supported", { directory: Instance.directory, platform: process.platform })
84+
return
85+
}
8186

82-
const backend = getBackend()
83-
if (!backend) {
84-
log.error("watcher backend not supported", { directory: Instance.directory, platform: process.platform })
85-
return
86-
}
87+
const w = watcher()
88+
if (!w) return
8789

88-
const w = watcher()
89-
if (!w) return
90+
log.info("watcher backend", { directory: Instance.directory, platform: process.platform, backend })
9091

91-
log.info("watcher backend", { directory: Instance.directory, platform: process.platform, backend })
92+
const subs: ParcelWatcher.AsyncSubscription[] = []
93+
yield* Effect.addFinalizer(() =>
94+
Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
95+
)
9296

93-
const subs: ParcelWatcher.AsyncSubscription[] = []
94-
yield* Effect.addFinalizer(() =>
95-
Effect.promise(() => Promise.allSettled(subs.map((sub) => sub.unsubscribe()))),
97+
const cb: ParcelWatcher.SubscribeCallback = Instance.bind((err, evts) => {
98+
if (err) return
99+
for (const evt of evts) {
100+
if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
101+
if (evt.type === "update") void Bus.publish(Event.Updated, { file: evt.path, event: "change" })
102+
if (evt.type === "delete") void Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
103+
}
104+
})
105+
106+
const subscribe = (dir: string, ignore: string[]) => {
107+
const pending = w.subscribe(dir, cb, { ignore, backend })
108+
return Effect.gen(function* () {
109+
const sub = yield* Effect.promise(() => pending)
110+
subs.push(sub)
111+
}).pipe(
112+
Effect.timeout(SUBSCRIBE_TIMEOUT_MS),
113+
Effect.catchCause((cause) => {
114+
log.error("failed to subscribe", { dir, cause: Cause.pretty(cause) })
115+
pending.then((s) => s.unsubscribe()).catch(() => {})
116+
return Effect.void
117+
}),
96118
)
97-
98-
const cb: ParcelWatcher.SubscribeCallback = Instance.bind((err, evts) => {
99-
if (err) return
100-
for (const evt of evts) {
101-
if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
102-
if (evt.type === "update") void Bus.publish(Event.Updated, { file: evt.path, event: "change" })
103-
if (evt.type === "delete") void Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
104-
}
119+
}
120+
121+
const cfg = yield* config.get()
122+
const cfgIgnores = cfg.watcher?.ignore ?? []
123+
124+
if (yield* Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
125+
yield* subscribe(Instance.directory, [
126+
...FileIgnore.PATTERNS,
127+
...cfgIgnores,
128+
...protecteds(Instance.directory),
129+
])
130+
}
131+
132+
if (Instance.project.vcs === "git") {
133+
const result = yield* git.run(["rev-parse", "--git-dir"], {
134+
cwd: Instance.project.worktree,
105135
})
106-
107-
const subscribe = (dir: string, ignore: string[]) => {
108-
const pending = w.subscribe(dir, cb, { ignore, backend })
109-
return Effect.gen(function* () {
110-
const sub = yield* Effect.promise(() => pending)
111-
subs.push(sub)
112-
}).pipe(
113-
Effect.timeout(SUBSCRIBE_TIMEOUT_MS),
114-
Effect.catchCause((cause) => {
115-
log.error("failed to subscribe", { dir, cause: Cause.pretty(cause) })
116-
pending.then((s) => s.unsubscribe()).catch(() => {})
117-
return Effect.void
118-
}),
136+
const vcsDir =
137+
result.exitCode === 0 ? path.resolve(Instance.project.worktree, result.text().trim()) : undefined
138+
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
139+
const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter(
140+
(entry) => entry !== "HEAD",
119141
)
142+
yield* subscribe(vcsDir, ignore)
120143
}
144+
}
145+
},
146+
Effect.catchCause((cause) => {
147+
log.error("failed to init watcher service", { cause: Cause.pretty(cause) })
148+
return Effect.void
149+
}),
150+
),
151+
)
121152

122-
const cfg = yield* config.get()
123-
const cfgIgnores = cfg.watcher?.ignore ?? []
124-
125-
if (yield* Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
126-
yield* subscribe(Instance.directory, [
127-
...FileIgnore.PATTERNS,
128-
...cfgIgnores,
129-
...protecteds(Instance.directory),
130-
])
131-
}
153+
return Service.of({
154+
init: Effect.fn("FileWatcher.init")(function* () {
155+
yield* InstanceState.get(state)
156+
}),
157+
})
158+
}),
159+
)
132160

133-
if (Instance.project.vcs === "git") {
134-
const result = yield* git.run(["rev-parse", "--git-dir"], {
135-
cwd: Instance.project.worktree,
136-
})
137-
const vcsDir =
138-
result.exitCode === 0 ? path.resolve(Instance.project.worktree, result.text().trim()) : undefined
139-
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
140-
const ignore = (yield* Effect.promise(() => readdir(vcsDir).catch(() => []))).filter(
141-
(entry) => entry !== "HEAD",
142-
)
143-
yield* subscribe(vcsDir, ignore)
144-
}
145-
}
146-
},
147-
Effect.catchCause((cause) => {
148-
log.error("failed to init watcher service", { cause: Cause.pretty(cause) })
149-
return Effect.void
150-
}),
151-
),
152-
)
153-
154-
return Service.of({
155-
init: Effect.fn("FileWatcher.init")(function* () {
156-
yield* InstanceState.get(state)
157-
}),
158-
})
159-
}),
160-
)
161+
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Git.defaultLayer))
161162

162-
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Git.defaultLayer))
163-
}
163+
export * as FileWatcher from "./watcher"

0 commit comments

Comments
 (0)