metro-file-map 0.84.2 → 0.84.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Watcher.d.ts +6 -9
- package/src/Watcher.js +66 -39
- package/src/Watcher.js.flow +84 -51
- package/src/crawlers/node/index.d.ts +3 -5
- package/src/crawlers/node/index.js +4 -1
- package/src/crawlers/node/index.js.flow +8 -6
- package/src/crawlers/watchman/index.d.ts +5 -12
- package/src/crawlers/watchman/index.js.flow +2 -6
- package/src/flow-types.d.ts +81 -32
- package/src/flow-types.js.flow +89 -29
- package/src/index.d.ts +4 -4
- package/src/index.js +145 -120
- package/src/index.js.flow +199 -149
- package/src/lib/FileSystemChangeAggregator.d.ts +40 -0
- package/src/lib/FileSystemChangeAggregator.js +89 -0
- package/src/lib/FileSystemChangeAggregator.js.flow +143 -0
- package/src/lib/TreeFS.d.ts +16 -8
- package/src/lib/TreeFS.js +67 -16
- package/src/lib/TreeFS.js.flow +89 -16
- package/src/plugins/DependencyPlugin.d.ts +5 -36
- package/src/plugins/DependencyPlugin.js +26 -48
- package/src/plugins/DependencyPlugin.js.flow +22 -100
- package/src/plugins/FileDataPlugin.d.ts +55 -0
- package/src/plugins/FileDataPlugin.js +41 -0
- package/src/plugins/FileDataPlugin.js.flow +76 -0
- package/src/plugins/HastePlugin.d.ts +3 -11
- package/src/plugins/HastePlugin.js +11 -11
- package/src/plugins/HastePlugin.js.flow +12 -12
- package/src/plugins/MockPlugin.d.ts +3 -5
- package/src/plugins/MockPlugin.js +17 -20
- package/src/plugins/MockPlugin.js.flow +18 -22
- package/src/watchers/FallbackWatcher.js +19 -3
- package/src/watchers/FallbackWatcher.js.flow +28 -5
- package/src/watchers/NativeWatcher.d.ts +2 -2
- package/src/watchers/NativeWatcher.js +27 -5
- package/src/watchers/NativeWatcher.js.flow +33 -6
- package/src/watchers/common.d.ts +3 -1
- package/src/watchers/common.js +6 -1
- package/src/watchers/common.js.flow +1 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true,
|
|
5
|
+
});
|
|
6
|
+
exports.FileSystemChangeAggregator = void 0;
|
|
7
|
+
class FileSystemChangeAggregator {
|
|
8
|
+
#addedDirectories = new Set();
|
|
9
|
+
#removedDirectories = new Set();
|
|
10
|
+
#addedFiles = new Map();
|
|
11
|
+
#modifiedFiles = new Map();
|
|
12
|
+
#removedFiles = new Map();
|
|
13
|
+
#initialMetadata = new Map();
|
|
14
|
+
directoryAdded(canonicalPath) {
|
|
15
|
+
if (!this.#removedDirectories.delete(canonicalPath)) {
|
|
16
|
+
this.#addedDirectories.add(canonicalPath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
directoryRemoved(canonicalPath) {
|
|
20
|
+
if (!this.#addedDirectories.delete(canonicalPath)) {
|
|
21
|
+
this.#removedDirectories.add(canonicalPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
fileAdded(canonicalPath, data) {
|
|
25
|
+
if (this.#removedFiles.delete(canonicalPath)) {
|
|
26
|
+
this.#modifiedFiles.set(canonicalPath, data);
|
|
27
|
+
} else {
|
|
28
|
+
this.#addedFiles.set(canonicalPath, data);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
fileModified(canonicalPath, oldData, newData) {
|
|
32
|
+
if (this.#addedFiles.has(canonicalPath)) {
|
|
33
|
+
this.#addedFiles.set(canonicalPath, newData);
|
|
34
|
+
} else {
|
|
35
|
+
if (!this.#initialMetadata.has(canonicalPath)) {
|
|
36
|
+
this.#initialMetadata.set(canonicalPath, oldData);
|
|
37
|
+
}
|
|
38
|
+
this.#modifiedFiles.set(canonicalPath, newData);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
fileRemoved(canonicalPath, data) {
|
|
42
|
+
if (!this.#addedFiles.delete(canonicalPath)) {
|
|
43
|
+
let initialData = this.#initialMetadata.get(canonicalPath);
|
|
44
|
+
if (!initialData) {
|
|
45
|
+
initialData = data;
|
|
46
|
+
this.#initialMetadata.set(canonicalPath, initialData);
|
|
47
|
+
}
|
|
48
|
+
this.#modifiedFiles.delete(canonicalPath);
|
|
49
|
+
this.#removedFiles.set(canonicalPath, initialData);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getSize() {
|
|
53
|
+
return (
|
|
54
|
+
this.#addedDirectories.size +
|
|
55
|
+
this.#removedDirectories.size +
|
|
56
|
+
this.#addedFiles.size +
|
|
57
|
+
this.#modifiedFiles.size +
|
|
58
|
+
this.#removedFiles.size
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
getView() {
|
|
62
|
+
return {
|
|
63
|
+
addedDirectories: this.#addedDirectories,
|
|
64
|
+
removedDirectories: this.#removedDirectories,
|
|
65
|
+
addedFiles: this.#addedFiles,
|
|
66
|
+
modifiedFiles: this.#modifiedFiles,
|
|
67
|
+
removedFiles: this.#removedFiles,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
getMappedView(metadataMapFn) {
|
|
71
|
+
return {
|
|
72
|
+
addedDirectories: this.#addedDirectories,
|
|
73
|
+
removedDirectories: this.#removedDirectories,
|
|
74
|
+
addedFiles: mapIterable(this.#addedFiles, metadataMapFn),
|
|
75
|
+
modifiedFiles: mapIterable(this.#modifiedFiles, metadataMapFn),
|
|
76
|
+
removedFiles: mapIterable(this.#removedFiles, metadataMapFn),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.FileSystemChangeAggregator = FileSystemChangeAggregator;
|
|
81
|
+
function mapIterable(map, metadataMapFn) {
|
|
82
|
+
return {
|
|
83
|
+
*[Symbol.iterator]() {
|
|
84
|
+
for (const [path, metadata] of map) {
|
|
85
|
+
yield [path, metadataMapFn(metadata)];
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @flow strict-local
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall react_native
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
CanonicalPath,
|
|
14
|
+
FileMetadata,
|
|
15
|
+
FileSystemListener,
|
|
16
|
+
ReadonlyFileSystemChanges,
|
|
17
|
+
} from '../flow-types';
|
|
18
|
+
|
|
19
|
+
export class FileSystemChangeAggregator implements FileSystemListener {
|
|
20
|
+
// Mutually exclusive with removedDirectories
|
|
21
|
+
+#addedDirectories: Set<CanonicalPath> = new Set();
|
|
22
|
+
// Mutually exclusive with addedDirectories
|
|
23
|
+
+#removedDirectories: Set<CanonicalPath> = new Set();
|
|
24
|
+
|
|
25
|
+
// Mutually exclusive with modified and removed files
|
|
26
|
+
+#addedFiles: Map<CanonicalPath, FileMetadata> = new Map();
|
|
27
|
+
// Mutually exclusive with added and removed files
|
|
28
|
+
+#modifiedFiles: Map<CanonicalPath, FileMetadata> = new Map();
|
|
29
|
+
// Mutually exclusive with added and modified files
|
|
30
|
+
+#removedFiles: Map<CanonicalPath, FileMetadata> = new Map();
|
|
31
|
+
|
|
32
|
+
// Removed files must be paired with the file's metadata the last time it was
|
|
33
|
+
// observable by consumers - ie, immediately *before* this batch. To report
|
|
34
|
+
// this accurately with minimal overhead, we'll note the current metadata of
|
|
35
|
+
// a file the first time it is modified or removed within a batch. If it is
|
|
36
|
+
// re-added, modified and removed again, we still have the initial metadata.
|
|
37
|
+
// This is particularly important if, say, a regular file is replaced by a
|
|
38
|
+
// symlink, or vice-versa.
|
|
39
|
+
+#initialMetadata: Map<CanonicalPath, FileMetadata> = new Map();
|
|
40
|
+
|
|
41
|
+
directoryAdded(canonicalPath: CanonicalPath): void {
|
|
42
|
+
// Only add to newDirectories if this directory wasn't previously removed
|
|
43
|
+
// (i.e., it's truly new). If it was removed and re-added, the net effect
|
|
44
|
+
// is no directory change.
|
|
45
|
+
if (!this.#removedDirectories.delete(canonicalPath)) {
|
|
46
|
+
this.#addedDirectories.add(canonicalPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
directoryRemoved(canonicalPath: CanonicalPath): void {
|
|
51
|
+
if (!this.#addedDirectories.delete(canonicalPath)) {
|
|
52
|
+
this.#removedDirectories.add(canonicalPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void {
|
|
57
|
+
if (this.#removedFiles.delete(canonicalPath)) {
|
|
58
|
+
// File was removed then re-added in the same batch - treat as modification
|
|
59
|
+
this.#modifiedFiles.set(canonicalPath, data);
|
|
60
|
+
} else {
|
|
61
|
+
// New file
|
|
62
|
+
this.#addedFiles.set(canonicalPath, data);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fileModified(
|
|
67
|
+
canonicalPath: CanonicalPath,
|
|
68
|
+
oldData: FileMetadata,
|
|
69
|
+
newData: FileMetadata,
|
|
70
|
+
): void {
|
|
71
|
+
if (this.#addedFiles.has(canonicalPath)) {
|
|
72
|
+
// File did not exist before this batch. Further modification only
|
|
73
|
+
// updates metadata
|
|
74
|
+
this.#addedFiles.set(canonicalPath, newData);
|
|
75
|
+
} else {
|
|
76
|
+
if (!this.#initialMetadata.has(canonicalPath)) {
|
|
77
|
+
this.#initialMetadata.set(canonicalPath, oldData);
|
|
78
|
+
}
|
|
79
|
+
this.#modifiedFiles.set(canonicalPath, newData);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void {
|
|
84
|
+
// Check if this file was added in the same batch
|
|
85
|
+
if (!this.#addedFiles.delete(canonicalPath)) {
|
|
86
|
+
let initialData = this.#initialMetadata.get(canonicalPath);
|
|
87
|
+
if (!initialData) {
|
|
88
|
+
initialData = data;
|
|
89
|
+
this.#initialMetadata.set(canonicalPath, initialData);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// File was not added in this batch, so add to removed with last metadata
|
|
93
|
+
this.#modifiedFiles.delete(canonicalPath);
|
|
94
|
+
this.#removedFiles.set(canonicalPath, initialData);
|
|
95
|
+
}
|
|
96
|
+
// else: File was added then removed in the same batch - no net change
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getSize(): number {
|
|
100
|
+
return (
|
|
101
|
+
this.#addedDirectories.size +
|
|
102
|
+
this.#removedDirectories.size +
|
|
103
|
+
this.#addedFiles.size +
|
|
104
|
+
this.#modifiedFiles.size +
|
|
105
|
+
this.#removedFiles.size
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getView(): ReadonlyFileSystemChanges<FileMetadata> {
|
|
110
|
+
return {
|
|
111
|
+
addedDirectories: this.#addedDirectories,
|
|
112
|
+
removedDirectories: this.#removedDirectories,
|
|
113
|
+
addedFiles: this.#addedFiles,
|
|
114
|
+
modifiedFiles: this.#modifiedFiles,
|
|
115
|
+
removedFiles: this.#removedFiles,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getMappedView<T>(
|
|
120
|
+
metadataMapFn: (metadata: FileMetadata) => T,
|
|
121
|
+
): ReadonlyFileSystemChanges<T> {
|
|
122
|
+
return {
|
|
123
|
+
addedDirectories: this.#addedDirectories,
|
|
124
|
+
removedDirectories: this.#removedDirectories,
|
|
125
|
+
addedFiles: mapIterable(this.#addedFiles, metadataMapFn),
|
|
126
|
+
modifiedFiles: mapIterable(this.#modifiedFiles, metadataMapFn),
|
|
127
|
+
removedFiles: mapIterable(this.#removedFiles, metadataMapFn),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function mapIterable<T>(
|
|
133
|
+
map: Map<CanonicalPath, FileMetadata>,
|
|
134
|
+
metadataMapFn: (metadata: FileMetadata) => T,
|
|
135
|
+
): Iterable<Readonly<[CanonicalPath, T]>> {
|
|
136
|
+
return {
|
|
137
|
+
*[Symbol.iterator](): Iterator<Readonly<[CanonicalPath, T]>> {
|
|
138
|
+
for (const [path, metadata] of map) {
|
|
139
|
+
yield [path, metadataMapFn(metadata)];
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
package/src/lib/TreeFS.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
7
|
* @noformat
|
|
8
|
-
* @generated SignedSource<<
|
|
8
|
+
* @generated SignedSource<<65a3c4140d459a56b8c949e52b32ea1b>>
|
|
9
9
|
*
|
|
10
10
|
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
|
|
11
11
|
* Original file: packages/metro-file-map/src/lib/TreeFS.js
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
FileData,
|
|
20
20
|
FileMetadata,
|
|
21
21
|
FileStats,
|
|
22
|
+
FileSystemListener,
|
|
22
23
|
LookupResult,
|
|
23
24
|
MutableFileSystem,
|
|
24
25
|
Path,
|
|
@@ -104,10 +105,10 @@ declare class TreeFS implements MutableFileSystem {
|
|
|
104
105
|
getSerializableSnapshot(): CacheData['fileSystemData'];
|
|
105
106
|
static fromDeserializedSnapshot(args: DeserializedSnapshotInput): TreeFS;
|
|
106
107
|
getSize(mixedPath: Path): null | undefined | number;
|
|
107
|
-
getDifference(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
108
|
+
getDifference(
|
|
109
|
+
files: FileData,
|
|
110
|
+
options?: Readonly<{subpath?: string}>,
|
|
111
|
+
): {changedFiles: FileData; removedFiles: Set<string>};
|
|
111
112
|
getSha1(mixedPath: Path): null | undefined | string;
|
|
112
113
|
getOrComputeSha1(
|
|
113
114
|
mixedPath: Path,
|
|
@@ -122,9 +123,16 @@ declare class TreeFS implements MutableFileSystem {
|
|
|
122
123
|
* for example: `a/b.js` -> `./a/b.js`
|
|
123
124
|
*/
|
|
124
125
|
matchFiles(opts: MatchFilesOptions): Iterable<Path>;
|
|
125
|
-
addOrModify(
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
addOrModify(
|
|
127
|
+
mixedPath: Path,
|
|
128
|
+
metadata: FileMetadata,
|
|
129
|
+
changeListener?: FileSystemListener,
|
|
130
|
+
): void;
|
|
131
|
+
bulkAddOrModify(
|
|
132
|
+
addedOrModifiedFiles: FileData,
|
|
133
|
+
changeListener?: FileSystemListener,
|
|
134
|
+
): void;
|
|
135
|
+
remove(mixedPath: Path, changeListener?: FileSystemListener): void;
|
|
128
136
|
/**
|
|
129
137
|
* Given a start path (which need not exist), a subpath and type, and
|
|
130
138
|
* optionally a 'breakOnSegment', performs the following:
|
package/src/lib/TreeFS.js
CHANGED
|
@@ -48,13 +48,33 @@ class TreeFS {
|
|
|
48
48
|
const fileMetadata = this.#getFileData(mixedPath);
|
|
49
49
|
return (fileMetadata && fileMetadata[_constants.default.SIZE]) ?? null;
|
|
50
50
|
}
|
|
51
|
-
getDifference(files) {
|
|
51
|
+
getDifference(files, options) {
|
|
52
52
|
const changedFiles = new Map(files);
|
|
53
53
|
const removedFiles = new Set();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
const subpath = options?.subpath;
|
|
55
|
+
let rootNode = this.#rootNode;
|
|
56
|
+
let prefix = "";
|
|
57
|
+
if (subpath != null && subpath !== "") {
|
|
58
|
+
const lookupResult = this.#lookupByNormalPath(subpath, {
|
|
59
|
+
followLeaf: true,
|
|
60
|
+
});
|
|
61
|
+
if (!lookupResult.exists || !isDirectory(lookupResult.node)) {
|
|
62
|
+
return {
|
|
63
|
+
changedFiles,
|
|
64
|
+
removedFiles,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
rootNode = lookupResult.node;
|
|
68
|
+
prefix = lookupResult.canonicalPath;
|
|
69
|
+
}
|
|
70
|
+
for (const { canonicalPath, metadata } of this.#metadataIterator(
|
|
71
|
+
rootNode,
|
|
72
|
+
{
|
|
73
|
+
includeNodeModules: true,
|
|
74
|
+
includeSymlinks: true,
|
|
75
|
+
},
|
|
76
|
+
prefix,
|
|
77
|
+
)) {
|
|
58
78
|
const newMetadata = files.get(canonicalPath);
|
|
59
79
|
if (newMetadata) {
|
|
60
80
|
if (isRegularFile(newMetadata) !== isRegularFile(metadata)) {
|
|
@@ -253,11 +273,12 @@ class TreeFS {
|
|
|
253
273
|
}
|
|
254
274
|
}
|
|
255
275
|
}
|
|
256
|
-
addOrModify(mixedPath, metadata) {
|
|
276
|
+
addOrModify(mixedPath, metadata, changeListener) {
|
|
257
277
|
const normalPath = this.#normalizePath(mixedPath);
|
|
258
278
|
const parentDirNode = this.#lookupByNormalPath(
|
|
259
279
|
_path.default.dirname(normalPath),
|
|
260
280
|
{
|
|
281
|
+
changeListener,
|
|
261
282
|
makeDirectories: true,
|
|
262
283
|
},
|
|
263
284
|
);
|
|
@@ -271,9 +292,9 @@ class TreeFS {
|
|
|
271
292
|
_path.default.sep +
|
|
272
293
|
_path.default.basename(normalPath),
|
|
273
294
|
);
|
|
274
|
-
this.bulkAddOrModify(new Map([[canonicalPath, metadata]]));
|
|
295
|
+
this.bulkAddOrModify(new Map([[canonicalPath, metadata]]), changeListener);
|
|
275
296
|
}
|
|
276
|
-
bulkAddOrModify(addedOrModifiedFiles) {
|
|
297
|
+
bulkAddOrModify(addedOrModifiedFiles, changeListener) {
|
|
277
298
|
let lastDir;
|
|
278
299
|
let directoryNode;
|
|
279
300
|
for (const [normalPath, metadata] of addedOrModifiedFiles) {
|
|
@@ -283,6 +304,7 @@ class TreeFS {
|
|
|
283
304
|
lastSepIdx === -1 ? normalPath : normalPath.slice(lastSepIdx + 1);
|
|
284
305
|
if (directoryNode == null || dirname !== lastDir) {
|
|
285
306
|
const lookup = this.#lookupByNormalPath(dirname, {
|
|
307
|
+
changeListener,
|
|
286
308
|
followLeaf: false,
|
|
287
309
|
makeDirectories: true,
|
|
288
310
|
});
|
|
@@ -301,30 +323,53 @@ class TreeFS {
|
|
|
301
323
|
lastDir = dirname;
|
|
302
324
|
directoryNode = lookup.node;
|
|
303
325
|
}
|
|
326
|
+
if (changeListener != null) {
|
|
327
|
+
const existingNode = directoryNode.get(basename);
|
|
328
|
+
if (existingNode != null) {
|
|
329
|
+
(0, _invariant.default)(
|
|
330
|
+
!isDirectory(existingNode),
|
|
331
|
+
"Detected addition or modification of file %s, but it is tracked as a non-empty directory",
|
|
332
|
+
normalPath,
|
|
333
|
+
);
|
|
334
|
+
changeListener.fileModified(normalPath, existingNode, metadata);
|
|
335
|
+
} else {
|
|
336
|
+
changeListener.fileAdded(normalPath, metadata);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
304
339
|
directoryNode.set(basename, metadata);
|
|
305
340
|
}
|
|
306
341
|
}
|
|
307
|
-
remove(mixedPath) {
|
|
342
|
+
remove(mixedPath, changeListener) {
|
|
308
343
|
const normalPath = this.#normalizePath(mixedPath);
|
|
309
344
|
const result = this.#lookupByNormalPath(normalPath, {
|
|
310
345
|
followLeaf: false,
|
|
311
346
|
});
|
|
312
347
|
if (!result.exists) {
|
|
313
|
-
return
|
|
348
|
+
return;
|
|
314
349
|
}
|
|
315
350
|
const { parentNode, canonicalPath, node } = result;
|
|
316
351
|
if (isDirectory(node) && node.size > 0) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
352
|
+
for (const basename of node.keys()) {
|
|
353
|
+
this.remove(
|
|
354
|
+
canonicalPath + _path.default.sep + basename,
|
|
355
|
+
changeListener,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
320
359
|
}
|
|
321
360
|
if (parentNode != null) {
|
|
361
|
+
if (changeListener != null) {
|
|
362
|
+
if (isDirectory(node)) {
|
|
363
|
+
changeListener.directoryRemoved(canonicalPath);
|
|
364
|
+
} else {
|
|
365
|
+
changeListener.fileRemoved(canonicalPath, node);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
322
368
|
parentNode.delete(_path.default.basename(canonicalPath));
|
|
323
369
|
if (parentNode.size === 0 && parentNode !== this.#rootNode) {
|
|
324
|
-
this.remove(_path.default.dirname(canonicalPath));
|
|
370
|
+
this.remove(_path.default.dirname(canonicalPath), changeListener);
|
|
325
371
|
}
|
|
326
372
|
}
|
|
327
|
-
return isDirectory(node) ? null : node;
|
|
328
373
|
}
|
|
329
374
|
#lookupByNormalPath(
|
|
330
375
|
requestedNormalPath,
|
|
@@ -338,7 +383,7 @@ class TreeFS {
|
|
|
338
383
|
let fromIdx = opts.start?.pathIdx ?? 0;
|
|
339
384
|
let parentNode = opts.start?.node ?? this.#rootNode;
|
|
340
385
|
let ancestorOfRootIdx = opts.start?.ancestorOfRootIdx ?? 0;
|
|
341
|
-
const collectAncestors = opts
|
|
386
|
+
const { collectAncestors, changeListener } = opts;
|
|
342
387
|
let unseenPathFromIdx = 0;
|
|
343
388
|
while (targetNormalPath.length > fromIdx) {
|
|
344
389
|
const nextSepIdx = targetNormalPath.indexOf(_path.default.sep, fromIdx);
|
|
@@ -369,6 +414,12 @@ class TreeFS {
|
|
|
369
414
|
}
|
|
370
415
|
segmentNode = new Map();
|
|
371
416
|
if (opts.makeDirectories === true) {
|
|
417
|
+
if (changeListener != null) {
|
|
418
|
+
const canonicalPath = isLastSegment
|
|
419
|
+
? targetNormalPath
|
|
420
|
+
: targetNormalPath.slice(0, fromIdx - 1);
|
|
421
|
+
changeListener.directoryAdded(canonicalPath);
|
|
422
|
+
}
|
|
372
423
|
parentNode.set(segmentName, segmentNode);
|
|
373
424
|
}
|
|
374
425
|
}
|
package/src/lib/TreeFS.js.flow
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
FileData,
|
|
14
14
|
FileMetadata,
|
|
15
15
|
FileStats,
|
|
16
|
+
FileSystemListener,
|
|
16
17
|
LookupResult,
|
|
17
18
|
MutableFileSystem,
|
|
18
19
|
Path,
|
|
@@ -156,16 +157,45 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
156
157
|
return (fileMetadata && fileMetadata[H.SIZE]) ?? null;
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
getDifference(
|
|
160
|
+
getDifference(
|
|
161
|
+
files: FileData,
|
|
162
|
+
options?: Readonly<{
|
|
163
|
+
// Only consider files under this normal subdirectory when computing
|
|
164
|
+
// removedFiles. If not provided, all files in the file system are
|
|
165
|
+
// considered.
|
|
166
|
+
subpath?: string,
|
|
167
|
+
}>,
|
|
168
|
+
): {
|
|
160
169
|
changedFiles: FileData,
|
|
161
170
|
removedFiles: Set<string>,
|
|
162
171
|
} {
|
|
163
172
|
const changedFiles: FileData = new Map(files);
|
|
164
173
|
const removedFiles: Set<string> = new Set();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
const subpath = options?.subpath;
|
|
175
|
+
|
|
176
|
+
// If a subpath is specified, start iteration from that node
|
|
177
|
+
let rootNode: DirectoryNode = this.#rootNode;
|
|
178
|
+
let prefix: string = '';
|
|
179
|
+
if (subpath != null && subpath !== '') {
|
|
180
|
+
const lookupResult = this.#lookupByNormalPath(subpath, {
|
|
181
|
+
followLeaf: true,
|
|
182
|
+
});
|
|
183
|
+
if (!lookupResult.exists || !isDirectory(lookupResult.node)) {
|
|
184
|
+
// Directory doesn't exist, nothing to compare - all files are new
|
|
185
|
+
return {changedFiles, removedFiles};
|
|
186
|
+
}
|
|
187
|
+
rootNode = lookupResult.node;
|
|
188
|
+
prefix = lookupResult.canonicalPath;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (const {canonicalPath, metadata} of this.#metadataIterator(
|
|
192
|
+
rootNode,
|
|
193
|
+
{
|
|
194
|
+
includeNodeModules: true,
|
|
195
|
+
includeSymlinks: true,
|
|
196
|
+
},
|
|
197
|
+
prefix,
|
|
198
|
+
)) {
|
|
169
199
|
const newMetadata = files.get(canonicalPath);
|
|
170
200
|
if (newMetadata) {
|
|
171
201
|
if (isRegularFile(newMetadata) !== isRegularFile(metadata)) {
|
|
@@ -378,11 +408,16 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
378
408
|
}
|
|
379
409
|
}
|
|
380
410
|
|
|
381
|
-
addOrModify(
|
|
411
|
+
addOrModify(
|
|
412
|
+
mixedPath: Path,
|
|
413
|
+
metadata: FileMetadata,
|
|
414
|
+
changeListener?: FileSystemListener,
|
|
415
|
+
): void {
|
|
382
416
|
const normalPath = this.#normalizePath(mixedPath);
|
|
383
417
|
// Walk the tree to find the *real* path of the parent node, creating
|
|
384
418
|
// directories as we need.
|
|
385
419
|
const parentDirNode = this.#lookupByNormalPath(path.dirname(normalPath), {
|
|
420
|
+
changeListener,
|
|
386
421
|
makeDirectories: true,
|
|
387
422
|
});
|
|
388
423
|
if (!parentDirNode.exists) {
|
|
@@ -394,10 +429,13 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
394
429
|
const canonicalPath = this.#normalizePath(
|
|
395
430
|
parentDirNode.canonicalPath + path.sep + path.basename(normalPath),
|
|
396
431
|
);
|
|
397
|
-
this.bulkAddOrModify(new Map([[canonicalPath, metadata]]));
|
|
432
|
+
this.bulkAddOrModify(new Map([[canonicalPath, metadata]]), changeListener);
|
|
398
433
|
}
|
|
399
434
|
|
|
400
|
-
bulkAddOrModify(
|
|
435
|
+
bulkAddOrModify(
|
|
436
|
+
addedOrModifiedFiles: FileData,
|
|
437
|
+
changeListener?: FileSystemListener,
|
|
438
|
+
): void {
|
|
401
439
|
// Optimisation: Bulk FileData are typically clustered by directory, so we
|
|
402
440
|
// optimise for that case by remembering the last directory we looked up.
|
|
403
441
|
// Experiments with large result sets show this to be significantly (~30%)
|
|
@@ -413,6 +451,7 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
413
451
|
|
|
414
452
|
if (directoryNode == null || dirname !== lastDir) {
|
|
415
453
|
const lookup = this.#lookupByNormalPath(dirname, {
|
|
454
|
+
changeListener,
|
|
416
455
|
followLeaf: false,
|
|
417
456
|
makeDirectories: true,
|
|
418
457
|
});
|
|
@@ -433,24 +472,48 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
433
472
|
lastDir = dirname;
|
|
434
473
|
directoryNode = lookup.node;
|
|
435
474
|
}
|
|
475
|
+
if (changeListener != null) {
|
|
476
|
+
const existingNode = directoryNode.get(basename);
|
|
477
|
+
if (existingNode != null) {
|
|
478
|
+
invariant(
|
|
479
|
+
!isDirectory(existingNode),
|
|
480
|
+
'Detected addition or modification of file %s, but it is tracked as a non-empty directory',
|
|
481
|
+
normalPath,
|
|
482
|
+
);
|
|
483
|
+
// File already exists - this is a modification
|
|
484
|
+
changeListener.fileModified(normalPath, existingNode, metadata);
|
|
485
|
+
} else {
|
|
486
|
+
// New file
|
|
487
|
+
changeListener.fileAdded(normalPath, metadata);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
436
490
|
directoryNode.set(basename, metadata);
|
|
437
491
|
}
|
|
438
492
|
}
|
|
439
493
|
|
|
440
|
-
remove(mixedPath: Path):
|
|
494
|
+
remove(mixedPath: Path, changeListener?: FileSystemListener): void {
|
|
441
495
|
const normalPath = this.#normalizePath(mixedPath);
|
|
442
496
|
const result = this.#lookupByNormalPath(normalPath, {followLeaf: false});
|
|
443
497
|
if (!result.exists) {
|
|
444
|
-
return
|
|
498
|
+
return;
|
|
445
499
|
}
|
|
446
500
|
const {parentNode, canonicalPath, node} = result;
|
|
447
501
|
|
|
448
502
|
if (isDirectory(node) && node.size > 0) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
503
|
+
for (const basename of node.keys()) {
|
|
504
|
+
this.remove(canonicalPath + path.sep + basename, changeListener);
|
|
505
|
+
}
|
|
506
|
+
// Removing the last file will delete this directory
|
|
507
|
+
return;
|
|
452
508
|
}
|
|
453
509
|
if (parentNode != null) {
|
|
510
|
+
if (changeListener != null) {
|
|
511
|
+
if (isDirectory(node)) {
|
|
512
|
+
changeListener.directoryRemoved(canonicalPath);
|
|
513
|
+
} else {
|
|
514
|
+
changeListener.fileRemoved(canonicalPath, node);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
454
517
|
parentNode.delete(path.basename(canonicalPath));
|
|
455
518
|
if (parentNode.size === 0 && parentNode !== this.#rootNode) {
|
|
456
519
|
// NB: This isn't the most efficient algorithm - in the case of
|
|
@@ -458,10 +521,9 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
458
521
|
// that's not expected to be a case common enough to justify
|
|
459
522
|
// implementation complexity, or slowing down more common uses of
|
|
460
523
|
// _lookupByNormalPath.
|
|
461
|
-
this.remove(path.dirname(canonicalPath));
|
|
524
|
+
this.remove(path.dirname(canonicalPath), changeListener);
|
|
462
525
|
}
|
|
463
526
|
}
|
|
464
|
-
return isDirectory(node) ? null : node;
|
|
465
527
|
}
|
|
466
528
|
|
|
467
529
|
/**
|
|
@@ -492,6 +554,10 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
492
554
|
// be added. Omit for performance if not needed.
|
|
493
555
|
collectLinkPaths?: ?Set<string>,
|
|
494
556
|
|
|
557
|
+
// Low-level callbacks called on mutations of TreeFS data.
|
|
558
|
+
// Omit for performance if not needed.
|
|
559
|
+
changeListener?: FileSystemListener,
|
|
560
|
+
|
|
495
561
|
// Like lstat vs stat, whether to follow a symlink at the basename of
|
|
496
562
|
// the given path, or return the details of the symlink itself.
|
|
497
563
|
followLeaf?: boolean,
|
|
@@ -541,7 +607,8 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
541
607
|
// null.
|
|
542
608
|
let ancestorOfRootIdx: ?number = opts.start?.ancestorOfRootIdx ?? 0;
|
|
543
609
|
|
|
544
|
-
const collectAncestors = opts
|
|
610
|
+
const {collectAncestors, changeListener} = opts;
|
|
611
|
+
|
|
545
612
|
// Used only when collecting ancestors, to avoid double-counting nodes and
|
|
546
613
|
// paths when traversing a symlink takes us back to rootNode and out again.
|
|
547
614
|
// This tracks the first character of the first segment not already
|
|
@@ -583,6 +650,12 @@ export default class TreeFS implements MutableFileSystem {
|
|
|
583
650
|
}
|
|
584
651
|
segmentNode = new Map();
|
|
585
652
|
if (opts.makeDirectories === true) {
|
|
653
|
+
if (changeListener != null) {
|
|
654
|
+
const canonicalPath = isLastSegment
|
|
655
|
+
? targetNormalPath
|
|
656
|
+
: targetNormalPath.slice(0, fromIdx - 1);
|
|
657
|
+
changeListener.directoryAdded(canonicalPath);
|
|
658
|
+
}
|
|
586
659
|
parentNode.set(segmentName, segmentNode);
|
|
587
660
|
}
|
|
588
661
|
}
|