metro-file-map 0.83.4 → 0.83.6

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.
Files changed (70) hide show
  1. package/package.json +3 -2
  2. package/src/Watcher.d.ts +74 -0
  3. package/src/Watcher.js +68 -48
  4. package/src/Watcher.js.flow +84 -51
  5. package/src/cache/DiskCacheManager.d.ts +49 -0
  6. package/src/cache/DiskCacheManager.js +1 -5
  7. package/src/constants.d.ts +22 -0
  8. package/src/crawlers/node/hasNativeFindSupport.d.ts +19 -0
  9. package/src/crawlers/node/index.d.ts +21 -0
  10. package/src/crawlers/node/index.js +6 -10
  11. package/src/crawlers/node/index.js.flow +8 -6
  12. package/src/crawlers/watchman/index.d.ts +23 -0
  13. package/src/crawlers/watchman/index.js +2 -9
  14. package/src/crawlers/watchman/index.js.flow +2 -6
  15. package/src/flow-types.d.ts +460 -0
  16. package/src/flow-types.js.flow +89 -29
  17. package/src/index.d.ts +182 -0
  18. package/src/index.js +148 -132
  19. package/src/index.js.flow +200 -155
  20. package/src/lib/FileProcessor.d.ts +60 -0
  21. package/src/lib/FileProcessor.js +1 -5
  22. package/src/lib/FileSystemChangeAggregator.d.ts +40 -0
  23. package/src/lib/FileSystemChangeAggregator.js +89 -0
  24. package/src/lib/FileSystemChangeAggregator.js.flow +143 -0
  25. package/src/lib/RootPathUtils.d.ts +30 -0
  26. package/src/lib/RootPathUtils.js +2 -9
  27. package/src/lib/TreeFS.d.ts +174 -0
  28. package/src/lib/TreeFS.js +68 -21
  29. package/src/lib/TreeFS.js.flow +89 -16
  30. package/src/lib/checkWatchmanCapabilities.d.ts +20 -0
  31. package/src/lib/normalizePathSeparatorsToPosix.d.ts +20 -0
  32. package/src/lib/normalizePathSeparatorsToPosix.js +1 -4
  33. package/src/lib/normalizePathSeparatorsToSystem.d.ts +20 -0
  34. package/src/lib/normalizePathSeparatorsToSystem.js +1 -4
  35. package/src/lib/rootRelativeCacheKeys.d.ts +24 -0
  36. package/src/lib/rootRelativeCacheKeys.js +1 -5
  37. package/src/lib/sorting.d.ts +23 -0
  38. package/src/plugins/DependencyPlugin.d.ts +52 -0
  39. package/src/plugins/DependencyPlugin.js +1 -3
  40. package/src/plugins/DependencyPlugin.js.flow +1 -16
  41. package/src/plugins/HastePlugin.d.ts +69 -0
  42. package/src/plugins/HastePlugin.js +12 -16
  43. package/src/plugins/HastePlugin.js.flow +12 -12
  44. package/src/plugins/MockPlugin.d.ts +48 -0
  45. package/src/plugins/MockPlugin.js +18 -25
  46. package/src/plugins/MockPlugin.js.flow +18 -22
  47. package/src/plugins/dependencies/dependencyExtractor.d.ts +1 -1
  48. package/src/plugins/haste/DuplicateHasteCandidatesError.d.ts +31 -0
  49. package/src/plugins/haste/DuplicateHasteCandidatesError.js +1 -5
  50. package/src/plugins/haste/HasteConflictsError.d.ts +23 -0
  51. package/src/plugins/haste/HasteConflictsError.js +1 -5
  52. package/src/plugins/haste/computeConflicts.d.ts +34 -0
  53. package/src/plugins/haste/computeConflicts.js +1 -5
  54. package/src/plugins/haste/getPlatformExtension.d.ts +21 -0
  55. package/src/plugins/mocks/getMockName.d.ts +20 -0
  56. package/src/plugins/mocks/getMockName.js +1 -4
  57. package/src/watchers/AbstractWatcher.d.ts +41 -0
  58. package/src/watchers/AbstractWatcher.js +2 -9
  59. package/src/watchers/FallbackWatcher.d.ts +28 -0
  60. package/src/watchers/FallbackWatcher.js +21 -12
  61. package/src/watchers/FallbackWatcher.js.flow +28 -5
  62. package/src/watchers/NativeWatcher.d.ts +55 -0
  63. package/src/watchers/NativeWatcher.js +28 -9
  64. package/src/watchers/NativeWatcher.js.flow +33 -6
  65. package/src/watchers/RecrawlWarning.d.ts +32 -0
  66. package/src/watchers/WatchmanWatcher.d.ts +34 -0
  67. package/src/watchers/WatchmanWatcher.js +2 -9
  68. package/src/watchers/common.d.ts +70 -0
  69. package/src/watchers/common.js +7 -6
  70. package/src/watchers/common.js.flow +1 -0
