metro-file-map 0.81.0 → 0.81.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/package.json +10 -7
- package/src/Watcher.js +35 -34
- package/src/Watcher.js.flow +41 -62
- package/src/cache/DiskCacheManager.d.ts +4 -3
- package/src/cache/DiskCacheManager.js +71 -13
- package/src/cache/DiskCacheManager.js.flow +91 -17
- package/src/flow-types.d.ts +51 -25
- package/src/flow-types.js.flow +140 -28
- package/src/index.d.ts +5 -1
- package/src/index.js +269 -345
- package/src/index.js.flow +311 -418
- package/src/lib/FileProcessor.js +164 -0
- package/src/lib/FileProcessor.js.flow +243 -0
- package/src/lib/TreeFS.js +39 -2
- package/src/lib/TreeFS.js.flow +63 -7
- package/src/lib/sorting.js +27 -0
- package/src/lib/sorting.js.flow +35 -0
- package/src/{lib/MutableHasteMap.js → plugins/HastePlugin.js} +139 -48
- package/src/{lib/MutableHasteMap.js.flow → plugins/HastePlugin.js.flow} +173 -63
- package/src/plugins/MockPlugin.js +171 -0
- package/src/plugins/MockPlugin.js.flow +205 -0
- package/src/{lib → plugins/haste}/DuplicateHasteCandidatesError.js +1 -1
- package/src/{lib → plugins/haste}/DuplicateHasteCandidatesError.js.flow +2 -2
- package/src/plugins/haste/HasteConflictsError.js +56 -0
- package/src/plugins/haste/HasteConflictsError.js.flow +62 -0
- package/src/plugins/haste/computeConflicts.js +73 -0
- package/src/plugins/haste/computeConflicts.js.flow +105 -0
- package/src/watchers/AbstractWatcher.js +76 -0
- package/src/watchers/AbstractWatcher.js.flow +92 -0
- package/src/watchers/{NodeWatcher.js → FallbackWatcher.js} +112 -71
- package/src/watchers/{NodeWatcher.js.flow → FallbackWatcher.js.flow} +125 -99
- package/src/watchers/NativeWatcher.js +109 -0
- package/src/watchers/NativeWatcher.js.flow +136 -0
- package/src/watchers/WatchmanWatcher.js +70 -63
- package/src/watchers/WatchmanWatcher.js.flow +67 -79
- package/src/watchers/common.js +9 -61
- package/src/watchers/common.js.flow +19 -90
- package/src/worker.js +14 -28
- package/src/worker.js.flow +5 -23
- package/src/lib/DuplicateError.js +0 -14
- package/src/lib/DuplicateError.js.flow +0 -22
- package/src/lib/MockMap.js +0 -22
- package/src/lib/MockMap.js.flow +0 -31
- package/src/watchers/FSEventsWatcher.js +0 -179
- package/src/watchers/FSEventsWatcher.js.flow +0 -231
- /package/src/{lib → plugins/haste}/getPlatformExtension.js +0 -0
- /package/src/{lib → plugins/haste}/getPlatformExtension.js.flow +0 -0
- /package/src/{getMockName.js → plugins/mocks/getMockName.js} +0 -0
- /package/src/{getMockName.js.flow → plugins/mocks/getMockName.js.flow} +0 -0
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metro-file-map",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.2",
|
|
4
4
|
"description": "[Experimental] - 🚇 File crawling, watching and mapping for Metro",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./package.json": "./package.json",
|
|
9
|
+
"./private/*": "./src/*.js",
|
|
10
|
+
"./src": "./src/index.js",
|
|
11
|
+
"./src/*.js": "./src/*.js",
|
|
12
|
+
"./src/*": "./src/*.js"
|
|
13
|
+
},
|
|
6
14
|
"repository": {
|
|
7
15
|
"type": "git",
|
|
8
16
|
"url": "git@github.com:facebook/metro.git"
|
|
@@ -13,24 +21,19 @@
|
|
|
13
21
|
},
|
|
14
22
|
"license": "MIT",
|
|
15
23
|
"dependencies": {
|
|
16
|
-
"anymatch": "^3.0.3",
|
|
17
24
|
"debug": "^2.2.0",
|
|
18
25
|
"fb-watchman": "^2.0.0",
|
|
19
26
|
"flow-enums-runtime": "^0.0.6",
|
|
20
27
|
"graceful-fs": "^4.2.4",
|
|
21
28
|
"invariant": "^2.2.4",
|
|
22
|
-
"jest-worker": "^29.
|
|
29
|
+
"jest-worker": "^29.7.0",
|
|
23
30
|
"micromatch": "^4.0.4",
|
|
24
|
-
"node-abort-controller": "^3.1.1",
|
|
25
31
|
"nullthrows": "^1.1.1",
|
|
26
32
|
"walker": "^1.0.7"
|
|
27
33
|
},
|
|
28
34
|
"devDependencies": {
|
|
29
35
|
"slash": "^3.0.0"
|
|
30
36
|
},
|
|
31
|
-
"optionalDependencies": {
|
|
32
|
-
"fsevents": "^2.3.2"
|
|
33
|
-
},
|
|
34
37
|
"engines": {
|
|
35
38
|
"node": ">=18.18"
|
|
36
39
|
}
|
package/src/Watcher.js
CHANGED
|
@@ -7,10 +7,12 @@ exports.Watcher = void 0;
|
|
|
7
7
|
var _node = _interopRequireDefault(require("./crawlers/node"));
|
|
8
8
|
var _watchman = _interopRequireDefault(require("./crawlers/watchman"));
|
|
9
9
|
var _common = require("./watchers/common");
|
|
10
|
-
var
|
|
11
|
-
require("./watchers/
|
|
10
|
+
var _FallbackWatcher = _interopRequireDefault(
|
|
11
|
+
require("./watchers/FallbackWatcher")
|
|
12
|
+
);
|
|
13
|
+
var _NativeWatcher = _interopRequireDefault(
|
|
14
|
+
require("./watchers/NativeWatcher")
|
|
12
15
|
);
|
|
13
|
-
var _NodeWatcher = _interopRequireDefault(require("./watchers/NodeWatcher"));
|
|
14
16
|
var _WatchmanWatcher = _interopRequireDefault(
|
|
15
17
|
require("./watchers/WatchmanWatcher")
|
|
16
18
|
);
|
|
@@ -60,8 +62,8 @@ class Watcher extends _events.default {
|
|
|
60
62
|
async crawl() {
|
|
61
63
|
this._options.perfLogger?.point("crawl_start");
|
|
62
64
|
const options = this._options;
|
|
63
|
-
const
|
|
64
|
-
options.
|
|
65
|
+
const ignoreForCrawl = (filePath) =>
|
|
66
|
+
options.ignoreForCrawl(filePath) ||
|
|
65
67
|
path.basename(filePath).startsWith(this._options.healthCheckFilePrefix);
|
|
66
68
|
const crawl = options.useWatchman ? _watchman.default : _node.default;
|
|
67
69
|
let crawler = crawl === _watchman.default ? "watchman" : "node";
|
|
@@ -73,7 +75,7 @@ class Watcher extends _events.default {
|
|
|
73
75
|
includeSymlinks: options.enableSymlinks,
|
|
74
76
|
extensions: options.extensions,
|
|
75
77
|
forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
|
|
76
|
-
ignore,
|
|
78
|
+
ignore: ignoreForCrawl,
|
|
77
79
|
onStatus: (status) => {
|
|
78
80
|
this.emit("status", status);
|
|
79
81
|
},
|
|
@@ -123,17 +125,17 @@ class Watcher extends _events.default {
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
async watch(onChange) {
|
|
126
|
-
const { extensions,
|
|
128
|
+
const { extensions, ignorePatternForWatch, useWatchman } = this._options;
|
|
127
129
|
const WatcherImpl = useWatchman
|
|
128
130
|
? _WatchmanWatcher.default
|
|
129
|
-
:
|
|
130
|
-
?
|
|
131
|
-
:
|
|
132
|
-
let watcher = "
|
|
131
|
+
: _NativeWatcher.default.isSupported()
|
|
132
|
+
? _NativeWatcher.default
|
|
133
|
+
: _FallbackWatcher.default;
|
|
134
|
+
let watcher = "fallback";
|
|
133
135
|
if (WatcherImpl === _WatchmanWatcher.default) {
|
|
134
136
|
watcher = "watchman";
|
|
135
|
-
} else if (WatcherImpl ===
|
|
136
|
-
watcher = "
|
|
137
|
+
} else if (WatcherImpl === _NativeWatcher.default) {
|
|
138
|
+
watcher = "native";
|
|
137
139
|
}
|
|
138
140
|
debug(`Using watcher: ${watcher}`);
|
|
139
141
|
this._options.perfLogger?.annotate({
|
|
@@ -145,39 +147,38 @@ class Watcher extends _events.default {
|
|
|
145
147
|
const createWatcherBackend = (root) => {
|
|
146
148
|
const watcherOptions = {
|
|
147
149
|
dot: true,
|
|
148
|
-
|
|
150
|
+
globs: [
|
|
149
151
|
"**/package.json",
|
|
150
152
|
"**/" + this._options.healthCheckFilePrefix + "*",
|
|
151
153
|
...extensions.map((extension) => "**/*." + extension),
|
|
152
154
|
],
|
|
153
|
-
ignored:
|
|
155
|
+
ignored: ignorePatternForWatch,
|
|
154
156
|
watchmanDeferStates: this._options.watchmanDeferStates,
|
|
155
157
|
};
|
|
156
158
|
const watcher = new WatcherImpl(root, watcherOptions);
|
|
157
|
-
return new Promise((resolve, reject) => {
|
|
159
|
+
return new Promise(async (resolve, reject) => {
|
|
158
160
|
const rejectTimeout = setTimeout(
|
|
159
161
|
() => reject(new Error("Failed to start watch mode.")),
|
|
160
162
|
MAX_WAIT_TIME
|
|
161
163
|
);
|
|
162
|
-
watcher.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
);
|
|
173
|
-
this._handleHealthCheckObservation(basename);
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
164
|
+
watcher.onFileEvent((change) => {
|
|
165
|
+
const basename = path.basename(change.relativePath);
|
|
166
|
+
if (basename.startsWith(this._options.healthCheckFilePrefix)) {
|
|
167
|
+
if (change.event === _common.TOUCH_EVENT) {
|
|
168
|
+
debug(
|
|
169
|
+
"Observed possible health check cookie: %s in %s",
|
|
170
|
+
change.relativePath,
|
|
171
|
+
root
|
|
172
|
+
);
|
|
173
|
+
this._handleHealthCheckObservation(basename);
|
|
176
174
|
}
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
onChange(change);
|
|
180
178
|
});
|
|
179
|
+
await watcher.startWatching();
|
|
180
|
+
clearTimeout(rejectTimeout);
|
|
181
|
+
resolve(watcher);
|
|
181
182
|
});
|
|
182
183
|
};
|
|
183
184
|
this._backends = await Promise.all(
|
|
@@ -192,7 +193,7 @@ class Watcher extends _events.default {
|
|
|
192
193
|
resolveHealthCheck();
|
|
193
194
|
}
|
|
194
195
|
async close() {
|
|
195
|
-
await Promise.all(this._backends.map((watcher) => watcher.
|
|
196
|
+
await Promise.all(this._backends.map((watcher) => watcher.stopWatching()));
|
|
196
197
|
this._activeWatcher = null;
|
|
197
198
|
}
|
|
198
199
|
async checkHealth(timeout) {
|
package/src/Watcher.js.flow
CHANGED
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type {
|
|
12
|
-
ChangeEventMetadata,
|
|
13
12
|
Console,
|
|
14
13
|
CrawlerOptions,
|
|
15
14
|
FileData,
|
|
16
15
|
Path,
|
|
17
16
|
PerfLogger,
|
|
17
|
+
WatcherBackend,
|
|
18
|
+
WatcherBackendChangeEvent,
|
|
18
19
|
WatchmanClocks,
|
|
19
20
|
} from './flow-types';
|
|
20
21
|
import type {WatcherOptions as WatcherBackendOptions} from './watchers/common';
|
|
21
|
-
import type {AbortSignal} from 'node-abort-controller';
|
|
22
22
|
|
|
23
23
|
import nodeCrawl from './crawlers/node';
|
|
24
24
|
import watchmanCrawl from './crawlers/watchman';
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import
|
|
25
|
+
import {TOUCH_EVENT} from './watchers/common';
|
|
26
|
+
import FallbackWatcher from './watchers/FallbackWatcher';
|
|
27
|
+
import NativeWatcher from './watchers/NativeWatcher';
|
|
28
28
|
import WatchmanWatcher from './watchers/WatchmanWatcher';
|
|
29
29
|
import EventEmitter from 'events';
|
|
30
30
|
import * as fs from 'fs';
|
|
@@ -50,8 +50,8 @@ type WatcherOptions = {
|
|
|
50
50
|
extensions: $ReadOnlyArray<string>,
|
|
51
51
|
forceNodeFilesystemAPI: boolean,
|
|
52
52
|
healthCheckFilePrefix: string,
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
ignoreForCrawl: string => boolean,
|
|
54
|
+
ignorePatternForWatch: RegExp,
|
|
55
55
|
previousState: CrawlerOptions['previousState'],
|
|
56
56
|
perfLogger: ?PerfLogger,
|
|
57
57
|
roots: $ReadOnlyArray<string>,
|
|
@@ -61,11 +61,6 @@ type WatcherOptions = {
|
|
|
61
61
|
watchmanDeferStates: $ReadOnlyArray<string>,
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
interface WatcherBackend {
|
|
65
|
-
getPauseReason(): ?string;
|
|
66
|
-
close(): Promise<void>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
64
|
let nextInstanceId = 0;
|
|
70
65
|
|
|
71
66
|
export type HealthCheckResult =
|
|
@@ -92,8 +87,8 @@ export class Watcher extends EventEmitter {
|
|
|
92
87
|
this._options.perfLogger?.point('crawl_start');
|
|
93
88
|
|
|
94
89
|
const options = this._options;
|
|
95
|
-
const
|
|
96
|
-
options.
|
|
90
|
+
const ignoreForCrawl = (filePath: string) =>
|
|
91
|
+
options.ignoreForCrawl(filePath) ||
|
|
97
92
|
path.basename(filePath).startsWith(this._options.healthCheckFilePrefix);
|
|
98
93
|
const crawl = options.useWatchman ? watchmanCrawl : nodeCrawl;
|
|
99
94
|
let crawler = crawl === watchmanCrawl ? 'watchman' : 'node';
|
|
@@ -107,7 +102,7 @@ export class Watcher extends EventEmitter {
|
|
|
107
102
|
includeSymlinks: options.enableSymlinks,
|
|
108
103
|
extensions: options.extensions,
|
|
109
104
|
forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
|
|
110
|
-
ignore,
|
|
105
|
+
ignore: ignoreForCrawl,
|
|
111
106
|
onStatus: status => {
|
|
112
107
|
this.emit('status', status);
|
|
113
108
|
},
|
|
@@ -163,28 +158,21 @@ export class Watcher extends EventEmitter {
|
|
|
163
158
|
}
|
|
164
159
|
}
|
|
165
160
|
|
|
166
|
-
async watch(
|
|
167
|
-
|
|
168
|
-
type: string,
|
|
169
|
-
filePath: string,
|
|
170
|
-
root: string,
|
|
171
|
-
metadata: ChangeEventMetadata,
|
|
172
|
-
) => void,
|
|
173
|
-
) {
|
|
174
|
-
const {extensions, ignorePattern, useWatchman} = this._options;
|
|
161
|
+
async watch(onChange: (change: WatcherBackendChangeEvent) => void) {
|
|
162
|
+
const {extensions, ignorePatternForWatch, useWatchman} = this._options;
|
|
175
163
|
|
|
176
|
-
// WatchmanWatcher >
|
|
164
|
+
// WatchmanWatcher > NativeWatcher > FallbackWatcher
|
|
177
165
|
const WatcherImpl = useWatchman
|
|
178
166
|
? WatchmanWatcher
|
|
179
|
-
:
|
|
180
|
-
?
|
|
181
|
-
:
|
|
167
|
+
: NativeWatcher.isSupported()
|
|
168
|
+
? NativeWatcher
|
|
169
|
+
: FallbackWatcher;
|
|
182
170
|
|
|
183
|
-
let watcher = '
|
|
171
|
+
let watcher = 'fallback';
|
|
184
172
|
if (WatcherImpl === WatchmanWatcher) {
|
|
185
173
|
watcher = 'watchman';
|
|
186
|
-
} else if (WatcherImpl ===
|
|
187
|
-
watcher = '
|
|
174
|
+
} else if (WatcherImpl === NativeWatcher) {
|
|
175
|
+
watcher = 'native';
|
|
188
176
|
}
|
|
189
177
|
debug(`Using watcher: ${watcher}`);
|
|
190
178
|
this._options.perfLogger?.annotate({string: {watcher}});
|
|
@@ -193,7 +181,7 @@ export class Watcher extends EventEmitter {
|
|
|
193
181
|
const createWatcherBackend = (root: Path): Promise<WatcherBackend> => {
|
|
194
182
|
const watcherOptions: WatcherBackendOptions = {
|
|
195
183
|
dot: true,
|
|
196
|
-
|
|
184
|
+
globs: [
|
|
197
185
|
// Ensure we always include package.json files, which are crucial for
|
|
198
186
|
/// module resolution.
|
|
199
187
|
'**/package.json',
|
|
@@ -201,44 +189,35 @@ export class Watcher extends EventEmitter {
|
|
|
201
189
|
'**/' + this._options.healthCheckFilePrefix + '*',
|
|
202
190
|
...extensions.map(extension => '**/*.' + extension),
|
|
203
191
|
],
|
|
204
|
-
ignored:
|
|
192
|
+
ignored: ignorePatternForWatch,
|
|
205
193
|
watchmanDeferStates: this._options.watchmanDeferStates,
|
|
206
194
|
};
|
|
207
|
-
const watcher = new WatcherImpl(root, watcherOptions);
|
|
195
|
+
const watcher: WatcherBackend = new WatcherImpl(root, watcherOptions);
|
|
208
196
|
|
|
209
|
-
return new Promise((resolve, reject) => {
|
|
197
|
+
return new Promise(async (resolve, reject) => {
|
|
210
198
|
const rejectTimeout = setTimeout(
|
|
211
199
|
() => reject(new Error('Failed to start watch mode.')),
|
|
212
200
|
MAX_WAIT_TIME,
|
|
213
201
|
);
|
|
214
202
|
|
|
215
|
-
watcher.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
'Observed possible health check cookie: %s in %s',
|
|
230
|
-
filePath,
|
|
231
|
-
root,
|
|
232
|
-
);
|
|
233
|
-
this._handleHealthCheckObservation(basename);
|
|
234
|
-
}
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
onChange(type, filePath, root, metadata);
|
|
238
|
-
},
|
|
239
|
-
);
|
|
240
|
-
resolve(watcher);
|
|
203
|
+
watcher.onFileEvent(change => {
|
|
204
|
+
const basename = path.basename(change.relativePath);
|
|
205
|
+
if (basename.startsWith(this._options.healthCheckFilePrefix)) {
|
|
206
|
+
if (change.event === TOUCH_EVENT) {
|
|
207
|
+
debug(
|
|
208
|
+
'Observed possible health check cookie: %s in %s',
|
|
209
|
+
change.relativePath,
|
|
210
|
+
root,
|
|
211
|
+
);
|
|
212
|
+
this._handleHealthCheckObservation(basename);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
onChange(change);
|
|
241
217
|
});
|
|
218
|
+
await watcher.startWatching();
|
|
219
|
+
clearTimeout(rejectTimeout);
|
|
220
|
+
resolve(watcher);
|
|
242
221
|
});
|
|
243
222
|
};
|
|
244
223
|
|
|
@@ -256,7 +235,7 @@ export class Watcher extends EventEmitter {
|
|
|
256
235
|
}
|
|
257
236
|
|
|
258
237
|
async close() {
|
|
259
|
-
await Promise.all(this._backends.map(watcher => watcher.
|
|
238
|
+
await Promise.all(this._backends.map(watcher => watcher.stopWatching()));
|
|
260
239
|
this._activeWatcher = null;
|
|
261
240
|
}
|
|
262
241
|
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
BuildParameters,
|
|
13
13
|
CacheData,
|
|
14
14
|
CacheManager,
|
|
15
|
-
|
|
15
|
+
CacheManagerWriteOptions,
|
|
16
16
|
} from '../flow-types';
|
|
17
17
|
|
|
18
18
|
export interface DiskCacheConfig {
|
|
@@ -31,7 +31,8 @@ export class DiskCacheManager implements CacheManager {
|
|
|
31
31
|
getCacheFilePath(): string;
|
|
32
32
|
read(): Promise<CacheData | null>;
|
|
33
33
|
write(
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
getSnapshot: () => CacheData,
|
|
35
|
+
opts: CacheManagerWriteOptions,
|
|
36
36
|
): Promise<void>;
|
|
37
|
+
end(): Promise<void>;
|
|
37
38
|
}
|
|
@@ -7,22 +7,42 @@ exports.DiskCacheManager = void 0;
|
|
|
7
7
|
var _rootRelativeCacheKeys = _interopRequireDefault(
|
|
8
8
|
require("../lib/rootRelativeCacheKeys")
|
|
9
9
|
);
|
|
10
|
-
var
|
|
10
|
+
var _fs = require("fs");
|
|
11
11
|
var _os = require("os");
|
|
12
12
|
var _path = _interopRequireDefault(require("path"));
|
|
13
|
+
var _timers = require("timers");
|
|
13
14
|
var _v = require("v8");
|
|
14
15
|
function _interopRequireDefault(e) {
|
|
15
16
|
return e && e.__esModule ? e : { default: e };
|
|
16
17
|
}
|
|
18
|
+
const debug = require("debug")("Metro:FileMapCache");
|
|
17
19
|
const DEFAULT_PREFIX = "metro-file-map";
|
|
18
20
|
const DEFAULT_DIRECTORY = (0, _os.tmpdir)();
|
|
21
|
+
const DEFAULT_AUTO_SAVE_DEBOUNCE_MS = 5000;
|
|
19
22
|
class DiskCacheManager {
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
#autoSaveOpts;
|
|
24
|
+
#cachePath;
|
|
25
|
+
#debounceTimeout = null;
|
|
26
|
+
#writePromise = Promise.resolve();
|
|
27
|
+
#hasUnwrittenChanges = false;
|
|
28
|
+
#tryWrite;
|
|
29
|
+
#stopListening;
|
|
30
|
+
constructor(
|
|
31
|
+
{ buildParameters },
|
|
32
|
+
{ autoSave = {}, cacheDirectory, cacheFilePrefix }
|
|
33
|
+
) {
|
|
34
|
+
this.#cachePath = DiskCacheManager.getCacheFilePath(
|
|
22
35
|
buildParameters,
|
|
23
36
|
cacheFilePrefix,
|
|
24
37
|
cacheDirectory
|
|
25
38
|
);
|
|
39
|
+
if (autoSave) {
|
|
40
|
+
const { debounceMs = DEFAULT_AUTO_SAVE_DEBOUNCE_MS } =
|
|
41
|
+
autoSave === true ? {} : autoSave;
|
|
42
|
+
this.#autoSaveOpts = {
|
|
43
|
+
debounceMs,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
26
46
|
}
|
|
27
47
|
static getCacheFilePath(buildParameters, cacheFilePrefix, cacheDirectory) {
|
|
28
48
|
const { rootDirHash, relativeConfigHash } = (0,
|
|
@@ -35,13 +55,11 @@ class DiskCacheManager {
|
|
|
35
55
|
);
|
|
36
56
|
}
|
|
37
57
|
getCacheFilePath() {
|
|
38
|
-
return this
|
|
58
|
+
return this.#cachePath;
|
|
39
59
|
}
|
|
40
60
|
async read() {
|
|
41
61
|
try {
|
|
42
|
-
return (0, _v.deserialize)(
|
|
43
|
-
(0, _gracefulFs.readFileSync)(this._cachePath)
|
|
44
|
-
);
|
|
62
|
+
return (0, _v.deserialize)(await _fs.promises.readFile(this.#cachePath));
|
|
45
63
|
} catch (e) {
|
|
46
64
|
if (e?.code === "ENOENT") {
|
|
47
65
|
return null;
|
|
@@ -49,13 +67,53 @@ class DiskCacheManager {
|
|
|
49
67
|
throw e;
|
|
50
68
|
}
|
|
51
69
|
}
|
|
52
|
-
async write(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
async write(
|
|
71
|
+
getSnapshot,
|
|
72
|
+
{ changedSinceCacheRead, eventSource, onWriteError }
|
|
73
|
+
) {
|
|
74
|
+
const tryWrite = (this.#tryWrite = () => {
|
|
75
|
+
this.#writePromise = this.#writePromise
|
|
76
|
+
.then(async () => {
|
|
77
|
+
if (!this.#hasUnwrittenChanges) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const data = getSnapshot();
|
|
81
|
+
this.#hasUnwrittenChanges = false;
|
|
82
|
+
await _fs.promises.writeFile(
|
|
83
|
+
this.#cachePath,
|
|
84
|
+
(0, _v.serialize)(data)
|
|
85
|
+
);
|
|
86
|
+
debug("Written cache to %s", this.#cachePath);
|
|
87
|
+
})
|
|
88
|
+
.catch(onWriteError);
|
|
89
|
+
return this.#writePromise;
|
|
90
|
+
});
|
|
91
|
+
if (this.#autoSaveOpts) {
|
|
92
|
+
const autoSave = this.#autoSaveOpts;
|
|
93
|
+
this.#stopListening?.();
|
|
94
|
+
this.#stopListening = eventSource.onChange(() => {
|
|
95
|
+
this.#hasUnwrittenChanges = true;
|
|
96
|
+
if (this.#debounceTimeout) {
|
|
97
|
+
this.#debounceTimeout.refresh();
|
|
98
|
+
} else {
|
|
99
|
+
this.#debounceTimeout = (0, _timers.setTimeout)(
|
|
100
|
+
() => tryWrite(),
|
|
101
|
+
autoSave.debounceMs
|
|
102
|
+
).unref();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (changedSinceCacheRead) {
|
|
107
|
+
this.#hasUnwrittenChanges = true;
|
|
108
|
+
await tryWrite();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async end() {
|
|
112
|
+
if (this.#debounceTimeout) {
|
|
113
|
+
(0, _timers.clearTimeout)(this.#debounceTimeout);
|
|
58
114
|
}
|
|
115
|
+
this.#stopListening?.();
|
|
116
|
+
await this.#tryWrite?.();
|
|
59
117
|
}
|
|
60
118
|
}
|
|
61
119
|
exports.DiskCacheManager = DiskCacheManager;
|
|
@@ -12,38 +12,59 @@
|
|
|
12
12
|
import type {
|
|
13
13
|
BuildParameters,
|
|
14
14
|
CacheData,
|
|
15
|
-
CacheDelta,
|
|
16
15
|
CacheManager,
|
|
16
|
+
CacheManagerFactoryOptions,
|
|
17
|
+
CacheManagerWriteOptions,
|
|
17
18
|
} from '../flow-types';
|
|
18
19
|
|
|
19
20
|
import rootRelativeCacheKeys from '../lib/rootRelativeCacheKeys';
|
|
20
|
-
import {
|
|
21
|
+
import {promises as fsPromises} from 'fs';
|
|
21
22
|
import {tmpdir} from 'os';
|
|
22
23
|
import path from 'path';
|
|
24
|
+
import {Timeout, clearTimeout, setTimeout} from 'timers';
|
|
23
25
|
import {deserialize, serialize} from 'v8';
|
|
24
26
|
|
|
27
|
+
const debug = require('debug')('Metro:FileMapCache');
|
|
28
|
+
|
|
29
|
+
type AutoSaveOptions = $ReadOnly<{
|
|
30
|
+
debounceMs: number,
|
|
31
|
+
}>;
|
|
32
|
+
|
|
25
33
|
type DiskCacheConfig = {
|
|
26
|
-
|
|
34
|
+
autoSave?: Partial<AutoSaveOptions> | boolean,
|
|
27
35
|
cacheFilePrefix?: ?string,
|
|
28
36
|
cacheDirectory?: ?string,
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
const DEFAULT_PREFIX = 'metro-file-map';
|
|
32
40
|
const DEFAULT_DIRECTORY = tmpdir();
|
|
41
|
+
const DEFAULT_AUTO_SAVE_DEBOUNCE_MS = 5000;
|
|
33
42
|
|
|
34
43
|
export class DiskCacheManager implements CacheManager {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
+#autoSaveOpts: ?AutoSaveOptions;
|
|
45
|
+
+#cachePath: string;
|
|
46
|
+
#debounceTimeout: ?Timeout = null;
|
|
47
|
+
#writePromise: Promise<void> = Promise.resolve();
|
|
48
|
+
#hasUnwrittenChanges: boolean = false;
|
|
49
|
+
#tryWrite: ?() => Promise<void>;
|
|
50
|
+
#stopListening: ?() => void;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
{buildParameters}: CacheManagerFactoryOptions,
|
|
54
|
+
{autoSave = {}, cacheDirectory, cacheFilePrefix}: DiskCacheConfig,
|
|
55
|
+
) {
|
|
56
|
+
this.#cachePath = DiskCacheManager.getCacheFilePath(
|
|
43
57
|
buildParameters,
|
|
44
58
|
cacheFilePrefix,
|
|
45
59
|
cacheDirectory,
|
|
46
60
|
);
|
|
61
|
+
|
|
62
|
+
// Normalise auto-save options.
|
|
63
|
+
if (autoSave) {
|
|
64
|
+
const {debounceMs = DEFAULT_AUTO_SAVE_DEBOUNCE_MS} =
|
|
65
|
+
autoSave === true ? {} : autoSave;
|
|
66
|
+
this.#autoSaveOpts = {debounceMs};
|
|
67
|
+
}
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
static getCacheFilePath(
|
|
@@ -63,12 +84,12 @@ export class DiskCacheManager implements CacheManager {
|
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
getCacheFilePath(): string {
|
|
66
|
-
return this
|
|
87
|
+
return this.#cachePath;
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
async read(): Promise<?CacheData> {
|
|
70
91
|
try {
|
|
71
|
-
return deserialize(
|
|
92
|
+
return deserialize(await fsPromises.readFile(this.#cachePath));
|
|
72
93
|
} catch (e) {
|
|
73
94
|
if (e?.code === 'ENOENT') {
|
|
74
95
|
// Cache file not found - not considered an error.
|
|
@@ -80,11 +101,64 @@ export class DiskCacheManager implements CacheManager {
|
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
async write(
|
|
83
|
-
|
|
84
|
-
{
|
|
104
|
+
getSnapshot: () => CacheData,
|
|
105
|
+
{
|
|
106
|
+
changedSinceCacheRead,
|
|
107
|
+
eventSource,
|
|
108
|
+
onWriteError,
|
|
109
|
+
}: CacheManagerWriteOptions,
|
|
85
110
|
): Promise<void> {
|
|
86
|
-
|
|
87
|
-
|
|
111
|
+
// Initialise a writer function using a promise queue to ensure writes are
|
|
112
|
+
// sequenced.
|
|
113
|
+
const tryWrite = (this.#tryWrite = () => {
|
|
114
|
+
this.#writePromise = this.#writePromise
|
|
115
|
+
.then(async () => {
|
|
116
|
+
if (!this.#hasUnwrittenChanges) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const data = getSnapshot();
|
|
120
|
+
this.#hasUnwrittenChanges = false;
|
|
121
|
+
await fsPromises.writeFile(this.#cachePath, serialize(data));
|
|
122
|
+
debug('Written cache to %s', this.#cachePath);
|
|
123
|
+
})
|
|
124
|
+
.catch(onWriteError);
|
|
125
|
+
return this.#writePromise;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Set up auto-save on changes, if enabled.
|
|
129
|
+
if (this.#autoSaveOpts) {
|
|
130
|
+
const autoSave = this.#autoSaveOpts;
|
|
131
|
+
this.#stopListening?.();
|
|
132
|
+
this.#stopListening = eventSource.onChange(() => {
|
|
133
|
+
this.#hasUnwrittenChanges = true;
|
|
134
|
+
if (this.#debounceTimeout) {
|
|
135
|
+
this.#debounceTimeout.refresh();
|
|
136
|
+
} else {
|
|
137
|
+
this.#debounceTimeout = setTimeout(
|
|
138
|
+
() => tryWrite(),
|
|
139
|
+
autoSave.debounceMs,
|
|
140
|
+
).unref();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
88
143
|
}
|
|
144
|
+
|
|
145
|
+
// Write immediately if state has changed since the cache was read.
|
|
146
|
+
if (changedSinceCacheRead) {
|
|
147
|
+
this.#hasUnwrittenChanges = true;
|
|
148
|
+
await tryWrite();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async end() {
|
|
153
|
+
// Clear any timers
|
|
154
|
+
if (this.#debounceTimeout) {
|
|
155
|
+
clearTimeout(this.#debounceTimeout);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Remove event listeners
|
|
159
|
+
this.#stopListening?.();
|
|
160
|
+
|
|
161
|
+
// Flush unwritten changes to disk (no-op if no changes)
|
|
162
|
+
await this.#tryWrite?.();
|
|
89
163
|
}
|
|
90
164
|
}
|