metro-file-map 0.71.2 → 0.72.1

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.71.2",
3
+ "version": "0.72.1",
4
4
  "description": "[Experimental] - 🚇 File crawling, watching and mapping for Metro",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
+ "abort-controller": "^3.0.0",
16
17
  "anymatch": "^3.0.3",
17
18
  "debug": "^2.2.0",
18
19
  "fb-watchman": "^2.0.0",
package/src/HasteFS.js CHANGED
@@ -9,6 +9,8 @@ var _constants = _interopRequireDefault(require("./constants"));
9
9
 
10
10
  var fastPath = _interopRequireWildcard(require("./lib/fast_path"));
11
11
 
12
+ var path = _interopRequireWildcard(require("path"));
13
+
12
14
  var _jestUtil = require("jest-util");
13
15
 
14
16
  function _getRequireWildcardCache(nodeInterop) {
@@ -66,7 +68,6 @@ function _interopRequireDefault(obj) {
66
68
  * @format
67
69
  *
68
70
  */
69
- // $FlowFixMe[untyped-import] - jest-util
70
71
  class HasteFS {
71
72
  constructor({ rootDir, files }) {
72
73
  this._rootDir = rootDir;
@@ -141,6 +142,42 @@ class HasteFS {
141
142
 
142
143
  return files;
143
144
  }
145
+ /**
146
+ * Given a search context, return a list of file paths matching the query.
147
+ * The query matches against normalized paths which start with `./`,
148
+ * for example: `a/b.js` -> `./a/b.js`
149
+ */
150
+
151
+ matchFilesWithContext(root, context) {
152
+ const files = [];
153
+ const prefix = "./";
154
+
155
+ for (const file of this.getAbsoluteFileIterator()) {
156
+ const filePath = fastPath.relative(root, file);
157
+ const isUnderRoot = filePath && !filePath.startsWith(".."); // Ignore everything outside of the provided `root`.
158
+
159
+ if (!isUnderRoot) {
160
+ continue;
161
+ } // Prevent searching in child directories during a non-recursive search.
162
+
163
+ if (!context.recursive && filePath.includes(path.sep)) {
164
+ continue;
165
+ }
166
+
167
+ if (
168
+ context.filter.test(
169
+ // NOTE(EvanBacon): Ensure files start with `./` for matching purposes
170
+ // this ensures packages work across Metro and Webpack (ex: Storybook for React DOM / React Native).
171
+ // `a/b.js` -> `./a/b.js`
172
+ prefix + filePath.replace(/\\/g, "/")
173
+ )
174
+ ) {
175
+ files.push(file);
176
+ }
177
+ }
178
+
179
+ return files;
180
+ }
144
181
 
145
182
  matchFilesWithGlob(globs, root) {
146
183
  const files = new Set();
@@ -8,16 +8,13 @@
8
8
  * @flow strict-local
9
9
  */
10
10
 
11
- import type {FileData, Path} from './flow-types';
11
+ import type {FileData, FileMetaData, Glob, Path} from './flow-types';
12
12
 
13
13
  import H from './constants';
14
14
  import * as fastPath from './lib/fast_path';
15
- // $FlowFixMe[untyped-import] - jest-util
15
+ import * as path from 'path';
16
16
  import {globsToMatcher, replacePathSepForGlob} from 'jest-util';
17
17
 
18
- // $FlowFixMe[unclear-type] - Check TS Config.Glob
19
- type Glob = any;
20
-
21
18
  export default class HasteFS {
22
19
  +_rootDir: Path;
23
20
  +_files: FileData;
@@ -84,6 +81,52 @@ export default class HasteFS {
84
81
  return files;
85
82
  }
86
83
 
84
+ /**
85
+ * Given a search context, return a list of file paths matching the query.
86
+ * The query matches against normalized paths which start with `./`,
87
+ * for example: `a/b.js` -> `./a/b.js`
88
+ */
89
+ matchFilesWithContext(
90
+ root: Path,
91
+ context: $ReadOnly<{
92
+ /* Should search for files recursively. */
93
+ recursive: boolean,
94
+ /* Filter relative paths against a pattern. */
95
+ filter: RegExp,
96
+ }>,
97
+ ): Array<Path> {
98
+ const files = [];
99
+ const prefix = './';
100
+
101
+ for (const file of this.getAbsoluteFileIterator()) {
102
+ const filePath = fastPath.relative(root, file);
103
+
104
+ const isUnderRoot = filePath && !filePath.startsWith('..');
105
+ // Ignore everything outside of the provided `root`.
106
+ if (!isUnderRoot) {
107
+ continue;
108
+ }
109
+
110
+ // Prevent searching in child directories during a non-recursive search.
111
+ if (!context.recursive && filePath.includes(path.sep)) {
112
+ continue;
113
+ }
114
+
115
+ if (
116
+ context.filter.test(
117
+ // NOTE(EvanBacon): Ensure files start with `./` for matching purposes
118
+ // this ensures packages work across Metro and Webpack (ex: Storybook for React DOM / React Native).
119
+ // `a/b.js` -> `./a/b.js`
120
+ prefix + filePath.replace(/\\/g, '/'),
121
+ )
122
+ ) {
123
+ files.push(file);
124
+ }
125
+ }
126
+
127
+ return files;
128
+ }
129
+
87
130
  matchFilesWithGlob(globs: $ReadOnlyArray<Glob>, root: ?Path): Set<Path> {
88
131
  const files = new Set<string>();
89
132
  const matcher = globsToMatcher(globs);
@@ -97,7 +140,7 @@ export default class HasteFS {
97
140
  return files;
98
141
  }
99
142
 
100
- _getFileData(file: Path) {
143
+ _getFileData(file: Path): void | FileMetaData {
101
144
  const relativePath = fastPath.relative(this._rootDir, file);
102
145
  return this._files.get(relativePath);
103
146
  }
@@ -184,7 +184,7 @@ export default class ModuleMap implements IModuleMap<SerializableModuleMap> {
184
184
  platform: string,
185
185
  supportsNativePlatform: boolean,
186
186
  relativePathSet: ?DuplicatesSet,
187
- ) {
187
+ ): void {
188
188
  if (relativePathSet == null) {
189
189
  return;
190
190
  }
@@ -30,7 +30,6 @@ function _interopRequireDefault(obj) {
30
30
  *
31
31
  * @format
32
32
  */
33
- // $FlowFixMe[missing-export] - serialize and deserialize missing typedefs
34
33
  const DEFAULT_PREFIX = "metro-file-map";
35
34
  const DEFAULT_DIRECTORY = (0, _os.tmpdir)();
36
35
 
@@ -19,7 +19,6 @@ import rootRelativeCacheKeys from '../lib/rootRelativeCacheKeys';
19
19
  import {readFileSync, writeFileSync} from 'graceful-fs';
20
20
  import {tmpdir} from 'os';
21
21
  import path from 'path';
22
- // $FlowFixMe[missing-export] - serialize and deserialize missing typedefs
23
22
  import {deserialize, serialize} from 'v8';
24
23
 
25
24
  type DiskCacheConfig = {
@@ -91,8 +91,12 @@ async function capabilityCheck(client, caps) {
91
91
  // @ts-expect-error: incorrectly typed
92
92
  caps,
93
93
  (error, response) => {
94
- if (error) {
95
- reject(error);
94
+ if (error != null || response == null) {
95
+ reject(
96
+ error !== null && error !== void 0
97
+ ? error
98
+ : new Error("capabilityCheck: Response missing")
99
+ );
96
100
  } else {
97
101
  resolve(response);
98
102
  }
@@ -102,6 +106,8 @@ async function capabilityCheck(client, caps) {
102
106
  }
103
107
 
104
108
  module.exports = async function watchmanCrawl(options) {
109
+ var _options$abortSignal;
110
+
105
111
  const fields = ["name", "exists", "mtime_ms", "size"];
106
112
  const { data, extensions, ignore, rootDir, roots, perfLogger } = options;
107
113
  const clocks = data.clocks;
@@ -109,6 +115,10 @@ module.exports = async function watchmanCrawl(options) {
109
115
  ? void 0
110
116
  : perfLogger.point("watchmanCrawl_start");
111
117
  const client = new watchman.Client();
118
+ (_options$abortSignal = options.abortSignal) === null ||
119
+ _options$abortSignal === void 0
120
+ ? void 0
121
+ : _options$abortSignal.addEventListener("abort", () => client.end());
112
122
  perfLogger === null || perfLogger === void 0
113
123
  ? void 0
114
124
  : perfLogger.point("watchmanCrawl/negotiateCapabilities_start"); // https://facebook.github.io/watchman/docs/capabilities.html
@@ -273,7 +283,7 @@ module.exports = async function watchmanCrawl(options) {
273
283
  * directories. Therefore `glob` < `suffix`.
274
284
  */
275
285
 
276
- let queryGenerator = undefined;
286
+ let queryGenerator;
277
287
 
278
288
  if (since != null) {
279
289
  // Use the `since` generator and filter by both path and extension.
@@ -346,6 +356,7 @@ module.exports = async function watchmanCrawl(options) {
346
356
  const changedFiles = new Map();
347
357
  let results;
348
358
  let isFresh = false;
359
+ let queryError;
349
360
 
350
361
  try {
351
362
  const watchmanRoots = await getWatchmanRoots(roots);
@@ -359,15 +370,56 @@ module.exports = async function watchmanCrawl(options) {
359
370
  }
360
371
 
361
372
  results = watchmanFileResults.results;
362
- } finally {
363
- client.end();
373
+ } catch (e) {
374
+ queryError = e;
364
375
  }
365
376
 
366
- if (clientError) {
377
+ client.end();
378
+
379
+ if (results == null) {
380
+ var _ref, _queryError;
381
+
382
+ if (clientError) {
383
+ var _clientError$message;
384
+
385
+ perfLogger === null || perfLogger === void 0
386
+ ? void 0
387
+ : perfLogger.annotate({
388
+ string: {
389
+ "watchmanCrawl/client_error":
390
+ (_clientError$message = clientError.message) !== null &&
391
+ _clientError$message !== void 0
392
+ ? _clientError$message
393
+ : "[message missing]",
394
+ },
395
+ });
396
+ }
397
+
398
+ if (queryError) {
399
+ var _queryError$message;
400
+
401
+ perfLogger === null || perfLogger === void 0
402
+ ? void 0
403
+ : perfLogger.annotate({
404
+ string: {
405
+ "watchmanCrawl/query_error":
406
+ (_queryError$message = queryError.message) !== null &&
407
+ _queryError$message !== void 0
408
+ ? _queryError$message
409
+ : "[message missing]",
410
+ },
411
+ });
412
+ }
413
+
367
414
  perfLogger === null || perfLogger === void 0
368
415
  ? void 0
369
416
  : perfLogger.point("watchmanCrawl_end");
370
- throw clientError;
417
+ throw (_ref =
418
+ (_queryError = queryError) !== null && _queryError !== void 0
419
+ ? _queryError
420
+ : clientError) !== null && _ref !== void 0
421
+ ? _ref
422
+ : new Error("Watchman file results missing");
371
423
  }
372
424
 
373
425
  perfLogger === null || perfLogger === void 0
@@ -91,8 +91,8 @@ async function capabilityCheck(
91
91
  // @ts-expect-error: incorrectly typed
92
92
  caps,
93
93
  (error, response) => {
94
- if (error) {
95
- reject(error);
94
+ if (error != null || response == null) {
95
+ reject(error ?? new Error('capabilityCheck: Response missing'));
96
96
  } else {
97
97
  resolve(response);
98
98
  }
@@ -114,6 +114,7 @@ module.exports = async function watchmanCrawl(
114
114
 
115
115
  perfLogger?.point('watchmanCrawl_start');
116
116
  const client = new watchman.Client();
117
+ options.abortSignal?.addEventListener('abort', () => client.end());
117
118
 
118
119
  perfLogger?.point('watchmanCrawl/negotiateCapabilities_start');
119
120
  // https://facebook.github.io/watchman/docs/capabilities.html
@@ -268,11 +269,7 @@ module.exports = async function watchmanCrawl(
268
269
  * repo but Haste map projects are focused on a handful of
269
270
  * directories. Therefore `glob` < `suffix`.
270
271
  */
271
- let queryGenerator: ?(
272
- | $TEMPORARY$string<'glob'>
273
- | $TEMPORARY$string<'since'>
274
- | $TEMPORARY$string<'suffix'>
275
- ) = undefined;
272
+ let queryGenerator: ?string;
276
273
  if (since != null) {
277
274
  // Use the `since` generator and filter by both path and extension.
278
275
  query.since = since;
@@ -339,6 +336,7 @@ module.exports = async function watchmanCrawl(
339
336
  const changedFiles = new Map();
340
337
  let results: Map<string, WatchmanQueryResponse>;
341
338
  let isFresh = false;
339
+ let queryError: ?Error;
342
340
  try {
343
341
  const watchmanRoots = await getWatchmanRoots(roots);
344
342
  const watchmanFileResults = await queryWatchmanForDirs(watchmanRoots);
@@ -352,13 +350,32 @@ module.exports = async function watchmanCrawl(
352
350
  }
353
351
 
354
352
  results = watchmanFileResults.results;
355
- } finally {
356
- client.end();
353
+ } catch (e) {
354
+ queryError = e;
357
355
  }
358
-
359
- if (clientError) {
356
+ client.end();
357
+
358
+ if (results == null) {
359
+ if (clientError) {
360
+ perfLogger?.annotate({
361
+ string: {
362
+ 'watchmanCrawl/client_error':
363
+ clientError.message ?? '[message missing]',
364
+ },
365
+ });
366
+ }
367
+ if (queryError) {
368
+ perfLogger?.annotate({
369
+ string: {
370
+ 'watchmanCrawl/query_error':
371
+ queryError.message ?? '[message missing]',
372
+ },
373
+ });
374
+ }
360
375
  perfLogger?.point('watchmanCrawl_end');
361
- throw clientError;
376
+ throw (
377
+ queryError ?? clientError ?? new Error('Watchman file results missing')
378
+ );
362
379
  }
363
380
 
364
381
  perfLogger?.point('watchmanCrawl/processResults_start');
@@ -61,6 +61,7 @@ export type ChangeEvent = {
61
61
  export type Console = typeof global.console;
62
62
 
63
63
  export type CrawlerOptions = {
64
+ abortSignal: ?AbortSignal,
64
65
  computeSha1: boolean,
65
66
  enableSymlinks: boolean,
66
67
  data: InternalData,
@@ -125,6 +126,8 @@ export type FileMetaData = [
125
126
  /* sha1 */ ?string,
126
127
  ];
127
128
 
129
+ export type Glob = string;
130
+
128
131
  export interface IModuleMap<S = SerializableModuleMap> {
129
132
  getModule(
130
133
  name: string,
package/src/index.js CHANGED
@@ -72,6 +72,8 @@ var _jestWorker = require("jest-worker");
72
72
 
73
73
  var path = _interopRequireWildcard(require("path"));
74
74
 
75
+ var _abortController = _interopRequireDefault(require("abort-controller"));
76
+
75
77
  function _getRequireWildcardCache(nodeInterop) {
76
78
  if (typeof WeakMap !== "function") return null;
77
79
  var cacheBabelInterop = new WeakMap();
@@ -131,6 +133,7 @@ function _interopRequireDefault(obj) {
131
133
  // $FlowFixMe[untyped-import] - WatchmanWatcher
132
134
  // $FlowFixMe[untyped-import] - jest-regex-util
133
135
  // $FlowFixMe[untyped-import] - jest-worker
136
+ // $FlowFixMe[untyped-import] - this is a polyfill
134
137
  const nodeCrawl = require("./crawlers/node");
135
138
 
136
139
  const watchmanCrawl = require("./crawlers/watchman");
@@ -141,7 +144,7 @@ exports.DuplicateHasteCandidatesError = DuplicateHasteCandidatesError;
141
144
  // This should be bumped whenever a code change to `metro-file-map` itself
142
145
  // would cause a change to the cache data structure and/or content (for a given
143
146
  // filesystem state and build parameters).
144
- const CACHE_BREAKER = "1";
147
+ const CACHE_BREAKER = "2";
145
148
  const CHANGE_INTERVAL = 30;
146
149
  const MAX_WAIT_TIME = 240000;
147
150
  const NODE_MODULES = path.sep + "node_modules" + path.sep;
@@ -341,6 +344,7 @@ class HasteMap extends _events.default {
341
344
  _this$_options$perfLo === void 0
342
345
  ? void 0
343
346
  : _this$_options$perfLo.point("constructor_end");
347
+ this._crawlerAbortController = new _abortController.default();
344
348
  }
345
349
 
346
350
  static getCacheFilePath(cacheDirectory, cacheFilePrefix, buildParameters) {
@@ -889,6 +893,7 @@ class HasteMap extends _events.default {
889
893
  const crawl =
890
894
  canUseWatchman && this._options.useWatchman ? watchmanCrawl : nodeCrawl;
891
895
  const crawlerOptions = {
896
+ abortSignal: this._crawlerAbortController.signal,
892
897
  computeSha1: options.computeSha1,
893
898
  data: hasteMap,
894
899
  enableSymlinks: options.enableSymlinks,
@@ -983,7 +988,12 @@ class HasteMap extends _events.default {
983
988
  const createWatcher = (root) => {
984
989
  const watcher = new WatcherImpl(root, {
985
990
  dot: true,
986
- glob: extensions.map((extension) => "**/*." + extension),
991
+ glob: [
992
+ // Ensure we always include package.json files, which are crucial for
993
+ /// module resolution.
994
+ "**/package.json",
995
+ ...extensions.map((extension) => "**/*." + extension),
996
+ ],
987
997
  ignored: ignorePattern,
988
998
  watchmanDeferStates: this._options.watchmanDeferStates,
989
999
  });
@@ -1255,6 +1265,8 @@ class HasteMap extends _events.default {
1255
1265
 
1256
1266
  await Promise.all(this._watchers.map((watcher) => watcher.close()));
1257
1267
  this._watchers = [];
1268
+
1269
+ this._crawlerAbortController.abort();
1258
1270
  }
1259
1271
  /**
1260
1272
  * Helpers
package/src/index.js.flow CHANGED
@@ -56,6 +56,8 @@ import {escapePathForRegex} from 'jest-regex-util';
56
56
  // $FlowFixMe[untyped-import] - jest-worker
57
57
  import {Worker} from 'jest-worker';
58
58
  import * as path from 'path';
59
+ // $FlowFixMe[untyped-import] - this is a polyfill
60
+ import AbortController from 'abort-controller';
59
61
 
60
62
  const nodeCrawl = require('./crawlers/node');
61
63
  const watchmanCrawl = require('./crawlers/watchman');
@@ -133,7 +135,7 @@ export type {
133
135
  // This should be bumped whenever a code change to `metro-file-map` itself
134
136
  // would cause a change to the cache data structure and/or content (for a given
135
137
  // filesystem state and build parameters).
136
- const CACHE_BREAKER = '1';
138
+ const CACHE_BREAKER = '2';
137
139
 
138
140
  const CHANGE_INTERVAL = 30;
139
141
  const MAX_WAIT_TIME = 240000;
@@ -237,6 +239,7 @@ export default class HasteMap extends EventEmitter {
237
239
  _watchers: Array<Watcher>;
238
240
  _worker: ?WorkerInterface;
239
241
  _cacheManager: CacheManager;
242
+ _crawlerAbortController: typeof AbortController;
240
243
 
241
244
  static create(options: InputOptions): HasteMap {
242
245
  return new HasteMap(options);
@@ -320,6 +323,7 @@ export default class HasteMap extends EventEmitter {
320
323
  this._watchers = [];
321
324
  this._worker = null;
322
325
  this._options.perfLogger?.point('constructor_end');
326
+ this._crawlerAbortController = new AbortController();
323
327
  }
324
328
 
325
329
  static getCacheFilePath(
@@ -774,13 +778,21 @@ export default class HasteMap extends EventEmitter {
774
778
  return this._worker;
775
779
  }
776
780
 
777
- _crawl(hasteMap: InternalData) {
781
+ _crawl(hasteMap: InternalData): Promise<?(
782
+ | Promise<{
783
+ changedFiles?: FileData,
784
+ hasteMap: InternalData,
785
+ removedFiles: FileData,
786
+ }>
787
+ | {changedFiles?: FileData, hasteMap: InternalData, removedFiles: FileData}
788
+ )> {
778
789
  this._options.perfLogger?.point('crawl_start');
779
790
  const options = this._options;
780
- const ignore = filePath => this._ignore(filePath);
791
+ const ignore = (filePath: string) => this._ignore(filePath);
781
792
  const crawl =
782
793
  canUseWatchman && this._options.useWatchman ? watchmanCrawl : nodeCrawl;
783
794
  const crawlerOptions: CrawlerOptions = {
795
+ abortSignal: this._crawlerAbortController.signal,
784
796
  computeSha1: options.computeSha1,
785
797
  data: hasteMap,
786
798
  enableSymlinks: options.enableSymlinks,
@@ -862,7 +874,12 @@ export default class HasteMap extends EventEmitter {
862
874
  const createWatcher = (root: Path): Promise<Watcher> => {
863
875
  const watcher = new WatcherImpl(root, {
864
876
  dot: true,
865
- glob: extensions.map(extension => '**/*.' + extension),
877
+ glob: [
878
+ // Ensure we always include package.json files, which are crucial for
879
+ /// module resolution.
880
+ '**/package.json',
881
+ ...extensions.map(extension => '**/*.' + extension),
882
+ ],
866
883
  ignored: ignorePattern,
867
884
  watchmanDeferStates: this._options.watchmanDeferStates,
868
885
  });
@@ -1118,6 +1135,7 @@ export default class HasteMap extends EventEmitter {
1118
1135
  await Promise.all(this._watchers.map(watcher => watcher.close()));
1119
1136
 
1120
1137
  this._watchers = [];
1138
+ this._crawlerAbortController.abort();
1121
1139
  }
1122
1140
 
1123
1141
  /**
@@ -61,7 +61,8 @@ export default class FSEventsWatcher extends EventEmitter {
61
61
 
62
62
  static _normalizeProxy(
63
63
  callback: (normalizedPath: string, stats: fs.Stats) => void,
64
- ) {
64
+ // $FlowFixMe[cannot-resolve-name]
65
+ ): (filepath: string, stats: Stats) => void {
65
66
  return (filepath: string, stats: fs.Stats): void =>
66
67
  callback(path.normalize(filepath), stats);
67
68
  }
@@ -149,7 +150,7 @@ export default class FSEventsWatcher extends EventEmitter {
149
150
  }
150
151
  }
151
152
 
152
- _isFileIncluded(relativePath: string) {
153
+ _isFileIncluded(relativePath: string): boolean {
153
154
  if (this.doIgnore(relativePath)) {
154
155
  return false;
155
156
  }