11'use strict' ;
22
33const {
4- ArrayPrototypePush,
5- SafePromiseAllReturnVoid,
64 Promise,
7- PromisePrototypeThen,
85 SafeMap,
96 SafeSet,
107 StringPrototypeStartsWith,
@@ -31,47 +28,19 @@ const {
3128} = require ( 'path' ) ;
3229
3330let internalSync ;
34- let internalPromises ;
35-
36- function lazyLoadFsPromises ( ) {
37- internalPromises ??= require ( 'fs/promises' ) ;
38- return internalPromises ;
39- }
4031
4132function lazyLoadFsSync ( ) {
4233 internalSync ??= require ( 'fs' ) ;
4334 return internalSync ;
4435}
45- let kResistStopPropagation ;
46-
47- async function traverse ( dir , files = new SafeMap ( ) , symbolicLinks = new SafeSet ( ) ) {
48- const { opendir } = lazyLoadFsPromises ( ) ;
49-
50- const filenames = await opendir ( dir ) ;
51- const subdirectories = [ ] ;
52-
53- for await ( const file of filenames ) {
54- const f = pathJoin ( dir , file . name ) ;
55-
56- files . set ( f , file ) ;
57-
58- // Do not follow symbolic links
59- if ( file . isSymbolicLink ( ) ) {
60- symbolicLinks . add ( f ) ;
61- } else if ( file . isDirectory ( ) ) {
62- ArrayPrototypePush ( subdirectories , traverse ( f , files ) ) ;
63- }
64- }
65-
66- await SafePromiseAllReturnVoid ( subdirectories ) ;
6736
68- return files ;
69- }
37+ let kResistStopPropagation ;
7038
7139class FSWatcher extends EventEmitter {
7240 #options = null ;
7341 #closed = false ;
7442 #files = new SafeMap ( ) ;
43+ #watchers = new SafeMap ( ) ;
7544 #symbolicFiles = new SafeSet ( ) ;
7645 #rootPath = pathResolve ( ) ;
7746 #watchingFile = false ;
@@ -111,11 +80,11 @@ class FSWatcher extends EventEmitter {
11180 return ;
11281 }
11382
114- const { unwatchFile } = lazyLoadFsSync ( ) ;
11583 this . #closed = true ;
11684
11785 for ( const file of this . #files. keys ( ) ) {
118- unwatchFile ( file ) ;
86+ this . #watchers. get ( file ) . close ( ) ;
87+ this . #watchers. delete ( file ) ;
11988 }
12089
12190 this . #files. clear ( ) ;
@@ -124,24 +93,26 @@ class FSWatcher extends EventEmitter {
12493 }
12594
12695 #unwatchFiles( file ) {
127- const { unwatchFile } = lazyLoadFsSync ( ) ;
128-
12996 this . #symbolicFiles. delete ( file ) ;
13097
13198 for ( const filename of this . #files. keys ( ) ) {
13299 if ( StringPrototypeStartsWith ( filename , file ) ) {
133- unwatchFile ( filename ) ;
100+ this . #files. delete ( filename ) ;
101+ this . #watchers. get ( filename ) . close ( ) ;
102+ this . #watchers. delete ( filename ) ;
134103 }
135104 }
136105 }
137106
138- async #watchFolder( folder ) {
139- const { opendir } = lazyLoadFsPromises ( ) ;
107+ #watchFolder( folder ) {
108+ const { readdirSync } = lazyLoadFsSync ( ) ;
140109
141110 try {
142- const files = await opendir ( folder ) ;
111+ const files = readdirSync ( folder , {
112+ withFileTypes : true ,
113+ } ) ;
143114
144- for await ( const file of files ) {
115+ for ( const file of files ) {
145116 if ( this . #closed) {
146117 break ;
147118 }
@@ -155,11 +126,9 @@ class FSWatcher extends EventEmitter {
155126 this . #symbolicFiles. add ( f ) ;
156127 }
157128
158- this . #files. set ( f , file ) ;
159- if ( file . isFile ( ) ) {
160- this . #watchFile( f ) ;
161- } else if ( file . isDirectory ( ) && ! file . isSymbolicLink ( ) ) {
162- await this . #watchFolder( f ) ;
129+ this . #watchFile( f ) ;
130+ if ( file . isDirectory ( ) && ! file . isSymbolicLink ( ) ) {
131+ this . #watchFolder( f ) ;
163132 }
164133 }
165134 }
@@ -173,22 +142,30 @@ class FSWatcher extends EventEmitter {
173142 return ;
174143 }
175144
176- const { watchFile } = lazyLoadFsSync ( ) ;
177- const existingStat = this . #files. get ( file ) ;
145+ const { watch, statSync } = lazyLoadFsSync ( ) ;
146+
147+ if ( this . #files. has ( file ) ) {
148+ return ;
149+ }
150+
151+ {
152+ const existingStat = statSync ( file ) ;
153+ this . #files. set ( file , existingStat ) ;
154+ }
178155
179- watchFile ( file , {
156+ const watcher = watch ( file , {
180157 persistent : this . #options. persistent ,
181- } , ( currentStats , previousStats ) => {
182- if ( existingStat && ! existingStat . isDirectory ( ) &&
183- currentStats . nlink !== 0 && existingStat . mtimeMs === currentStats . mtimeMs ) {
184- return ;
185- }
158+ } , ( eventType , filename ) => {
159+ const existingStat = this . #files. get ( file ) ;
160+ const currentStats = statSync ( file ) ;
186161
187162 this . #files. set ( file , currentStats ) ;
188163
189- if ( currentStats . birthtimeMs === 0 && previousStats . birthtimeMs !== 0 ) {
164+ if ( currentStats . birthtimeMs === 0 && existingStat . birthtimeMs !== 0 ) {
190165 // The file is now deleted
191166 this . #files. delete ( file ) ;
167+ this . #watchers. delete ( file ) ;
168+ watcher . close ( ) ;
192169 this . emit ( 'change' , 'rename' , pathRelative ( this . #rootPath, file ) ) ;
193170 this . #unwatchFiles( file ) ;
194171 } else if ( file === this . #rootPath && this . #watchingFile) {
@@ -205,6 +182,7 @@ class FSWatcher extends EventEmitter {
205182 this . emit ( 'change' , 'change' , pathRelative ( this . #rootPath, file ) ) ;
206183 }
207184 } ) ;
185+ this . #watchers. set ( file , watcher ) ;
208186 }
209187
210188 [ kFSWatchStart ] ( filename ) {
@@ -217,19 +195,9 @@ class FSWatcher extends EventEmitter {
217195 this . #closed = false ;
218196 this . #watchingFile = file . isFile ( ) ;
219197
198+ this . #watchFile( filename ) ;
220199 if ( file . isDirectory ( ) ) {
221- this . #files. set ( filename , file ) ;
222-
223- PromisePrototypeThen (
224- traverse ( filename , this . #files, this . #symbolicFiles) ,
225- ( ) => {
226- for ( const f of this . #files. keys ( ) ) {
227- this . #watchFile( f ) ;
228- }
229- } ,
230- ) ;
231- } else {
232- this . #watchFile( filename ) ;
200+ this . #watchFolder( filename ) ;
233201 }
234202 } catch ( error ) {
235203 if ( error . code === 'ENOENT' ) {
@@ -264,7 +232,10 @@ class FSWatcher extends EventEmitter {
264232 resolve ( { __proto__ : null , value : { eventType, filename } } ) ;
265233 } ) ;
266234 } : ( resolve , reject ) => {
267- const onAbort = ( ) => reject ( new AbortError ( undefined , { cause : signal . reason } ) ) ;
235+ const onAbort = ( ) => {
236+ this . close ( ) ;
237+ reject ( new AbortError ( undefined , { cause : signal . reason } ) ) ;
238+ } ;
268239 if ( signal . aborted ) return onAbort ( ) ;
269240 kResistStopPropagation ??= require ( 'internal/event_target' ) . kResistStopPropagation ;
270241 signal . addEventListener ( 'abort' , onAbort , { __proto__ : null , once : true , [ kResistStopPropagation ] : true } ) ;
@@ -277,6 +248,10 @@ class FSWatcher extends EventEmitter {
277248 next : ( ) => ( this . #closed ?
278249 { __proto__ : null , done : true } :
279250 new Promise ( promiseExecutor ) ) ,
251+ return : ( ) => {
252+ this . close ( ) ;
253+ return { __proto__ : null , done : true } ;
254+ } ,
280255 [ SymbolAsyncIterator ] ( ) { return this ; } ,
281256 } ;
282257 }
0 commit comments