@@ -14,10 +14,10 @@ const { TIMEOUT_MAX } = require('internal/timers');
14
14
15
15
const EventEmitter = require ( 'events' ) ;
16
16
const { addAbortListener } = require ( 'internal/events/abort_listener' ) ;
17
- const { watch } = require ( 'fs' ) ;
17
+ const { watch, existsSync } = require ( 'fs' ) ;
18
18
const { fileURLToPath } = require ( 'internal/url' ) ;
19
19
const { resolve, dirname } = require ( 'path' ) ;
20
- const { setTimeout } = require ( 'timers' ) ;
20
+ const { setTimeout, clearTimeout , setInterval , clearInterval } = require ( 'timers' ) ;
21
21
22
22
const supportsRecursiveWatching = process . platform === 'win32' ||
23
23
process . platform === 'darwin' ;
@@ -29,16 +29,26 @@ class FilesWatcher extends EventEmitter {
29
29
#depencencyOwners = new SafeMap ( ) ;
30
30
#ownerDependencies = new SafeMap ( ) ;
31
31
#debounce;
32
+ #renameInterval;
33
+ #renameTimeout;
32
34
#mode;
33
35
#signal;
34
36
35
- constructor ( { debounce = 200 , mode = 'filter' , signal } = kEmptyObject ) {
37
+ constructor ( {
38
+ debounce = 200 ,
39
+ mode = 'filter' ,
40
+ renameInterval = 1000 ,
41
+ renameTimeout = 60_000 ,
42
+ signal,
43
+ } = kEmptyObject ) {
36
44
super ( { __proto__ : null , captureRejections : true } ) ;
37
45
38
46
validateNumber ( debounce , 'options.debounce' , 0 , TIMEOUT_MAX ) ;
39
47
validateOneOf ( mode , 'options.mode' , [ 'filter' , 'all' ] ) ;
40
48
this . #debounce = debounce ;
41
49
this . #mode = mode ;
50
+ this . #renameInterval = renameInterval ;
51
+ this . #renameTimeout = renameTimeout ;
42
52
this . #signal = signal ;
43
53
44
54
if ( signal ) {
@@ -74,7 +84,10 @@ class FilesWatcher extends EventEmitter {
74
84
watcher . handle . close ( ) ;
75
85
}
76
86
77
- #onChange( trigger ) {
87
+ #onChange( eventType , trigger , recursive ) {
88
+ if ( eventType === 'rename' && ! recursive ) {
89
+ return this . #rewatch( trigger ) ;
90
+ }
78
91
if ( this . #debouncing. has ( trigger ) ) {
79
92
return ;
80
93
}
@@ -89,6 +102,39 @@ class FilesWatcher extends EventEmitter {
89
102
} , this . #debounce) . unref ( ) ;
90
103
}
91
104
105
+ // When a file is removed, wait for it to be re-added.
106
+ // Often this re-add is immediate - some editors (e.g., gedit) and some docker mount modes do this.
107
+ #rewatch( path ) {
108
+ if ( this . #isPathWatched( path ) ) {
109
+ this . #unwatch( this . #watchers. get ( path ) ) ;
110
+ this . #watchers. delete ( path ) ;
111
+ if ( existsSync ( path ) ) {
112
+ this . watchPath ( path , false ) ;
113
+ // This might be redundant. If the file was re-added due to a save event, we will probably see change -> rename.
114
+ // However, in certain situations it's entirely possible for the content to have changed after the rename
115
+ // In these situations we'd miss the change after the rename event
116
+ this . #onChange( 'change' , path , false ) ;
117
+ return ;
118
+ }
119
+ let timeout ;
120
+
121
+ // Wait for the file to exist - check every `renameInterval` ms
122
+ const interval = setInterval ( async ( ) => {
123
+ if ( existsSync ( path ) ) {
124
+ clearInterval ( interval ) ;
125
+ clearTimeout ( timeout ) ;
126
+ this . watchPath ( path , false ) ;
127
+ this . #onChange( 'change' , path , false ) ;
128
+ }
129
+ } , this . #renameInterval) . unref ( ) ;
130
+
131
+ // Don't wait forever - after `renameTimeout` ms, stop trying
132
+ timeout = setTimeout ( ( ) => {
133
+ clearInterval ( interval ) ;
134
+ } , this . #renameTimeout) . unref ( ) ;
135
+ }
136
+ }
137
+
92
138
get watchedPaths ( ) {
93
139
return [ ...this . #watchers. keys ( ) ] ;
94
140
}
@@ -101,7 +147,7 @@ class FilesWatcher extends EventEmitter {
101
147
watcher . on ( 'change' , ( eventType , fileName ) => {
102
148
// `fileName` can be `null` if it cannot be determined. See
103
149
// https://github.com/nodejs/node/pull/49891#issuecomment-1744673430.
104
- this . #onChange( recursive ? resolve ( path , fileName ?? '' ) : path ) ;
150
+ this . #onChange( eventType , recursive ? resolve ( path , fileName ) : path , recursive ) ;
105
151
} ) ;
106
152
this . #watchers. set ( path , { handle : watcher , recursive } ) ;
107
153
if ( recursive ) {
0 commit comments