@@ -0,0 +1,40 @@
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
+ * @noformat
8
+ * @oncall react_native
9
+ * @generated SignedSource<<5feda1b197530a9a5fdbc57200633ac5>>
10
+ *
11
+ * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
12
+ * Original file: packages/metro-file-map/src/lib/FileSystemChangeAggregator.js
13
+ * To regenerate, run:
14
+ * js1 build metro-ts-defs (internal) OR
15
+ * yarn run build-ts-defs (OSS)
16
+ */
17
+
18
+ import type {
19
+ CanonicalPath,
20
+ FileMetadata,
21
+ FileSystemListener,
22
+ ReadonlyFileSystemChanges,
23
+ } from '../flow-types';
24
+
25
+ export declare class FileSystemChangeAggregator implements FileSystemListener {
26
+ directoryAdded(canonicalPath: CanonicalPath): void;
27
+ directoryRemoved(canonicalPath: CanonicalPath): void;
28
+ fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void;
29
+ fileModified(
30
+ canonicalPath: CanonicalPath,
31
+ oldData: FileMetadata,
32
+ newData: FileMetadata,
33
+ ): void;
34
+ fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void;
35
+ getSize(): number;
36
+ getView(): ReadonlyFileSystemChanges<FileMetadata>;
37
+ getMappedView<T>(
38
+ metadataMapFn: (metadata: FileMetadata) => T,
39
+ ): ReadonlyFileSystemChanges<T>;
40
+ }
@@ -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
+ }
@@ -0,0 +1,30 @@
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
+ * @noformat
8
+ * @generated SignedSource<<5ecdb559fce5f5c6ed50df6e4eaebf20>>
9
+ *
10
+ * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
11
+ * Original file: packages/metro-file-map/src/lib/RootPathUtils.js
12
+ * To regenerate, run:
13
+ * js1 build metro-ts-defs (internal) OR
14
+ * yarn run build-ts-defs (OSS)
15
+ */
16
+
17
+ export declare class RootPathUtils {
18
+ constructor(rootDir: string);
19
+ getBasenameOfNthAncestor(n: number): string;
20
+ getParts(): ReadonlyArray<string>;
21
+ absoluteToNormal(absolutePath: string): string;
22
+ normalToAbsolute(normalPath: string): string;
23
+ relativeToNormal(relativePath: string): string;
24
+ getAncestorOfRootIdx(normalPath: string): null | undefined | number;
25
+ joinNormalToRelative(
26
+ normalPath: string,
27
+ relativePath: string,
28
+ ): {normalPath: string; collapsedSegments: number};
29
+ relative(from: string, to: string): string;
30
+ }
@@ -14,10 +14,7 @@ function _interopRequireWildcard(e, t) {
14
14
  if (!t && e && e.__esModule) return e;
15
15
  var o,
16
16
  i,
17
- f = {
18
- __proto__: null,
19
- default: e,
20
- };
17
+ f = { __proto__: null, default: e };
21
18
  if (null === e || ("object" != typeof e && "function" != typeof e))
22
19
  return f;
23
20
  if ((o = t ? n : r)) {
@@ -37,11 +34,7 @@ function _interopRequireWildcard(e, t) {
37
34
  })(e, t);
38
35
  }
39
36
  function _interopRequireDefault(e) {
40
- return e && e.__esModule
41
- ? e
42
- : {
43
- default: e,
44
- };
37
+ return e && e.__esModule ? e : { default: e };
45
38
  }
