@expo/metro-file-map 56.0.1 → 56.0.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.
@@ -5,6 +5,13 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { BuildParameters, CacheData, CacheManager, CacheManagerFactoryOptions, CacheManagerWriteOptions } from '../types';
8
+ declare global {
9
+ namespace NodeJS {
10
+ interface Process {
11
+ isBun?: boolean;
12
+ }
13
+ }
14
+ }
8
15
  interface AutoSaveOptions {
9
16
  readonly debounceMs: number;
10
17
  }
@@ -17,7 +17,12 @@ const timers_1 = require("timers");
17
17
  const v8_1 = require("v8");
18
18
  const rootRelativeCacheKeys_1 = __importDefault(require("../lib/rootRelativeCacheKeys"));
19
19
  const debug = require('debug')('Metro:FileMapCache');
20
- const DEFAULT_PREFIX = 'metro-file-map';
20
+ let DEFAULT_PREFIX = 'metro-file-map';
21
+ if (process.isBun) {
22
+ // NOTE(@kitten): The v8 serialize/deserialize format isn't 100% compatible between
23
+ // Node and Bun and therefore we should fork the cache file
24
+ DEFAULT_PREFIX += '-bun';
25
+ }
21
26
  const DEFAULT_DIRECTORY = (0, os_1.tmpdir)();
22
27
  const DEFAULT_AUTO_SAVE_DEBOUNCE_MS = 5000;
23
28
  // NOTE(@kitten): We're incompatible with Metro, so need our own naming
@@ -68,7 +68,8 @@ function createFallbackFilesystem(opts) {
68
68
  }
