@expo/metro-file-map 56.0.0 → 56.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/build/cache/DiskCacheManager.d.ts +7 -0
- package/build/cache/DiskCacheManager.js +6 -1
- package/build/crawlers/node/fallback.js +2 -1
- package/build/crawlers/node/index.js +8 -2
- package/build/crawlers/watchman/index.js +2 -2
- package/build/lib/RootPathUtils.js +26 -6
- package/build/lib/TreeFS.js +523 -174
- package/build/lib/isWatcherExcluded.d.ts +1 -0
- package/build/lib/isWatcherExcluded.js +7 -0
- package/build/watchers/AbstractWatcher.js +3 -3
- package/package.json +2 -2
- package/build/lib/isVcsPath.d.ts +0 -1
- package/build/lib/isVcsPath.js +0 -7
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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();
|
|
@@ -48,6 +48,15 @@ const UP_FRAGMENT_SEP = '..' + path_1.default.sep;
|
|
|
48
48
|
const SEP_UP_FRAGMENT = path_1.default.sep + '..';
|
|
49
49
|
const UP_FRAGMENT_SEP_LENGTH = UP_FRAGMENT_SEP.length;
|
|
50
50
|
const CURRENT_FRAGMENT = '.' + path_1.default.sep;
|
|
51
|
+
const IS_WIN32 = path_1.default.sep === '\\';
|
|
52
|
+
const ROOT_BASE_IDX = IS_WIN32 ? 0 : 1;
|
|
53
|
+
function startsWithDriveLetter(str) {
|
|
54
|
+
if (!IS_WIN32 || str.charCodeAt(1) !== 58 /* ':' */) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const c = str.charCodeAt(0);
|
|
58
|
+
return (c >= 65 && c <= 90) /* A-Z */ || (c >= 97 && c <= 122) /* a-z */;
|
|
59
|
+
}
|
|
51
60
|
class RootPathUtils {
|
|
52
61
|
#rootDir;
|
|
53
62
|
#rootDirnames;
|
|
@@ -121,6 +130,13 @@ class RootPathUtils {
|
|
|
121
130
|
if (right.length === 0) {
|
|
122
131
|
return left;
|
|
123
132
|
}
|
|
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.
|
|
138
|
+
return right;
|
|
139
|
+
}
|
|
124
140
|
// left may already end in a path separator only if it is a filesystem root,
|
|
125
141
|
// '/' or 'X:\'.
|
|
126
142
|
if (i === this.#rootDepth) {
|
|
@@ -135,8 +151,11 @@ class RootPathUtils {
|
|
|
135
151
|
resolveSymlinkToNormal(symlinkNormalPath, readlinkResult) {
|
|
136
152
|
let target = (0, normalizePathSeparatorsToSystem_1.default)(readlinkResult);
|
|
137
153
|
// WARN: This only applies to Windows + Node 20 case, where the value is completely
|
|
138
|
-
// unnormalized and a trailing slash may be returned
|
|
139
|
-
|
|
154
|
+
// unnormalized and a trailing slash may be returned. Skip the strip when the target
|
|
155
|
+
// is a filesystem root: POSIX '/' or Windows 'X:\'
|
|
156
|
+
const len = target.length;
|
|
157
|
+
const isFsRoot = len === 1 || (len === 3 && startsWithDriveLetter(target));
|
|
158
|
+
if (!isFsRoot && target[len - 1] === path_1.default.sep) {
|
|
140
159
|
target = target.slice(0, -1);
|
|
141
160
|
}
|
|
142
161
|
if (path_1.default.isAbsolute(target)) {
|
|
@@ -176,7 +195,7 @@ class RootPathUtils {
|
|
|
176
195
|
if (relativePath === '') {
|
|
177
196
|
return { collapsedSegments: 0, normalPath };
|
|
178
197
|
}
|
|
179
|
-
const left = normalPath + path_1.default.sep;
|
|
198
|
+
const left = normalPath.endsWith(path_1.default.sep) ? normalPath : normalPath + path_1.default.sep;
|
|
180
199
|
const rawPath = left + relativePath;
|
|
181
200
|
if (normalPath === '..' || normalPath.endsWith(SEP_UP_FRAGMENT)) {
|
|
182
201
|
const collapsed = this.#tryCollapseIndirectionsInSuffix(rawPath, 0, 0);
|
|
@@ -262,9 +281,10 @@ class RootPathUtils {
|
|
|
262
281
|
collapsedSegments,
|
|
263
282
|
};
|
|
264
283
|
}
|
|
265
|
-
// Cap the number of indirections at the total number of root
|
|
266
|
-
// File systems treat '..' at the root as '.'.
|
|
267
|
-
|
|
284
|
+
// Cap the number of indirections at the total number of root parts.
|
|
285
|
+
// File systems treat '..' at the root as '.'. For Windows, cross-device
|
|
286
|
+
// paths need to survive this
|
|
287
|
+
if (totalUpIndirections < this.#rootParts.length - ROOT_BASE_IDX) {
|
|
268
288
|
totalUpIndirections++;
|
|
269
289
|
}
|
|
270
290
|
if (nextIndirection !== pos + 1 || // Fallback when ./ later in the path, or leading
|
package/build/lib/TreeFS.js
CHANGED
|
@@ -13,10 +13,10 @@ 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
21
|
return node instanceof Map;
|
|
22
22
|
}
|
|
@@ -116,8 +116,11 @@ class TreeFS {
|
|
|
116
116
|
return tfs;
|
|
117
117
|
}
|
|
118
118
|
getSize(mixedPath) {
|
|
119
|
-
const
|
|
120
|
-
|
|
119
|
+
const result = this.#lookup(this.#normalizePath(mixedPath));
|
|
120
|
+
if (!result.exists || isDirectory(result.node)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return result.node[constants_1.default.SIZE] ?? null;
|
|
121
124
|
}
|
|
122
125
|
getDifference(files, options) {
|
|
123
126
|
const changedFiles = new Map(files);
|
|
@@ -127,9 +130,7 @@ class TreeFS {
|
|
|
127
130
|
let rootNode = this.#rootNode;
|
|
128
131
|
let prefix = '';
|
|
129
132
|
if (subpath != null && subpath !== '') {
|
|
130
|
-
const lookupResult = this.#
|
|
131
|
-
followLeaf: true,
|
|
132
|
-
});
|
|
133
|
+
const lookupResult = this.#lookup(subpath);
|
|
133
134
|
if (!lookupResult.exists || !isDirectory(lookupResult.node)) {
|
|
134
135
|
// Directory doesn't exist, nothing to compare - all files are new
|
|
135
136
|
return { changedFiles, removedFiles };
|
|
@@ -137,15 +138,12 @@ class TreeFS {
|
|
|
137
138
|
rootNode = lookupResult.node;
|
|
138
139
|
prefix = lookupResult.canonicalPath;
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
-
includeNodeModules: true,
|
|
142
|
-
includeSymlinks: true,
|
|
143
|
-
}, prefix)) {
|
|
141
|
+
this.#forEachMetadata(rootNode, { includeNodeModules: true, includeSymlinks: true }, prefix, (_baseName, canonicalPath, metadata) => {
|
|
144
142
|
const newMetadata = files.get(canonicalPath);
|
|
145
143
|
if (newMetadata) {
|
|
146
144
|
if (isRegularFile(newMetadata) !== isRegularFile(metadata)) {
|
|
147
145
|
// Types differ, file has changed
|
|
148
|
-
|
|
146
|
+
return;
|
|
149
147
|
}
|
|
150
148
|
if (newMetadata[constants_1.default.MTIME] != null &&
|
|
151
149
|
newMetadata[constants_1.default.MTIME] !== 0 &&
|
|
@@ -170,28 +168,29 @@ class TreeFS {
|
|
|
170
168
|
else {
|
|
171
169
|
removedFiles.add(canonicalPath);
|
|
172
170
|
}
|
|
173
|
-
}
|
|
171
|
+
});
|
|
174
172
|
return {
|
|
175
173
|
changedFiles,
|
|
176
174
|
removedFiles,
|
|
177
175
|
};
|
|
178
176
|
}
|
|
179
177
|
getMtimeByNormalPath(normalPath) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
// skipFallback=true: this is a cache-validation lookup; consulting the
|
|
179
|
+
// fallback filesystem here would be expensive AND mutate the tree as a
|
|
180
|
+
// side effect via `#populateFromFilesystem`.
|
|
181
|
+
const result = this.#walkLookupNoFollow(this.#rootNode, 0, 0, normalPath, undefined, true);
|
|
184
182
|
return result.exists && !isDirectory(result.node) ? result.node[constants_1.default.MTIME] : null;
|
|
185
183
|
}
|
|
186
184
|
getSha1(mixedPath) {
|
|
187
|
-
const
|
|
188
|
-
|
|
185
|
+
const result = this.#lookup(this.#normalizePath(mixedPath));
|
|
186
|
+
if (!result.exists || isDirectory(result.node)) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
return result.node[constants_1.default.SHA1] ?? null;
|
|
189
190
|
}
|
|
190
191
|
async getOrComputeSha1(mixedPath) {
|
|
191
192
|
const normalPath = this.#normalizePath(mixedPath);
|
|
192
|
-
const result = this.#
|
|
193
|
-
followLeaf: true,
|
|
194
|
-
});
|
|
193
|
+
const result = this.#lookup(normalPath);
|
|
195
194
|
if (!result.exists || isDirectory(result.node)) {
|
|
196
195
|
return null;
|
|
197
196
|
}
|
|
@@ -230,16 +229,13 @@ class TreeFS {
|
|
|
230
229
|
: { sha1 };
|
|
231
230
|
}
|
|
232
231
|
exists(mixedPath) {
|
|
233
|
-
const result = this.#
|
|
234
|
-
return result
|
|
232
|
+
const result = this.#lookup(this.#normalizePath(mixedPath));
|
|
233
|
+
return result.exists && !isDirectory(result.node);
|
|
235
234
|
}
|
|
236
235
|
lookup(mixedPath) {
|
|
237
236
|
const normalPath = this.#normalizePath(mixedPath);
|
|
238
237
|
const links = new Set();
|
|
239
|
-
const result = this.#
|
|
240
|
-
collectLinkPaths: links,
|
|
241
|
-
followLeaf: true,
|
|
242
|
-
});
|
|
238
|
+
const result = this.#lookup(normalPath, links);
|
|
243
239
|
if (!result.exists) {
|
|
244
240
|
const { canonicalMissingPath } = result;
|
|
245
241
|
return {
|
|
@@ -257,19 +253,33 @@ class TreeFS {
|
|
|
257
253
|
return { exists: true, links, realPath, type: 'f', metadata: node };
|
|
258
254
|
}
|
|
259
255
|
getAllFiles() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
const result = [];
|
|
257
|
+
this.#collectAllFilesInto(this.#rootNode, '', result);
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
// NOTE(@kitten): Specialize the `getAllFiles` collector, to avoid a megamorphic deopt in V8
|
|
261
|
+
#collectAllFilesInto(node, prefix, result) {
|
|
262
|
+
for (const [name, child] of node) {
|
|
263
|
+
if (child == null) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const prefixedName = prefix === '' ? name : prefix + path_1.default.sep + name;
|
|
267
|
+
if (isDirectory(child)) {
|
|
268
|
+
this.#collectAllFilesInto(child, prefixedName, result);
|
|
269
|
+
}
|
|
270
|
+
else if (isRegularFile(child)) {
|
|
271
|
+
result.push(this.#pathUtils.normalToAbsolute(prefixedName));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
264
274
|
}
|
|
265
275
|
linkStats(mixedPath) {
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
276
|
+
const result = this.#lookupNoFollow(this.#normalizePath(mixedPath));
|
|
277
|
+
if (!result.exists || isDirectory(result.node)) {
|
|
268
278
|
return null;
|
|
269
279
|
}
|
|
270
|
-
const
|
|
280
|
+
const fileMetadata = result.node;
|
|
271
281
|
return {
|
|
272
|
-
fileType,
|
|
282
|
+
fileType: isRegularFile(fileMetadata) ? 'f' : 'l',
|
|
273
283
|
modifiedTime: fileMetadata[constants_1.default.MTIME],
|
|
274
284
|
size: fileMetadata[constants_1.default.SIZE],
|
|
275
285
|
};
|
|
@@ -282,7 +292,7 @@ class TreeFS {
|
|
|
282
292
|
*matchFiles(opts) {
|
|
283
293
|
const { filter = null, filterCompareAbsolute = false, filterComparePosix = false, follow = false, recursive = true, rootDir = null, } = opts;
|
|
284
294
|
const normalRoot = rootDir == null ? '' : this.#normalizePath(rootDir);
|
|
285
|
-
const contextRootResult = this.#
|
|
295
|
+
const contextRootResult = this.#lookup(normalRoot);
|
|
286
296
|
if (!contextRootResult.exists) {
|
|
287
297
|
return;
|
|
288
298
|
}
|
|
@@ -295,13 +305,14 @@ class TreeFS {
|
|
|
295
305
|
const contextRootAbsolutePathForComparison = filterComparePosix && path_1.default.sep !== '/'
|
|
296
306
|
? contextRootAbsolutePath.replaceAll(path_1.default.sep, '/')
|
|
297
307
|
: contextRootAbsolutePath;
|
|
298
|
-
|
|
308
|
+
const matches = [];
|
|
309
|
+
this.#forEachPath(contextRoot, contextRootParent, ancestorOfRootIdx, {
|
|
299
310
|
alwaysYieldPosix: filterComparePosix,
|
|
300
311
|
canonicalPathOfRoot: rootRealPath,
|
|
301
312
|
follow,
|
|
302
313
|
recursive,
|
|
303
314
|
subtreeOnly: rootDir != null,
|
|
304
|
-
})) {
|
|
315
|
+
}, '', new Set(), (relativePathForComparison) => {
|
|
305
316
|
if (filter == null ||
|
|
306
317
|
filter.test(
|
|
307
318
|
// NOTE(EvanBacon): Ensure files start with `./` for matching purposes
|
|
@@ -313,24 +324,44 @@ class TreeFS {
|
|
|
313
324
|
const relativePath = filterComparePosix === true && path_1.default.sep !== '/'
|
|
314
325
|
? relativePathForComparison.replaceAll('/', path_1.default.sep)
|
|
315
326
|
: relativePathForComparison;
|
|
316
|
-
|
|
327
|
+
matches.push(path_1.default.join(contextRootAbsolutePath, relativePath));
|
|
317
328
|
}
|
|
318
|
-
}
|
|
329
|
+
});
|
|
330
|
+
yield* matches;
|
|
319
331
|
}
|
|
320
332
|
addOrModify(mixedPath, metadata, changeListener) {
|
|
321
333
|
const normalPath = this.#normalizePath(mixedPath);
|
|
334
|
+
const dirname = path_1.default.dirname(normalPath);
|
|
335
|
+
const basename = path_1.default.basename(normalPath);
|
|
322
336
|
// Walk the tree to find the *real* path of the parent node, creating
|
|
323
337
|
// directories as we need.
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
338
|
+
const onSegment = changeListener
|
|
339
|
+
? (_node, segmentNormalPath, _segmentName, _idx, isNewlyCreated) => {
|
|
340
|
+
if (isNewlyCreated) {
|
|
341
|
+
changeListener.directoryAdded(segmentNormalPath);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
: undefined;
|
|
345
|
+
const parentDirNode = this.#walkAndMakeDirectories(this.#rootNode, 0, 0, dirname, true, onSegment);
|
|
328
346
|
if (!parentDirNode.exists) {
|
|
329
347
|
throw new Error(`TreeFS: Failed to make parent directory entry for ${mixedPath}`);
|
|
330
348
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
349
|
+
if (!isDirectory(parentDirNode.node)) {
|
|
350
|
+
throw new Error(`TreeFS: Could not add directory ${dirname}, adding ${mixedPath}. ` +
|
|
351
|
+
`${dirname} already exists in the file map as a file.`);
|
|
352
|
+
}
|
|
353
|
+
const canonicalPath = this.#normalizePath(parentDirNode.canonicalPath + path_1.default.sep + basename);
|
|
354
|
+
if (changeListener != null) {
|
|
355
|
+
const existingNode = parentDirNode.node.get(basename);
|
|
356
|
+
if (existingNode != null) {
|
|
357
|
+
(0, invariant_1.default)(!isDirectory(existingNode), 'Detected addition or modification of file %s, but it is tracked as a non-empty directory', canonicalPath);
|
|
358
|
+
changeListener.fileModified(canonicalPath, existingNode, metadata);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
changeListener.fileAdded(canonicalPath, metadata);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
parentDirNode.node.set(basename, metadata);
|
|
334
365
|
}
|
|
335
366
|
bulkAddOrModify(addedOrModifiedFiles, changeListener) {
|
|
336
367
|
// Optimisation: Bulk FileData are typically clustered by directory, so we
|
|
@@ -339,16 +370,19 @@ class TreeFS {
|
|
|
339
370
|
// faster than caching all lookups in a Map, and 70% faster than no cache.
|
|
340
371
|
let lastDir;
|
|
341
372
|
let directoryNode;
|
|
373
|
+
const onSegment = changeListener
|
|
374
|
+
? (_node, segmentNormalPath, _segmentName, _idx, isNewlyCreated) => {
|
|
375
|
+
if (isNewlyCreated) {
|
|
376
|
+
changeListener.directoryAdded(segmentNormalPath);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
: undefined;
|
|
342
380
|
for (const [normalPath, metadata] of addedOrModifiedFiles) {
|
|
343
381
|
const lastSepIdx = normalPath.lastIndexOf(path_1.default.sep);
|
|
344
382
|
const dirname = lastSepIdx === -1 ? '' : normalPath.slice(0, lastSepIdx);
|
|
345
383
|
const basename = lastSepIdx === -1 ? normalPath : normalPath.slice(lastSepIdx + 1);
|
|
346
384
|
if (directoryNode == null || dirname !== lastDir) {
|
|
347
|
-
const lookup = this.#
|
|
348
|
-
changeListener,
|
|
349
|
-
followLeaf: false,
|
|
350
|
-
makeDirectories: true,
|
|
351
|
-
});
|
|
385
|
+
const lookup = this.#walkAndMakeDirectories(this.#rootNode, 0, 0, dirname, false, onSegment);
|
|
352
386
|
if (!lookup.exists) {
|
|
353
387
|
// This should only be possible if the input is non-real and
|
|
354
388
|
// lookup hits a broken symlink.
|
|
@@ -379,14 +413,17 @@ class TreeFS {
|
|
|
379
413
|
}
|
|
380
414
|
remove(mixedPath, changeListener) {
|
|
381
415
|
const normalPath = this.#normalizePath(mixedPath);
|
|
382
|
-
|
|
416
|
+
this.#removeNormalPath(normalPath, changeListener);
|
|
417
|
+
}
|
|
418
|
+
#removeNormalPath(normalPath, changeListener) {
|
|
419
|
+
const result = this.#lookupNoFollow(normalPath);
|
|
383
420
|
if (!result.exists) {
|
|
384
421
|
return;
|
|
385
422
|
}
|
|
386
423
|
const { parentNode, canonicalPath, node } = result;
|
|
387
424
|
if (isDirectory(node) && node.size > 0) {
|
|
388
425
|
for (const basename of node.keys()) {
|
|
389
|
-
this
|
|
426
|
+
this.#removeNormalPath(canonicalPath + path_1.default.sep + basename, changeListener);
|
|
390
427
|
}
|
|
391
428
|
// Removing the last file will delete this directory
|
|
392
429
|
return;
|
|
@@ -405,9 +442,8 @@ class TreeFS {
|
|
|
405
442
|
// NB: This isn't the most efficient algorithm - in the case of
|
|
406
443
|
// removing the last file in a deep hierarchy it's O(depth^2), but
|
|
407
444
|
// that's not expected to be a case common enough to justify
|
|
408
|
-
// implementation complexity, or slowing down more common
|
|
409
|
-
|
|
410
|
-
this.remove(path_1.default.dirname(canonicalPath), changeListener);
|
|
445
|
+
// implementation complexity, or slowing down more common lookups.
|
|
446
|
+
this.#removeNormalPath(path_1.default.dirname(canonicalPath), changeListener);
|
|
411
447
|
}
|
|
412
448
|
}
|
|
413
449
|
}
|
|
@@ -425,8 +461,15 @@ class TreeFS {
|
|
|
425
461
|
*
|
|
426
462
|
* Note that this code is extremely hot during resolution, being the most
|
|
427
463
|
* expensive part of a file existence check. Benchmark any modifications!
|
|
464
|
+
*
|
|
465
|
+
* Each flag combination is implemented as its own specialised walker
|
|
466
|
+
* (`#walkLookup`, `#walkLookupNoFollow`, `#walkAndMakeDirectories`) so
|
|
467
|
+
* the hot inner loop has no per-iteration
|
|
468
|
+
* `opts.X` branches. Thin convenience wrappers below (`#lookup`,
|
|
469
|
+
* `#lookupNoFollow`, `#lookupFromNode`, `#walkAndStream`) cover the
|
|
470
|
+
* common call shapes.
|
|
428
471
|
*/
|
|
429
|
-
#
|
|
472
|
+
#walkLookup(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, onSegment, collectLinkPaths) {
|
|
430
473
|
// We'll update the target if we hit a symlink.
|
|
431
474
|
let targetNormalPath = requestedNormalPath;
|
|
432
475
|
// Lazy-initialised set of seen target paths, to detect symlink cycles.
|
|
@@ -434,20 +477,11 @@ class TreeFS {
|
|
|
434
477
|
// Set when a symlink is followed, to allow fallback population outside
|
|
435
478
|
// the boundary for paths reachable transitively through symlinks.
|
|
436
479
|
let followedSymlink = false;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let
|
|
440
|
-
//
|
|
441
|
-
let parentNode = opts.start?.node ?? this.#rootNode;
|
|
442
|
-
// If a returned node is (an ancestor of) the root, this is the number of
|
|
443
|
-
// levels below the root, i.e. '' is 0, '..' is 1, '../..' is 2, otherwise
|
|
444
|
-
// null.
|
|
445
|
-
let ancestorOfRootIdx = opts.start?.ancestorOfRootIdx ?? 0;
|
|
446
|
-
const { collectAncestors, changeListener } = opts;
|
|
447
|
-
// Used only when collecting ancestors, to avoid double-counting nodes and
|
|
480
|
+
let fromIdx = startPathIdx;
|
|
481
|
+
let parentNode = startNode;
|
|
482
|
+
let ancestorOfRootIdx = startAncestorOfRootIdx;
|
|
483
|
+
// Used only when streaming ancestors, to avoid double-yielding nodes and
|
|
448
484
|
// paths when traversing a symlink takes us back to rootNode and out again.
|
|
449
|
-
// This tracks the first character of the first segment not already
|
|
450
|
-
// collected.
|
|
451
485
|
let unseenPathFromIdx = 0;
|
|
452
486
|
while (targetNormalPath.length > fromIdx) {
|
|
453
487
|
const nextSepIdx = targetNormalPath.indexOf(path_1.default.sep, fromIdx);
|
|
@@ -461,24 +495,22 @@ class TreeFS {
|
|
|
461
495
|
continue;
|
|
462
496
|
}
|
|
463
497
|
let segmentNode = parentNode.get(segmentName);
|
|
464
|
-
// In normal paths all indirections are at the prefix, so we are at the
|
|
465
|
-
// nth ancestor of the root iff the path so far is n '..' segments.
|
|
466
498
|
if (segmentName === '..' && ancestorOfRootIdx != null) {
|
|
467
499
|
ancestorOfRootIdx++;
|
|
468
500
|
}
|
|
469
501
|
else if (segmentNode != null) {
|
|
470
|
-
ancestorOfRootIdx =
|
|
502
|
+
ancestorOfRootIdx = undefined;
|
|
471
503
|
}
|
|
472
504
|
if (segmentNode == null) {
|
|
473
|
-
if (
|
|
474
|
-
if (
|
|
505
|
+
if (segmentName !== '..') {
|
|
506
|
+
if (this.#fallbackFilesystem != null) {
|
|
475
507
|
const parentEnd = isLastSegment
|
|
476
508
|
? fromIdx - segmentName.length - 1
|
|
477
509
|
: fromIdx - segmentName.length - 2;
|
|
478
510
|
const parentCanonicalPath = parentEnd > 0 ? targetNormalPath.slice(0, parentEnd) : '';
|
|
479
511
|
segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlink);
|
|
480
512
|
if (segmentNode != null) {
|
|
481
|
-
ancestorOfRootIdx =
|
|
513
|
+
ancestorOfRootIdx = undefined;
|
|
482
514
|
}
|
|
483
515
|
}
|
|
484
516
|
if (segmentNode == null) {
|
|
@@ -493,16 +525,7 @@ class TreeFS {
|
|
|
493
525
|
}
|
|
494
526
|
if (segmentNode == null) {
|
|
495
527
|
segmentNode = new Map();
|
|
496
|
-
if (
|
|
497
|
-
if (changeListener != null) {
|
|
498
|
-
const canonicalPath = isLastSegment
|
|
499
|
-
? targetNormalPath
|
|
500
|
-
: targetNormalPath.slice(0, fromIdx - 1);
|
|
501
|
-
changeListener.directoryAdded(canonicalPath);
|
|
502
|
-
}
|
|
503
|
-
parentNode.set(segmentName, segmentNode);
|
|
504
|
-
}
|
|
505
|
-
else if (!opts.skipFallback && this.#fallbackFilesystem != null) {
|
|
528
|
+
if (this.#fallbackFilesystem != null) {
|
|
506
529
|
parentNode.set(segmentName, segmentNode);
|
|
507
530
|
}
|
|
508
531
|
}
|
|
@@ -511,13 +534,12 @@ class TreeFS {
|
|
|
511
534
|
if (
|
|
512
535
|
// ...at a directory node and the only subsequent character is `/`, or
|
|
513
536
|
(nextSepIdx === targetNormalPath.length - 1 && isDirectory(segmentNode)) ||
|
|
514
|
-
// there are no subsequent `/`, and this node is
|
|
515
|
-
//
|
|
516
|
-
(isLastSegment &&
|
|
517
|
-
(isDirectory(segmentNode) || isRegularFile(segmentNode) || opts.followLeaf === false))) {
|
|
537
|
+
// ...there are no subsequent `/`, and this node is a directory or a
|
|
538
|
+
// regular file. (A leaf symlink falls through and is followed.)
|
|
539
|
+
(isLastSegment && (isDirectory(segmentNode) || isRegularFile(segmentNode)))) {
|
|
518
540
|
return {
|
|
519
541
|
ancestorOfRootIdx,
|
|
520
|
-
canonicalPath: isLastSegment ? targetNormalPath : targetNormalPath.slice(0, -1),
|
|
542
|
+
canonicalPath: isLastSegment ? targetNormalPath : targetNormalPath.slice(0, -1),
|
|
521
543
|
exists: true,
|
|
522
544
|
node: segmentNode,
|
|
523
545
|
parentNode,
|
|
@@ -526,16 +548,11 @@ class TreeFS {
|
|
|
526
548
|
// If the next node is a directory, go into it
|
|
527
549
|
if (isDirectory(segmentNode)) {
|
|
528
550
|
parentNode = segmentNode;
|
|
529
|
-
if (
|
|
551
|
+
if (onSegment != null && isUnseen) {
|
|
530
552
|
const currentPath = isLastSegment
|
|
531
553
|
? targetNormalPath
|
|
532
554
|
: targetNormalPath.slice(0, fromIdx - 1);
|
|
533
|
-
|
|
534
|
-
ancestorOfRootIdx,
|
|
535
|
-
node: segmentNode,
|
|
536
|
-
normalPath: currentPath,
|
|
537
|
-
segmentName,
|
|
538
|
-
});
|
|
555
|
+
onSegment(segmentNode, currentPath, segmentName, ancestorOfRootIdx);
|
|
539
556
|
}
|
|
540
557
|
}
|
|
541
558
|
else {
|
|
@@ -559,8 +576,8 @@ class TreeFS {
|
|
|
559
576
|
missingSegmentName: segmentName,
|
|
560
577
|
};
|
|
561
578
|
}
|
|
562
|
-
if (
|
|
563
|
-
|
|
579
|
+
if (collectLinkPaths != null) {
|
|
580
|
+
collectLinkPaths.add(this.#pathUtils.normalToAbsolute(currentPath));
|
|
564
581
|
}
|
|
565
582
|
const remainingTargetPath = isLastSegment ? '' : targetNormalPath.slice(fromIdx);
|
|
566
583
|
// Append any subsequent path segments to the symlink target, and reset
|
|
@@ -569,18 +586,19 @@ class TreeFS {
|
|
|
569
586
|
targetNormalPath = joinedResult.normalPath;
|
|
570
587
|
// Two special cases (covered by unit tests):
|
|
571
588
|
//
|
|
572
|
-
// If the symlink target is the root, the root should be
|
|
573
|
-
//
|
|
574
|
-
//
|
|
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.
|
|
575
592
|
//
|
|
576
593
|
// If the symlink target is an ancestor of the root *and* joining it
|
|
577
594
|
// with the remaining path results in collapsing segments, e.g:
|
|
578
|
-
// '../..' + 'parentofroot/root/foo.js' = 'foo.js', then we must
|
|
595
|
+
// '../..' + 'parentofroot/root/foo.js' = 'foo.js', then we must yield
|
|
579
596
|
// parentofroot and root as ancestors.
|
|
580
|
-
if (
|
|
597
|
+
if (onSegment != null &&
|
|
581
598
|
!isLastSegment &&
|
|
582
599
|
// No-op optimisation to bail out the common case of nothing to do.
|
|
583
|
-
((ancestorOfRootIdx =
|
|
600
|
+
((ancestorOfRootIdx =
|
|
601
|
+
this.#pathUtils.getAncestorOfRootIdx(normalSymlinkTarget) ?? undefined) === 0 ||
|
|
584
602
|
joinedResult.collapsedSegments > 0)) {
|
|
585
603
|
let node = this.#rootNode;
|
|
586
604
|
let collapsedPath = '';
|
|
@@ -602,12 +620,16 @@ class TreeFS {
|
|
|
602
620
|
node = node.get('..') ?? new Map();
|
|
603
621
|
collapsedPath = collapsedPath === '' ? '..' : collapsedPath + path_1.default.sep + '..';
|
|
604
622
|
}
|
|
605
|
-
|
|
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
|
+
}
|
|
606
629
|
}
|
|
607
|
-
// For the purpose of
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
// onwards.
|
|
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.
|
|
611
633
|
unseenPathFromIdx = normalSymlinkTarget.lastIndexOf(path_1.default.sep) + 1;
|
|
612
634
|
if (seen == null) {
|
|
613
635
|
// Optimisation: set this lazily only when we've encountered a symlink
|
|
@@ -634,9 +656,289 @@ class TreeFS {
|
|
|
634
656
|
canonicalPath: targetNormalPath,
|
|
635
657
|
exists: true,
|
|
636
658
|
node: this.#rootNode,
|
|
637
|
-
parentNode:
|
|
659
|
+
parentNode: undefined,
|
|
638
660
|
};
|
|
639
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Specialised body for lstat-style lookups: followLeaf=false,
|
|
664
|
+
* makeDirectories=false. A symlink at the leaf is returned as-is.
|
|
665
|
+
* `skipFallback=true` disables the fallback-FS consultation (used by
|
|
666
|
+
* `getMtimeByNormalPath`, which must not trigger fallback population as
|
|
667
|
+
* a side effect of validation).
|
|
668
|
+
*/
|
|
669
|
+
#walkLookupNoFollow(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, onSegment, skipFallback) {
|
|
670
|
+
let targetNormalPath = requestedNormalPath;
|
|
671
|
+
let seen;
|
|
672
|
+
let followedSymlink = false;
|
|
673
|
+
let fromIdx = startPathIdx;
|
|
674
|
+
let parentNode = startNode;
|
|
675
|
+
let ancestorOfRootIdx = startAncestorOfRootIdx;
|
|
676
|
+
let unseenPathFromIdx = 0;
|
|
677
|
+
while (targetNormalPath.length > fromIdx) {
|
|
678
|
+
const nextSepIdx = targetNormalPath.indexOf(path_1.default.sep, fromIdx);
|
|
679
|
+
const isLastSegment = nextSepIdx === -1;
|
|
680
|
+
const segmentName = isLastSegment
|
|
681
|
+
? targetNormalPath.slice(fromIdx)
|
|
682
|
+
: targetNormalPath.slice(fromIdx, nextSepIdx);
|
|
683
|
+
const isUnseen = fromIdx >= unseenPathFromIdx;
|
|
684
|
+
fromIdx = !isLastSegment ? nextSepIdx + 1 : targetNormalPath.length;
|
|
685
|
+
if (segmentName === '.') {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
let segmentNode = parentNode.get(segmentName);
|
|
689
|
+
if (segmentName === '..' && ancestorOfRootIdx != null) {
|
|
690
|
+
ancestorOfRootIdx++;
|
|
691
|
+
}
|
|
692
|
+
else if (segmentNode != null) {
|
|
693
|
+
ancestorOfRootIdx = undefined;
|
|
694
|
+
}
|
|
695
|
+
if (segmentNode == null) {
|
|
696
|
+
if (segmentName !== '..') {
|
|
697
|
+
if (!skipFallback && this.#fallbackFilesystem != null) {
|
|
698
|
+
const parentEnd = isLastSegment
|
|
699
|
+
? fromIdx - segmentName.length - 1
|
|
700
|
+
: fromIdx - segmentName.length - 2;
|
|
701
|
+
const parentCanonicalPath = parentEnd > 0 ? targetNormalPath.slice(0, parentEnd) : '';
|
|
702
|
+
segmentNode = this.#populateFromFilesystem(parentNode, segmentName, parentCanonicalPath, followedSymlink);
|
|
703
|
+
if (segmentNode != null) {
|
|
704
|
+
ancestorOfRootIdx = undefined;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (segmentNode == null) {
|
|
708
|
+
return {
|
|
709
|
+
canonicalMissingPath: isLastSegment
|
|
710
|
+
? targetNormalPath
|
|
711
|
+
: targetNormalPath.slice(0, fromIdx - 1),
|
|
712
|
+
exists: false,
|
|
713
|
+
missingSegmentName: segmentName,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (segmentNode == null) {
|
|
718
|
+
segmentNode = new Map();
|
|
719
|
+
if (!skipFallback && this.#fallbackFilesystem != null) {
|
|
720
|
+
parentNode.set(segmentName, segmentNode);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
// Done: at the last segment we return whatever we found (no leaf
|
|
725
|
+
// follow). Also done if the only remaining character is the trailing
|
|
726
|
+
// path separator and the node is a directory.
|
|
727
|
+
if (isLastSegment ||
|
|
728
|
+
(nextSepIdx === targetNormalPath.length - 1 && isDirectory(segmentNode))) {
|
|
729
|
+
return {
|
|
730
|
+
ancestorOfRootIdx,
|
|
731
|
+
canonicalPath: isLastSegment ? targetNormalPath : targetNormalPath.slice(0, -1),
|
|
732
|
+
exists: true,
|
|
733
|
+
node: segmentNode,
|
|
734
|
+
parentNode,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
if (isDirectory(segmentNode)) {
|
|
738
|
+
parentNode = segmentNode;
|
|
739
|
+
if (onSegment != null && isUnseen) {
|
|
740
|
+
const currentPath = targetNormalPath.slice(0, fromIdx - 1);
|
|
741
|
+
onSegment(segmentNode, currentPath, segmentName, ancestorOfRootIdx);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
const currentPath = targetNormalPath.slice(0, fromIdx - 1);
|
|
746
|
+
if (isRegularFile(segmentNode)) {
|
|
747
|
+
return {
|
|
748
|
+
canonicalMissingPath: currentPath,
|
|
749
|
+
exists: false,
|
|
750
|
+
missingSegmentName: segmentName,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
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
|
+
};
|
|
762
|
+
}
|
|
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;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
(0, invariant_1.default)(parentNode === this.#rootNode, 'Unexpectedly escaped traversal');
|
|
809
|
+
return {
|
|
810
|
+
ancestorOfRootIdx: 0,
|
|
811
|
+
canonicalPath: targetNormalPath,
|
|
812
|
+
exists: true,
|
|
813
|
+
node: this.#rootNode,
|
|
814
|
+
parentNode: undefined,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Specialised body that creates missing directory nodes as it walks.
|
|
819
|
+
* `followLeaf` is parameterised so callers that look up `dirname(...)`
|
|
820
|
+
* can choose lstat-style or stat-style at the leaf. `onSegment` fires
|
|
821
|
+
* for every directory encountered, with `isNewlyCreated` distinguishing
|
|
822
|
+
* dirs the walker just created from dirs that already existed.
|
|
823
|
+
*/
|
|
824
|
+
#walkAndMakeDirectories(startNode, startPathIdx, startAncestorOfRootIdx, requestedNormalPath, followLeaf, onSegment) {
|
|
825
|
+
let targetNormalPath = requestedNormalPath;
|
|
826
|
+
let seen;
|
|
827
|
+
let fromIdx = startPathIdx;
|
|
828
|
+
let parentNode = startNode;
|
|
829
|
+
let ancestorOfRootIdx = startAncestorOfRootIdx;
|
|
830
|
+
let unseenPathFromIdx = 0;
|
|
831
|
+
while (targetNormalPath.length > fromIdx) {
|
|
832
|
+
const nextSepIdx = targetNormalPath.indexOf(path_1.default.sep, fromIdx);
|
|
833
|
+
const isLastSegment = nextSepIdx === -1;
|
|
834
|
+
const segmentName = isLastSegment
|
|
835
|
+
? targetNormalPath.slice(fromIdx)
|
|
836
|
+
: targetNormalPath.slice(fromIdx, nextSepIdx);
|
|
837
|
+
const isUnseen = fromIdx >= unseenPathFromIdx;
|
|
838
|
+
fromIdx = !isLastSegment ? nextSepIdx + 1 : targetNormalPath.length;
|
|
839
|
+
if (segmentName === '.') {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
let segmentNode = parentNode.get(segmentName);
|
|
843
|
+
let wasJustCreated = false;
|
|
844
|
+
if (segmentName === '..' && ancestorOfRootIdx != null) {
|
|
845
|
+
ancestorOfRootIdx++;
|
|
846
|
+
}
|
|
847
|
+
else if (segmentNode != null) {
|
|
848
|
+
ancestorOfRootIdx = undefined;
|
|
849
|
+
}
|
|
850
|
+
if (segmentNode == null) {
|
|
851
|
+
segmentNode = new Map();
|
|
852
|
+
parentNode.set(segmentName, segmentNode);
|
|
853
|
+
wasJustCreated = true;
|
|
854
|
+
}
|
|
855
|
+
if ((nextSepIdx === targetNormalPath.length - 1 && isDirectory(segmentNode)) ||
|
|
856
|
+
(isLastSegment && (isDirectory(segmentNode) || isRegularFile(segmentNode) || !followLeaf))) {
|
|
857
|
+
if (wasJustCreated && onSegment != null) {
|
|
858
|
+
const currentPath = isLastSegment
|
|
859
|
+
? targetNormalPath
|
|
860
|
+
: targetNormalPath.slice(0, fromIdx - 1);
|
|
861
|
+
onSegment(segmentNode, currentPath, segmentName, ancestorOfRootIdx, true);
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
ancestorOfRootIdx,
|
|
865
|
+
canonicalPath: isLastSegment ? targetNormalPath : targetNormalPath.slice(0, -1),
|
|
866
|
+
exists: true,
|
|
867
|
+
node: segmentNode,
|
|
868
|
+
parentNode,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
if (isDirectory(segmentNode)) {
|
|
872
|
+
parentNode = segmentNode;
|
|
873
|
+
if (onSegment != null && isUnseen) {
|
|
874
|
+
const currentPath = isLastSegment
|
|
875
|
+
? targetNormalPath
|
|
876
|
+
: targetNormalPath.slice(0, fromIdx - 1);
|
|
877
|
+
onSegment(segmentNode, currentPath, segmentName, ancestorOfRootIdx, wasJustCreated);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
const currentPath = isLastSegment
|
|
882
|
+
? targetNormalPath
|
|
883
|
+
: targetNormalPath.slice(0, fromIdx - 1);
|
|
884
|
+
if (isRegularFile(segmentNode)) {
|
|
885
|
+
return {
|
|
886
|
+
canonicalMissingPath: currentPath,
|
|
887
|
+
exists: false,
|
|
888
|
+
missingSegmentName: segmentName,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
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]);
|
|
906
|
+
}
|
|
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;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
(0, invariant_1.default)(parentNode === this.#rootNode, 'Unexpectedly escaped traversal');
|
|
921
|
+
return {
|
|
922
|
+
ancestorOfRootIdx: 0,
|
|
923
|
+
canonicalPath: targetNormalPath,
|
|
924
|
+
exists: true,
|
|
925
|
+
node: this.#rootNode,
|
|
926
|
+
parentNode: undefined,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
// Convenience wrappers for the common walker call shapes.
|
|
930
|
+
#lookup(normalPath, collectLinkPaths) {
|
|
931
|
+
return this.#walkLookup(this.#rootNode, 0, 0, normalPath, undefined, collectLinkPaths);
|
|
932
|
+
}
|
|
933
|
+
#lookupNoFollow(normalPath) {
|
|
934
|
+
return this.#walkLookupNoFollow(this.#rootNode, 0, 0, normalPath, undefined, false);
|
|
935
|
+
}
|
|
936
|
+
#lookupFromNode(startNode, startPathIdx, startAncestorOfRootIdx, target, collectLinkPaths) {
|
|
937
|
+
return this.#walkLookup(startNode, startPathIdx, startAncestorOfRootIdx, target, undefined, collectLinkPaths);
|
|
938
|
+
}
|
|
939
|
+
#walkAndStream(normalPath, onSegment, collectLinkPaths) {
|
|
940
|
+
return this.#walkLookup(this.#rootNode, 0, 0, normalPath, onSegment, collectLinkPaths);
|
|
941
|
+
}
|
|
640
942
|
/**
|
|
641
943
|
* Given a start path (which need not exist), a subpath and type, and
|
|
642
944
|
* optionally a 'breakOnSegment', performs the following:
|
|
@@ -663,13 +965,22 @@ class TreeFS {
|
|
|
663
965
|
hierarchicalLookup(mixedStartPath, subpath, opts) {
|
|
664
966
|
const ancestorsOfInput = [];
|
|
665
967
|
const normalPath = this.#normalizePath(mixedStartPath);
|
|
666
|
-
const invalidatedBy = opts.invalidatedBy;
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
968
|
+
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);
|
|
671
978
|
if (closestLookup.exists && isDirectory(closestLookup.node)) {
|
|
672
|
-
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(closestLookup.canonicalPath, subpath, opts.subpathType, invalidatedBy,
|
|
979
|
+
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(closestLookup.canonicalPath, subpath, opts.subpathType, invalidatedBy, {
|
|
980
|
+
ancestorOfRootIdx: closestLookup.ancestorOfRootIdx,
|
|
981
|
+
node: closestLookup.node,
|
|
982
|
+
pathIdx: closestLookup.canonicalPath.length > 0 ? closestLookup.canonicalPath.length + 1 : 0,
|
|
983
|
+
});
|
|
673
984
|
if (maybeAbsolutePathMatch != null) {
|
|
674
985
|
return {
|
|
675
986
|
absolutePath: maybeAbsolutePathMatch,
|
|
@@ -712,7 +1023,7 @@ class TreeFS {
|
|
|
712
1023
|
commonRoot = ancestor.node;
|
|
713
1024
|
}
|
|
714
1025
|
}
|
|
715
|
-
// Phase 1: Consider
|
|
1026
|
+
// Phase 1: Consider descendants of the common root, from deepest to
|
|
716
1027
|
// shallowest.
|
|
717
1028
|
for (let candidateIdx = ancestorsOfInput.length - 1; candidateIdx >= commonRootDepth; --candidateIdx) {
|
|
718
1029
|
const candidate = ancestorsOfInput[candidateIdx];
|
|
@@ -725,15 +1036,12 @@ class TreeFS {
|
|
|
725
1036
|
pathIdx: candidate.normalPath.length > 0 ? candidate.normalPath.length + 1 : 0,
|
|
726
1037
|
});
|
|
727
1038
|
if (maybeAbsolutePathMatch != null) {
|
|
728
|
-
// Determine the input path relative to the current candidate.
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
// The next 5 lines are equivalent to (but faster than)
|
|
736
|
-
// normalPath.split('/').slice(candidateIdx + 1).join('/').
|
|
1039
|
+
// Determine the input path relative to the current candidate. The
|
|
1040
|
+
// candidate path is always canonical (real); the input may contain
|
|
1041
|
+
// symlinks, so the candidate is not necessarily a prefix of the input.
|
|
1042
|
+
// Use the fact that each remaining candidate corresponds to a leading
|
|
1043
|
+
// segment of the input normal path, and discard the first
|
|
1044
|
+
// candidateIdx + 1 segments of the input path.
|
|
737
1045
|
let prefixLength = commonRootDepth * 3; // Leading '../'
|
|
738
1046
|
for (let i = commonRootDepth; i <= candidateIdx; i++) {
|
|
739
1047
|
prefixLength = normalPath.indexOf(path_1.default.sep, prefixLength + 1);
|
|
@@ -745,14 +1053,17 @@ class TreeFS {
|
|
|
745
1053
|
};
|
|
746
1054
|
}
|
|
747
1055
|
}
|
|
748
|
-
// Phase 2: Consider the common root and its ancestors
|
|
749
|
-
// This will be '', '..', '../..', etc.
|
|
1056
|
+
// Phase 2: Consider the common root and its ancestors.
|
|
750
1057
|
let candidateNormalPath = commonRootDepth > 0 ? normalPath.slice(0, 3 * commonRootDepth - 1) : '';
|
|
751
1058
|
const remainingNormalPath = normalPath.slice(commonRootDepth * 3);
|
|
752
1059
|
let nextNode = commonRoot;
|
|
753
1060
|
let depthBelowCommonRoot = 0;
|
|
754
1061
|
while (isDirectory(nextNode)) {
|
|
755
|
-
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(candidateNormalPath, subpath, opts.subpathType, invalidatedBy,
|
|
1062
|
+
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(candidateNormalPath, subpath, opts.subpathType, invalidatedBy, {
|
|
1063
|
+
ancestorOfRootIdx: commonRootDepth + depthBelowCommonRoot,
|
|
1064
|
+
node: nextNode,
|
|
1065
|
+
pathIdx: candidateNormalPath.length > 0 ? candidateNormalPath.length + 1 : 0,
|
|
1066
|
+
});
|
|
756
1067
|
if (maybeAbsolutePathMatch != null) {
|
|
757
1068
|
const rootDirParts = this.#pathUtils.getParts();
|
|
758
1069
|
const relativeParts = depthBelowCommonRoot > 0
|
|
@@ -774,9 +1085,51 @@ class TreeFS {
|
|
|
774
1085
|
return null;
|
|
775
1086
|
}
|
|
776
1087
|
#checkCandidateHasSubpath(normalCandidatePath, subpath, subpathType, invalidatedBy, start) {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1088
|
+
// NOTE(@kitten): The most common call for package.json only needs a simple map
|
|
1089
|
+
// lookup, and can skip traversal entirely
|
|
1090
|
+
if (start != null &&
|
|
1091
|
+
subpath.length > 0 &&
|
|
1092
|
+
subpath !== '.' &&
|
|
1093
|
+
subpath !== '..' &&
|
|
1094
|
+
subpath.indexOf(path_1.default.sep) === -1) {
|
|
1095
|
+
const child = start.node.get(subpath);
|
|
1096
|
+
if (child == null) {
|
|
1097
|
+
if (this.#fallbackFilesystem != null) {
|
|
1098
|
+
// noop: fall through to slow-path
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
if (invalidatedBy) {
|
|
1102
|
+
invalidatedBy.add(this.#pathUtils.normalToAbsolute(normalCandidatePath === '' ? subpath : normalCandidatePath + path_1.default.sep + subpath));
|
|
1103
|
+
}
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
const childIsDirectory = isDirectory(child);
|
|
1109
|
+
if (!childIsDirectory && !isRegularFile(child)) {
|
|
1110
|
+
// noop: Handle symlinks in the slow path, since it needs state tracking
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
const absolutePath = this.#pathUtils.normalToAbsolute(normalCandidatePath === '' ? subpath : normalCandidatePath + path_1.default.sep + subpath);
|
|
1114
|
+
if (childIsDirectory === (subpathType === 'd')) {
|
|
1115
|
+
return absolutePath;
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
if (invalidatedBy)
|
|
1119
|
+
invalidatedBy.add(absolutePath);
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// NOTE(@kitten): We can forward `start` if there's no indirection in the candidate path
|
|
1126
|
+
const canForwardStart = start != null &&
|
|
1127
|
+
normalCandidatePath !== '..' &&
|
|
1128
|
+
!normalCandidatePath.endsWith(path_1.default.sep + '..');
|
|
1129
|
+
const target = this.#pathUtils.joinNormalToRelative(normalCandidatePath, subpath).normalPath;
|
|
1130
|
+
const lookupResult = canForwardStart
|
|
1131
|
+
? this.#lookupFromNode(start.node, start.pathIdx, start.ancestorOfRootIdx, target, invalidatedBy)
|
|
1132
|
+
: this.#lookup(target, invalidatedBy);
|
|
780
1133
|
if (lookupResult.exists &&
|
|
781
1134
|
// Should be a Map iff subpathType is directory
|
|
782
1135
|
isDirectory(lookupResult.node) === (subpathType === 'd')) {
|
|
@@ -787,10 +1140,14 @@ class TreeFS {
|
|
|
787
1140
|
}
|
|
788
1141
|
return null;
|
|
789
1142
|
}
|
|
790
|
-
metadataIterator(opts) {
|
|
791
|
-
|
|
1143
|
+
*metadataIterator(opts) {
|
|
1144
|
+
const buffer = [];
|
|
1145
|
+
this.#forEachMetadata(this.#rootNode, opts, '', (baseName, canonicalPath, metadata) => {
|
|
1146
|
+
buffer.push({ baseName, canonicalPath, metadata });
|
|
1147
|
+
});
|
|
1148
|
+
yield* buffer;
|
|
792
1149
|
}
|
|
793
|
-
|
|
1150
|
+
#forEachMetadata(rootNode, opts, prefix, callback) {
|
|
794
1151
|
for (const [name, node] of rootNode) {
|
|
795
1152
|
if (node == null) {
|
|
796
1153
|
continue;
|
|
@@ -800,10 +1157,10 @@ class TreeFS {
|
|
|
800
1157
|
}
|
|
801
1158
|
const prefixedName = prefix === '' ? name : prefix + path_1.default.sep + name;
|
|
802
1159
|
if (isDirectory(node)) {
|
|
803
|
-
|
|
1160
|
+
this.#forEachMetadata(node, opts, prefixedName, callback);
|
|
804
1161
|
}
|
|
805
1162
|
else if (isRegularFile(node) || opts.includeSymlinks) {
|
|
806
|
-
|
|
1163
|
+
callback(name, prefixedName, node);
|
|
807
1164
|
}
|
|
808
1165
|
}
|
|
809
1166
|
}
|
|
@@ -812,17 +1169,13 @@ class TreeFS {
|
|
|
812
1169
|
? this.#pathUtils.absoluteToNormal(relativeOrAbsolutePath)
|
|
813
1170
|
: this.#pathUtils.relativeToNormal(relativeOrAbsolutePath);
|
|
814
1171
|
}
|
|
815
|
-
*#directoryNodeIterator(node, parent, ancestorOfRootIdx) {
|
|
816
|
-
if (ancestorOfRootIdx != null && ancestorOfRootIdx > 0 && parent) {
|
|
817
|
-
yield [this.#pathUtils.getBasenameOfNthAncestor(ancestorOfRootIdx - 1), parent];
|
|
818
|
-
}
|
|
819
|
-
yield* node.entries();
|
|
820
|
-
}
|
|
821
1172
|
/**
|
|
822
1173
|
* Enumerate paths under a given node, including symlinks and through
|
|
823
|
-
* symlinks (if `follow` is enabled).
|
|
1174
|
+
* symlinks (if `follow` is enabled). Invokes `callback` for each matching
|
|
1175
|
+
* path. Inlines what was previously `#directoryNodeIterator` (yielding the
|
|
1176
|
+
* parent entry first when `ancestorOfRootIdx > 0`).
|
|
824
1177
|
*/
|
|
825
|
-
|
|
1178
|
+
#forEachPath(iterationRootNode, iterationRootParentNode, ancestorOfRootIdx, opts, pathPrefix, followedLinks, callback) {
|
|
826
1179
|
const pathSep = opts.alwaysYieldPosix ? '/' : path_1.default.sep;
|
|
827
1180
|
const prefixWithSep = pathPrefix === '' ? pathPrefix : pathPrefix + pathSep;
|
|
828
1181
|
// Optimization: We can attempt to eagerly populate directories we're visiting
|
|
@@ -835,18 +1188,18 @@ class TreeFS {
|
|
|
835
1188
|
const rootCanonical = pathPrefix === '' ? canonicalRoot : canonicalRoot + path_1.default.sep + pathPrefix;
|
|
836
1189
|
this.#populateDirFromFilesystem(iterationRootNode, rootCanonical, false, false);
|
|
837
1190
|
}
|
|
838
|
-
|
|
1191
|
+
const visitEntry = (name, node) => {
|
|
839
1192
|
if (node == null) {
|
|
840
|
-
|
|
1193
|
+
return;
|
|
841
1194
|
}
|
|
842
1195
|
else if (opts.subtreeOnly && name === '..') {
|
|
843
|
-
|
|
1196
|
+
return;
|
|
844
1197
|
}
|
|
845
1198
|
const nodePath = prefixWithSep + name;
|
|
846
1199
|
if (!isDirectory(node)) {
|
|
847
1200
|
if (isRegularFile(node)) {
|
|
848
1201
|
// regular file
|
|
849
|
-
|
|
1202
|
+
callback(nodePath);
|
|
850
1203
|
}
|
|
851
1204
|
else {
|
|
852
1205
|
// symlink
|
|
@@ -858,24 +1211,22 @@ class TreeFS {
|
|
|
858
1211
|
// its normal path, and we need a canonical path for resolution
|
|
859
1212
|
// (imagine our normal path contains a symlink 'bar' -> '.', and we
|
|
860
1213
|
// are at /foo/bar/baz where baz -> '..' - that should resolve to
|
|
861
|
-
// /foo, not /foo/bar). We *can* use
|
|
862
|
-
//
|
|
863
|
-
const resolved = this.#
|
|
864
|
-
followLeaf: true,
|
|
865
|
-
});
|
|
1214
|
+
// /foo, not /foo/bar). We *can* use #walkLookup to walk to the
|
|
1215
|
+
// canonical symlink, and then to its target.
|
|
1216
|
+
const resolved = this.#lookup(normalPathOfSymlink);
|
|
866
1217
|
if (!resolved.exists) {
|
|
867
1218
|
// Symlink goes nowhere, nothing to report.
|
|
868
|
-
|
|
1219
|
+
return;
|
|
869
1220
|
}
|
|
870
1221
|
const target = resolved.node;
|
|
871
1222
|
if (!isDirectory(target)) {
|
|
872
1223
|
// Symlink points to a file, just yield the path of the symlink.
|
|
873
|
-
|
|
1224
|
+
callback(nodePath);
|
|
874
1225
|
}
|
|
875
1226
|
else if (opts.recursive && opts.follow && !followedLinks.has(node)) {
|
|
876
1227
|
// Symlink points to a directory - iterate over its contents using
|
|
877
1228
|
// the path where we found the symlink as a prefix.
|
|
878
|
-
|
|
1229
|
+
this.#forEachPath(target, resolved.parentNode, resolved.ancestorOfRootIdx, opts, nodePath, new Set([...followedLinks, node]), callback);
|
|
879
1230
|
}
|
|
880
1231
|
}
|
|
881
1232
|
}
|
|
@@ -889,8 +1240,16 @@ class TreeFS {
|
|
|
889
1240
|
: opts.canonicalPathOfRoot + path_1.default.sep + nodePathWithSystemSeparators;
|
|
890
1241
|
this.#populateDirFromFilesystem(node, canonicalPath, false, false);
|
|
891
1242
|
}
|
|
892
|
-
|
|
1243
|
+
this.#forEachPath(node, iterationRootParentNode, ancestorOfRootIdx != null && ancestorOfRootIdx > 0 ? ancestorOfRootIdx - 1 : undefined, opts, nodePath, followedLinks, callback);
|
|
893
1244
|
}
|
|
1245
|
+
};
|
|
1246
|
+
// Inlined #directoryNodeIterator: yield the parent entry first when we're
|
|
1247
|
+
// at a directory above the root, then iterate `node.entries()`.
|
|
1248
|
+
if (ancestorOfRootIdx != null && ancestorOfRootIdx > 0 && iterationRootParentNode) {
|
|
1249
|
+
visitEntry(this.#pathUtils.getBasenameOfNthAncestor(ancestorOfRootIdx - 1), iterationRootParentNode);
|
|
1250
|
+
}
|
|
1251
|
+
for (const [name, node] of iterationRootNode) {
|
|
1252
|
+
visitEntry(name, node);
|
|
894
1253
|
}
|
|
895
1254
|
}
|
|
896
1255
|
#resolveSymlinkTargetToNormalPath(symlinkNode, canonicalPathOfSymlink) {
|
|
@@ -906,30 +1265,20 @@ class TreeFS {
|
|
|
906
1265
|
return normalTarget;
|
|
907
1266
|
}
|
|
908
1267
|
catch {
|
|
909
|
-
return
|
|
1268
|
+
return undefined;
|
|
910
1269
|
}
|
|
911
1270
|
}
|
|
912
1271
|
else if (symlinkTarget === 0 || symlinkTarget == null) {
|
|
913
1272
|
// WARN: We shouldn't call this method on non-symlinks. Outside of tests
|
|
914
1273
|
// this condition shouldn't trigger. It's fine not to resolve a symlink if
|
|
915
1274
|
// it does trigger however
|
|
916
|
-
return
|
|
1275
|
+
return undefined;
|
|
917
1276
|
}
|
|
918
1277
|
else {
|
|
919
1278
|
(0, invariant_1.default)(typeof symlinkTarget === 'string', 'Expected symlink target to be populated.');
|
|
920
1279
|
return (0, normalizePathSeparatorsToSystem_1.default)(symlinkTarget);
|
|
921
1280
|
}
|
|
922
1281
|
}
|
|
923
|
-
#getFileData(filePath, opts = { followLeaf: true }) {
|
|
924
|
-
const normalPath = this.#normalizePath(filePath);
|
|
925
|
-
const result = this.#lookupByNormalPath(normalPath, {
|
|
926
|
-
followLeaf: opts.followLeaf,
|
|
927
|
-
});
|
|
928
|
-
if (!result.exists || isDirectory(result.node)) {
|
|
929
|
-
return null;
|
|
930
|
-
}
|
|
931
|
-
return result.node;
|
|
932
|
-
}
|
|
933
1282
|
/**
|
|
934
1283
|
* Return a filtered view of the tree containing only content under watched
|
|
935
1284
|
* roots. Walk each root path from #rootNode, creating intermediate directory
|
|
@@ -1037,7 +1386,7 @@ class TreeFS {
|
|
|
1037
1386
|
}
|
|
1038
1387
|
/**
|
|
1039
1388
|
* Populate an existing (potentially empty sentinel) directory node from
|
|
1040
|
-
* the filesystem. Used by #
|
|
1389
|
+
* the filesystem. Used by #forEachPath to fill lazy directories before
|
|
1041
1390
|
* iteration, and by #populateFromFilesystem for optimistic parent
|
|
1042
1391
|
* population.
|
|
1043
1392
|
*/
|
|
@@ -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
|
|
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,
|
|
64
|
-
:
|
|
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.
|
|
3
|
+
"version": "56.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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "51c27fce31a5b3a877a4b05d832dabf4a99db5e1",
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "expo-module tsc",
|
|
48
48
|
"clean": "expo-module clean",
|
package/build/lib/isVcsPath.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function isVcsPath(filePath: string): boolean;
|
package/build/lib/isVcsPath.js
DELETED