46
39
  const UP_FRAGMENT_SEP = ".." + path.sep;
47
40
  const SEP_UP_FRAGMENT = path.sep + "..";
@@ -0,0 +1,174 @@
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
+ * @noformat
8
+ * @generated SignedSource<<65a3c4140d459a56b8c949e52b32ea1b>>
9
+ *
10
+ * This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
11
+ * Original file: packages/metro-file-map/src/lib/TreeFS.js
12
+ * To regenerate, run:
13
+ * js1 build metro-ts-defs (internal) OR
14
+ * yarn run build-ts-defs (OSS)
15
+ */
16
+
17
+ import type {
18
+ CacheData,
19
+ FileData,
20
+ FileMetadata,
21
+ FileStats,
22
+ FileSystemListener,
23
+ LookupResult,
24
+ MutableFileSystem,
25
+ Path,
26
+ ProcessFileFunction,
27
+ } from '../flow-types';
28
+
29
+ type DirectoryNode = Map<string, MixedNode>;
30
+ type FileNode = FileMetadata;
31
+ type MixedNode = FileNode | DirectoryNode;
32
+ type DeserializedSnapshotInput = {
33
+ rootDir: string;
34
+ fileSystemData: DirectoryNode;
35
+ processFile: ProcessFileFunction;
36
+ };
37
+ type TreeFSOptions = {
38
+ rootDir: Path;
39
+ files?: FileData;
40
+ processFile: ProcessFileFunction;
41
+ };
42
+ type MatchFilesOptions = Readonly<{
43
+ filter?: null | undefined | RegExp;
44
+ filterCompareAbsolute?: boolean;
45
+ filterComparePosix?: boolean;
46
+ follow?: boolean;
47
+ recursive?: boolean;
48
+ rootDir?: null | undefined | Path;
49
+ }>;
50
+ type MetadataIteratorOptions = Readonly<{
51
+ includeSymlinks: boolean;
52
+ includeNodeModules: boolean;
53
+ }>;
54
+ /**
55
+ * OVERVIEW:
56
+ *
57
+ * TreeFS is Metro's in-memory representation of the file system. It is
58
+ * structured as a tree of non-empty maps and leaves (tuples), with the root
59
+ * node representing the given `rootDir`, typically Metro's _project root_
60
+ * (not a filesystem root). Map keys are path segments, and branches outside
61
+ * the project root are accessed via `'..'`.
62
+ *
63
+ * EXAMPLE:
64
+ *
65
+ * For a root dir '/data/project', the file '/data/other/app/index.js' would
66
+ * have metadata at #rootNode.get('..').get('other').get('app').get('index.js')
67
+ *
68
+ * SERIALISATION:
69
+ *
70
+ * #rootNode is designed to be directly serialisable and directly portable (for
71
+ * a given project) between different root directories and operating systems.
72
+ *
73
+ * SYMLINKS:
74
+ *
75
+ * Symlinks are represented as nodes whose metadata contains their literal
76
+ * target. Literal targets are resolved to normal paths at runtime, and cached.
77
+ * If a symlink is encountered during traversal, we restart traversal at the
78
+ * root node targeting join(normal symlink target, remaining path suffix).
79
+ *
80
+ * NODE TYPES:
81
+ *
82
+ * - A directory (including a parent directory at '..') is represented by a
83
+ * `Map` of basenames to any other node type.
84
+ * - A file is represented by an `Array` (tuple) of metadata, of which:
85
+ * - A regular file has node[H.SYMLINK] === 0
86
+ * - A symlink has node[H.SYMLINK] === 1 or
87
+ * typeof node[H.SYMLINK] === 'string', where a string is the literal
88
+ * content of the symlink (i.e. from readlink), if known.
89
+ *
90
+ * TERMINOLOGY:
91
+ *
92
+ * - mixedPath
93
+ * A root-relative or absolute path
94
+ * - relativePath
95
+ * A root-relative path
96
+ * - normalPath
97
+ * A root-relative, normalised path (no extraneous '.' or '..'), may have a
98
+ * single trailing slash
99
+ * - canonicalPath
100
+ * A root-relative, normalised, real path (no symlinks in dirname), never has
101
+ * a trailing slash
102
+ */
103
+ declare class TreeFS implements MutableFileSystem {
104
+ constructor(opts: TreeFSOptions);
105
+ getSerializableSnapshot(): CacheData['fileSystemData'];
106
+ static fromDeserializedSnapshot(args: DeserializedSnapshotInput): TreeFS;
107
+ getSize(mixedPath: Path): null | undefined | number;
108
+ getDifference(
109
+ files: FileData,
110
+ options?: Readonly<{subpath?: string}>,
111
+ ): {changedFiles: FileData; removedFiles: Set<string>};
112
+ getSha1(mixedPath: Path): null | undefined | string;
113
+ getOrComputeSha1(
114
+ mixedPath: Path,
115
+ ): Promise<null | undefined | {sha1: string; content?: Buffer}>;
116
+ exists(mixedPath: Path): boolean;
117
+ lookup(mixedPath: Path): LookupResult;
118
+ getAllFiles(): Array<Path>;
119
+ linkStats(mixedPath: Path): null | undefined | FileStats;
120
+ /**
121
+ * Given a search context, return a list of file paths matching the query.
122
+ * The query matches against normalized paths which start with `./`,
123
+ * for example: `a/b.js` -> `./a/b.js`
124
+ */
125
+ matchFiles(opts: MatchFilesOptions): Iterable<Path>;
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;
136
+ /**
137
+ * Given a start path (which need not exist), a subpath and type, and
138
+ * optionally a 'breakOnSegment', performs the following:
139
+ *
140
+ * X = mixedStartPath
141
+ * do
142
+ * if basename(X) === opts.breakOnSegment
143
+ * return null
144
+ * if X + subpath exists and has type opts.subpathType
145
+ * return {
146
+ * absolutePath: realpath(X + subpath)
147
+ * containerRelativePath: relative(mixedStartPath, X)
148
+ * }
149
+ * X = dirname(X)
150
+ * while X !== dirname(X)
151
+ *
152
+ * If opts.invalidatedBy is given, collects all absolute, real paths that if
153
+ * added or removed may invalidate this result.
154
+ *
155
+ * Useful for finding the closest package scope (subpath: package.json,
156
+ * type f, breakOnSegment: node_modules) or closest potential package root
157
+ * (subpath: node_modules/pkg, type: d) in Node.js resolution.
158
+ */
159
+ hierarchicalLookup(
160
+ mixedStartPath: string,
161
+ subpath: string,
162
+ opts: {
163
+ breakOnSegment: null | undefined | string;
164
+ invalidatedBy: null | undefined | Set<string>;
165
+ subpathType: 'f' | 'd';
166
+ },
167
+ ): null | undefined | {absolutePath: string; containerRelativePath: string};
168
+ metadataIterator(opts: MetadataIteratorOptions): Iterator<{
169
+ baseName: string;
170
+ canonicalPath: string;
171
+ metadata: FileMetadata;
172
+ }>;
173
+ }
174
+ export default TreeFS;
package/src/lib/TreeFS.js CHANGED
@@ -9,11 +9,7 @@ var _RootPathUtils = require("./RootPathUtils");
9
9
  var _invariant = _interopRequireDefault(require("invariant"));
