@angular/build 22.2.2 → 22.2.3
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/extract-i18n/application-extraction.js +1 -0
- package/src/builders/extract-i18n/application-extraction.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/test-discovery.js +4 -2
- package/src/builders/unit-test/test-discovery.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/esbuild/persistent-load-result-cache.js +2 -4
- package/src/tools/esbuild/persistent-load-result-cache.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/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 +404 -76
- package/src/utils/watcher.js.map +1 -1
package/src/utils/watcher.js
CHANGED
|
@@ -43,7 +43,7 @@ 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;
|
|
@@ -362,122 +362,450 @@ function extractNodeModulesPackageDir(posixPath) {
|
|
|
362
362
|
}
|
|
363
363
|
return `${nodeModulesPath}/${segments[0]}`;
|
|
364
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Maximum number of concurrent external directory subscriptions before coalescing
|
|
367
|
+
* sibling/cousin directories into common ancestor directories.
|
|
368
|
+
* Kept well below macOS's ~512 per-process FSEvents stream limit.
|
|
369
|
+
*/
|
|
370
|
+
const DEFAULT_MAX_EXTERNAL_SUBSCRIPTIONS = 256;
|
|
371
|
+
/**
|
|
372
|
+
* Checks whether any strict ancestor of the given trie node is already marked as a target.
|
|
373
|
+
*/
|
|
374
|
+
function hasTargetAncestor(node) {
|
|
375
|
+
let current = node.parent;
|
|
376
|
+
while (current) {
|
|
377
|
+
if (current.isTarget) {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
current = current.parent;
|
|
381
|
+
}
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Manages `@parcel/watcher` directory subscriptions for watched files located outside the
|
|
386
|
+
* primary `workspaceRoot`.
|
|
387
|
+
*
|
|
388
|
+
* Because `@parcel/watcher` watches directories recursively and macOS limits each process to
|
|
389
|
+
* ~512 concurrent `FSEvents` streams, this manager:
|
|
390
|
+
* - Normalizes external `node_modules` file paths to their package root directory.
|
|
391
|
+
* - Batches synchronous `watchFile` and `removeFile` calls within a microtask before subscribing.
|
|
392
|
+
* - Uses a directory prefix tree to subsume nested child directories and iteratively coalesce
|
|
393
|
+
* deepest branching subdirectories into common ancestors when the subscription budget is exceeded.
|
|
394
|
+
* - Dynamically lowers its subscription budget and coalesces further if `subscribe()` fails on
|
|
395
|
+
* an existing directory due to OS stream limits.
|
|
396
|
+
*/
|
|
365
397
|
class ParcelExternalManager {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
398
|
+
/** Active `@parcel/watcher` subscriptions keyed by subscribed directory lookup key. */
|
|
399
|
+
#extraSubscriptions = new Map();
|
|
400
|
+
/** Reference-counted watched files grouped by their target external directory lookup key. */
|
|
401
|
+
#externalDirFiles = new Map();
|
|
402
|
+
/** Reverse lookup mapping each watched file lookup key to its target directory lookup key. */
|
|
403
|
+
#fileToDirKey = new Map();
|
|
404
|
+
/** The `@parcel/watcher` module instance. */
|
|
405
|
+
#parcelWatcher;
|
|
406
|
+
/** Watcher configuration options including ignore patterns. */
|
|
407
|
+
#options;
|
|
408
|
+
/** Normalized POSIX path of the primary watched workspace root. */
|
|
409
|
+
#rootDirPosix;
|
|
410
|
+
/** Case-normalized lookup key of the primary watched workspace root. */
|
|
411
|
+
#rootDirLookupKey;
|
|
412
|
+
/** Whether the underlying filesystem is case-sensitive. */
|
|
413
|
+
#isCaseSensitive;
|
|
414
|
+
/** Callback invoked when `@parcel/watcher` emits filesystem events. */
|
|
415
|
+
#handleEvents;
|
|
416
|
+
/** Current maximum number of concurrent external directory subscriptions allowed before coalescing. */
|
|
417
|
+
#maxSubscriptions;
|
|
418
|
+
/** Whether external directory state has changed and requires a reconciliation pass. */
|
|
419
|
+
#needsReconcile = false;
|
|
420
|
+
/** Whether obsolete subscriptions should be unsubscribed before creating new ones (used after stream limit errors). */
|
|
421
|
+
#shrinkBeforeSubscribe = false;
|
|
422
|
+
/** In-flight promise for the currently scheduled or running reconciliation loop. */
|
|
423
|
+
#reconcilePromise;
|
|
424
|
+
/** Whether this manager has been closed. */
|
|
425
|
+
#isClosed = false;
|
|
426
|
+
/**
|
|
427
|
+
* Creates a new external directory subscription manager for `@parcel/watcher`.
|
|
428
|
+
*
|
|
429
|
+
* @param parcelWatcher The `@parcel/watcher` module.
|
|
430
|
+
* @param options Watcher configuration options.
|
|
431
|
+
* @param rootDirPosix Normalized POSIX path of the primary workspace root.
|
|
432
|
+
* @param rootDirLookupKey Case-normalized lookup key of the primary workspace root.
|
|
433
|
+
* @param isCaseSensitive Whether the filesystem at the workspace root is case-sensitive.
|
|
434
|
+
* @param handleEvents Callback to process incoming `@parcel/watcher` events.
|
|
435
|
+
* @param maxSubscriptions Maximum number of concurrent external directory subscriptions before coalescing.
|
|
436
|
+
*/
|
|
437
|
+
constructor(parcelWatcher, options, rootDirPosix, rootDirLookupKey, isCaseSensitive, handleEvents, maxSubscriptions = DEFAULT_MAX_EXTERNAL_SUBSCRIPTIONS) {
|
|
438
|
+
this.#parcelWatcher = parcelWatcher;
|
|
439
|
+
this.#options = options;
|
|
440
|
+
this.#rootDirPosix = rootDirPosix;
|
|
441
|
+
this.#rootDirLookupKey = rootDirLookupKey;
|
|
442
|
+
this.#isCaseSensitive = isCaseSensitive;
|
|
443
|
+
this.#handleEvents = handleEvents;
|
|
444
|
+
this.#maxSubscriptions = Math.max(1, maxSubscriptions);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Registers a file path to be watched if it resides outside the primary workspace root,
|
|
448
|
+
* and schedules an asynchronous subscription reconciliation pass if needed.
|
|
449
|
+
*
|
|
450
|
+
* @param posixPath Normalized POSIX file path preserving original casing.
|
|
451
|
+
* @param lookupKey Case-normalized lookup key for the file path.
|
|
452
|
+
*/
|
|
453
|
+
watchFile(posixPath, lookupKey) {
|
|
454
|
+
if (this.#isClosed ||
|
|
455
|
+
isPathInside(lookupKey, this.#rootDirLookupKey) ||
|
|
456
|
+
lookupKey === this.#rootDirLookupKey) {
|
|
381
457
|
return;
|
|
382
458
|
}
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
459
|
+
const pkgDir = extractNodeModulesPackageDir(posixPath);
|
|
460
|
+
const dirPath = pkgDir ?? getDirectoryPath(posixPath);
|
|
461
|
+
const dirKey = pkgDir
|
|
462
|
+
? toLookupKey(pkgDir, this.#isCaseSensitive)
|
|
463
|
+
: getDirectoryPath(lookupKey);
|
|
464
|
+
this.#fileToDirKey.set(lookupKey, dirKey);
|
|
465
|
+
let dirEntry = this.#externalDirFiles.get(dirKey);
|
|
466
|
+
const isNewDir = !dirEntry;
|
|
386
467
|
if (!dirEntry) {
|
|
387
468
|
dirEntry = { dirPath, files: new Set() };
|
|
388
|
-
this
|
|
469
|
+
this.#externalDirFiles.set(dirKey, dirEntry);
|
|
389
470
|
}
|
|
390
471
|
dirEntry.files.add(lookupKey);
|
|
391
|
-
|
|
472
|
+
if (isNewDir || !this.#isCoveredByActiveSubscription(dirKey)) {
|
|
473
|
+
this.#scheduleReconcile();
|
|
474
|
+
}
|
|
392
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Unregisters a previously watched external file and schedules a reconciliation pass
|
|
478
|
+
* if it was the last watched file in its target directory.
|
|
479
|
+
*
|
|
480
|
+
* @param lookupKey Case-normalized lookup key for the file path to remove.
|
|
481
|
+
*/
|
|
393
482
|
removeFile(lookupKey) {
|
|
394
|
-
|
|
483
|
+
const dirKey = this.#fileToDirKey.get(lookupKey);
|
|
484
|
+
if (!dirKey) {
|
|
395
485
|
return;
|
|
396
486
|
}
|
|
397
|
-
|
|
398
|
-
const dirEntry = this
|
|
487
|
+
this.#fileToDirKey.delete(lookupKey);
|
|
488
|
+
const dirEntry = this.#externalDirFiles.get(dirKey);
|
|
399
489
|
if (dirEntry) {
|
|
400
490
|
dirEntry.files.delete(lookupKey);
|
|
401
491
|
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
|
-
}
|
|
492
|
+
this.#externalDirFiles.delete(dirKey);
|
|
493
|
+
this.#scheduleReconcile();
|
|
413
494
|
}
|
|
414
495
|
}
|
|
415
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Waits for any scheduled or in-progress subscription reconciliation pass to complete.
|
|
499
|
+
*/
|
|
500
|
+
async flush() {
|
|
501
|
+
if (this.#reconcilePromise) {
|
|
502
|
+
await this.#reconcilePromise;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Closes the manager, awaiting any pending reconciliation and unsubscribing all active
|
|
507
|
+
* external directory subscriptions.
|
|
508
|
+
*/
|
|
416
509
|
async close() {
|
|
510
|
+
this.#isClosed = true;
|
|
511
|
+
this.#needsReconcile = false;
|
|
417
512
|
try {
|
|
418
|
-
if (this
|
|
419
|
-
await
|
|
513
|
+
if (this.#reconcilePromise) {
|
|
514
|
+
await this.#reconcilePromise.catch(() => { });
|
|
420
515
|
}
|
|
421
|
-
if (this
|
|
422
|
-
await Promise.allSettled(Array.from(this
|
|
516
|
+
if (this.#extraSubscriptions.size > 0) {
|
|
517
|
+
await Promise.allSettled(Array.from(this.#extraSubscriptions.values(), (sub) => sub.unsubscribe()));
|
|
423
518
|
}
|
|
424
519
|
}
|
|
425
520
|
finally {
|
|
426
|
-
this
|
|
427
|
-
this.
|
|
428
|
-
this.
|
|
521
|
+
this.#extraSubscriptions.clear();
|
|
522
|
+
this.#externalDirFiles.clear();
|
|
523
|
+
this.#fileToDirKey.clear();
|
|
429
524
|
}
|
|
430
525
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Schedules a microtask to reconcile active `@parcel/watcher` subscriptions with the
|
|
528
|
+
* current set of external directories.
|
|
529
|
+
*/
|
|
530
|
+
#scheduleReconcile() {
|
|
531
|
+
if (this.#isClosed) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
this.#needsReconcile = true;
|
|
535
|
+
this.#reconcilePromise ??= Promise.resolve()
|
|
536
|
+
.then(() => this.#runReconcileLoop())
|
|
537
|
+
.finally(() => {
|
|
538
|
+
this.#reconcilePromise = undefined;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Continuously executes reconciliation passes until no further changes are pending.
|
|
543
|
+
*/
|
|
544
|
+
async #runReconcileLoop() {
|
|
545
|
+
while (this.#needsReconcile && !this.#isClosed) {
|
|
546
|
+
this.#needsReconcile = false;
|
|
547
|
+
await this.#reconcileOnce();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Performs a single reconciliation pass: computes the desired coalesced directory set,
|
|
552
|
+
* subscribes to newly required directories, unsubscribes obsolete directories, and
|
|
553
|
+
* triggers fallback coalescing if OS stream limits are encountered.
|
|
554
|
+
*/
|
|
555
|
+
async #reconcileOnce() {
|
|
556
|
+
const desiredDirs = this.#computeDesiredDirectories();
|
|
557
|
+
if (this.#shrinkBeforeSubscribe) {
|
|
558
|
+
this.#shrinkBeforeSubscribe = false;
|
|
559
|
+
const toUnsubscribe = [];
|
|
560
|
+
for (const [existingKey, sub] of this.#extraSubscriptions.entries()) {
|
|
561
|
+
if (!desiredDirs.has(existingKey)) {
|
|
562
|
+
this.#extraSubscriptions.delete(existingKey);
|
|
563
|
+
toUnsubscribe.push(sub.unsubscribe().catch(() => { }));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (toUnsubscribe.length > 0) {
|
|
567
|
+
await Promise.allSettled(toUnsubscribe);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (this.#isClosed) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const toSubscribe = [];
|
|
574
|
+
for (const [dirKey, dirPath] of desiredDirs.entries()) {
|
|
575
|
+
if (!this.#extraSubscriptions.has(dirKey)) {
|
|
576
|
+
toSubscribe.push([dirKey, dirPath]);
|
|
435
577
|
}
|
|
436
578
|
}
|
|
437
|
-
|
|
438
|
-
|
|
579
|
+
let hadExistingDirFailure = false;
|
|
580
|
+
if (toSubscribe.length > 0) {
|
|
581
|
+
await Promise.allSettled(toSubscribe.map(async ([dirKey, dirPath]) => {
|
|
582
|
+
const ignore = isPathInside(this.#rootDirLookupKey, dirKey)
|
|
583
|
+
? [...(this.#options?.ignored ?? []), this.#rootDirPosix, `${this.#rootDirPosix}/**`]
|
|
584
|
+
: this.#options?.ignored;
|
|
585
|
+
try {
|
|
586
|
+
const sub = await this.#parcelWatcher.subscribe(dirPath, (err, events) => {
|
|
587
|
+
if (!err) {
|
|
588
|
+
this.#handleEvents(events);
|
|
589
|
+
}
|
|
590
|
+
}, { ignore });
|
|
591
|
+
if (this.#isClosed) {
|
|
592
|
+
await sub.unsubscribe().catch(() => { });
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
this.#extraSubscriptions.set(dirKey, sub);
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
// Ignore errors for missing directories; if the directory exists on disk,
|
|
599
|
+
// mark failure so the manager can coalesce further if over OS stream limits.
|
|
600
|
+
if (!this.#isClosed && fs.existsSync(dirPath)) {
|
|
601
|
+
hadExistingDirFailure = true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
if (this.#isClosed) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
// Unsubscribe any previous subscriptions that are no longer desired and whose
|
|
610
|
+
// remaining watched files (if any) are now covered by another active subscription.
|
|
611
|
+
for (const [existingKey, sub] of this.#extraSubscriptions.entries()) {
|
|
612
|
+
if (!desiredDirs.has(existingKey) && this.#canRemoveSubscription(existingKey)) {
|
|
613
|
+
this.#extraSubscriptions.delete(existingKey);
|
|
614
|
+
sub.unsubscribe().catch(() => { });
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// If subscribing to an existing directory failed (e.g. macOS FSEvents per-process limit),
|
|
618
|
+
// dynamically lower the subscription budget and re-reconcile to coalesce into ancestors.
|
|
619
|
+
if (hadExistingDirFailure && desiredDirs.size > 1 && this.#maxSubscriptions > 1) {
|
|
620
|
+
const nextMax = this.#extraSubscriptions.size > 0
|
|
621
|
+
? Math.max(1, Math.floor(this.#extraSubscriptions.size / 2))
|
|
622
|
+
: Math.max(1, Math.floor(Math.min(this.#maxSubscriptions, desiredDirs.size) / 2));
|
|
623
|
+
if (nextMax < desiredDirs.size && nextMax < this.#maxSubscriptions) {
|
|
624
|
+
this.#maxSubscriptions = nextMax;
|
|
625
|
+
this.#shrinkBeforeSubscribe = true;
|
|
626
|
+
this.#needsReconcile = true;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Checks whether a directory lookup key is already covered by an active subscription.
|
|
632
|
+
*/
|
|
633
|
+
#isCoveredByActiveSubscription(dirLookupKey) {
|
|
634
|
+
for (const existingDir of this.#extraSubscriptions.keys()) {
|
|
635
|
+
if (dirLookupKey === existingDir || isPathInside(dirLookupKey, existingDir)) {
|
|
439
636
|
return true;
|
|
440
637
|
}
|
|
441
638
|
}
|
|
442
639
|
return false;
|
|
443
640
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
641
|
+
/**
|
|
642
|
+
* Determines whether an existing subscription can be safely removed without leaving
|
|
643
|
+
* any remaining watched target directories uncovered.
|
|
644
|
+
*/
|
|
645
|
+
#canRemoveSubscription(subKey) {
|
|
646
|
+
for (const targetDirKey of this.#externalDirFiles.keys()) {
|
|
647
|
+
if (targetDirKey === subKey || isPathInside(targetDirKey, subKey)) {
|
|
648
|
+
let coveredByOther = false;
|
|
649
|
+
for (const otherSubKey of this.#extraSubscriptions.keys()) {
|
|
650
|
+
if (otherSubKey !== subKey &&
|
|
651
|
+
(targetDirKey === otherSubKey || isPathInside(targetDirKey, otherSubKey))) {
|
|
652
|
+
coveredByOther = true;
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (!coveredByOther) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Computes the minimal set of external directories to subscribe to (mapped from `dirKey` to `dirPath`),
|
|
665
|
+
* subsuming nested directories and coalescing branching ancestors to stay within `#maxSubscriptions`.
|
|
666
|
+
*/
|
|
667
|
+
#computeDesiredDirectories() {
|
|
668
|
+
if (this.#externalDirFiles.size === 0) {
|
|
669
|
+
return new Map();
|
|
447
670
|
}
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
671
|
+
const nodesByKey = new Map();
|
|
672
|
+
const roots = [];
|
|
673
|
+
for (const [dirKey, { dirPath }] of this.#externalDirFiles.entries()) {
|
|
674
|
+
const chain = [];
|
|
675
|
+
let currentKey = dirKey;
|
|
676
|
+
let currentPath = dirPath;
|
|
677
|
+
while (true) {
|
|
678
|
+
chain.push({ dirKey: currentKey, dirPath: currentPath });
|
|
679
|
+
const parentKey = getDirectoryPath(currentKey);
|
|
680
|
+
if (parentKey === currentKey) {
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
currentKey = parentKey;
|
|
684
|
+
currentPath = getDirectoryPath(currentPath);
|
|
451
685
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
686
|
+
let parentNode;
|
|
687
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
688
|
+
const { dirKey: nodeKey, dirPath: nodePath } = chain[i];
|
|
689
|
+
const depth = chain.length - 1 - i;
|
|
690
|
+
let node = nodesByKey.get(nodeKey);
|
|
691
|
+
if (!node) {
|
|
692
|
+
node = {
|
|
693
|
+
dirKey: nodeKey,
|
|
694
|
+
dirPath: nodePath,
|
|
695
|
+
depth,
|
|
696
|
+
parent: parentNode,
|
|
697
|
+
children: new Map(),
|
|
698
|
+
isTarget: false,
|
|
699
|
+
leafCount: 0,
|
|
700
|
+
};
|
|
701
|
+
nodesByKey.set(nodeKey, node);
|
|
702
|
+
if (parentNode) {
|
|
703
|
+
parentNode.children.set(nodeKey, node);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
roots.push(node);
|
|
466
707
|
}
|
|
467
708
|
}
|
|
709
|
+
parentNode = node;
|
|
468
710
|
}
|
|
469
|
-
|
|
470
|
-
|
|
711
|
+
if (parentNode) {
|
|
712
|
+
parentNode.isTarget = true;
|
|
471
713
|
}
|
|
472
714
|
}
|
|
473
|
-
|
|
474
|
-
|
|
715
|
+
const initSubtree = (node) => {
|
|
716
|
+
if (node.isTarget) {
|
|
717
|
+
node.children.clear();
|
|
718
|
+
node.leafCount = 1;
|
|
719
|
+
return 1;
|
|
720
|
+
}
|
|
721
|
+
let count = 0;
|
|
722
|
+
for (const child of node.children.values()) {
|
|
723
|
+
count += initSubtree(child);
|
|
724
|
+
}
|
|
725
|
+
node.leafCount = count;
|
|
726
|
+
return count;
|
|
727
|
+
};
|
|
728
|
+
let totalLeaves = 0;
|
|
729
|
+
for (const root of roots) {
|
|
730
|
+
totalLeaves += initSubtree(root);
|
|
475
731
|
}
|
|
476
|
-
|
|
477
|
-
|
|
732
|
+
if (totalLeaves > this.#maxSubscriptions) {
|
|
733
|
+
// Tier 1: non-ancestors of workspaceRoot (e.g., <monorepo>/libs, <monorepo>/node_modules)
|
|
734
|
+
// Tier 2: ancestors of workspaceRoot (e.g., <monorepo> when sibling apps/libs are directly under it)
|
|
735
|
+
const tier1ByDepth = new Map();
|
|
736
|
+
const tier2ByDepth = new Map();
|
|
737
|
+
const collectInternalNodes = (node) => {
|
|
738
|
+
if (node.isTarget) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (node.parent !== undefined && node.leafCount > 1) {
|
|
742
|
+
const isWorkspaceAncestor = isPathInside(this.#rootDirLookupKey, node.dirKey);
|
|
743
|
+
const bucketMap = isWorkspaceAncestor ? tier2ByDepth : tier1ByDepth;
|
|
744
|
+
let bucket = bucketMap.get(node.depth);
|
|
745
|
+
if (!bucket) {
|
|
746
|
+
bucket = [];
|
|
747
|
+
bucketMap.set(node.depth, bucket);
|
|
748
|
+
}
|
|
749
|
+
bucket.push(node);
|
|
750
|
+
}
|
|
751
|
+
for (const child of node.children.values()) {
|
|
752
|
+
collectInternalNodes(child);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
for (const root of roots) {
|
|
756
|
+
collectInternalNodes(root);
|
|
757
|
+
}
|
|
758
|
+
const collapseTier = (byDepth) => {
|
|
759
|
+
const sortedLevels = Array.from(byDepth.entries()).sort(([a], [b]) => b - a);
|
|
760
|
+
for (const [, nodes] of sortedLevels) {
|
|
761
|
+
if (totalLeaves <= this.#maxSubscriptions) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const candidates = nodes
|
|
765
|
+
.filter((n) => n.leafCount > 1 && !hasTargetAncestor(n))
|
|
766
|
+
.sort((a, b) => b.leafCount - a.leafCount || a.dirKey.localeCompare(b.dirKey));
|
|
767
|
+
for (const candidate of candidates) {
|
|
768
|
+
if (totalLeaves <= this.#maxSubscriptions) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (candidate.leafCount <= 1 || hasTargetAncestor(candidate)) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const reduction = candidate.leafCount - 1;
|
|
775
|
+
candidate.isTarget = true;
|
|
776
|
+
candidate.children.clear();
|
|
777
|
+
candidate.leafCount = 1;
|
|
778
|
+
totalLeaves -= reduction;
|
|
779
|
+
let ancestor = candidate.parent;
|
|
780
|
+
while (ancestor) {
|
|
781
|
+
ancestor.leafCount -= reduction;
|
|
782
|
+
ancestor = ancestor.parent;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
collapseTier(tier1ByDepth);
|
|
788
|
+
if (totalLeaves > this.#maxSubscriptions) {
|
|
789
|
+
collapseTier(tier2ByDepth);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const desired = new Map();
|
|
793
|
+
const collectTargets = (node) => {
|
|
794
|
+
if (node.isTarget) {
|
|
795
|
+
desired.set(node.dirKey, node.dirPath);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
for (const child of node.children.values()) {
|
|
799
|
+
collectTargets(child);
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
for (const root of roots) {
|
|
803
|
+
collectTargets(root);
|
|
478
804
|
}
|
|
805
|
+
return desired;
|
|
479
806
|
}
|
|
480
807
|
}
|
|
808
|
+
exports.ParcelExternalManager = ParcelExternalManager;
|
|
481
809
|
async function createParcelWatcher(options, parcelWatcher) {
|
|
482
810
|
const watchedFiles = new Set();
|
|
483
811
|
const queue = new WatcherQueue();
|
|
@@ -516,7 +844,7 @@ async function createParcelWatcher(options, parcelWatcher) {
|
|
|
516
844
|
}, {
|
|
517
845
|
ignore: options?.ignored,
|
|
518
846
|
});
|
|
519
|
-
const externalManager = new ParcelExternalManager(parcelWatcher, options, rootDirLookupKey, handleEvents);
|
|
847
|
+
const externalManager = new ParcelExternalManager(parcelWatcher, options, rootDirPosix, rootDirLookupKey, isCaseSensitive, handleEvents);
|
|
520
848
|
const buildWatcher = {
|
|
521
849
|
[Symbol.asyncIterator]() {
|
|
522
850
|
return this;
|
|
@@ -531,7 +859,7 @@ async function createParcelWatcher(options, parcelWatcher) {
|
|
|
531
859
|
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
|
|
532
860
|
if (!watchedFiles.has(lookupKey)) {
|
|
533
861
|
watchedFiles.add(lookupKey);
|
|
534
|
-
|
|
862
|
+
externalManager.watchFile(posixPath, lookupKey);
|
|
535
863
|
}
|
|
536
864
|
}
|
|
537
865
|
},
|