@expo/metro-file-map 56.0.0-0 → 56.0.0-2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -6,6 +6,13 @@ type FallbackFilesystemOptions = {
6
6
  ignore: IgnoreMatcher;
7
7
  includeSymlinks: boolean;
8
8
  };
9
+ /**
10
+ * Whether a directory node was created or populated by the fallback filesystem.
11
+ * Used by TreeFS to determine if a directory outside the fallback boundary was
12
+ * reached legitimately (e.g., via symlink traversal) and should allow further
13
+ * fallback lookups.
14
+ */
15
+ export declare function isFallbackDir(dirNode: any): boolean;
9
16
  /**
10
17
  * Create a FallbackFilesystem that synchronously queries the real filesystem.
11
18
  *
@@ -3,17 +3,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isFallbackDir = isFallbackDir;
6
7
  exports.default = createFallbackFilesystem;
7
8
  exports.shouldFallbackCrawlDir = shouldFallbackCrawlDir;
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const path_1 = __importDefault(require("path"));
10
11
  const normalizePathSeparatorsToPosix_1 = __importDefault(require("../../lib/normalizePathSeparatorsToPosix"));
11
- const readdirMarker = Symbol.for('fallbackDir');
12
- function markDir(dirNode) {
13
- dirNode[readdirMarker] = true;
12
+ const FALLBACK_DIR = Symbol.for('fallbackDir');
13
+ var FallbackFlag;
14
+ (function (FallbackFlag) {
15
+ FallbackFlag[FallbackFlag["VISITED"] = 0] = "VISITED";
16
+ FallbackFlag[FallbackFlag["CRAWLED"] = 1] = "CRAWLED";
17
+ })(FallbackFlag || (FallbackFlag = {}));
18
+ function markDir(dirNode, flag) {
19
+ dirNode[FALLBACK_DIR] = flag;
14
20
  }
15
- function isMarkedDir(dirNode) {
16
- return !!dirNode[readdirMarker];
21
+ function getDirFlag(dirNode) {
22
+ return dirNode[FALLBACK_DIR];
23
+ }
24
+ /**
25
+ * Whether a directory node was created or populated by the fallback filesystem.
26
+ * Used by TreeFS to determine if a directory outside the fallback boundary was
27
+ * reached legitimately (e.g., via symlink traversal) and should allow further
28
+ * fallback lookups.
29
+ */
30
+ function isFallbackDir(dirNode) {
31
+ return dirNode[FALLBACK_DIR] != null;
17
32
  }
18
33
  function isDirectory(node) {
19
34
  return node instanceof Map;
@@ -34,7 +49,7 @@ function createFallbackFilesystem(opts) {
34
49
  return acc;
35
50
  }, {});
36
51
  function readdir(_normalPath, absolutePath, dirNode) {
37
- if (dirNode != null && isMarkedDir(dirNode)) {
52
+ if (dirNode != null && getDirFlag(dirNode) === FallbackFlag.CRAWLED) {
38
53
  return dirNode;
39
54
  }
40
55
  let dirEntries;
@@ -53,7 +68,9 @@ function createFallbackFilesystem(opts) {
53
68
  }
54
69
  if (entry.isDirectory()) {
55
70
  if (!result.has(name)) {
56
- result.set(name, new Map());
71
+ const childDir = new Map();
72
+ markDir(childDir, FallbackFlag.VISITED);
73
+ result.set(name, childDir);
57
74
  }
58
75
  }
59
76
  else if (entry.isSymbolicLink()) {
@@ -69,7 +86,7 @@ function createFallbackFilesystem(opts) {
69
86
  }
70
87
  }
71
88
  }
72
- markDir(result);
89
+ markDir(result, FallbackFlag.CRAWLED);
73
90
  return result;
74
91
  }