10
10
  var _path = _interopRequireDefault(require("path"));
11
11
  function _interopRequireDefault(e) {
12
- return e && e.__esModule
13
- ? e
14
- : {
15
- default: e,
16
- };
12
+ return e && e.__esModule ? e : { default: e };
17
13
  }
18
14
  function isDirectory(node) {
19
15
  return node instanceof Map;
@@ -52,13 +48,33 @@ class TreeFS {
52
48
  const fileMetadata = this.#getFileData(mixedPath);
53
49
  return (fileMetadata && fileMetadata[_constants.default.SIZE]) ?? null;
54
50
  }
55
- getDifference(files) {
51
+ getDifference(files, options) {
56
52
  const changedFiles = new Map(files);
57
53
  const removedFiles = new Set();
58
- for (const { canonicalPath, metadata } of this.metadataIterator({
59
- includeNodeModules: true,
60
- includeSymlinks: true,
61
- })) {
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
+ )) {
62
78
  const newMetadata = files.get(canonicalPath);
63
79
  if (newMetadata) {
64
80
  if (isRegularFile(newMetadata) !== isRegularFile(metadata)) {
@@ -257,11 +273,12 @@ class TreeFS {
257
273
  }
258
274
  }
259
275
  }
260
- addOrModify(mixedPath, metadata) {
276
+ addOrModify(mixedPath, metadata, changeListener) {
261
277
  const normalPath = this.#normalizePath(mixedPath);
262
278
  const parentDirNode = this.#lookupByNormalPath(
263
279
  _path.default.dirname(normalPath),
264
280
  {
281
+ changeListener,
265
282
  makeDirectories: true,
266
283
  },
267
284
  );
@@ -275,9 +292,9 @@ class TreeFS {
275
292
  _path.default.sep +
276
293
  _path.default.basename(normalPath),
277
294
  );
278
- this.bulkAddOrModify(new Map([[canonicalPath, metadata]]));
295
+ this.bulkAddOrModify(new Map([[canonicalPath, metadata]]), changeListener);
279
296
  }
280
- bulkAddOrModify(addedOrModifiedFiles) {
297
+ bulkAddOrModify(addedOrModifiedFiles, changeListener) {
281
298
  let lastDir;
282
299
  let directoryNode;
283
300
  for (const [normalPath, metadata] of addedOrModifiedFiles) {
@@ -287,6 +304,7 @@ class TreeFS {
287
304
  lastSepIdx === -1 ? normalPath : normalPath.slice(lastSepIdx + 1);
288
305
  if (directoryNode == null || dirname !== lastDir) {
289
306
  const lookup = this.#lookupByNormalPath(dirname, {
307
+ changeListener,
290
308
  followLeaf: false,
291
309
  makeDirectories: true,
292
310
  });
@@ -305,30 +323,53 @@ class TreeFS {
305
323
  lastDir = dirname;
306
324
  directoryNode = lookup.node;
307
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
+ }
308
339
  directoryNode.set(basename, metadata);
309
340
  }
310
341
  }
311
- remove(mixedPath) {
342
+ remove(mixedPath, changeListener) {
312
343
  const normalPath = this.#normalizePath(mixedPath);
313
344
  const result = this.#lookupByNormalPath(normalPath, {
314
345
  followLeaf: false,
315
346
  });
316
347
  if (!result.exists) {
317
- return null;
348
+ return;
318
349
  }
319
350
  const { parentNode, canonicalPath, node } = result;
320
351
  if (isDirectory(node) && node.size > 0) {
321
- throw new Error(
322
- `TreeFS: remove called on a non-empty directory: ${mixedPath}`,
323
- );
352
+ for (const basename of node.keys()) {
353
+ this.remove(
354
+ canonicalPath + _path.default.sep + basename,
355
+ changeListener,
356
+ );
357
+ }
358
+ return;
324
359
  }
325
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
+ }
326
368
  parentNode.delete(_path.default.basename(canonicalPath));
327
369
  if (parentNode.size === 0 && parentNode !== this.#rootNode) {
328
- this.remove(_path.default.dirname(canonicalPath));
370
+ this.remove(_path.default.dirname(canonicalPath), changeListener);
329
371
  }
330
372
  }
331
- return isDirectory(node) ? null : node;
332
373
  }
333
374
  #lookupByNormalPath(
334
375
  requestedNormalPath,
@@ -342,7 +383,7 @@ class TreeFS {
342
383
  let fromIdx = opts.start?.pathIdx ?? 0;
343
384
  let parentNode = opts.start?.node ?? this.#rootNode;
344
385
  let ancestorOfRootIdx = opts.start?.ancestorOfRootIdx ?? 0;
345
- const collectAncestors = opts.collectAncestors;
386
+ const { collectAncestors, changeListener } = opts;
346
387
  let unseenPathFromIdx = 0;
347
388
  while (targetNormalPath.length > fromIdx) {
348
389
  const nextSepIdx = targetNormalPath.indexOf(_path.default.sep, fromIdx);
@@ -373,6 +414,12 @@ class TreeFS {
373
414
  }
374
415
  segmentNode = new Map();
375
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
+ }
376
423
  parentNode.set(segmentName, segmentNode);
377
424
  }
378
425
  }