69
69
  if (entry.isDirectory()) {
70
70
  // NOTE(@kitten): ".git" and ".hg" check replace the VCS_DIRECTORIES ignore pattern
71
- if (!result.has(name) && name !== '.git' && name !== '.hg') {
71
+ // NOTE(@kitten): `.cxx` is ephemeral and should always be safe to ignore
72
+ if (!result.has(name) && name !== '.git' && name !== '.hg' && name !== '.cxx') {
72
73
  const childDir = new Map();
73
74
  markDir(childDir, FallbackFlag.VISITED);
74
75
  result.set(name, childDir);
@@ -58,15 +58,21 @@ function find(roots, extensions, ignore, includeSymlinks, rootDir, console, prev
58
58
  fs.readdir(directory, { withFileTypes: true }, (err, entries) => {
59
59
  activeCalls--;
60
60
  if (err) {
61
- console.warn(`Error "${err.code ?? err.message}" reading contents of "${directory}", skipping. Add this directory to your ignore list to exclude it.`);
61
+ // NOTE(@kitten): This isn't necessarily a problem and we can ignore this
62
+ /*
63
+ console.warn(
64
+ `Error "${(err as any).code ?? err.message}" reading contents of "${directory}", skipping. Add this directory to your ignore list to exclude it.`
65
+ );
66
+ */
62
67
  }
63
68
  else {
64
69
  for (let idx = 0; idx < entries.length; idx++) {
65
70
  const entry = entries[idx];
66
71
  const name = entry.name;
67
72
  // NOTE(@kitten): This replaces the VCS_DIRECTORIES ignore pattern
73
+ // NOTE(@kitten): `.cxx` is ephemeral and should always be safe to ignore
68
74
  const isDirectory = entry.isDirectory();
69
- if (isDirectory && (name === '.git' || name === '.hg')) {
75
+ if (isDirectory && (name === '.git' || name === '.hg' || name === '.cxx')) {
70
76
  continue;
71
77
  }
72
78
  const file = directory + path.sep + name;
@@ -49,7 +49,7 @@ const path = __importStar(require("path"));
49
49
  const perf_hooks_1 = require("perf_hooks");
50
50
  const planQuery_1 = require("./planQuery");
51
51
  const RootPathUtils_1 = require("../../lib/RootPathUtils");
52
- const isVcsPath_1 = __importDefault(require("../../lib/isVcsPath"));
52
+ const isWatcherExcluded_1 = __importDefault(require("../../lib/isWatcherExcluded"));
53
53
  const normalizePathSeparatorsToPosix_1 = __importDefault(require("../../lib/normalizePathSeparatorsToPosix"));
54
54
  const normalizePathSeparatorsToSystem_1 = __importDefault(require("../../lib/normalizePathSeparatorsToSystem"));
55
55
  const WATCHMAN_WARNING_INITIAL_DELAY_MILLISECONDS = 10000;
@@ -248,7 +248,7 @@ async function watchmanCrawl({ abortSignal, computeSha1, extensions, ignore, inc
248
248
  // Whether watchman can return exists: false in a fresh instance
249
249
  // response is unknown, but there's nothing we need to do in that case.
250
250
  }
251
- else if (!(0, isVcsPath_1.default)(fileData.name) && !ignore(filePath)) {
251
+ else if (!(0, isWatcherExcluded_1.default)(fileData.name) && !ignore(filePath)) {
252
252
  const { mtime_ms, size } = fileData;
253
253
  (0, invariant_1.default)(mtime_ms != null && size != null, 'missing file data in watchman response');
254
254
  const mtime = typeof mtime_ms === 'number' ? mtime_ms : mtime_ms.toNumber();
@@ -130,10 +130,11 @@ class RootPathUtils {
130
130
  if (right.length === 0) {
131
131
  return left;
132
132
  }
133
- else if (pos > this.#rootDepth * UP_FRAGMENT_SEP_LENGTH) {
134
- // When we walk above the filesystem root, we emit the remaining path as is.
135
- // This is important on Windows, since we're canonicalizing cross-device paths
136
- // as relative paths from rootDir
133
+ else if (IS_WIN32 && pos > this.#rootDepth * UP_FRAGMENT_SEP_LENGTH) {
134
+ // On a real file system, navigating to `..` at the top level (posix `/`
135
+ // or Windows drive) is a no-op, but we can't respect that on Windows
136
+ // because Metro uses e.g. `..\..\D:\foo` to represent cross-drive
137
+ // relative paths.
137
138
  return right;
138
139
  }
139
140
  // left may already end in a path separator only if it is a filesystem root,
@@ -13,16 +13,19 @@ const fs_1 = __importDefault(require("fs"));
13
13
  const invariant_1 = __importDefault(require("invariant"));
14
14
  const path_1 = __importDefault(require("path"));
15
15
  const constants_1 = __importDefault(require("../constants"));
16
+ const RootPathUtils_1 = require("./RootPathUtils");
16
17
  const normalizePathSeparatorsToPosix_1 = __importDefault(require("./normalizePathSeparatorsToPosix"));
17
18
  const normalizePathSeparatorsToSystem_1 = __importDefault(require("./normalizePathSeparatorsToSystem"));
18
19
  const fallback_1 = require("../crawlers/node/fallback");
19
- const RootPathUtils_1 = require("./RootPathUtils");
20
20
  function isDirectory(node) {
21
- return node instanceof Map;
21
+ return node != null && typeof node.get === 'function';
22
22
  }
23
23
  function isRegularFile(node) {
24
24
  return node != null && node[constants_1.default.SYMLINK] === 0;
25
25
  }
26
+ // POSIX SYMLOOP_MAX. Bounded counter instead of a per-walk `Set` of
27
+ // visited targets — cycles are rare; bailing after N follows is enough.
28
+ const SYMLOOP_MAX = 40;
26
29
  /**
27
30
  * OVERVIEW:
28
31
  *
@@ -81,6 +84,32 @@ class TreeFS {
81
84
  #rootPattern;
82
85
  #roots;
83
86
  #rootNode = new Map();
87
+ #followScratch = {
88
+ parentNode: new Map(),
89
+ ancestorOfRootIdx: undefined,
90
+ targetNormalPath: '',
91
+ fromIdx: 0,
92
+ unseenPathFromIdx: 0,
93
+ followedSymlinks: 0,
94
+ };
95
+ // Hier isn't recursive; pool entries are mutated in place per call.
96
+ #hierAncestorsBuf = [];
97
+ #hierAncestorsPool = [];
98
+ #pushHierAncestor = (node, normalPath, segmentName, ancestorOfRootIdx) => {
99
+ const i = this.#hierAncestorsBuf.length;
100
+ let entry = this.#hierAncestorsPool[i];
101
+ if (entry === undefined) {
102
+ entry = { ancestorOfRootIdx, node, normalPath, segmentName };
103
+ this.#hierAncestorsPool[i] = entry;
104
+ }
105
+ else {
106
+ entry.ancestorOfRootIdx = ancestorOfRootIdx;
107
+ entry.node = node;
108
+ entry.normalPath = normalPath;
109
+ entry.segmentName = segmentName;
110
+ }
111
+ this.#hierAncestorsBuf.push(entry);
112
+ };
84
113
  constructor(opts) {
85
114
  const { rootDir, files, processFile, fallbackFilesystem, roots, serverRoot } = opts;
86
115
  this.#rootDir = rootDir;
@@ -468,15 +497,151 @@ class TreeFS {
468
497
  * `opts.X` branches. Thin convenience wrappers below (`#lookup`,
469
498
  * `#lookupNoFollow`, `#lookupFromNode`, `#walkAndStream`) cover the
470
499
  * common call shapes.
500
+ *
501
+ * Symlink-follow logic is shared via `#followSymlink`.
471
502
  */
472
- #walkLookup(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, onSegment, collectLinkPaths) {
473
- // We'll update the target if we hit a symlink.
503
+ /**
504
+ * For `..`-prefixed symlink targets: match remainder's leading segments
505
+ * against root parts (`/A/B/C/D` + remainder `B/C/foo.js` collapses two
506
+ * `..`s away). TreeFS has no downward back-links, so we climb up once
507
+ * from `#rootNode` to build a stack, then index into it.
508
+ */
509
+ #collapseAgainstRootParts(ancestorOfRootIdx, parentNode, remainder, onSegment) {
510
+ const ancestors = [this.#rootNode];
511
+ {
512
+ let n = this.#rootNode;
513
+ for (let k = 1; k <= ancestorOfRootIdx; k++) {
514
+ n = n.get('..') ?? new Map();
515
+ if (!isDirectory(n))
516
+ break;
517
+ ancestors.push(n);
518
+ }
519
+ }
520
+ const rootParts = this.#pathUtils.getParts();
521
+ let cursorIdx = 0;
522
+ // Trimmed by `'/..'` per step; `'..'.slice(0, -3) === ''` covers K=1→0.
523
+ let collapsedPath;
524
+ while (ancestorOfRootIdx > 0 &&
525
+ cursorIdx < remainder.length &&
526
+ ancestors.length > ancestorOfRootIdx - 1) {
527
+ const nextSep = remainder.indexOf(path_1.default.sep, cursorIdx);
528
+ const segEnd = nextSep === -1 ? remainder.length : nextSep;
529
+ const segName = remainder.slice(cursorIdx, segEnd);
530
+ const expectedPart = rootParts[rootParts.length - ancestorOfRootIdx];
531
+ if (segName !== expectedPart) {
532
+ break;
533
+ }
534
+ ancestorOfRootIdx--;
535
+ parentNode = ancestors[ancestorOfRootIdx];
536
+ cursorIdx = nextSep === -1 ? remainder.length : nextSep + 1;
537
+ if (onSegment != null) {
538
+ if (collapsedPath === undefined) {
539
+ collapsedPath = '';
540
+ for (let k = 0; k < ancestorOfRootIdx; k++) {
541
+ collapsedPath = k === 0 ? '..' : collapsedPath + path_1.default.sep + '..';
542
+ }
543
+ }
544
+ else {
545
+ collapsedPath = collapsedPath.slice(0, -3);
546
+ }
547
+ onSegment(parentNode, collapsedPath, segName, ancestorOfRootIdx);
548
+ }
549
+ }
550
+ return { ancestorOfRootIdx, parentNode, cursorIdx };
551
+ }
552
+ /**
553
+ * Resolve an interior symlink. Returns a `WalkResult` to return
554
+ * immediately, or `undefined` after writing continue-state to
555
+ * `#followScratch` for the caller to read.
556
+ */
557
+ #followSymlink(symlinkNode, currentPath, segmentName, remainingTargetPath, followedSymlinks, onSegment, collectLinkPaths) {
558
+ const normalSymlinkTarget = this.#resolveSymlinkTargetToNormalPath(symlinkNode, currentPath);
559
+ if (normalSymlinkTarget == null) {
560
+ return {
561
+ canonicalMissingPath: currentPath,
562
+ exists: false,
563
+ missingSegmentName: segmentName,
564
+ };
565
+ }
566
+ else if (++followedSymlinks >= SYMLOOP_MAX) {
567
+ return {
568
+ canonicalMissingPath: normalSymlinkTarget,
569
+ exists: false,
570
+ missingSegmentName: segmentName,
571
+ };
572
+ }
573
+ else if (collectLinkPaths != null) {
574
+ collectLinkPaths.add(this.#pathUtils.normalToAbsolute(currentPath));
575
+ }
576
+ // `onSegment: undefined` so target's interior dirs aren't emitted;
577
+ // only the destination is emitted (manually, below).
578
+ const subResult = this.#walkLookup(this.#rootNode, 0, 0, normalSymlinkTarget, undefined, collectLinkPaths, followedSymlinks);
579
+ if (!subResult.exists || remainingTargetPath === '') {
580
+ return subResult;
581
+ }
582
+ else if (!isDirectory(subResult.node)) {
583
+ return {
584
+ exists: false,
585
+ canonicalMissingPath: subResult.canonicalPath,
586
+ missingSegmentName: segmentName,
587
+ };
588
+ }
589
+ let parentNode = subResult.node;
590
+ let ancestorOfRootIdx = subResult.ancestorOfRootIdx;
591
+ if (onSegment != null) {
592
+ const subBaseStart = subResult.canonicalPath.lastIndexOf(path_1.default.sep) + 1;
593
+ const subBaseName = subResult.canonicalPath.slice(subBaseStart);
594
+ onSegment(parentNode, subResult.canonicalPath, subBaseName, ancestorOfRootIdx);
595
+ }
596
+ let cursorIdx = 0;
597
+ if (ancestorOfRootIdx != null && ancestorOfRootIdx > 0) {
598
+ const collapsed = this.#collapseAgainstRootParts(ancestorOfRootIdx, parentNode, remainingTargetPath, onSegment);
599
+ ancestorOfRootIdx = collapsed.ancestorOfRootIdx;
600
+ parentNode = collapsed.parentNode;
601
+ cursorIdx = collapsed.cursorIdx;
602
+ }
603
+ // Canonical-to-parentNode: `..`-string above root, empty at root, or
604
+ // sub-walk's canonical path inside the tree.
605
+ const realRemainder = remainingTargetPath.slice(cursorIdx);
606
+ let canonicalToParent;
607
+ if (ancestorOfRootIdx != null && ancestorOfRootIdx > 0) {
608
+ canonicalToParent = '..';
609
+ for (let k = 1; k < ancestorOfRootIdx; k++) {
610
+ canonicalToParent += path_1.default.sep + '..';
611
+ }
612
+ }
613
+ else if (ancestorOfRootIdx === 0) {
614
+ canonicalToParent = '';
615
+ }
616
+ else {
617
+ canonicalToParent = subResult.canonicalPath;
618
+ }
619
+ const scratch = this.#followScratch;
620
+ scratch.parentNode = parentNode;
621
+ scratch.ancestorOfRootIdx = ancestorOfRootIdx;
622
+ scratch.followedSymlinks = followedSymlinks;
623
+ if (canonicalToParent === '') {
624
+ scratch.targetNormalPath = realRemainder;
625
+ scratch.fromIdx = 0;
626
+ scratch.unseenPathFromIdx = 0;
627
+ }
628
+ else if (realRemainder === '') {
629
+ scratch.targetNormalPath = canonicalToParent;
630
+ scratch.fromIdx = canonicalToParent.length;
631
+ scratch.unseenPathFromIdx = canonicalToParent.length;
632
+ }
633
+ else {
634
+ scratch.targetNormalPath = canonicalToParent + path_1.default.sep + realRemainder;
635
+ scratch.fromIdx = canonicalToParent.length + 1;
636
+ scratch.unseenPathFromIdx = canonicalToParent.length + 1;
637
+ }
638
+ return undefined;
639
+ }
640
+ #walkLookup(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, onSegment, collectLinkPaths, initialFollows) {
641
+ // Cycle counter as a local — faster than an instance-field read in
642
+ // the per-segment loop. Recursive sub-walk inherits via `initialFollows`.
474
643
  let targetNormalPath = requestedNormalPath;
475
- // Lazy-initialised set of seen target paths, to detect symlink cycles.
476
- let seen;
477
- // Set when a symlink is followed, to allow fallback population outside
478
- // the boundary for paths reachable transitively through symlinks.
479
- let followedSymlink = false;
644
+ let followedSymlinks = initialFollows;
480
645
  let fromIdx = startPathIdx;
481
646
  let parentNode = startNode;
482
647
  let ancestorOfRootIdx = startAncestorOfRootIdx;
@@ -508,7 +673,7 @@ class TreeFS {
508
673
  ? fromIdx - segmentName.length - 1
509
674
  : fromIdx - segmentName.length - 2;
510
675
  const parentCanonicalPath = parentEnd > 0 ? targetNormalPath.slice(0, parentEnd) : '';
511
- segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlink);
676
+ segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlinks > 0);
512
677
  if (segmentNode != null) {
513
678
  ancestorOfRootIdx = undefined;
514
679
  }
@@ -567,87 +732,17 @@ class TreeFS {
567
732
  missingSegmentName: segmentName,
568
733
  };
569
734
  }
570
- // Symlink in a directory path
571
- const normalSymlinkTarget = this.#resolveSymlinkTargetToNormalPath(segmentNode, currentPath);
572
- if (normalSymlinkTarget == null) {
573
- return {
574
- canonicalMissingPath: currentPath,
575
- exists: false,
576
- missingSegmentName: segmentName,
577
- };
578
- }
579
- if (collectLinkPaths != null) {
580
- collectLinkPaths.add(this.#pathUtils.normalToAbsolute(currentPath));
581
- }
582
- const remainingTargetPath = isLastSegment ? '' : targetNormalPath.slice(fromIdx);
583
- // Append any subsequent path segments to the symlink target, and reset
584
- // with our new target.
585
- const joinedResult = this.#pathUtils.joinNormalToRelative(normalSymlinkTarget, remainingTargetPath);
586
- targetNormalPath = joinedResult.normalPath;
587
- // Two special cases (covered by unit tests):
588
- //
589
- // If the symlink target is the root, the root should be counted as an
590
- // ancestor. We'd otherwise miss it because new ancestors are only
591
- // streamed when entering a directory.
592
- //
593
- // If the symlink target is an ancestor of the root *and* joining it
594
- // with the remaining path results in collapsing segments, e.g:
595
- // '../..' + 'parentofroot/root/foo.js' = 'foo.js', then we must yield
596
- // parentofroot and root as ancestors.
597
- if (onSegment != null &&
598
- !isLastSegment &&
599
- // No-op optimisation to bail out the common case of nothing to do.
600
- ((ancestorOfRootIdx =
601
- this.#pathUtils.getAncestorOfRootIdx(normalSymlinkTarget) ?? undefined) === 0 ||
602
- joinedResult.collapsedSegments > 0)) {
603
- let node = this.#rootNode;
604
- let collapsedPath = '';
605
- const reverseAncestors = [];
606
- for (let i = 0; i <= joinedResult.collapsedSegments && isDirectory(node); i++) {
607
- if (
608
- // Add the root only if the target is the root or we have
609
- // collapsed segments.
610
- i > 0 ||
611
- ancestorOfRootIdx === 0 ||
612
- joinedResult.collapsedSegments > 0) {
613
- reverseAncestors.push({
614
- ancestorOfRootIdx: i,
615
- node,
616
- normalPath: collapsedPath,
617
- segmentName: this.#pathUtils.getBasenameOfNthAncestor(i),
618
- });
619
- }
620
- node = node.get('..') ?? new Map();
621
- collapsedPath = collapsedPath === '' ? '..' : collapsedPath + path_1.default.sep + '..';
622
- }
623
- // Emit in shallowest-first order, matching today's
624
- // collectAncestors.push(...reverseAncestors.reverse()).
625
- for (let i = reverseAncestors.length - 1; i >= 0; i--) {
626
- const a = reverseAncestors[i];
627
- onSegment(a.node, a.normalPath, a.segmentName, a.ancestorOfRootIdx);
628
- }
629
- }
630
- // For the purpose of streaming ancestors: Ignore the traversal to the
631
- // symlink target, and start yielding ancestors only from the target
632
- // itself (i.e. the basename of the normal target path) onwards.
633
- unseenPathFromIdx = normalSymlinkTarget.lastIndexOf(path_1.default.sep) + 1;
634
- if (seen == null) {
635
- // Optimisation: set this lazily only when we've encountered a symlink
636
- seen = new Set([requestedNormalPath]);
735
+ const followResult = this.#followSymlink(segmentNode, currentPath, segmentName, isLastSegment ? '' : targetNormalPath.slice(fromIdx), followedSymlinks, onSegment, collectLinkPaths);
736
+ if (followResult !== undefined) {
737
+ return followResult;
637
738
  }
638
- if (seen.has(targetNormalPath)) {
639
- // TODO: Warn `Symlink cycle detected: ${[...seen, node].join(' -> ')}`
640
- return {
641
- canonicalMissingPath: targetNormalPath,
642
- exists: false,
643
- missingSegmentName: segmentName,
644
- };
645
- }
646
- seen.add(targetNormalPath);
647
- followedSymlink = true;
648
- fromIdx = 0;
649
- parentNode = this.#rootNode;
650
- ancestorOfRootIdx = 0;
739
+ const scratch = this.#followScratch;
740
+ parentNode = scratch.parentNode;
741
+ ancestorOfRootIdx = scratch.ancestorOfRootIdx;
742
+ targetNormalPath = scratch.targetNormalPath;
743
+ fromIdx = scratch.fromIdx;
744
+ unseenPathFromIdx = scratch.unseenPathFromIdx;
745
+ followedSymlinks = scratch.followedSymlinks;
651
746
  }
652
747
  }
653
748
  (0, invariant_1.default)(parentNode === this.#rootNode, 'Unexpectedly escaped traversal');
@@ -668,8 +763,7 @@ class TreeFS {
668
763
  */
669
764
  #walkLookupNoFollow(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, onSegment, skipFallback) {
670
765
  let targetNormalPath = requestedNormalPath;
671
- let seen;
672
- let followedSymlink = false;
766
+ let followedSymlinks = 0;
673
767
  let fromIdx = startPathIdx;
674
768
  let parentNode = startNode;
675
769
  let ancestorOfRootIdx = startAncestorOfRootIdx;
@@ -699,7 +793,7 @@ class TreeFS {
699
793
  ? fromIdx - segmentName.length - 1
700
794
  : fromIdx - segmentName.length - 2;
701
795
  const parentCanonicalPath = parentEnd > 0 ? targetNormalPath.slice(0, parentEnd) : '';
702
- segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlink);
796
+ segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlinks > 0);
703
797
  if (segmentNode != null) {
704
798
  ancestorOfRootIdx = undefined;
705
799
  }
@@ -750,59 +844,17 @@ class TreeFS {
750
844
  missingSegmentName: segmentName,
751
845
  };
752
846
  }
753
- // Symlink in an interior position still follow it (only the leaf
754
- // is exempt from following under followLeaf=false).
755
- const normalSymlinkTarget = this.#resolveSymlinkTargetToNormalPath(segmentNode, currentPath);
756
- if (normalSymlinkTarget == null) {
757
- return {
758
- canonicalMissingPath: currentPath,
759
- exists: false,
760
- missingSegmentName: segmentName,
761
- };
847
+ const followResult = this.#followSymlink(segmentNode, currentPath, segmentName, targetNormalPath.slice(fromIdx), followedSymlinks, onSegment, undefined);
848
+ if (followResult !== undefined) {
849
+ return followResult;
762
850
  }
763
- const remainingTargetPath = targetNormalPath.slice(fromIdx);
764
- const joinedResult = this.#pathUtils.joinNormalToRelative(normalSymlinkTarget, remainingTargetPath);
765
- targetNormalPath = joinedResult.normalPath;
766
- if (onSegment != null &&
767
- ((ancestorOfRootIdx =
768
- this.#pathUtils.getAncestorOfRootIdx(normalSymlinkTarget) ?? undefined) === 0 ||
769
- joinedResult.collapsedSegments > 0)) {
770
- let node = this.#rootNode;
771
- let collapsedPath = '';
772
- const reverseAncestors = [];
773
- for (let i = 0; i <= joinedResult.collapsedSegments && isDirectory(node); i++) {
774
- if (i > 0 || ancestorOfRootIdx === 0 || joinedResult.collapsedSegments > 0) {
775
- reverseAncestors.push({
776
- ancestorOfRootIdx: i,
777
- node,
778
- normalPath: collapsedPath,
779
- segmentName: this.#pathUtils.getBasenameOfNthAncestor(i),
780
- });
781
- }
782
- node = node.get('..') ?? new Map();
783
- collapsedPath = collapsedPath === '' ? '..' : collapsedPath + path_1.default.sep + '..';
784
- }
785
- for (let i = reverseAncestors.length - 1; i >= 0; i--) {
786
- const a = reverseAncestors[i];
787
- onSegment(a.node, a.normalPath, a.segmentName, a.ancestorOfRootIdx);
788
- }
789
- }
790
- unseenPathFromIdx = normalSymlinkTarget.lastIndexOf(path_1.default.sep) + 1;
791
- if (seen == null) {
792
- seen = new Set([requestedNormalPath]);
793
- }
794
- if (seen.has(targetNormalPath)) {
795
- return {
796
- canonicalMissingPath: targetNormalPath,
797
- exists: false,
798
- missingSegmentName: segmentName,
799
- };
800
- }
801
- seen.add(targetNormalPath);
802
- followedSymlink = true;
803
- fromIdx = 0;
804
- parentNode = this.#rootNode;
805
- ancestorOfRootIdx = 0;
851
+ const scratch = this.#followScratch;
852
+ parentNode = scratch.parentNode;
853
+ ancestorOfRootIdx = scratch.ancestorOfRootIdx;
854
+ targetNormalPath = scratch.targetNormalPath;
855
+ fromIdx = scratch.fromIdx;
856
+ unseenPathFromIdx = scratch.unseenPathFromIdx;
857
+ followedSymlinks = scratch.followedSymlinks;
806
858
  }
807
859
  }
808
860
  (0, invariant_1.default)(parentNode === this.#rootNode, 'Unexpectedly escaped traversal');
@@ -823,7 +875,7 @@ class TreeFS {
823
875
  */
824
876
  #walkAndMakeDirectories(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, followLeaf, onSegment) {
825
877
  let targetNormalPath = requestedNormalPath;
826
- let seen;
878
+ let followedSymlinks = 0;
827
879
  let fromIdx = startPathIdx;
828
880
  let parentNode = startNode;
829
881
  let ancestorOfRootIdx = startAncestorOfRootIdx;
@@ -888,33 +940,17 @@ class TreeFS {
888
940
  missingSegmentName: segmentName,
889
941
  };
890
942
  }
891
- // Interior symlink: follow it.
892
- const normalSymlinkTarget = this.#resolveSymlinkTargetToNormalPath(segmentNode, currentPath);
893
- if (normalSymlinkTarget == null) {
894
- return {
895
- canonicalMissingPath: currentPath,
896
- exists: false,
897
- missingSegmentName: segmentName,
898
- };
899
- }
900
- const remainingTargetPath = isLastSegment ? '' : targetNormalPath.slice(fromIdx);
901
- const joinedResult = this.#pathUtils.joinNormalToRelative(normalSymlinkTarget, remainingTargetPath);
902
- targetNormalPath = joinedResult.normalPath;
903
- unseenPathFromIdx = normalSymlinkTarget.lastIndexOf(path_1.default.sep) + 1;
904
- if (seen == null) {
905
- seen = new Set([requestedNormalPath]);
943
+ const followResult = this.#followSymlink(segmentNode, currentPath, segmentName, isLastSegment ? '' : targetNormalPath.slice(fromIdx), followedSymlinks, undefined, undefined);
944
+ if (followResult !== undefined) {
945
+ return followResult;
906
946
  }
907
- if (seen.has(targetNormalPath)) {
908
- return {
909
- canonicalMissingPath: targetNormalPath,
910
- exists: false,
911
- missingSegmentName: segmentName,
912
- };
913
- }
914
- seen.add(targetNormalPath);
915
- fromIdx = 0;
916
- parentNode = this.#rootNode;
917
- ancestorOfRootIdx = 0;
947
+ const scratch = this.#followScratch;
948
+ parentNode = scratch.parentNode;
949
+ ancestorOfRootIdx = scratch.ancestorOfRootIdx;
950
+ targetNormalPath = scratch.targetNormalPath;
951
+ fromIdx = scratch.fromIdx;
952
+ unseenPathFromIdx = scratch.unseenPathFromIdx;
953
+ followedSymlinks = scratch.followedSymlinks;
918
954
  }
919
955
  }
920
956
  (0, invariant_1.default)(parentNode === this.#rootNode, 'Unexpectedly escaped traversal');
@@ -926,18 +962,18 @@ class TreeFS {
926
962
  parentNode: undefined,
927
963
  };
928
964
  }
929
- // Convenience wrappers for the common walker call shapes.
965
+ // Convenience wrappers. Top-level entries pass `0` for `initialFollows`.
930
966
  #lookup(normalPath, collectLinkPaths) {
931
- return this.#walkLookup(this.#rootNode, 0, 0, normalPath, undefined, collectLinkPaths);
967
+ return this.#walkLookup(this.#rootNode, 0, 0, normalPath, undefined, collectLinkPaths, 0);
932
968
  }
933
969
  #lookupNoFollow(normalPath) {
934
970
  return this.#walkLookupNoFollow(this.#rootNode, 0, 0, normalPath, undefined, false);
935
971
  }
936
972
  #lookupFromNode(startNode, startPathIdx, startAncestorOfRootIdx, target, collectLinkPaths) {
937
- return this.#walkLookup(startNode, startPathIdx, startAncestorOfRootIdx, target, undefined, collectLinkPaths);
973
+ return this.#walkLookup(startNode, startPathIdx, startAncestorOfRootIdx, target, undefined, collectLinkPaths, 0);
938
974
  }
939
975
  #walkAndStream(normalPath, onSegment, collectLinkPaths) {
940
- return this.#walkLookup(this.#rootNode, 0, 0, normalPath, onSegment, collectLinkPaths);
976
+ return this.#walkLookup(this.#rootNode, 0, 0, normalPath, onSegment, collectLinkPaths, 0);
941
977
  }
942
978
  /**
943
979
  * Given a start path (which need not exist), a subpath and type, and
@@ -963,18 +999,11 @@ class TreeFS {
963
999
  * (subpath: node_modules/pkg, type: d) in Node.js resolution.
964
1000
  */
965
1001
  hierarchicalLookup(mixedStartPath, subpath, opts) {
966
- const ancestorsOfInput = [];
1002
+ const ancestorsOfInput = this.#hierAncestorsBuf;
1003
+ ancestorsOfInput.length = 0;
967
1004
  const normalPath = this.#normalizePath(mixedStartPath);
968
1005
  const invalidatedBy = opts.invalidatedBy ?? undefined;
969
- const onSegment = (node, normalPathOfDir, segmentName, ancestorOfRootIdx) => {
970
- ancestorsOfInput.push({
971
- ancestorOfRootIdx,
972
- node,
973
- normalPath: normalPathOfDir,
974
- segmentName,
975
- });
976
- };
977
- const closestLookup = this.#walkAndStream(normalPath, onSegment, invalidatedBy);
1006
+ const closestLookup = this.#walkAndStream(normalPath, this.#pushHierAncestor, invalidatedBy);
978
1007
  if (closestLookup.exists && isDirectory(closestLookup.node)) {
979
1008
  const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(closestLookup.canonicalPath, subpath, opts.subpathType, invalidatedBy, {
980
1009
  ancestorOfRootIdx: closestLookup.ancestorOfRootIdx,
@@ -0,0 +1 @@
1
+ export default function isWatcherExcluded(filePath: string): boolean;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = isWatcherExcluded;
4
+ const EXCLUDED_DIR_SEGMENT = /(?:^|[/\\])\.(?:git|hg|cxx)[/\\]/;
5
+ function isWatcherExcluded(filePath) {
6
+ return EXCLUDED_DIR_SEGMENT.test(filePath);
7
+ }
@@ -45,7 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.AbstractWatcher = void 0;
46
46
  const events_1 = __importDefault(require("events"));
47
47
  const path = __importStar(require("path"));
48
- const isVcsPath_1 = __importDefault(require("../lib/isVcsPath"));
48
+ const isWatcherExcluded_1 = __importDefault(require("../lib/isWatcherExcluded"));
49
49
  const common_1 = require("./common");
50
50
  class AbstractWatcher {
51
51
  root;
@@ -60,8 +60,8 @@ class AbstractWatcher {
60
60
  this.ignored = ignored;
61
61
  this.globs = globs;
62
62
  this.doIgnore = ignored
63
- ? (filePath) => (0, isVcsPath_1.default)(filePath) || (0, common_1.posixPathMatchesPattern)(ignored, filePath)
64
- : isVcsPath_1.default;
63
+ ? (filePath) => (0, isWatcherExcluded_1.default)(filePath) || (0, common_1.posixPathMatchesPattern)(ignored, filePath)
64
+ : isWatcherExcluded_1.default;
65
65
  this.root = path.resolve(dir);
66
66
  }
67
67
  onFileEvent(listener) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/metro-file-map",
3
- "version": "56.0.1",
3
+ "version": "56.0.3",
4
4
  "description": "A metro-file-map fork for Expo used with the Metro bundler",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -42,7 +42,7 @@
42
42
  "publishConfig": {
43
43
  "access": "public"
44
44
  },
45
- "gitHead": "42013232893cb2aa71ab218e9b422d4a8476b3f0",
45
+ "gitHead": "f26be3dd9396bf7c399a1d607865d0fabdbc0d64",
46
46
  "scripts": {
47
47
  "build": "expo-module tsc",
48
48
  "clean": "expo-module clean",
@@ -1 +0,0 @@
1
- export default function isVcsPath(filePath: string): boolean;
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = isVcsPath;
4
- const VCS_DIR_SEGMENT = /(?:^|[/\\])\.(?:git|hg)[/\\]/;
5
- function isVcsPath(filePath) {
6
- return VCS_DIR_SEGMENT.test(filePath);
7
- }