75
92
  return {
@@ -87,9 +104,15 @@ function createFallbackFilesystem(opts) {
87
104
  }
88
105
  if (stat.isDirectory()) {
89
106
  const dirNode = isDirectory(prevNode) ? prevNode : null;
90
- return shouldFallbackCrawlDir(absolutePath)
91
- ? readdir(normalPath, absolutePath, dirNode)
92
- : (dirNode ?? new Map());
107
+ if (shouldFallbackCrawlDir(absolutePath)) {
108
+ return readdir(normalPath, absolutePath, dirNode);
109
+ }
110
+ if (dirNode != null) {
111
+ return dirNode;
112
+ }
113
+ const newDir = new Map();
114
+ markDir(newDir, FallbackFlag.VISITED);
115
+ return newDir;
93
116
  }
94
117
  else if (stat.isSymbolicLink()) {
95
118
  if (!includeSymlinks) {
@@ -78,7 +78,11 @@ function find(roots, extensions, ignore, includeSymlinks, rootDir, console, prev
78
78
  ? name
79
79
  : dirNormal + path.sep + name;
80
80
  if (entry.isDirectory()) {
81
- search(file, childNormal, isWithinRoot || childNormal === '');
81
+ // NOTE(@kitten): We'd like to be able to apply excludes to directories selectively based
82
+ // on their normal paths, so we can exclude using `^...`
83
+ if (!ignore(childNormal)) {
84
+ search(file, childNormal, isWithinRoot || childNormal === '');
85
+ }
82
86
  continue;
83
87
  }
84
88
  const ext = path.extname(file).substr(1);
@@ -79,6 +79,7 @@ class TreeFS {
79
79
  #processFile;
80
80
  #rootDir;
81
81
  #rootPattern;
82
+ #roots;
82
83
  #rootNode = new Map();
83
84
  constructor(opts) {
84
85
  const { rootDir, files, processFile, fallbackFilesystem, roots, serverRoot } = opts;
@@ -92,13 +93,15 @@ class TreeFS {
92
93
  else {
93
94
  this.#fallbackBoundaryDepth = null;
94
95
  }
96
+ const normalRoots = (roots ?? []).map((r) => this.#pathUtils.absoluteToNormal(r));
97
+ this.#roots = normalRoots;
95
98
  this.#rootPattern = (0, RootPathUtils_1.pathsToPattern)(roots ?? [], this.#pathUtils);
96
99
  if (files != null) {
97
100
  this.bulkAddOrModify(files);
98
101
  }
99
102
  }
100
103
  getSerializableSnapshot() {
101
- return this.#cloneTree(this.#rootNode, '');
104
+ return this.#cloneTree(this.#rootNode);
102
105
  }
103
106
  static fromDeserializedSnapshot(args) {
104
107
  const { rootDir, fileSystemData, processFile, fallbackFilesystem, roots, serverRoot } = args;
@@ -428,6 +431,9 @@ class TreeFS {
428
431
  let targetNormalPath = requestedNormalPath;
429
432
  // Lazy-initialised set of seen target paths, to detect symlink cycles.
430
433
  let seen;
434
+ // Set when a symlink is followed, to allow fallback population outside
435
+ // the boundary for paths reachable transitively through symlinks.
436
+ let followedSymlink = false;
431
437
  // Pointer to the first character of the current path segment in
432
438
  // targetNormalPath.
433
439
  let fromIdx = opts.start?.pathIdx ?? 0;
@@ -470,7 +476,7 @@ class TreeFS {
470
476
  ? fromIdx - segmentName.length - 1
471
477
  : fromIdx - segmentName.length - 2;
472
478
  const parentCanonicalPath = parentEnd > 0 ? targetNormalPath.slice(0, parentEnd) : '';
473
- segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath);
479
+ segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlink);
474
480
  if (segmentNode != null) {
475
481
  ancestorOfRootIdx = null;
476
482
  }
@@ -496,8 +502,7 @@ class TreeFS {
496
502
  }
497
503
  parentNode.set(segmentName, segmentNode);
498
504
  }
499
- else if (!opts.skipFallback &&
500
- this.#fallbackFilesystem != null) {
505
+ else if (!opts.skipFallback && this.#fallbackFilesystem != null) {
501
506
  parentNode.set(segmentName, segmentNode);
502
507
  }
503
508
  }
@@ -617,6 +622,7 @@ class TreeFS {
617
622
  };
618
623
  }
619
624
  seen.add(targetNormalPath);
625
+ followedSymlink = true;
620
626
  fromIdx = 0;
621
627
  parentNode = this.#rootNode;
622
628
  ancestorOfRootIdx = 0;
@@ -827,7 +833,7 @@ class TreeFS {
827
833
  !pathPrefix.endsWith(pathSep + '..')) {
828
834
  const canonicalRoot = opts.canonicalPathOfRoot;
829
835
  const rootCanonical = pathPrefix === '' ? canonicalRoot : canonicalRoot + path_1.default.sep + pathPrefix;
830
- this.#populateDirFromFilesystem(iterationRootNode, rootCanonical, false);
836
+ this.#populateDirFromFilesystem(iterationRootNode, rootCanonical, false, false);
831
837
  }
832
838
  for (const [name, node] of this.#directoryNodeIterator(iterationRootNode, iterationRootParentNode, ancestorOfRootIdx)) {
833
839
  if (node == null) {
@@ -881,7 +887,7 @@ class TreeFS {
881
887
  const canonicalPath = opts.canonicalPathOfRoot === ''
882
888
  ? nodePathWithSystemSeparators
883
889
  : opts.canonicalPathOfRoot + path_1.default.sep + nodePathWithSystemSeparators;
884
- this.#populateDirFromFilesystem(node, canonicalPath, false);
890
+ this.#populateDirFromFilesystem(node, canonicalPath, false, false);
885
891
  }
886
892
  yield* this.#pathIterator(node, iterationRootParentNode, ancestorOfRootIdx != null && ancestorOfRootIdx > 0 ? ancestorOfRootIdx - 1 : null, opts, nodePath, followedLinks);
887
893
  }
@@ -924,25 +930,70 @@ class TreeFS {
924
930
  }
925
931
  return result.node;
926
932
  }
927
- #cloneTree(root, prefix) {
928
- const clone = new Map();
929
- for (const [name, node] of root) {
930
- if (node == null) {
931
- continue;
933
+ /**
934
+ * Return a filtered view of the tree containing only content under watched
935
+ * roots. Walk each root path from #rootNode, creating intermediate directory
936
+ * nodes as needed, and reference the subtree at each root endpoint directly.
937
+ * Since roots are non-overlapping, each contributes independently.
938
+ */
939
+ #cloneTree(rootNode) {
940
+ // NOTE(@kitten): The upstream version deeply clones this structure, but this
941
+ // isn't necessary since it's serialized right away by the DiskCacheManager.
942
+ // Even if it isn't, the intention is to store it faithfully, so we're okay
943
+ // with more modifications
944
+ function copyRootInto(normalRoot, source, clone) {
945
+ let currentSource = source;
946
+ let currentClone = clone;
947
+ let fromIdx = 0;
948
+ while (fromIdx < normalRoot.length) {
949
+ const nextSepIdx = normalRoot.indexOf(path_1.default.sep, fromIdx);
950
+ const isLastSegment = nextSepIdx === -1;
951
+ const seg = isLastSegment
952
+ ? normalRoot.slice(fromIdx)
953
+ : normalRoot.slice(fromIdx, nextSepIdx);
954
+ fromIdx = isLastSegment ? normalRoot.length : nextSepIdx + 1;
955
+ const sourceChild = currentSource.get(seg);
956
+ if (sourceChild == null || !isDirectory(sourceChild)) {
957
+ return;
958
+ }
959
+ else if (isLastSegment || fromIdx >= normalRoot.length) {
960
+ currentClone.set(seg, sourceChild);
961
+ }
962
+ else {
963
+ let cloneChild = currentClone.get(seg);
964
+ if (cloneChild == null || !isDirectory(cloneChild)) {
965
+ cloneChild = new Map();
966
+ currentClone.set(seg, cloneChild);
967
+ }
968
+ currentSource = sourceChild;
969
+ currentClone = cloneChild;
970
+ }
932
971
  }
933
- else if (isDirectory(node)) {
934
- const childPath = prefix === '' ? name : prefix + path_1.default.sep + name;
935
- if (this.#rootPattern == null || this.#rootPattern.test(childPath + path_1.default.sep)) {
936
- clone.set(name, this.#cloneTree(node, childPath));
972
+ }
973
+ if (this.#roots.length === 0) {
974
+ return rootNode;
975
+ }
976
+ const clone = new Map();
977
+ for (const normalRoot of this.#roots) {
978
+ if (normalRoot === '') {
979
+ // Root is rootDir itself — include everything except '..'
980
+ for (const [name, node] of rootNode) {
981
+ if (node != null && name !== '..') {
982
+ clone.set(name, node);
983
+ }
937
984
  }
938
985
  }
939
986
  else {
940
- clone.set(name, [...node]);
987
+ copyRootInto(normalRoot, rootNode, clone);
941
988
  }
942
989
  }
943
990
  return clone;
944
991
  }
945
- #isOutsideFallbackBoundary(canonicalPath) {
992
+ #isOutsideFallbackBoundary(canonicalPath, dirNode) {
993
+ // We allow any directory that's already been crawled
994
+ if ((0, fallback_1.isFallbackDir)(dirNode)) {
995
+ return false;
996
+ }
946
997
  const maxDepth = this.#fallbackBoundaryDepth;
947
998
  return maxDepth != null && (0, RootPathUtils_1.getAncestorOfRootIdx)(canonicalPath) > maxDepth;
948
999
  }
@@ -951,20 +1002,26 @@ class TreeFS {
951
1002
  * fallback filesystem. The fallback returns tree-compatible nodes
952
1003
  * (FileMetadata tuples or directory Maps) that are inserted directly.
953
1004
  *
1005
+ * Accepts `wasFollowing` to allow traversal on symlinks that were followed.
1006
+ * If we're resolving a symlink target, we allow the lookup to escape the
1007
+ * fallback scope.
1008
+ *
954
1009
  * Returns the newly created node, or null if the path doesn't exist on disk.
955
1010
  */
956
- #populateFromFilesystem(parentNode, segmentName, parentCanonicalPath) {
1011
+ #populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, wasFollowing) {
957
1012
  const fallback = this.#fallbackFilesystem;
958
1013
  if (fallback == null) {
959
1014
  return null;
960
1015
  }
1016
+ // A symlink traversal (wasFollowing) or a parent created by the fallback
1017
+ // (isFallbackDir) lets us skip the boundary check.
961
1018
  const childCanonicalPath = parentCanonicalPath === '' ? segmentName : parentCanonicalPath + path_1.default.sep + segmentName;
962
1019
  if (this.#rootPattern?.test(childCanonicalPath + path_1.default.sep) ||
963
- this.#isOutsideFallbackBoundary(childCanonicalPath)) {
1020
+ (!wasFollowing && this.#isOutsideFallbackBoundary(childCanonicalPath, parentNode))) {
964
1021
  return null;
965
1022
  }
966
1023
  else if (parentCanonicalPath !== '' && (0, fallback_1.shouldFallbackCrawlDir)(parentCanonicalPath)) {
967
- this.#populateDirFromFilesystem(parentNode, parentCanonicalPath, true);
1024
+ this.#populateDirFromFilesystem(parentNode, parentCanonicalPath, true, wasFollowing);
968
1025
  return parentNode.get(segmentName) ?? null;
969
1026
  }
970
1027
  else if (parentNode.has(segmentName)) {
@@ -984,12 +1041,12 @@ class TreeFS {
984
1041
  * iteration, and by #populateFromFilesystem for optimistic parent
985
1042
  * population.
986
1043
  */
987
- #populateDirFromFilesystem(dirNode, canonicalPath, skipCheck) {
1044
+ #populateDirFromFilesystem(dirNode, canonicalPath, skipCheck, wasFollowed) {
988
1045
  const fallback = this.#fallbackFilesystem;
989
1046
  if (fallback == null ||
990
1047
  (!skipCheck &&
991
1048
  (this.#rootPattern?.test(canonicalPath + path_1.default.sep) ||
992
- this.#isOutsideFallbackBoundary(canonicalPath)))) {
1049
+ (!wasFollowed && this.#isOutsideFallbackBoundary(canonicalPath, dirNode))))) {
993
1050
  return;
994
1051
  }
995
1052
  const absolutePath = this.#pathUtils.normalToAbsolute(canonicalPath);
package/package.json CHANGED
@@ -1,18 +1,9 @@
1
1
  {
2
2
  "name": "@expo/metro-file-map",
3
- "version": "56.0.0-0",
3
+ "version": "56.0.0-2",
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",
7
- "scripts": {
8
- "build": "expo-module tsc",
9
- "clean": "expo-module clean",
10
- "lint": "expo-module lint",
11
- "prepublishOnly": "pnpm run clean && pnpm run build",
12
- "test": "expo-module test",
13
- "typecheck": "expo-module typecheck",
14
- "watch": "expo-module tsc --watch --preserveWatchOutput"
15
- },
16
7
  "repository": {
17
8
  "type": "git",
18
9
  "url": "https://github.com/expo/expo.git",
@@ -45,11 +36,19 @@
45
36
  "@types/invariant": "^2.2.37",
46
37
  "@types/micromatch": "^4.0.10",
47
38
  "@types/node": "^22.14.0",
48
- "expo-module-scripts": "56.0.0",
49
- "memfs": "^3.6.0"
39
+ "memfs": "^3.6.0",
40
+ "expo-module-scripts": "56.0.2"
50
41
  },
51
42
  "publishConfig": {
52
43
  "access": "public"
53
44
  },
54
- "gitHead": "6dcd9ce9d615d25df78597f60520492e35e02efb"
55
- }
45
+ "gitHead": "a8ab3da510a34b7bdb2262aa9887d4f78b102280",
46
+ "scripts": {
47
+ "build": "expo-module tsc",
48
+ "clean": "expo-module clean",
49
+ "lint": "expo-module lint",
50
+ "test": "expo-module test",
51
+ "typecheck": "expo-module typecheck",
52
+ "watch": "expo-module tsc --watch --preserveWatchOutput"
53
+ }
54
+ }