metro-file-map 0.73.1 → 0.73.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro-file-map",
3
- "version": "0.73.1",
3
+ "version": "0.73.3",
4
4
  "description": "[Experimental] - 🚇 File crawling, watching and mapping for Metro",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -24,6 +24,7 @@
24
24
  "jest-util": "^27.2.0",
25
25
  "jest-worker": "^27.2.0",
26
26
  "micromatch": "^4.0.4",
27
+ "nullthrows": "^1.1.1",
27
28
  "walker": "^1.0.7"
28
29
  },
29
30
  "devDependencies": {
@@ -23,7 +23,7 @@ import H from './constants';
23
23
  import * as fastPath from './lib/fast_path';
24
24
 
25
25
  const EMPTY_OBJ: {[string]: ModuleMetaData} = {};
26
- const EMPTY_MAP = new Map();
26
+ const EMPTY_MAP = new Map<'g' | 'native' | string, ?DuplicatesSet>();
27
27
 
28
28
  export default class ModuleMap implements IModuleMap<SerializableModuleMap> {
29
29
  static DuplicateHasteCandidatesError: Class<DuplicateHasteCandidatesError>;
@@ -191,7 +191,7 @@ export default class ModuleMap implements IModuleMap<SerializableModuleMap> {
191
191
  }
192
192
  // Force flow refinement
193
193
  const previousSet = relativePathSet;
194
- const duplicates = new Map();
194
+ const duplicates = new Map<string, number>();
195
195
 
196
196
  for (const [relativePath, type] of previousSet) {
197
197
  const duplicatePath = fastPath.resolve(this._raw.rootDir, relativePath);
package/src/Watcher.js ADDED
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true,
5
+ });
6
+ exports.Watcher = void 0;
7
+
8
+ var _watchman = _interopRequireDefault(require("./crawlers/watchman"));
9
+
10
+ var _node = _interopRequireDefault(require("./crawlers/node"));
11
+
12
+ var _WatchmanWatcher = _interopRequireDefault(
13
+ require("./watchers/WatchmanWatcher")
14
+ );
15
+
16
+ var _FSEventsWatcher = _interopRequireDefault(
17
+ require("./watchers/FSEventsWatcher")
18
+ );
19
+
20
+ var _NodeWatcher = _interopRequireDefault(require("./watchers/NodeWatcher"));
21
+
22
+ var path = _interopRequireWildcard(require("path"));
23
+
24
+ var fs = _interopRequireWildcard(require("fs"));
25
+
26
+ var _common = require("./watchers/common");
27
+
28
+ var _perf_hooks = require("perf_hooks");
29
+
30
+ var _nullthrows = _interopRequireDefault(require("nullthrows"));
31
+
32
+ function _getRequireWildcardCache(nodeInterop) {
33
+ if (typeof WeakMap !== "function") return null;
34
+ var cacheBabelInterop = new WeakMap();
35
+ var cacheNodeInterop = new WeakMap();
36
+ return (_getRequireWildcardCache = function (nodeInterop) {
37
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
38
+ })(nodeInterop);
39
+ }
40
+
41
+ function _interopRequireWildcard(obj, nodeInterop) {
42
+ if (!nodeInterop && obj && obj.__esModule) {
43
+ return obj;
44
+ }
45
+ if (obj === null || (typeof obj !== "object" && typeof obj !== "function")) {
46
+ return { default: obj };
47
+ }
48
+ var cache = _getRequireWildcardCache(nodeInterop);
49
+ if (cache && cache.has(obj)) {
50
+ return cache.get(obj);
51
+ }
52
+ var newObj = {};
53
+ var hasPropertyDescriptor =
54
+ Object.defineProperty && Object.getOwnPropertyDescriptor;
55
+ for (var key in obj) {
56
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
57
+ var desc = hasPropertyDescriptor
58
+ ? Object.getOwnPropertyDescriptor(obj, key)
59
+ : null;
60
+ if (desc && (desc.get || desc.set)) {
61
+ Object.defineProperty(newObj, key, desc);
62
+ } else {
63
+ newObj[key] = obj[key];
64
+ }
65
+ }
66
+ }
67
+ newObj.default = obj;
68
+ if (cache) {
69
+ cache.set(obj, newObj);
70
+ }
71
+ return newObj;
72
+ }
73
+
74
+ function _interopRequireDefault(obj) {
75
+ return obj && obj.__esModule ? obj : { default: obj };
76
+ }
77
+
78
+ /**
79
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
80
+ *
81
+ * This source code is licensed under the MIT license found in the
82
+ * LICENSE file in the root directory of this source tree.
83
+ *
84
+ * @format
85
+ *
86
+ */
87
+ // $FlowFixMe[untyped-import] - it's a fork: https://github.com/facebook/jest/pull/10919
88
+ const debug = require("debug")("Metro:Watcher");
89
+
90
+ const MAX_WAIT_TIME = 240000;
91
+ let nextInstanceId = 0;
92
+
93
+ class Watcher {
94
+ _backends = [];
95
+ _nextHealthCheckId = 0;
96
+ _pendingHealthChecks = new Map();
97
+
98
+ constructor(options) {
99
+ this._options = options;
100
+ this._instanceId = nextInstanceId++;
101
+ }
102
+
103
+ async crawl() {
104
+ var _this$_options$perfLo;
105
+
106
+ (_this$_options$perfLo = this._options.perfLogger) === null ||
107
+ _this$_options$perfLo === void 0
108
+ ? void 0
109
+ : _this$_options$perfLo.point("crawl_start");
110
+ const options = this._options;
111
+
112
+ const ignore = (filePath) =>
113
+ options.ignore(filePath) ||
114
+ path.basename(filePath).startsWith(this._options.healthCheckFilePrefix);
115
+
116
+ const crawl = options.useWatchman ? _watchman.default : _node.default;
117
+ const crawlerOptions = {
118
+ abortSignal: options.abortSignal,
119
+ computeSha1: options.computeSha1,
120
+ data: options.initialData,
121
+ enableSymlinks: options.enableSymlinks,
122
+ extensions: options.extensions,
123
+ forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
124
+ ignore,
125
+ perfLogger: options.perfLogger,
126
+ rootDir: options.rootDir,
127
+ roots: options.roots,
128
+ };
129
+
130
+ const retry = (error) => {
131
+ if (crawl === _watchman.default) {
132
+ options.console.warn(
133
+ "metro-file-map: Watchman crawl failed. Retrying once with node " +
134
+ "crawler.\n" +
135
+ " Usually this happens when watchman isn't running. Create an " +
136
+ "empty `.watchmanconfig` file in your project's root folder or " +
137
+ "initialize a git or hg repository in your project.\n" +
138
+ " " +
139
+ error.toString()
140
+ );
141
+ return (0, _node.default)(crawlerOptions).catch((e) => {
142
+ throw new Error(
143
+ "Crawler retry failed:\n" +
144
+ ` Original error: ${error.message}\n` +
145
+ ` Retry error: ${e.message}\n`
146
+ );
147
+ });
148
+ }
149
+
150
+ throw error;
151
+ };
152
+
153
+ const logEnd = (result) => {
154
+ var _this$_options$perfLo2;
155
+
156
+ (_this$_options$perfLo2 = this._options.perfLogger) === null ||
157
+ _this$_options$perfLo2 === void 0
158
+ ? void 0
159
+ : _this$_options$perfLo2.point("crawl_end");
160
+ return result;
161
+ };
162
+
163
+ try {
164
+ return crawl(crawlerOptions).catch(retry).then(logEnd);
165
+ } catch (error) {
166
+ return retry(error).then(logEnd);
167
+ }
168
+ }
169
+
170
+ async watch(onChange) {
171
+ var _this$_options$perfLo3;
172
+
173
+ const { extensions, ignorePattern, useWatchman } = this._options; // WatchmanWatcher > FSEventsWatcher > sane.NodeWatcher
174
+
175
+ const WatcherImpl = useWatchman
176
+ ? _WatchmanWatcher.default
177
+ : _FSEventsWatcher.default.isSupported()
178
+ ? _FSEventsWatcher.default
179
+ : _NodeWatcher.default;
180
+ let watcher = "node";
181
+
182
+ if (WatcherImpl === _WatchmanWatcher.default) {
183
+ watcher = "watchman";
184
+ } else if (WatcherImpl === _FSEventsWatcher.default) {
185
+ watcher = "fsevents";
186
+ }
187
+
188
+ debug(`Using watcher: ${watcher}`);
189
+ (_this$_options$perfLo3 = this._options.perfLogger) === null ||
190
+ _this$_options$perfLo3 === void 0
191
+ ? void 0
192
+ : _this$_options$perfLo3.annotate({
193
+ string: {
194
+ watcher,
195
+ },
196
+ });
197
+ this._activeWatcher = watcher;
198
+
199
+ const createWatcherBackend = (root) => {
200
+ const watcherOptions = {
201
+ dot: true,
202
+ glob: [
203
+ // Ensure we always include package.json files, which are crucial for
204
+ /// module resolution.
205
+ "**/package.json", // Ensure we always watch any health check files
206
+ "**/" + this._options.healthCheckFilePrefix + "*",
207
+ ...extensions.map((extension) => "**/*." + extension),
208
+ ],
209
+ ignored: ignorePattern,
210
+ watchmanDeferStates: this._options.watchmanDeferStates,
211
+ };
212
+ const watcher = new WatcherImpl(root, watcherOptions);
213
+ return new Promise((resolve, reject) => {
214
+ const rejectTimeout = setTimeout(
215
+ () => reject(new Error("Failed to start watch mode.")),
216
+ MAX_WAIT_TIME
217
+ );
218
+ watcher.once("ready", () => {
219
+ clearTimeout(rejectTimeout);
220
+ watcher.on("all", (type, filePath, root, stat) => {
221
+ const basename = path.basename(filePath);
222
+
223
+ if (basename.startsWith(this._options.healthCheckFilePrefix)) {
224
+ if (type === _common.ADD_EVENT || type === _common.CHANGE_EVENT) {
225
+ debug(
226
+ "Observed possible health check cookie: %s in %s",
227
+ filePath,
228
+ root
229
+ );
230
+
231
+ this._handleHealthCheckObservation(basename);
232
+ }
233
+
234
+ return;
235
+ }
236
+
237
+ onChange(type, filePath, root, stat);
238
+ });
239
+ resolve(watcher);
240
+ });
241
+ });
242
+ };
243
+
244
+ this._backends = await Promise.all(
245
+ this._options.roots.map(createWatcherBackend)
246
+ );
247
+ }
248
+
249
+ _handleHealthCheckObservation(basename) {
250
+ const resolveHealthCheck = this._pendingHealthChecks.get(basename);
251
+
252
+ if (!resolveHealthCheck) {
253
+ return;
254
+ }
255
+
256
+ resolveHealthCheck();
257
+ }
258
+
259
+ async close() {
260
+ await Promise.all(this._backends.map((watcher) => watcher.close()));
261
+ this._activeWatcher = null;
262
+ }
263
+
264
+ async checkHealth(timeout) {
265
+ const healthCheckId = this._nextHealthCheckId++;
266
+
267
+ if (healthCheckId === Number.MAX_SAFE_INTEGER) {
268
+ this._nextHealthCheckId = 0;
269
+ }
270
+
271
+ const watcher = this._activeWatcher;
272
+ const basename =
273
+ this._options.healthCheckFilePrefix +
274
+ "-" +
275
+ process.pid +
276
+ "-" +
277
+ this._instanceId +
278
+ "-" +
279
+ healthCheckId;
280
+ const healthCheckPath = path.join(this._options.rootDir, basename);
281
+ let result;
282
+ const timeoutPromise = new Promise((resolve) =>
283
+ setTimeout(resolve, timeout)
284
+ ).then(() => {
285
+ if (!result) {
286
+ var _this$_backends$;
287
+
288
+ result = {
289
+ type: "timeout",
290
+ pauseReason:
291
+ (_this$_backends$ = this._backends[0]) === null ||
292
+ _this$_backends$ === void 0
293
+ ? void 0
294
+ : _this$_backends$.getPauseReason(),
295
+ timeout,
296
+ watcher,
297
+ };
298
+ }
299
+ });
300
+
301
+ const startTime = _perf_hooks.performance.now();
302
+
303
+ debug("Creating health check cookie: %s", healthCheckPath);
304
+ const creationPromise = fs.promises
305
+ .writeFile(healthCheckPath, String(startTime))
306
+ .catch((error) => {
307
+ if (!result) {
308
+ result = {
309
+ type: "error",
310
+ error,
311
+ timeout,
312
+ watcher,
313
+ };
314
+ }
315
+ });
316
+ const observationPromise = new Promise((resolve) => {
317
+ this._pendingHealthChecks.set(basename, resolve);
318
+ }).then(() => {
319
+ if (!result) {
320
+ result = {
321
+ type: "success",
322
+ timeElapsed: _perf_hooks.performance.now() - startTime,
323
+ timeout,
324
+ watcher,
325
+ };
326
+ }
327
+ });
328
+ await Promise.race([
329
+ timeoutPromise,
330
+ creationPromise.then(() => observationPromise),
331
+ ]);
332
+
333
+ this._pendingHealthChecks.delete(basename); // Chain a deletion to the creation promise (which may not have even settled yet!),
334
+ // don't await it, and swallow errors. This is just best-effort cleanup.
335
+
336
+ creationPromise.then(() =>
337
+ fs.promises.unlink(healthCheckPath).catch(() => {})
338
+ );
339
+ debug("Health check result: %o", result);
340
+ return (0, _nullthrows.default)(result);
341
+ }
342
+ }
343
+
344
+ exports.Watcher = Watcher;
@@ -0,0 +1,305 @@
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
+ * @format
8
+ * @flow strict-local
9
+ */
10
+
11
+ import type {
12
+ Console,
13
+ CrawlerOptions,
14
+ FileData,
15
+ InternalData,
16
+ Path,
17
+ PerfLogger,
18
+ } from './flow-types';
19
+ import type {WatcherOptions as WatcherBackendOptions} from './watchers/common';
20
+ import type {Stats} from 'fs';
21
+
22
+ import watchmanCrawl from './crawlers/watchman';
23
+ import nodeCrawl from './crawlers/node';
24
+ import WatchmanWatcher from './watchers/WatchmanWatcher';
25
+ import FSEventsWatcher from './watchers/FSEventsWatcher';
26
+ // $FlowFixMe[untyped-import] - it's a fork: https://github.com/facebook/jest/pull/10919
27
+ import NodeWatcher from './watchers/NodeWatcher';
28
+ import * as path from 'path';
29
+ import * as fs from 'fs';
30
+ import {ADD_EVENT, CHANGE_EVENT} from './watchers/common';
31
+ import {performance} from 'perf_hooks';
32
+ import nullthrows from 'nullthrows';
33
+
34
+ const debug = require('debug')('Metro:Watcher');
35
+
36
+ const MAX_WAIT_TIME = 240000;
37
+
38
+ type WatcherOptions = {
39
+ abortSignal: AbortSignal,
40
+ computeSha1: boolean,
41
+ console: Console,
42
+ enableSymlinks: boolean,
43
+ extensions: $ReadOnlyArray<string>,
44
+ forceNodeFilesystemAPI: boolean,
45
+ healthCheckFilePrefix: string,
46
+ ignore: string => boolean,
47
+ ignorePattern: RegExp,
48
+ initialData: InternalData,
49
+ perfLogger: ?PerfLogger,
50
+ roots: $ReadOnlyArray<string>,
51
+ rootDir: string,
52
+ useWatchman: boolean,
53
+ watch: boolean,
54
+ watchmanDeferStates: $ReadOnlyArray<string>,
55
+ };
56
+
57
+ interface WatcherBackend {
58
+ getPauseReason(): ?string;
59
+ close(): Promise<void>;
60
+ }
61
+
62
+ let nextInstanceId = 0;
63
+
64
+ export type HealthCheckResult =
65
+ | {type: 'error', timeout: number, error: Error, watcher: ?string}
66
+ | {type: 'success', timeout: number, timeElapsed: number, watcher: ?string}
67
+ | {type: 'timeout', timeout: number, watcher: ?string, pauseReason: ?string};
68
+
69
+ export class Watcher {
70
+ _options: WatcherOptions;
71
+ _backends: $ReadOnlyArray<WatcherBackend> = [];
72
+ _instanceId: number;
73
+ _nextHealthCheckId: number = 0;
74
+ _pendingHealthChecks: Map</* basename */ string, /* resolve */ () => void> =
75
+ new Map();
76
+ _activeWatcher: ?string;
77
+
78
+ constructor(options: WatcherOptions) {
79
+ this._options = options;
80
+ this._instanceId = nextInstanceId++;
81
+ }
82
+
83
+ async crawl(): Promise<?(
84
+ | Promise<{
85
+ changedFiles?: FileData,
86
+ hasteMap: InternalData,
87
+ removedFiles: FileData,
88
+ }>
89
+ | {changedFiles?: FileData, hasteMap: InternalData, removedFiles: FileData}
90
+ )> {
91
+ this._options.perfLogger?.point('crawl_start');
92
+
93
+ const options = this._options;
94
+ const ignore = (filePath: string) =>
95
+ options.ignore(filePath) ||
96
+ path.basename(filePath).startsWith(this._options.healthCheckFilePrefix);
97
+ const crawl = options.useWatchman ? watchmanCrawl : nodeCrawl;
98
+ const crawlerOptions: CrawlerOptions = {
99
+ abortSignal: options.abortSignal,
100
+ computeSha1: options.computeSha1,
101
+ data: options.initialData,
102
+ enableSymlinks: options.enableSymlinks,
103
+ extensions: options.extensions,
104
+ forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
105
+ ignore,
106
+ perfLogger: options.perfLogger,
107
+ rootDir: options.rootDir,
108
+ roots: options.roots,
109
+ };
110
+
111
+ const retry = (error: Error) => {
112
+ if (crawl === watchmanCrawl) {
113
+ options.console.warn(
114
+ 'metro-file-map: Watchman crawl failed. Retrying once with node ' +
115
+ 'crawler.\n' +
116
+ " Usually this happens when watchman isn't running. Create an " +
117
+ "empty `.watchmanconfig` file in your project's root folder or " +
118
+ 'initialize a git or hg repository in your project.\n' +
119
+ ' ' +
120
+ error.toString(),
121
+ );
122
+ return nodeCrawl(crawlerOptions).catch(e => {
123
+ throw new Error(
124
+ 'Crawler retry failed:\n' +
125
+ ` Original error: ${error.message}\n` +
126
+ ` Retry error: ${e.message}\n`,
127
+ );
128
+ });
129
+ }
130
+
131
+ throw error;
132
+ };
133
+
134
+ const logEnd = <T>(result: T): T => {
135
+ this._options.perfLogger?.point('crawl_end');
136
+ return result;
137
+ };
138
+
139
+ try {
140
+ return crawl(crawlerOptions).catch(retry).then(logEnd);
141
+ } catch (error) {
142
+ return retry(error).then(logEnd);
143
+ }
144
+ }
145
+
146
+ async watch(
147
+ onChange: (
148
+ type: string,
149
+ filePath: string,
150
+ root: string,
151
+ stat?: Stats,
152
+ ) => void,
153
+ ) {
154
+ const {extensions, ignorePattern, useWatchman} = this._options;
155
+
156
+ // WatchmanWatcher > FSEventsWatcher > sane.NodeWatcher
157
+ const WatcherImpl = useWatchman
158
+ ? WatchmanWatcher
159
+ : FSEventsWatcher.isSupported()
160
+ ? FSEventsWatcher
161
+ : NodeWatcher;
162
+
163
+ let watcher = 'node';
164
+ if (WatcherImpl === WatchmanWatcher) {
165
+ watcher = 'watchman';
166
+ } else if (WatcherImpl === FSEventsWatcher) {
167
+ watcher = 'fsevents';
168
+ }
169
+ debug(`Using watcher: ${watcher}`);
170
+ this._options.perfLogger?.annotate({string: {watcher}});
171
+ this._activeWatcher = watcher;
172
+
173
+ const createWatcherBackend = (root: Path): Promise<WatcherBackend> => {
174
+ const watcherOptions: WatcherBackendOptions = {
175
+ dot: true,
176
+ glob: [
177
+ // Ensure we always include package.json files, which are crucial for
178
+ /// module resolution.
179
+ '**/package.json',
180
+ // Ensure we always watch any health check files
181
+ '**/' + this._options.healthCheckFilePrefix + '*',
182
+ ...extensions.map(extension => '**/*.' + extension),
183
+ ],
184
+ ignored: ignorePattern,
185
+ watchmanDeferStates: this._options.watchmanDeferStates,
186
+ };
187
+ const watcher = new WatcherImpl(root, watcherOptions);
188
+
189
+ return new Promise((resolve, reject) => {
190
+ const rejectTimeout = setTimeout(
191
+ () => reject(new Error('Failed to start watch mode.')),
192
+ MAX_WAIT_TIME,
193
+ );
194
+
195
+ watcher.once('ready', () => {
196
+ clearTimeout(rejectTimeout);
197
+ watcher.on(
198
+ 'all',
199
+ (type: string, filePath: string, root: string, stat?: Stats) => {
200
+ const basename = path.basename(filePath);
201
+ if (basename.startsWith(this._options.healthCheckFilePrefix)) {
202
+ if (type === ADD_EVENT || type === CHANGE_EVENT) {
203
+ debug(
204
+ 'Observed possible health check cookie: %s in %s',
205
+ filePath,
206
+ root,
207
+ );
208
+ this._handleHealthCheckObservation(basename);
209
+ }
210
+ return;
211
+ }
212
+ onChange(type, filePath, root, stat);
213
+ },
214
+ );
215
+ resolve(watcher);
216
+ });
217
+ });
218
+ };
219
+
220
+ this._backends = await Promise.all(
221
+ this._options.roots.map(createWatcherBackend),
222
+ );
223
+ }
224
+
225
+ _handleHealthCheckObservation(basename: string) {
226
+ const resolveHealthCheck = this._pendingHealthChecks.get(basename);
227
+ if (!resolveHealthCheck) {
228
+ return;
229
+ }
230
+ resolveHealthCheck();
231
+ }
232
+
233
+ async close() {
234
+ await Promise.all(this._backends.map(watcher => watcher.close()));
235
+ this._activeWatcher = null;
236
+ }
237
+
238
+ async checkHealth(timeout: number): Promise<HealthCheckResult> {
239
+ const healthCheckId = this._nextHealthCheckId++;
240
+ if (healthCheckId === Number.MAX_SAFE_INTEGER) {
241
+ this._nextHealthCheckId = 0;
242
+ }
243
+ const watcher = this._activeWatcher;
244
+ const basename =
245
+ this._options.healthCheckFilePrefix +
246
+ '-' +
247
+ process.pid +
248
+ '-' +
249
+ this._instanceId +
250
+ '-' +
251
+ healthCheckId;
252
+ const healthCheckPath = path.join(this._options.rootDir, basename);
253
+ let result: ?HealthCheckResult;
254
+ const timeoutPromise = new Promise(resolve =>
255
+ setTimeout(resolve, timeout),
256
+ ).then(() => {
257
+ if (!result) {
258
+ result = {
259
+ type: 'timeout',
260
+ pauseReason: this._backends[0]?.getPauseReason(),
261
+ timeout,
262
+ watcher,
263
+ };
264
+ }
265
+ });
266
+ const startTime = performance.now();
267
+ debug('Creating health check cookie: %s', healthCheckPath);
268
+ const creationPromise = fs.promises
269
+ .writeFile(healthCheckPath, String(startTime))
270
+ .catch(error => {
271
+ if (!result) {
272
+ result = {
273
+ type: 'error',
274
+ error,
275
+ timeout,
276
+ watcher,
277
+ };
278
+ }
279
+ });
280
+ const observationPromise = new Promise(resolve => {
281
+ this._pendingHealthChecks.set(basename, resolve);
282
+ }).then(() => {
283
+ if (!result) {
284
+ result = {
285
+ type: 'success',
286
+ timeElapsed: performance.now() - startTime,
287
+ timeout,
288
+ watcher,
289
+ };
290
+ }
291
+ });
292
+ await Promise.race([
293
+ timeoutPromise,
294
+ creationPromise.then(() => observationPromise),
295
+ ]);
296
+ this._pendingHealthChecks.delete(basename);
297
+ // Chain a deletion to the creation promise (which may not have even settled yet!),
298
+ // don't await it, and swallow errors. This is just best-effort cleanup.
299
+ creationPromise.then(() =>
300
+ fs.promises.unlink(healthCheckPath).catch(() => {}),
301
+ );
302
+ debug('Health check result: %o', result);
303
+ return nullthrows(result);
304
+ }
305
+ }
@@ -9,6 +9,7 @@
9
9
  * @oncall react_native
10
10
  */
11
11
 
12
+ import type {Path, FileMetaData} from '../flow-types';
12
13
  import type {
13
14
  CrawlerOptions,
14
15
  FileData,
@@ -224,7 +225,7 @@ module.exports = async function nodeCrawl(options: CrawlerOptions): Promise<{
224
225
 
225
226
  return new Promise(resolve => {
226
227
  const callback = (list: Result) => {
227
- const files = new Map();
228
+ const files = new Map<Path, FileMetaData>();
228
229
  const removedFiles = new Map(data.files);
229
230
  list.forEach(fileData => {
230
231
  const [filePath, mtime, size] = fileData;