@@ -19,145 +19,145 @@ import { Log } from "../util"
1919
2020declare 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