@angular/build 22.2.2 → 22.2.4
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/package.json +3 -3
- package/src/builders/application/index-html-generator.js +6 -1
- package/src/builders/application/index-html-generator.js.map +1 -1
- package/src/builders/application/options.js +2 -2
- package/src/builders/application/options.js.map +1 -1
- package/src/builders/application/schema.json +1 -1
- package/src/builders/dev-server/schema.d.ts +4 -3
- package/src/builders/dev-server/schema.json +2 -2
- package/src/builders/dev-server/vite/index.js +6 -0
- package/src/builders/dev-server/vite/index.js.map +1 -1
- package/src/builders/extract-i18n/application-extraction.js +1 -0
- package/src/builders/extract-i18n/application-extraction.js.map +1 -1
- package/src/builders/karma/schema.json +2 -2
- package/src/builders/unit-test/runners/karma/executor.js +1 -1
- package/src/builders/unit-test/runners/karma/executor.js.map +1 -1
- package/src/builders/unit-test/runners/vitest/browser-provider.js +2 -1
- package/src/builders/unit-test/runners/vitest/browser-provider.js.map +1 -1
- package/src/builders/unit-test/runners/vitest/executor.js +0 -5
- package/src/builders/unit-test/runners/vitest/executor.js.map +1 -1
- package/src/builders/unit-test/runners/vitest/plugins.js +12 -0
- package/src/builders/unit-test/runners/vitest/plugins.js.map +1 -1
- package/src/builders/unit-test/schema.d.ts +4 -4
- package/src/builders/unit-test/schema.json +1 -1
- package/src/builders/unit-test/test-discovery.js +4 -2
- package/src/builders/unit-test/test-discovery.js.map +1 -1
- package/src/tools/angular/angular-host.js +7 -2
- package/src/tools/angular/angular-host.js.map +1 -1
- package/src/tools/angular/compilation/aot-compilation.js +2 -1
- package/src/tools/angular/compilation/aot-compilation.js.map +1 -1
- package/src/tools/angular/compilation/compiler-options.d.ts +1 -0
- package/src/tools/angular/compilation/compiler-options.js +4 -2
- package/src/tools/angular/compilation/compiler-options.js.map +1 -1
- package/src/tools/angular/compilation/diagnostics.js +2 -2
- package/src/tools/angular/compilation/diagnostics.js.map +1 -1
- package/src/tools/esbuild/angular/compiler-plugin.js +2 -0
- package/src/tools/esbuild/angular/compiler-plugin.js.map +1 -1
- package/src/tools/esbuild/angular/component-stylesheets.js +14 -1
- package/src/tools/esbuild/angular/component-stylesheets.js.map +1 -1
- package/src/tools/esbuild/application-code-bundle.js +7 -5
- package/src/tools/esbuild/application-code-bundle.js.map +1 -1
- package/src/tools/esbuild/global-styles.js +3 -1
- package/src/tools/esbuild/global-styles.js.map +1 -1
- package/src/tools/esbuild/persistent-load-result-cache.js +2 -4
- package/src/tools/esbuild/persistent-load-result-cache.js.map +1 -1
- package/src/tools/esbuild/stylesheets/sass-language.js +18 -24
- package/src/tools/esbuild/stylesheets/sass-language.js.map +1 -1
- package/src/tools/sass/rebasing-importer.d.ts +1 -1
- package/src/tools/sass/rebasing-importer.js +6 -3
- package/src/tools/sass/rebasing-importer.js.map +1 -1
- package/src/utils/concurrency.d.ts +4 -0
- package/src/utils/concurrency.js +5 -0
- package/src/utils/concurrency.js.map +1 -1
- package/src/utils/index-file/nonce.d.ts +2 -1
- package/src/utils/index-file/nonce.js +22 -4
- package/src/utils/index-file/nonce.js.map +1 -1
- package/src/utils/load-proxy-config.js +27 -1
- package/src/utils/load-proxy-config.js.map +1 -1
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/resolve-assets.js +28 -9
- package/src/utils/resolve-assets.js.map +1 -1
- package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.d.ts +1 -1
- package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +3 -3
- package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js.map +1 -1
- package/src/utils/server-rendering/esm-in-memory-loader/register-hooks.js +3 -2
- package/src/utils/server-rendering/esm-in-memory-loader/register-hooks.js.map +1 -1
- package/src/utils/watcher.d.ts +53 -0
- package/src/utils/watcher.js +521 -200
- package/src/utils/watcher.js.map +1 -1
package/src/utils/watcher.js
CHANGED
|
@@ -43,14 +43,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
43
43
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
44
|
};
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.ChangedFiles = void 0;
|
|
46
|
+
exports.ParcelExternalManager = exports.ChangedFiles = void 0;
|
|
47
47
|
exports.setupWatcher = setupWatcher;
|
|
48
48
|
exports.toPosixPathNormalized = toPosixPathNormalized;
|
|
49
49
|
exports.getDirectoryPath = getDirectoryPath;
|
|
50
50
|
exports.createWatcher = createWatcher;
|
|
51
51
|
exports.isPathInside = isPathInside;
|
|
52
52
|
exports.extractNodeModulesPackageDir = extractNodeModulesPackageDir;
|
|
53
|
-
const node_events_1 = require("node:events");
|
|
54
53
|
const fs = __importStar(require("node:fs"));
|
|
55
54
|
const path = __importStar(require("node:path"));
|
|
56
55
|
const picomatch_1 = __importDefault(require("picomatch"));
|
|
@@ -362,122 +361,450 @@ function extractNodeModulesPackageDir(posixPath) {
|
|
|
362
361
|
}
|
|
363
362
|
return `${nodeModulesPath}/${segments[0]}`;
|
|
364
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Maximum number of concurrent external directory subscriptions before coalescing
|
|
366
|
+
* sibling/cousin directories into common ancestor directories.
|
|
367
|
+
* Kept well below macOS's ~512 per-process FSEvents stream limit.
|
|
368
|
+
*/
|
|
369
|
+
const DEFAULT_MAX_EXTERNAL_SUBSCRIPTIONS = 256;
|
|
370
|
+
/**
|
|
371
|
+
* Checks whether any strict ancestor of the given trie node is already marked as a target.
|
|
372
|
+
*/
|
|
373
|
+
function hasTargetAncestor(node) {
|
|
374
|
+
let current = node.parent;
|
|
375
|
+
while (current) {
|
|
376
|
+
if (current.isTarget) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
current = current.parent;
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Manages `@parcel/watcher` directory subscriptions for watched files located outside the
|
|
385
|
+
* primary `workspaceRoot`.
|
|
386
|
+
*
|
|
387
|
+
* Because `@parcel/watcher` watches directories recursively and macOS limits each process to
|
|
388
|
+
* ~512 concurrent `FSEvents` streams, this manager:
|
|
389
|
+
* - Normalizes external `node_modules` file paths to their package root directory.
|
|
390
|
+
* - Batches synchronous `watchFile` and `removeFile` calls within a microtask before subscribing.
|
|
391
|
+
* - Uses a directory prefix tree to subsume nested child directories and iteratively coalesce
|
|
392
|
+
* deepest branching subdirectories into common ancestors when the subscription budget is exceeded.
|
|
393
|
+
* - Dynamically lowers its subscription budget and coalesces further if `subscribe()` fails on
|
|
394
|
+
* an existing directory due to OS stream limits.
|
|
395
|
+
*/
|
|
365
396
|
class ParcelExternalManager {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
397
|
+
/** Active `@parcel/watcher` subscriptions keyed by subscribed directory lookup key. */
|
|
398
|
+
#extraSubscriptions = new Map();
|
|
399
|
+
/** Reference-counted watched files grouped by their target external directory lookup key. */
|
|
400
|
+
#externalDirFiles = new Map();
|
|
401
|
+
/** Reverse lookup mapping each watched file lookup key to its target directory lookup key. */
|
|
402
|
+
#fileToDirKey = new Map();
|
|
403
|
+
/** The `@parcel/watcher` module instance. */
|
|
404
|
+
#parcelWatcher;
|
|
405
|
+
/** Watcher configuration options including ignore patterns. */
|
|
406
|
+
#options;
|
|
407
|
+
/** Normalized POSIX path of the primary watched workspace root. */
|
|
408
|
+
#rootDirPosix;
|
|
409
|
+
/** Case-normalized lookup key of the primary watched workspace root. */
|
|
410
|
+
#rootDirLookupKey;
|
|
411
|
+
/** Whether the underlying filesystem is case-sensitive. */
|
|
412
|
+
#isCaseSensitive;
|
|
413
|
+
/** Callback invoked when `@parcel/watcher` emits filesystem events. */
|
|
414
|
+
#handleEvents;
|
|
415
|
+
/** Current maximum number of concurrent external directory subscriptions allowed before coalescing. */
|
|
416
|
+
#maxSubscriptions;
|
|
417
|
+
/** Whether external directory state has changed and requires a reconciliation pass. */
|
|
418
|
+
#needsReconcile = false;
|
|
419
|
+
/** Whether obsolete subscriptions should be unsubscribed before creating new ones (used after stream limit errors). */
|
|
420
|
+
#shrinkBeforeSubscribe = false;
|
|
421
|
+
/** In-flight promise for the currently scheduled or running reconciliation loop. */
|
|
422
|
+
#reconcilePromise;
|
|
423
|
+
/** Whether this manager has been closed. */
|
|
424
|
+
#isClosed = false;
|
|
425
|
+
/**
|
|
426
|
+
* Creates a new external directory subscription manager for `@parcel/watcher`.
|
|
427
|
+
*
|
|
428
|
+
* @param parcelWatcher The `@parcel/watcher` module.
|
|
429
|
+
* @param options Watcher configuration options.
|
|
430
|
+
* @param rootDirPosix Normalized POSIX path of the primary workspace root.
|
|
431
|
+
* @param rootDirLookupKey Case-normalized lookup key of the primary workspace root.
|
|
432
|
+
* @param isCaseSensitive Whether the filesystem at the workspace root is case-sensitive.
|
|
433
|
+
* @param handleEvents Callback to process incoming `@parcel/watcher` events.
|
|
434
|
+
* @param maxSubscriptions Maximum number of concurrent external directory subscriptions before coalescing.
|
|
435
|
+
*/
|
|
436
|
+
constructor(parcelWatcher, options, rootDirPosix, rootDirLookupKey, isCaseSensitive, handleEvents, maxSubscriptions = DEFAULT_MAX_EXTERNAL_SUBSCRIPTIONS) {
|
|
437
|
+
this.#parcelWatcher = parcelWatcher;
|
|
438
|
+
this.#options = options;
|
|
439
|
+
this.#rootDirPosix = rootDirPosix;
|
|
440
|
+
this.#rootDirLookupKey = rootDirLookupKey;
|
|
441
|
+
this.#isCaseSensitive = isCaseSensitive;
|
|
442
|
+
this.#handleEvents = handleEvents;
|
|
443
|
+
this.#maxSubscriptions = Math.max(1, maxSubscriptions);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Registers a file path to be watched if it resides outside the primary workspace root,
|
|
447
|
+
* and schedules an asynchronous subscription reconciliation pass if needed.
|
|
448
|
+
*
|
|
449
|
+
* @param posixPath Normalized POSIX file path preserving original casing.
|
|
450
|
+
* @param lookupKey Case-normalized lookup key for the file path.
|
|
451
|
+
*/
|
|
452
|
+
watchFile(posixPath, lookupKey) {
|
|
453
|
+
if (this.#isClosed ||
|
|
454
|
+
isPathInside(lookupKey, this.#rootDirLookupKey) ||
|
|
455
|
+
lookupKey === this.#rootDirLookupKey) {
|
|
381
456
|
return;
|
|
382
457
|
}
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
458
|
+
const pkgDir = extractNodeModulesPackageDir(posixPath);
|
|
459
|
+
const dirPath = pkgDir ?? getDirectoryPath(posixPath);
|
|
460
|
+
const dirKey = pkgDir
|
|
461
|
+
? toLookupKey(pkgDir, this.#isCaseSensitive)
|
|
462
|
+
: getDirectoryPath(lookupKey);
|
|
463
|
+
this.#fileToDirKey.set(lookupKey, dirKey);
|
|
464
|
+
let dirEntry = this.#externalDirFiles.get(dirKey);
|
|
465
|
+
const isNewDir = !dirEntry;
|
|
386
466
|
if (!dirEntry) {
|
|
387
467
|
dirEntry = { dirPath, files: new Set() };
|
|
388
|
-
this
|
|
468
|
+
this.#externalDirFiles.set(dirKey, dirEntry);
|
|
389
469
|
}
|
|
390
470
|
dirEntry.files.add(lookupKey);
|
|
391
|
-
|
|
471
|
+
if (isNewDir || !this.#isCoveredByActiveSubscription(dirKey)) {
|
|
472
|
+
this.#scheduleReconcile();
|
|
473
|
+
}
|
|
392
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Unregisters a previously watched external file and schedules a reconciliation pass
|
|
477
|
+
* if it was the last watched file in its target directory.
|
|
478
|
+
*
|
|
479
|
+
* @param lookupKey Case-normalized lookup key for the file path to remove.
|
|
480
|
+
*/
|
|
393
481
|
removeFile(lookupKey) {
|
|
394
|
-
|
|
482
|
+
const dirKey = this.#fileToDirKey.get(lookupKey);
|
|
483
|
+
if (!dirKey) {
|
|
395
484
|
return;
|
|
396
485
|
}
|
|
397
|
-
|
|
398
|
-
const dirEntry = this
|
|
486
|
+
this.#fileToDirKey.delete(lookupKey);
|
|
487
|
+
const dirEntry = this.#externalDirFiles.get(dirKey);
|
|
399
488
|
if (dirEntry) {
|
|
400
489
|
dirEntry.files.delete(lookupKey);
|
|
401
490
|
if (dirEntry.files.size === 0) {
|
|
402
|
-
this
|
|
403
|
-
|
|
404
|
-
if (sub) {
|
|
405
|
-
this.extraSubscriptions.delete(dirKey);
|
|
406
|
-
sub.unsubscribe().catch(() => { });
|
|
407
|
-
for (const [remainingDirKey, remainingDirEntry] of this.externalDirFiles.entries()) {
|
|
408
|
-
if (!this.isCoveredByExistingExternal(remainingDirKey)) {
|
|
409
|
-
this.ensureDirWatched(remainingDirEntry.dirPath, remainingDirKey).catch(() => { });
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
491
|
+
this.#externalDirFiles.delete(dirKey);
|
|
492
|
+
this.#scheduleReconcile();
|
|
413
493
|
}
|
|
414
494
|
}
|
|
415
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Waits for any scheduled or in-progress subscription reconciliation pass to complete.
|
|
498
|
+
*/
|
|
499
|
+
async flush() {
|
|
500
|
+
if (this.#reconcilePromise) {
|
|
501
|
+
await this.#reconcilePromise;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Closes the manager, awaiting any pending reconciliation and unsubscribing all active
|
|
506
|
+
* external directory subscriptions.
|
|
507
|
+
*/
|
|
416
508
|
async close() {
|
|
509
|
+
this.#isClosed = true;
|
|
510
|
+
this.#needsReconcile = false;
|
|
417
511
|
try {
|
|
418
|
-
if (this
|
|
419
|
-
await
|
|
512
|
+
if (this.#reconcilePromise) {
|
|
513
|
+
await this.#reconcilePromise.catch(() => { });
|
|
420
514
|
}
|
|
421
|
-
if (this
|
|
422
|
-
await Promise.allSettled(Array.from(this
|
|
515
|
+
if (this.#extraSubscriptions.size > 0) {
|
|
516
|
+
await Promise.allSettled(Array.from(this.#extraSubscriptions.values(), (sub) => sub.unsubscribe()));
|
|
423
517
|
}
|
|
424
518
|
}
|
|
425
519
|
finally {
|
|
426
|
-
this
|
|
427
|
-
this.
|
|
428
|
-
this.
|
|
520
|
+
this.#extraSubscriptions.clear();
|
|
521
|
+
this.#externalDirFiles.clear();
|
|
522
|
+
this.#fileToDirKey.clear();
|
|
429
523
|
}
|
|
430
524
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
525
|
+
/**
|
|
526
|
+
* Schedules a microtask to reconcile active `@parcel/watcher` subscriptions with the
|
|
527
|
+
* current set of external directories.
|
|
528
|
+
*/
|
|
529
|
+
#scheduleReconcile() {
|
|
530
|
+
if (this.#isClosed) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
this.#needsReconcile = true;
|
|
534
|
+
this.#reconcilePromise ??= Promise.resolve()
|
|
535
|
+
.then(() => this.#runReconcileLoop())
|
|
536
|
+
.finally(() => {
|
|
537
|
+
this.#reconcilePromise = undefined;
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Continuously executes reconciliation passes until no further changes are pending.
|
|
542
|
+
*/
|
|
543
|
+
async #runReconcileLoop() {
|
|
544
|
+
while (this.#needsReconcile && !this.#isClosed) {
|
|
545
|
+
this.#needsReconcile = false;
|
|
546
|
+
await this.#reconcileOnce();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Performs a single reconciliation pass: computes the desired coalesced directory set,
|
|
551
|
+
* subscribes to newly required directories, unsubscribes obsolete directories, and
|
|
552
|
+
* triggers fallback coalescing if OS stream limits are encountered.
|
|
553
|
+
*/
|
|
554
|
+
async #reconcileOnce() {
|
|
555
|
+
const desiredDirs = this.#computeDesiredDirectories();
|
|
556
|
+
if (this.#shrinkBeforeSubscribe) {
|
|
557
|
+
this.#shrinkBeforeSubscribe = false;
|
|
558
|
+
const toUnsubscribe = [];
|
|
559
|
+
for (const [existingKey, sub] of this.#extraSubscriptions.entries()) {
|
|
560
|
+
if (!desiredDirs.has(existingKey)) {
|
|
561
|
+
this.#extraSubscriptions.delete(existingKey);
|
|
562
|
+
toUnsubscribe.push(sub.unsubscribe().catch(() => { }));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (toUnsubscribe.length > 0) {
|
|
566
|
+
await Promise.allSettled(toUnsubscribe);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (this.#isClosed) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const toSubscribe = [];
|
|
573
|
+
for (const [dirKey, dirPath] of desiredDirs.entries()) {
|
|
574
|
+
if (!this.#extraSubscriptions.has(dirKey)) {
|
|
575
|
+
toSubscribe.push([dirKey, dirPath]);
|
|
435
576
|
}
|
|
436
577
|
}
|
|
437
|
-
|
|
438
|
-
|
|
578
|
+
let hadExistingDirFailure = false;
|
|
579
|
+
if (toSubscribe.length > 0) {
|
|
580
|
+
await Promise.allSettled(toSubscribe.map(async ([dirKey, dirPath]) => {
|
|
581
|
+
const ignore = isPathInside(this.#rootDirLookupKey, dirKey)
|
|
582
|
+
? [...(this.#options?.ignored ?? []), this.#rootDirPosix, `${this.#rootDirPosix}/**`]
|
|
583
|
+
: this.#options?.ignored;
|
|
584
|
+
try {
|
|
585
|
+
const sub = await this.#parcelWatcher.subscribe(dirPath, (err, events) => {
|
|
586
|
+
if (!err) {
|
|
587
|
+
this.#handleEvents(events);
|
|
588
|
+
}
|
|
589
|
+
}, { ignore });
|
|
590
|
+
if (this.#isClosed) {
|
|
591
|
+
await sub.unsubscribe().catch(() => { });
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this.#extraSubscriptions.set(dirKey, sub);
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
// Ignore errors for missing directories; if the directory exists on disk,
|
|
598
|
+
// mark failure so the manager can coalesce further if over OS stream limits.
|
|
599
|
+
if (!this.#isClosed && fs.existsSync(dirPath)) {
|
|
600
|
+
hadExistingDirFailure = true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
if (this.#isClosed) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
// Unsubscribe any previous subscriptions that are no longer desired and whose
|
|
609
|
+
// remaining watched files (if any) are now covered by another active subscription.
|
|
610
|
+
for (const [existingKey, sub] of this.#extraSubscriptions.entries()) {
|
|
611
|
+
if (!desiredDirs.has(existingKey) && this.#canRemoveSubscription(existingKey)) {
|
|
612
|
+
this.#extraSubscriptions.delete(existingKey);
|
|
613
|
+
sub.unsubscribe().catch(() => { });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// If subscribing to an existing directory failed (e.g. macOS FSEvents per-process limit),
|
|
617
|
+
// dynamically lower the subscription budget and re-reconcile to coalesce into ancestors.
|
|
618
|
+
if (hadExistingDirFailure && desiredDirs.size > 1 && this.#maxSubscriptions > 1) {
|
|
619
|
+
const nextMax = this.#extraSubscriptions.size > 0
|
|
620
|
+
? Math.max(1, Math.floor(this.#extraSubscriptions.size / 2))
|
|
621
|
+
: Math.max(1, Math.floor(Math.min(this.#maxSubscriptions, desiredDirs.size) / 2));
|
|
622
|
+
if (nextMax < desiredDirs.size && nextMax < this.#maxSubscriptions) {
|
|
623
|
+
this.#maxSubscriptions = nextMax;
|
|
624
|
+
this.#shrinkBeforeSubscribe = true;
|
|
625
|
+
this.#needsReconcile = true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Checks whether a directory lookup key is already covered by an active subscription.
|
|
631
|
+
*/
|
|
632
|
+
#isCoveredByActiveSubscription(dirLookupKey) {
|
|
633
|
+
for (const existingDir of this.#extraSubscriptions.keys()) {
|
|
634
|
+
if (dirLookupKey === existingDir || isPathInside(dirLookupKey, existingDir)) {
|
|
439
635
|
return true;
|
|
440
636
|
}
|
|
441
637
|
}
|
|
442
638
|
return false;
|
|
443
639
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
640
|
+
/**
|
|
641
|
+
* Determines whether an existing subscription can be safely removed without leaving
|
|
642
|
+
* any remaining watched target directories uncovered.
|
|
643
|
+
*/
|
|
644
|
+
#canRemoveSubscription(subKey) {
|
|
645
|
+
for (const targetDirKey of this.#externalDirFiles.keys()) {
|
|
646
|
+
if (targetDirKey === subKey || isPathInside(targetDirKey, subKey)) {
|
|
647
|
+
let coveredByOther = false;
|
|
648
|
+
for (const otherSubKey of this.#extraSubscriptions.keys()) {
|
|
649
|
+
if (otherSubKey !== subKey &&
|
|
650
|
+
(targetDirKey === otherSubKey || isPathInside(targetDirKey, otherSubKey))) {
|
|
651
|
+
coveredByOther = true;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (!coveredByOther) {
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
447
659
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Computes the minimal set of external directories to subscribe to (mapped from `dirKey` to `dirPath`),
|
|
664
|
+
* subsuming nested directories and coalescing branching ancestors to stay within `#maxSubscriptions`.
|
|
665
|
+
*/
|
|
666
|
+
#computeDesiredDirectories() {
|
|
667
|
+
if (this.#externalDirFiles.size === 0) {
|
|
668
|
+
return new Map();
|
|
669
|
+
}
|
|
670
|
+
const nodesByKey = new Map();
|
|
671
|
+
const roots = [];
|
|
672
|
+
for (const [dirKey, { dirPath }] of this.#externalDirFiles.entries()) {
|
|
673
|
+
const chain = [];
|
|
674
|
+
let currentKey = dirKey;
|
|
675
|
+
let currentPath = dirPath;
|
|
676
|
+
while (true) {
|
|
677
|
+
chain.push({ dirKey: currentKey, dirPath: currentPath });
|
|
678
|
+
const parentKey = getDirectoryPath(currentKey);
|
|
679
|
+
if (parentKey === currentKey) {
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
currentKey = parentKey;
|
|
683
|
+
currentPath = getDirectoryPath(currentPath);
|
|
451
684
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
685
|
+
let parentNode;
|
|
686
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
687
|
+
const { dirKey: nodeKey, dirPath: nodePath } = chain[i];
|
|
688
|
+
const depth = chain.length - 1 - i;
|
|
689
|
+
let node = nodesByKey.get(nodeKey);
|
|
690
|
+
if (!node) {
|
|
691
|
+
node = {
|
|
692
|
+
dirKey: nodeKey,
|
|
693
|
+
dirPath: nodePath,
|
|
694
|
+
depth,
|
|
695
|
+
parent: parentNode,
|
|
696
|
+
children: new Map(),
|
|
697
|
+
isTarget: false,
|
|
698
|
+
leafCount: 0,
|
|
699
|
+
};
|
|
700
|
+
nodesByKey.set(nodeKey, node);
|
|
701
|
+
if (parentNode) {
|
|
702
|
+
parentNode.children.set(nodeKey, node);
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
roots.push(node);
|
|
466
706
|
}
|
|
467
707
|
}
|
|
708
|
+
parentNode = node;
|
|
468
709
|
}
|
|
469
|
-
|
|
470
|
-
|
|
710
|
+
if (parentNode) {
|
|
711
|
+
parentNode.isTarget = true;
|
|
471
712
|
}
|
|
472
713
|
}
|
|
473
|
-
|
|
474
|
-
|
|
714
|
+
const initSubtree = (node) => {
|
|
715
|
+
if (node.isTarget) {
|
|
716
|
+
node.children.clear();
|
|
717
|
+
node.leafCount = 1;
|
|
718
|
+
return 1;
|
|
719
|
+
}
|
|
720
|
+
let count = 0;
|
|
721
|
+
for (const child of node.children.values()) {
|
|
722
|
+
count += initSubtree(child);
|
|
723
|
+
}
|
|
724
|
+
node.leafCount = count;
|
|
725
|
+
return count;
|
|
726
|
+
};
|
|
727
|
+
let totalLeaves = 0;
|
|
728
|
+
for (const root of roots) {
|
|
729
|
+
totalLeaves += initSubtree(root);
|
|
475
730
|
}
|
|
476
|
-
|
|
477
|
-
|
|
731
|
+
if (totalLeaves > this.#maxSubscriptions) {
|
|
732
|
+
// Tier 1: non-ancestors of workspaceRoot (e.g., <monorepo>/libs, <monorepo>/node_modules)
|
|
733
|
+
// Tier 2: ancestors of workspaceRoot (e.g., <monorepo> when sibling apps/libs are directly under it)
|
|
734
|
+
const tier1ByDepth = new Map();
|
|
735
|
+
const tier2ByDepth = new Map();
|
|
736
|
+
const collectInternalNodes = (node) => {
|
|
737
|
+
if (node.isTarget) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (node.parent !== undefined && node.leafCount > 1) {
|
|
741
|
+
const isWorkspaceAncestor = isPathInside(this.#rootDirLookupKey, node.dirKey);
|
|
742
|
+
const bucketMap = isWorkspaceAncestor ? tier2ByDepth : tier1ByDepth;
|
|
743
|
+
let bucket = bucketMap.get(node.depth);
|
|
744
|
+
if (!bucket) {
|
|
745
|
+
bucket = [];
|
|
746
|
+
bucketMap.set(node.depth, bucket);
|
|
747
|
+
}
|
|
748
|
+
bucket.push(node);
|
|
749
|
+
}
|
|
750
|
+
for (const child of node.children.values()) {
|
|
751
|
+
collectInternalNodes(child);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
for (const root of roots) {
|
|
755
|
+
collectInternalNodes(root);
|
|
756
|
+
}
|
|
757
|
+
const collapseTier = (byDepth) => {
|
|
758
|
+
const sortedLevels = Array.from(byDepth.entries()).sort(([a], [b]) => b - a);
|
|
759
|
+
for (const [, nodes] of sortedLevels) {
|
|
760
|
+
if (totalLeaves <= this.#maxSubscriptions) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const candidates = nodes
|
|
764
|
+
.filter((n) => n.leafCount > 1 && !hasTargetAncestor(n))
|
|
765
|
+
.sort((a, b) => b.leafCount - a.leafCount || a.dirKey.localeCompare(b.dirKey));
|
|
766
|
+
for (const candidate of candidates) {
|
|
767
|
+
if (totalLeaves <= this.#maxSubscriptions) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (candidate.leafCount <= 1 || hasTargetAncestor(candidate)) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const reduction = candidate.leafCount - 1;
|
|
774
|
+
candidate.isTarget = true;
|
|
775
|
+
candidate.children.clear();
|
|
776
|
+
candidate.leafCount = 1;
|
|
777
|
+
totalLeaves -= reduction;
|
|
778
|
+
let ancestor = candidate.parent;
|
|
779
|
+
while (ancestor) {
|
|
780
|
+
ancestor.leafCount -= reduction;
|
|
781
|
+
ancestor = ancestor.parent;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
collapseTier(tier1ByDepth);
|
|
787
|
+
if (totalLeaves > this.#maxSubscriptions) {
|
|
788
|
+
collapseTier(tier2ByDepth);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const desired = new Map();
|
|
792
|
+
const collectTargets = (node) => {
|
|
793
|
+
if (node.isTarget) {
|
|
794
|
+
desired.set(node.dirKey, node.dirPath);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
for (const child of node.children.values()) {
|
|
798
|
+
collectTargets(child);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
for (const root of roots) {
|
|
802
|
+
collectTargets(root);
|
|
478
803
|
}
|
|
804
|
+
return desired;
|
|
479
805
|
}
|
|
480
806
|
}
|
|
807
|
+
exports.ParcelExternalManager = ParcelExternalManager;
|
|
481
808
|
async function createParcelWatcher(options, parcelWatcher) {
|
|
482
809
|
const watchedFiles = new Set();
|
|
483
810
|
const queue = new WatcherQueue();
|
|
@@ -516,7 +843,7 @@ async function createParcelWatcher(options, parcelWatcher) {
|
|
|
516
843
|
}, {
|
|
517
844
|
ignore: options?.ignored,
|
|
518
845
|
});
|
|
519
|
-
const externalManager = new ParcelExternalManager(parcelWatcher, options, rootDirLookupKey, handleEvents);
|
|
846
|
+
const externalManager = new ParcelExternalManager(parcelWatcher, options, rootDirPosix, rootDirLookupKey, isCaseSensitive, handleEvents);
|
|
520
847
|
const buildWatcher = {
|
|
521
848
|
[Symbol.asyncIterator]() {
|
|
522
849
|
return this;
|
|
@@ -531,7 +858,7 @@ async function createParcelWatcher(options, parcelWatcher) {
|
|
|
531
858
|
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
|
|
532
859
|
if (!watchedFiles.has(lookupKey)) {
|
|
533
860
|
watchedFiles.add(lookupKey);
|
|
534
|
-
|
|
861
|
+
externalManager.watchFile(posixPath, lookupKey);
|
|
535
862
|
}
|
|
536
863
|
}
|
|
537
864
|
},
|
|
@@ -559,119 +886,91 @@ async function createParcelWatcher(options, parcelWatcher) {
|
|
|
559
886
|
};
|
|
560
887
|
return buildWatcher;
|
|
561
888
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
watchedInPkg = new Set();
|
|
586
|
-
this.allowedPackages.set(packageKey, watchedInPkg);
|
|
587
|
-
watchedInPkg.add(lookupKey);
|
|
588
|
-
return { isPackage: true, newPkgDir: packageDir };
|
|
589
|
-
}
|
|
590
|
-
watchedInPkg.add(lookupKey);
|
|
591
|
-
return { isPackage: true };
|
|
592
|
-
}
|
|
593
|
-
removePackageFile(posixPath, lookupKey) {
|
|
594
|
-
const packageDir = extractNodeModulesPackageDir(posixPath);
|
|
595
|
-
if (!packageDir) {
|
|
596
|
-
return { isPackage: false };
|
|
597
|
-
}
|
|
598
|
-
const packageKey = toLookupKey(packageDir, this.isCaseSensitive);
|
|
599
|
-
const watchedInPkg = this.allowedPackages.get(packageKey);
|
|
600
|
-
if (watchedInPkg) {
|
|
601
|
-
watchedInPkg.delete(lookupKey);
|
|
602
|
-
if (watchedInPkg.size === 0) {
|
|
603
|
-
this.allowedPackages.delete(packageKey);
|
|
604
|
-
return { isPackage: true, unwatchPkgDir: packageDir };
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
return { isPackage: true };
|
|
608
|
-
}
|
|
609
|
-
isIgnored(filePath) {
|
|
610
|
-
// Fast path: avoid expensive normalization for non-node_modules files
|
|
611
|
-
if (!filePath.includes(NODE_MODULES)) {
|
|
612
|
-
return false;
|
|
613
|
-
}
|
|
614
|
-
const posixPath = toPosixPathNormalized(filePath);
|
|
615
|
-
const lookupKey = toLookupKey(posixPath, this.isCaseSensitive);
|
|
616
|
-
if (!`${lookupKey}/`.includes(NODE_MODULES_SEGMENT)) {
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
619
|
-
if (this.allowedPackages.has(lookupKey)) {
|
|
620
|
-
return false;
|
|
621
|
-
}
|
|
622
|
-
for (const pkg of this.allowedPackages.keys()) {
|
|
623
|
-
if (isPathInside(lookupKey, pkg)) {
|
|
889
|
+
function createChokidarIgnored(ignoredPatterns, isCaseSensitive, watchedFiles) {
|
|
890
|
+
// Strip static node_modules ignore patterns (e.g. `**/node_modules/**` from setupWatcher).
|
|
891
|
+
// If left in place, picomatch would unconditionally ignore all node_modules files and prevent
|
|
892
|
+
// watching explicitly added files inside node_modules.
|
|
893
|
+
const customIgnored = ignoredPatterns
|
|
894
|
+
?.filter((pattern) => {
|
|
895
|
+
const base = toPosixPathNormalized(pattern).replace(/\/\*\*$/, '');
|
|
896
|
+
return base !== NODE_MODULES && !base.endsWith(`/${NODE_MODULES}`);
|
|
897
|
+
})
|
|
898
|
+
.map((pattern) => {
|
|
899
|
+
if (/[*?[\]{}()]/.test(pattern)) {
|
|
900
|
+
const isMatch = (0, picomatch_1.default)(pattern, { dot: true });
|
|
901
|
+
return (filePath) => isMatch(toPosixPathNormalized(filePath));
|
|
902
|
+
}
|
|
903
|
+
return { path: toPosixPathNormalized(pattern), recursive: true };
|
|
904
|
+
}) ?? [];
|
|
905
|
+
// When watching a directory (e.g. projectRoot via NG_BUILD_WATCH_ROOT), prune `node_modules`
|
|
906
|
+
// directories unless explicitly added to `watchedFiles`. Explicitly added files inside
|
|
907
|
+
// `node_modules` do not end with `/node_modules` and remain watchable.
|
|
908
|
+
return [
|
|
909
|
+
...customIgnored,
|
|
910
|
+
(filePath) => {
|
|
911
|
+
if (!filePath.includes(NODE_MODULES)) {
|
|
624
912
|
return false;
|
|
625
913
|
}
|
|
914
|
+
const posixPath = toPosixPathNormalized(filePath);
|
|
915
|
+
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
|
|
916
|
+
return ((lookupKey === NODE_MODULES || lookupKey.endsWith(`/${NODE_MODULES}`)) &&
|
|
917
|
+
!watchedFiles.has(lookupKey));
|
|
918
|
+
},
|
|
919
|
+
];
|
|
920
|
+
}
|
|
921
|
+
// Chokidar initializes file watchers asynchronously (`await stat(path)` followed by
|
|
922
|
+
// `fs.watch` or `fs.watchFile`). If a file is modified or recreated immediately after
|
|
923
|
+
// `watcher.add()` is called, Chokidar may capture the post-modification state as its initial
|
|
924
|
+
// baseline (or suppress the initial `'add'` event due to `ignoreInitial: true`) and miss the
|
|
925
|
+
// change. Re-statting after a short delay catches any modifications or recreations that occurred
|
|
926
|
+
// during asynchronous watcher registration, while `fileStats` deduplicates if Chokidar also fired.
|
|
927
|
+
async function checkChokidarBaselines(baselineStats, watchedFiles, fileStats, watcher, handleEvent) {
|
|
928
|
+
await Promise.all(Array.from(baselineStats, async ([lookupKey, baseline]) => {
|
|
929
|
+
if (!watchedFiles.has(lookupKey)) {
|
|
930
|
+
return;
|
|
626
931
|
}
|
|
627
|
-
|
|
628
|
-
|
|
932
|
+
const currentStat = await fs.promises.stat(baseline.posixPath).catch(() => null);
|
|
933
|
+
if (!watchedFiles.has(lookupKey) || !currentStat?.isFile()) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (baseline.mtimeMs === undefined || baseline.size === undefined) {
|
|
937
|
+
const recorded = fileStats.get(lookupKey);
|
|
938
|
+
if (!recorded) {
|
|
939
|
+
watcher.add(baseline.posixPath);
|
|
940
|
+
handleEvent('added', path.normalize(baseline.posixPath));
|
|
941
|
+
}
|
|
942
|
+
else if (currentStat.mtimeMs !== recorded.mtimeMs || currentStat.size !== recorded.size) {
|
|
943
|
+
handleEvent('modified', path.normalize(baseline.posixPath));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
else if (currentStat.mtimeMs !== baseline.mtimeMs || currentStat.size !== baseline.size) {
|
|
947
|
+
handleEvent('modified', path.normalize(baseline.posixPath));
|
|
948
|
+
}
|
|
949
|
+
}));
|
|
629
950
|
}
|
|
630
951
|
async function createChokidarWatcher(options, chokidarModule) {
|
|
631
952
|
const chokidar = chokidarModule ?? (await Promise.resolve().then(() => __importStar(require('chokidar'))));
|
|
632
953
|
const watchedFiles = new Set();
|
|
633
954
|
const fileStats = new Map();
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
queue = new WatcherQueue();
|
|
643
|
-
}
|
|
955
|
+
const pendingTimers = new Set();
|
|
956
|
+
const pollingInterval = options?.interval ?? 100;
|
|
957
|
+
const debounceMs = Math.min(250, Math.max(100, Math.ceil(pollingInterval * 1.5)));
|
|
958
|
+
const queue = options?.polling
|
|
959
|
+
? new WatcherQueue(debounceMs, Math.max(500, debounceMs * 3))
|
|
960
|
+
: new WatcherQueue();
|
|
644
961
|
const rootDir = options?.cwd ?? process.cwd();
|
|
645
962
|
const isCaseSensitive = isFileSystemCaseSensitive(rootDir);
|
|
646
|
-
const
|
|
647
|
-
const
|
|
648
|
-
const nodeModulesManager = new ChokidarNodeModulesManager(isCaseSensitive);
|
|
649
|
-
// Strip static node_modules ignore patterns (e.g. `**/node_modules/**` from setupWatcher).
|
|
650
|
-
// If left in place, picomatch would unconditionally ignore all node_modules files and override
|
|
651
|
-
// our dynamic package allowlist above.
|
|
652
|
-
const customIgnored = options?.ignored
|
|
653
|
-
?.filter((pattern) => !pattern.includes(NODE_MODULES))
|
|
654
|
-
.map((pattern) => {
|
|
655
|
-
if (/[*?[\]{}()]/.test(pattern)) {
|
|
656
|
-
const isMatch = (0, picomatch_1.default)(pattern, { dot: true });
|
|
657
|
-
return (filePath) => isMatch(toPosixPathNormalized(filePath));
|
|
658
|
-
}
|
|
659
|
-
return { path: toPosixPathNormalized(pattern), recursive: true };
|
|
660
|
-
}) ?? [];
|
|
661
|
-
const ignored = [...customIgnored, (filePath) => nodeModulesManager.isIgnored(filePath)];
|
|
662
|
-
const watcher = chokidar.watch(rootDir, {
|
|
963
|
+
const ignored = createChokidarIgnored(options?.ignored, isCaseSensitive, watchedFiles);
|
|
964
|
+
const watcher = chokidar.watch([], {
|
|
663
965
|
ignoreInitial: true,
|
|
664
966
|
ignored,
|
|
665
967
|
followSymlinks: options?.followSymlinks,
|
|
666
968
|
usePolling: !!options?.polling,
|
|
667
969
|
interval: options?.interval,
|
|
668
970
|
});
|
|
971
|
+
// Prevent unhandled watcher errors (such as EMFILE or EACCES) from crashing the process.
|
|
972
|
+
watcher.on('error', () => { });
|
|
669
973
|
const initTime = Date.now();
|
|
670
|
-
// Wait for the watcher to complete its initial filesystem scan before returning.
|
|
671
|
-
// With `ignoreInitial: true`, any file visited during the initial scan is treated as the initial baseline
|
|
672
|
-
// and will not emit 'add' or 'change' events. Awaiting 'ready' ensures that rapid file modifications
|
|
673
|
-
// made right after watcher setup (e.g. in rebuild tests) are not swallowed as initial files.
|
|
674
|
-
await (0, node_events_1.once)(watcher, 'ready');
|
|
675
974
|
const handleEvent = (type, rawPath) => {
|
|
676
975
|
const posixPath = toPosixPathNormalized(rawPath);
|
|
677
976
|
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
|
|
@@ -684,13 +983,10 @@ async function createChokidarWatcher(options, chokidarModule) {
|
|
|
684
983
|
return;
|
|
685
984
|
}
|
|
686
985
|
const stat = fs.statSync(rawPath, { throwIfNoEntry: false });
|
|
687
|
-
if (!stat) {
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
986
|
// Ignore historical events from before watcher initialization, but allow a 1000 ms window
|
|
691
987
|
// to account for coarse filesystem timestamp resolution (e.g., ext4/overlayfs integer second
|
|
692
988
|
// mtime truncation on Linux) where files modified during startup may have truncated .000 ms mtimes.
|
|
693
|
-
if (stat.mtimeMs < initTime - 1000) {
|
|
989
|
+
if (!stat || stat.mtimeMs < initTime - 1000) {
|
|
694
990
|
return;
|
|
695
991
|
}
|
|
696
992
|
const previousStat = fileStats.get(lookupKey);
|
|
@@ -703,10 +999,45 @@ async function createChokidarWatcher(options, chokidarModule) {
|
|
|
703
999
|
fileStats.set(lookupKey, { mtimeMs: stat.mtimeMs, size: stat.size });
|
|
704
1000
|
queue.addChange(type, rawPath);
|
|
705
1001
|
};
|
|
1002
|
+
const scheduleBaselineCheck = (baselineStats) => {
|
|
1003
|
+
if (baselineStats.size === 0) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const timer = setTimeout(() => {
|
|
1007
|
+
pendingTimers.delete(timer);
|
|
1008
|
+
void checkChokidarBaselines(baselineStats, watchedFiles, fileStats, watcher, handleEvent);
|
|
1009
|
+
}, 50);
|
|
1010
|
+
timer.unref();
|
|
1011
|
+
pendingTimers.add(timer);
|
|
1012
|
+
};
|
|
706
1013
|
watcher.on('add', (path) => handleEvent('added', path));
|
|
707
1014
|
watcher.on('change', (path) => handleEvent('modified', path));
|
|
708
|
-
watcher.on('unlink', (
|
|
709
|
-
|
|
1015
|
+
watcher.on('unlink', (rawPath) => {
|
|
1016
|
+
const posixPath = toPosixPathNormalized(rawPath);
|
|
1017
|
+
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
|
|
1018
|
+
if (!isPathWatched(lookupKey, watchedFiles)) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
// When Chokidar processes a file removal, it closes the underlying file watcher unless only a
|
|
1022
|
+
// single directory is tracked (`_watched.size === 1`). Re-adding a directly watched path
|
|
1023
|
+
// ensures Chokidar continues watching either the recreated file (if replaced within the atomic
|
|
1024
|
+
// window) or its parent directory for future recreation.
|
|
1025
|
+
if (watchedFiles.has(lookupKey)) {
|
|
1026
|
+
fileStats.delete(lookupKey);
|
|
1027
|
+
watcher.add(posixPath);
|
|
1028
|
+
const stat = fs.statSync(rawPath, { throwIfNoEntry: false });
|
|
1029
|
+
if (stat?.isFile()) {
|
|
1030
|
+
handleEvent('modified', rawPath);
|
|
1031
|
+
scheduleBaselineCheck(new Map([[lookupKey, { posixPath, mtimeMs: stat.mtimeMs, size: stat.size }]]));
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
handleEvent('removed', rawPath);
|
|
1035
|
+
scheduleBaselineCheck(new Map([[lookupKey, { posixPath }]]));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
handleEvent('removed', rawPath);
|
|
1039
|
+
});
|
|
1040
|
+
return {
|
|
710
1041
|
[Symbol.asyncIterator]() {
|
|
711
1042
|
return this;
|
|
712
1043
|
},
|
|
@@ -728,26 +1059,22 @@ async function createChokidarWatcher(options, chokidarModule) {
|
|
|
728
1059
|
add(paths) {
|
|
729
1060
|
const targets = typeof paths === 'string' ? [paths] : paths;
|
|
730
1061
|
const newPaths = [];
|
|
1062
|
+
const baselineStats = new Map();
|
|
731
1063
|
for (const p of targets) {
|
|
732
1064
|
const posixPath = toPosixPathNormalized(p);
|
|
733
1065
|
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
|
|
734
1066
|
if (!watchedFiles.has(lookupKey)) {
|
|
735
1067
|
watchedFiles.add(lookupKey);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
newPaths.push(newPkgDir);
|
|
741
|
-
}
|
|
742
|
-
else if (!isPackage &&
|
|
743
|
-
!isPathInside(lookupKey, rootDirLookupKey) &&
|
|
744
|
-
lookupKey !== rootDirLookupKey) {
|
|
745
|
-
newPaths.push(posixPath);
|
|
1068
|
+
newPaths.push(posixPath);
|
|
1069
|
+
const stat = fs.statSync(posixPath, { throwIfNoEntry: false });
|
|
1070
|
+
if (stat?.isFile()) {
|
|
1071
|
+
baselineStats.set(lookupKey, { posixPath, mtimeMs: stat.mtimeMs, size: stat.size });
|
|
746
1072
|
}
|
|
747
1073
|
}
|
|
748
1074
|
}
|
|
749
1075
|
if (newPaths.length > 0) {
|
|
750
1076
|
watcher.add(newPaths);
|
|
1077
|
+
scheduleBaselineCheck(baselineStats);
|
|
751
1078
|
}
|
|
752
1079
|
},
|
|
753
1080
|
remove(paths) {
|
|
@@ -759,16 +1086,7 @@ async function createChokidarWatcher(options, chokidarModule) {
|
|
|
759
1086
|
if (watchedFiles.has(lookupKey)) {
|
|
760
1087
|
watchedFiles.delete(lookupKey);
|
|
761
1088
|
fileStats.delete(lookupKey);
|
|
762
|
-
|
|
763
|
-
const { isPackage, unwatchPkgDir } = nodeModulesManager.removePackageFile(posixPath, lookupKey);
|
|
764
|
-
if (unwatchPkgDir) {
|
|
765
|
-
removePaths.push(unwatchPkgDir);
|
|
766
|
-
}
|
|
767
|
-
else if (!isPackage &&
|
|
768
|
-
!isPathInside(lookupKey, rootDirLookupKey) &&
|
|
769
|
-
lookupKey !== rootDirLookupKey) {
|
|
770
|
-
removePaths.push(posixPath);
|
|
771
|
-
}
|
|
1089
|
+
removePaths.push(posixPath);
|
|
772
1090
|
}
|
|
773
1091
|
}
|
|
774
1092
|
if (removePaths.length > 0) {
|
|
@@ -776,6 +1094,10 @@ async function createChokidarWatcher(options, chokidarModule) {
|
|
|
776
1094
|
}
|
|
777
1095
|
},
|
|
778
1096
|
async close() {
|
|
1097
|
+
for (const timer of pendingTimers) {
|
|
1098
|
+
clearTimeout(timer);
|
|
1099
|
+
}
|
|
1100
|
+
pendingTimers.clear();
|
|
779
1101
|
try {
|
|
780
1102
|
await watcher.close();
|
|
781
1103
|
}
|
|
@@ -784,6 +1106,5 @@ async function createChokidarWatcher(options, chokidarModule) {
|
|
|
784
1106
|
}
|
|
785
1107
|
},
|
|
786
1108
|
};
|
|
787
|
-
return buildWatcher;
|
|
788
1109
|
}
|
|
789
1110
|
//# sourceMappingURL=watcher.js.map
|