gitnexus 1.6.11-rc.44 → 1.6.11-rc.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/analyze-watch.js +85 -32
- package/package.json +1 -1
|
@@ -179,13 +179,15 @@ export function shouldStopAfterWatchRefreshFailure(error, paths) {
|
|
|
179
179
|
export async function startWatchFileLoop(repoPath, debounceMs, refresh, onError, onWatcherError = (error) => onError(error, [])) {
|
|
180
180
|
let ignorePath = await createWatchIgnorePredicate(repoPath);
|
|
181
181
|
let ignoreControlValid = true;
|
|
182
|
+
let rearmPending = false;
|
|
183
|
+
let closed = false;
|
|
182
184
|
const queue = new WatchRefreshQueue(async (paths) => {
|
|
183
185
|
if (paths.some(isIgnoreControlPath) || !ignoreControlValid) {
|
|
184
186
|
const retryingInvalidControls = !ignoreControlValid;
|
|
185
187
|
try {
|
|
186
188
|
ignorePath = await createWatchIgnorePredicate(repoPath);
|
|
187
189
|
ignoreControlValid = true;
|
|
188
|
-
|
|
190
|
+
rearmPending = true;
|
|
189
191
|
}
|
|
190
192
|
catch (error) {
|
|
191
193
|
ignoreControlValid = false;
|
|
@@ -196,6 +198,12 @@ export async function startWatchFileLoop(repoPath, debounceMs, refresh, onError,
|
|
|
196
198
|
: error);
|
|
197
199
|
}
|
|
198
200
|
}
|
|
201
|
+
// Re-arm before refreshing, never after: the refresh reads the whole
|
|
202
|
+
// repository, so a write that lands while the replacement watcher is
|
|
203
|
+
// arming is still picked up by this refresh, and a write that lands
|
|
204
|
+
// afterwards is reported by the armed watcher.
|
|
205
|
+
if (rearmPending)
|
|
206
|
+
await rearmWatcher();
|
|
199
207
|
await refresh(paths);
|
|
200
208
|
}, onError, debounceMs, {
|
|
201
209
|
maxWaitMs: Math.max(2_000, debounceMs * 10),
|
|
@@ -203,46 +211,90 @@ export async function startWatchFileLoop(repoPath, debounceMs, refresh, onError,
|
|
|
203
211
|
holdEventsUntilInitialRefresh: true,
|
|
204
212
|
isPriorityPath: (filePath) => isIgnoreControlPath(filePath) || isConfigControlPath(filePath),
|
|
205
213
|
});
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
const createWatcher = () => {
|
|
215
|
+
const created = watch(repoPath, {
|
|
216
|
+
ignoreInitial: true,
|
|
217
|
+
atomic: true,
|
|
218
|
+
followSymlinks: false,
|
|
219
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 20 },
|
|
220
|
+
ignored: (candidate, stats) => {
|
|
221
|
+
const relative = repoRelativeWatchPath(repoPath, candidate);
|
|
222
|
+
if (relative !== null && isAnalyzerOwnedWatchPath(relative))
|
|
223
|
+
return true;
|
|
224
|
+
if (relative !== null && (isIgnoreControlPath(relative) || isConfigControlPath(relative))) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return ignorePath(candidate, stats?.isDirectory() ?? false);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
// Events from an instance being retired are kept: they overlap with the
|
|
231
|
+
// replacement's coverage and the queue coalesces the duplicates.
|
|
232
|
+
created.on('all', (event, changedPath) => {
|
|
233
|
+
if (event !== 'add' && event !== 'change' && event !== 'unlink')
|
|
234
|
+
return;
|
|
235
|
+
const relative = repoRelativeWatchPath(repoPath, changedPath);
|
|
236
|
+
if (relative && isRelevantWatchPath(relative) && !isAnalyzerOwnedWatchPath(relative)) {
|
|
237
|
+
queue.enqueue(relative);
|
|
217
238
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
239
|
+
});
|
|
240
|
+
created.on('error', (error) => {
|
|
241
|
+
// Replacement failures before the swap are reported through `waitUntilReady`.
|
|
242
|
+
if (created !== watcher)
|
|
243
|
+
return;
|
|
244
|
+
// Chokidar can surface a transient EPERM on Windows while an ignored
|
|
245
|
+
// analyzer-owned path is replaced. Re-arm the watcher and force one
|
|
246
|
+
// bounded catch-up refresh so a missed event cannot leave the graph
|
|
247
|
+
// stale. Other watcher errors may mean coverage was lost and stay fatal.
|
|
248
|
+
if (TRANSIENT_WATCH_ERROR_CODES.has(error.code ?? '')) {
|
|
249
|
+
rearmPending = true;
|
|
250
|
+
queue.enqueue(WATCH_FULL_REFRESH_PATH);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
onWatcherError(error);
|
|
254
|
+
});
|
|
255
|
+
return created;
|
|
256
|
+
};
|
|
257
|
+
let watcher = createWatcher();
|
|
258
|
+
// Chokidar emits `ready` once per instance and `add()` returns before the
|
|
259
|
+
// rescan it starts has finished, with no signal for that completion. A file
|
|
260
|
+
// an ignore-rule reload has just unignored is therefore still unregistered
|
|
261
|
+
// when `add()` returns, and because `ignoreInitial` suppresses the `add` the
|
|
262
|
+
// rescan would emit, an immediate rewrite of that file is dropped for good
|
|
263
|
+
// (reproduced on chokidar 4 and 5). So re-arm by arming a replacement
|
|
264
|
+
// watcher and awaiting its `ready` instead. The outgoing instance keeps
|
|
265
|
+
// reporting until the replacement is armed, so the swap has no blind window,
|
|
266
|
+
// and a replacement that fails to arm leaves the working instance in place.
|
|
267
|
+
const rearmWatcher = async () => {
|
|
268
|
+
rearmPending = false;
|
|
269
|
+
if (closed)
|
|
223
270
|
return;
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
271
|
+
const replacement = createWatcher();
|
|
272
|
+
try {
|
|
273
|
+
await waitUntilReady(replacement);
|
|
227
274
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return;
|
|
275
|
+
catch (error) {
|
|
276
|
+
rearmPending = true;
|
|
277
|
+
try {
|
|
278
|
+
await replacement.close();
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// The instance never became live; the arm error is the one to report.
|
|
282
|
+
}
|
|
283
|
+
throw new WatchControlReloadError(new Error('Unable to re-arm the filesystem watcher', { cause: error }));
|
|
238
284
|
}
|
|
239
|
-
|
|
240
|
-
|
|
285
|
+
const retired = watcher;
|
|
286
|
+
watcher = replacement;
|
|
287
|
+
await retired.close();
|
|
288
|
+
// `close()` can land between arming the replacement and the swap above.
|
|
289
|
+
if (closed)
|
|
290
|
+
await replacement.close();
|
|
291
|
+
};
|
|
241
292
|
try {
|
|
242
293
|
await waitUntilReady(watcher);
|
|
243
294
|
await queue.runInitial();
|
|
244
295
|
}
|
|
245
296
|
catch (error) {
|
|
297
|
+
closed = true;
|
|
246
298
|
await watcher.close();
|
|
247
299
|
await queue.close();
|
|
248
300
|
throw error;
|
|
@@ -250,6 +302,7 @@ export async function startWatchFileLoop(repoPath, debounceMs, refresh, onError,
|
|
|
250
302
|
return {
|
|
251
303
|
waitForIdle: () => queue.waitForIdle(),
|
|
252
304
|
close: async () => {
|
|
305
|
+
closed = true;
|
|
253
306
|
await watcher.close();
|
|
254
307
|
await queue.close();
|
|
255
308
|
},
|
package/package.json
CHANGED