metro-file-map 0.73.7 → 0.73.9

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.7",
3
+ "version": "0.73.9",
4
4
  "description": "[Experimental] - 🚇 File crawling, watching and mapping for Metro",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _anymatch = _interopRequireDefault(require("anymatch"));
8
8
  var _events = _interopRequireDefault(require("events"));
9
- var fs = _interopRequireWildcard(require("graceful-fs"));
9
+ var _fs = require("fs");
10
10
  var path = _interopRequireWildcard(require("path"));
11
11
  var _walker = _interopRequireDefault(require("walker"));
12
12
  var _common = require("./common");
@@ -69,8 +69,6 @@ function _interopRequireDefault(obj) {
69
69
 
70
70
  // $FlowFixMe[untyped-import] - walker
71
71
 
72
- // $FlowFixMe[untyped-import] - micromatch
73
- const micromatch = require("micromatch");
74
72
  const debug = require("debug")("Metro:FSEventsWatcher");
75
73
  let fsevents = null;
76
74
  try {
@@ -101,6 +99,7 @@ class FSEventsWatcher extends _events.default {
101
99
  dir,
102
100
  dirCallback,
103
101
  fileCallback,
102
+ symlinkCallback,
104
103
  // $FlowFixMe[unclear-type] Add types for callback
105
104
  endCallback,
106
105
  // $FlowFixMe[unclear-type] Add types for callback
@@ -113,6 +112,7 @@ class FSEventsWatcher extends _events.default {
113
112
  )
114
113
  .on("dir", FSEventsWatcher._normalizeProxy(dirCallback))
115
114
  .on("file", FSEventsWatcher._normalizeProxy(fileCallback))
115
+ .on("symlink", FSEventsWatcher._normalizeProxy(symlinkCallback))
116
116
  .on("error", errorCallback)
117
117
  .on("end", () => {
118
118
  endCallback();
@@ -132,25 +132,25 @@ class FSEventsWatcher extends _events.default {
132
132
  this.dot = opts.dot || false;
133
133
  this.ignored = opts.ignored;
134
134
  this.glob = Array.isArray(opts.glob) ? opts.glob : [opts.glob];
135
- this.hasIgnore =
136
- Boolean(opts.ignored) && !(Array.isArray(opts) && opts.length > 0);
137
135
  this.doIgnore = opts.ignored
138
136
  ? (0, _anymatch.default)(opts.ignored)
139
137
  : () => false;
140
138
  this.root = path.resolve(dir);
141
- this.fsEventsWatchStopper = fsevents.watch(this.root, (path) =>
142
- this._handleEvent(path)
143
- );
139
+ this.fsEventsWatchStopper = fsevents.watch(this.root, (path) => {
140
+ this._handleEvent(path).catch((error) => {
141
+ this.emit("error", error);
142
+ });
143
+ });
144
144
  debug(`Watching ${this.root}`);
145
145
  this._tracked = new Set();
146
+ const trackPath = (filePath) => {
147
+ this._tracked.add(filePath);
148
+ };
146
149
  FSEventsWatcher._recReaddir(
147
150
  this.root,
148
- (filepath) => {
149
- this._tracked.add(filepath);
150
- },
151
- (filepath) => {
152
- this._tracked.add(filepath);
153
- },
151
+ trackPath,
152
+ trackPath,
153
+ trackPath,
154
154
  // $FlowFixMe[method-unbinding] - Refactor
155
155
  this.emit.bind(this, "ready"),
156
156
  // $FlowFixMe[method-unbinding] - Refactor
@@ -170,39 +170,27 @@ class FSEventsWatcher extends _events.default {
170
170
  process.nextTick(callback.bind(null, null, true));
171
171
  }
172
172
  }
173
- _isFileIncluded(relativePath) {
174
- if (this.doIgnore(relativePath)) {
175
- return false;
176
- }
177
- return this.glob.length
178
- ? micromatch([relativePath], this.glob, {
179
- dot: this.dot,
180
- }).length > 0
181
- : this.dot || micromatch([relativePath], "**/*").length > 0;
182
- }
183
- _handleEvent(filepath) {
173
+ async _handleEvent(filepath) {
184
174
  const relativePath = path.relative(this.root, filepath);
185
- if (!this._isFileIncluded(relativePath)) {
186
- return;
187
- }
188
- fs.lstat(filepath, (error, stat) => {
189
- if (error && error.code !== "ENOENT") {
190
- this.emit("error", error);
191
- return;
192
- }
193
- if (error) {
194
- // Ignore files that aren't tracked and don't exist.
195
- if (!this._tracked.has(filepath)) {
196
- return;
197
- }
198
- this._emit(DELETE_EVENT, relativePath);
199
- this._tracked.delete(filepath);
200
- return;
201
- }
175
+ try {
176
+ const stat = await _fs.promises.lstat(filepath);
202
177
  const type = (0, _common.typeFromStat)(stat);
178
+
179
+ // Ignore files of an unrecognized type
203
180
  if (!type) {
204
181
  return;
205
182
  }
183
+ if (
184
+ !(0, _common.isIncluded)(
185
+ type,
186
+ this.glob,
187
+ this.dot,
188
+ this.doIgnore,
189
+ relativePath
190
+ )
191
+ ) {
192
+ return;
193
+ }
206
194
  const metadata = {
207
195
  type,
208
196
  modifiedTime: stat.mtime.getTime(),
@@ -214,7 +202,21 @@ class FSEventsWatcher extends _events.default {
214
202
  this._tracked.add(filepath);
215
203
  this._emit(ADD_EVENT, relativePath, metadata);
216
204
  }
217
- });
205
+ } catch (error) {
206
+ if (
207
+ (error === null || error === void 0 ? void 0 : error.code) !== "ENOENT"
208
+ ) {
209
+ this.emit("error", error);
210
+ return;
211
+ }
212
+
213
+ // Ignore files that aren't tracked and don't exist.
214
+ if (!this._tracked.has(filepath)) {
215
+ return;
216
+ }
217
+ this._emit(DELETE_EVENT, relativePath);
218
+ this._tracked.delete(filepath);
219
+ }
218
220
  }
219
221
 
220
222
  /**
@@ -9,20 +9,18 @@
9
9
  */
10
10
 
11
11
  import type {ChangeEventMetadata} from '../flow-types';
12
+ import type {Stats} from 'fs';
12
13
  // $FlowFixMe[cannot-resolve-module] - Optional, Darwin only
13
14
  import type {FSEvents} from 'fsevents';
14
15
 
15
16
  // $FlowFixMe[untyped-import] - anymatch
16
17
  import anymatch from 'anymatch';
17
18
  import EventEmitter from 'events';
18
- import * as fs from 'graceful-fs';
19
+ import {promises as fsPromises} from 'fs';
19
20
  import * as path from 'path';
20
21
  // $FlowFixMe[untyped-import] - walker
21
22
  import walker from 'walker';
22
- import {typeFromStat} from './common';
23
-
24
- // $FlowFixMe[untyped-import] - micromatch
25
- const micromatch = require('micromatch');
23
+ import {isIncluded, typeFromStat} from './common';
26
24
 
27
25
  const debug = require('debug')('Metro:FSEventsWatcher');
28
26
 
@@ -56,7 +54,6 @@ export default class FSEventsWatcher extends EventEmitter {
56
54
  +ignored: ?Matcher;
57
55
  +glob: $ReadOnlyArray<string>;
58
56
  +dot: boolean;
59
- +hasIgnore: boolean;
60
57
  +doIgnore: (path: string) => boolean;
61
58
  +fsEventsWatchStopper: () => Promise<void>;
62
59
  _tracked: Set<string>;
@@ -66,17 +63,18 @@ export default class FSEventsWatcher extends EventEmitter {
66
63
  }
67
64
 
68
65
  static _normalizeProxy(
69
- callback: (normalizedPath: string, stats: fs.Stats) => void,
66
+ callback: (normalizedPath: string, stats: Stats) => void,
70
67
  // $FlowFixMe[cannot-resolve-name]
71
68
  ): (filepath: string, stats: Stats) => void {
72
- return (filepath: string, stats: fs.Stats): void =>
69
+ return (filepath: string, stats: Stats): void =>
73
70
  callback(path.normalize(filepath), stats);
74
71
  }
75
72
 
76
73
  static _recReaddir(
77
74
  dir: string,
78
- dirCallback: (normalizedPath: string, stats: fs.Stats) => void,
79
- fileCallback: (normalizedPath: string, stats: fs.Stats) => void,
75
+ dirCallback: (normalizedPath: string, stats: Stats) => void,
76
+ fileCallback: (normalizedPath: string, stats: Stats) => void,
77
+ symlinkCallback: (normalizedPath: string, stats: Stats) => void,
80
78
  // $FlowFixMe[unclear-type] Add types for callback
81
79
  endCallback: Function,
82
80
  // $FlowFixMe[unclear-type] Add types for callback
@@ -89,6 +87,7 @@ export default class FSEventsWatcher extends EventEmitter {
89
87
  )
90
88
  .on('dir', FSEventsWatcher._normalizeProxy(dirCallback))
91
89
  .on('file', FSEventsWatcher._normalizeProxy(fileCallback))
90
+ .on('symlink', FSEventsWatcher._normalizeProxy(symlinkCallback))
92
91
  .on('error', errorCallback)
93
92
  .on('end', () => {
94
93
  endCallback();
@@ -116,28 +115,27 @@ export default class FSEventsWatcher extends EventEmitter {
116
115
  this.dot = opts.dot || false;
117
116
  this.ignored = opts.ignored;
118
117
  this.glob = Array.isArray(opts.glob) ? opts.glob : [opts.glob];
119
-
120
- this.hasIgnore =
121
- Boolean(opts.ignored) && !(Array.isArray(opts) && opts.length > 0);
122
118
  this.doIgnore = opts.ignored ? anymatch(opts.ignored) : () => false;
123
119
 
124
120
  this.root = path.resolve(dir);
125
121
 
126
- this.fsEventsWatchStopper = fsevents.watch(this.root, path =>
127
- this._handleEvent(path),
128
- );
122
+ this.fsEventsWatchStopper = fsevents.watch(this.root, path => {
123
+ this._handleEvent(path).catch(error => {
124
+ this.emit('error', error);
125
+ });
126
+ });
129
127
 
130
128
  debug(`Watching ${this.root}`);
131
129
 
132
130
  this._tracked = new Set();
131
+ const trackPath = (filePath: string) => {
132
+ this._tracked.add(filePath);
133
+ };
133
134
  FSEventsWatcher._recReaddir(
134
135
  this.root,
135
- (filepath: string) => {
136
- this._tracked.add(filepath);
137
- },
138
- (filepath: string) => {
139
- this._tracked.add(filepath);
140
- },
136
+ trackPath,
137
+ trackPath,
138
+ trackPath,
141
139
  // $FlowFixMe[method-unbinding] - Refactor
142
140
  this.emit.bind(this, 'ready'),
143
141
  // $FlowFixMe[method-unbinding] - Refactor
@@ -158,42 +156,22 @@ export default class FSEventsWatcher extends EventEmitter {
158
156
  }
159
157
  }
160
158
 
161
- _isFileIncluded(relativePath: string): boolean {
162
- if (this.doIgnore(relativePath)) {
163
- return false;
164
- }
165
- return this.glob.length
166
- ? micromatch([relativePath], this.glob, {dot: this.dot}).length > 0
167
- : this.dot || micromatch([relativePath], '**/*').length > 0;
168
- }
169
-
170
- _handleEvent(filepath: string) {
159
+ async _handleEvent(filepath: string) {
171
160
  const relativePath = path.relative(this.root, filepath);
172
- if (!this._isFileIncluded(relativePath)) {
173
- return;
174
- }
175
-
176
- fs.lstat(filepath, (error, stat) => {
177
- if (error && error.code !== 'ENOENT') {
178
- this.emit('error', error);
179
- return;
180
- }
181
161
 
182
- if (error) {
183
- // Ignore files that aren't tracked and don't exist.
184
- if (!this._tracked.has(filepath)) {
185
- return;
186
- }
162
+ try {
163
+ const stat = await fsPromises.lstat(filepath);
164
+ const type = typeFromStat(stat);
187
165
 
188
- this._emit(DELETE_EVENT, relativePath);
189
- this._tracked.delete(filepath);
166
+ // Ignore files of an unrecognized type
167
+ if (!type) {
190
168
  return;
191
169
  }
192
170
 
193
- const type = typeFromStat(stat);
194
- if (!type) {
171
+ if (!isIncluded(type, this.glob, this.dot, this.doIgnore, relativePath)) {
195
172
  return;
196
173
  }
174
+
197
175
  const metadata: ChangeEventMetadata = {
198
176
  type,
199
177
  modifiedTime: stat.mtime.getTime(),
@@ -206,7 +184,20 @@ export default class FSEventsWatcher extends EventEmitter {
206
184
  this._tracked.add(filepath);
207
185
  this._emit(ADD_EVENT, relativePath, metadata);
208
186
  }
209
- });
187
+ } catch (error) {
188
+ if (error?.code !== 'ENOENT') {
189
+ this.emit('error', error);
190
+ return;
191
+ }
192
+
193
+ // Ignore files that aren't tracked and don't exist.
194
+ if (!this._tracked.has(filepath)) {
195
+ return;
196
+ }
197
+
198
+ this._emit(DELETE_EVENT, relativePath);
199
+ this._tracked.delete(filepath);
200
+ }
210
201
  }
211
202
 
212
203
  /**
@@ -20,6 +20,7 @@ const { EventEmitter } = require("events");
20
20
  const fs = require("fs");
21
21
  const platform = require("os").platform();
22
22
  const path = require("path");
23
+ const fsPromises = fs.promises;
23
24
  const CHANGE_EVENT = common.CHANGE_EVENT;
24
25
  const DELETE_EVENT = common.DELETE_EVENT;
25
26
  const ADD_EVENT = common.ADD_EVENT;
@@ -46,7 +47,10 @@ module.exports = class NodeWatcher extends EventEmitter {
46
47
  this._watchdir(dir);
47
48
  },
48
49
  (filename) => {
49
- this._register(filename);
50
+ this._register(filename, "f");
51
+ },
52
+ (symlink) => {
53
+ this._register(symlink, "l");
50
54
  },
51
55
  () => {
52
56
  this.emit("ready");
@@ -70,7 +74,7 @@ module.exports = class NodeWatcher extends EventEmitter {
70
74
  *
71
75
  * Return false if ignored or already registered.
72
76
  */
73
- _register(filepath) {
77
+ _register(filepath, type) {
74
78
  const dir = path.dirname(filepath);
75
79
  const filename = path.basename(filepath);
76
80
  if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
@@ -78,7 +82,8 @@ module.exports = class NodeWatcher extends EventEmitter {
78
82
  }
79
83
  const relativePath = path.relative(this.root, filepath);
80
84
  if (
81
- !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
85
+ type === "f" &&
86
+ !common.isIncluded("f", this.globs, this.dot, this.doIgnore, relativePath)
82
87
  ) {
83
88
  return false;
84
89
  }
@@ -147,7 +152,7 @@ module.exports = class NodeWatcher extends EventEmitter {
147
152
  this.watched[dir] = watcher;
148
153
  watcher.on("error", this._checkedEmitError);
149
154
  if (this.root !== dir) {
150
- this._register(dir);
155
+ this._register(dir, "d");
151
156
  }
152
157
  return true;
153
158
  };
@@ -216,91 +221,108 @@ module.exports = class NodeWatcher extends EventEmitter {
216
221
  if (!file) {
217
222
  this._detectChangedFile(dir, event, (actualFile) => {
218
223
  if (actualFile) {
219
- this._processChange(dir, event, actualFile);
224
+ this._processChange(dir, event, actualFile).catch((error) =>
225
+ this.emit("error", error)
226
+ );
220
227
  }
221
228
  });
222
229
  } else {
223
- this._processChange(dir, event, path.normalize(file));
230
+ this._processChange(dir, event, path.normalize(file)).catch((error) =>
231
+ this.emit("error", error)
232
+ );
224
233
  }
225
234
  }
226
235
 
227
236
  /**
228
237
  * Process changes.
229
238
  */
230
- _processChange(dir, event, file) {
239
+ async _processChange(dir, event, file) {
231
240
  const fullPath = path.join(dir, file);
232
241
  const relativePath = path.join(path.relative(this.root, dir), file);
233
- fs.lstat(fullPath, (error, stat) => {
234
- if (error && error.code !== "ENOENT") {
235
- this.emit("error", error);
236
- } else if (!error && stat.isDirectory()) {
242
+ const registered = this._registered(fullPath);
243
+ try {
244
+ const stat = await fsPromises.lstat(fullPath);
245
+ if (stat.isDirectory()) {
246
+ // win32 emits usless change events on dirs.
237
247
  if (event === "change") {
238
- // win32 emits usless change events on dirs.
239
248
  return;
240
249
  }
241
250
  if (
242
- stat &&
243
- common.isFileIncluded(
251
+ !common.isIncluded(
252
+ "d",
244
253
  this.globs,
245
254
  this.dot,
246
255
  this.doIgnore,
247
256
  relativePath
248
257
  )
249
258
  ) {
250
- common.recReaddir(
251
- path.resolve(this.root, relativePath),
252
- (dir, stats) => {
253
- if (this._watchdir(dir)) {
254
- this._emitEvent(ADD_EVENT, path.relative(this.root, dir), {
255
- modifiedTime: stats.mtime.getTime(),
256
- size: stats.size,
257
- type: "d",
258
- });
259
- }
260
- },
261
- (file, stats) => {
262
- if (this._register(file)) {
263
- this._emitEvent(ADD_EVENT, path.relative(this.root, file), {
264
- modifiedTime: stats.mtime.getTime(),
265
- size: stats.size,
266
- type: "f",
267
- });
268
- }
269
- },
270
- function endCallback() {},
271
- this._checkedEmitError,
272
- this.ignored
273
- );
259
+ return;
274
260
  }
261
+ common.recReaddir(
262
+ path.resolve(this.root, relativePath),
263
+ (dir, stats) => {
264
+ if (this._watchdir(dir)) {
265
+ this._emitEvent(ADD_EVENT, path.relative(this.root, dir), {
266
+ modifiedTime: stats.mtime.getTime(),
267
+ size: stats.size,
268
+ type: "d",
269
+ });
270
+ }
271
+ },
272
+ (file, stats) => {
273
+ if (this._register(file, "f")) {
274
+ this._emitEvent(ADD_EVENT, path.relative(this.root, file), {
275
+ modifiedTime: stats.mtime.getTime(),
276
+ size: stats.size,
277
+ type: "f",
278
+ });
279
+ }
280
+ },
281
+ (symlink, stats) => {
282
+ if (this._register(symlink, "l")) {
283
+ this._rawEmitEvent(ADD_EVENT, path.relative(this.root, symlink), {
284
+ modifiedTime: stats.mtime.getTime(),
285
+ size: stats.size,
286
+ type: "l",
287
+ });
288
+ }
289
+ },
290
+ function endCallback() {},
291
+ this._checkedEmitError,
292
+ this.ignored
293
+ );
275
294
  } else {
276
- const registered = this._registered(fullPath);
277
- if (error && error.code === "ENOENT") {
278
- this._unregister(fullPath);
279
- this._stopWatching(fullPath);
280
- this._unregisterDir(fullPath);
281
- if (registered) {
282
- this._emitEvent(DELETE_EVENT, relativePath);
283
- }
295
+ const type = common.typeFromStat(stat);
296
+ if (type == null) {
297
+ return;
298
+ }
299
+ const metadata = {
300
+ modifiedTime: stat.mtime.getTime(),
301
+ size: stat.size,
302
+ type,
303
+ };
304
+ if (registered) {
305
+ this._emitEvent(CHANGE_EVENT, relativePath, metadata);
284
306
  } else {
285
- const type = common.typeFromStat(stat);
286
- if (type == null) {
287
- return;
288
- }
289
- const metadata = {
290
- modifiedTime: stat.mtime.getTime(),
291
- size: stat.size,
292
- type,
293
- };
294
- if (registered) {
295
- this._emitEvent(CHANGE_EVENT, relativePath, metadata);
296
- } else {
297
- if (this._register(fullPath)) {
298
- this._emitEvent(ADD_EVENT, relativePath, metadata);
299
- }
307
+ if (this._register(fullPath, type)) {
308
+ this._emitEvent(ADD_EVENT, relativePath, metadata);
300
309
  }
301
310
  }
302
311
  }
303
- });
312
+ } catch (error) {
313
+ if (
314
+ (error === null || error === void 0 ? void 0 : error.code) !== "ENOENT"
315
+ ) {
316
+ this.emit("error", error);
317
+ return;
318
+ }
319
+ this._unregister(fullPath);
320
+ this._stopWatching(fullPath);
321
+ this._unregisterDir(fullPath);
322
+ if (registered) {
323
+ this._emitEvent(DELETE_EVENT, relativePath);
324
+ }
325
+ }
304
326
  }
305
327
 
306
328
  /**
@@ -25,6 +25,8 @@ const fs = require('fs');
25
25
  const platform = require('os').platform();
26
26
  const path = require('path');
27
27
 
28
+ const fsPromises = fs.promises;
29
+
28
30
  const CHANGE_EVENT = common.CHANGE_EVENT;
29
31
  const DELETE_EVENT = common.DELETE_EVENT;
30
32
  const ADD_EVENT = common.ADD_EVENT;
@@ -46,7 +48,6 @@ module.exports = class NodeWatcher extends EventEmitter {
46
48
  doIgnore: string => boolean;
47
49
  dot: boolean;
48
50
  globs: $ReadOnlyArray<string>;
49
- hasIgnore: boolean;
50
51
  ignored: ?(boolean | RegExp);
51
52
  root: string;
52
53
  watched: {[key: string]: FSWatcher, __proto__: null};
@@ -68,7 +69,10 @@ module.exports = class NodeWatcher extends EventEmitter {
68
69
  this._watchdir(dir);
69
70
  },
70
71
  filename => {
71
- this._register(filename);
72
+ this._register(filename, 'f');
73
+ },
74
+ symlink => {
75
+ this._register(symlink, 'l');
72
76
  },
73
77
  () => {
74
78
  this.emit('ready');
@@ -92,7 +96,7 @@ module.exports = class NodeWatcher extends EventEmitter {
92
96
  *
93
97
  * Return false if ignored or already registered.
94
98
  */
95
- _register(filepath: string): boolean {
99
+ _register(filepath: string, type: ChangeEventMetadata['type']): boolean {
96
100
  const dir = path.dirname(filepath);
97
101
  const filename = path.basename(filepath);
98
102
  if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
@@ -101,7 +105,8 @@ module.exports = class NodeWatcher extends EventEmitter {
101
105
 
102
106
  const relativePath = path.relative(this.root, filepath);
103
107
  if (
104
- !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
108
+ type === 'f' &&
109
+ !common.isIncluded('f', this.globs, this.dot, this.doIgnore, relativePath)
105
110
  ) {
106
111
  return false;
107
112
  }
@@ -171,7 +176,7 @@ module.exports = class NodeWatcher extends EventEmitter {
171
176
  watcher.on('error', this._checkedEmitError);
172
177
 
173
178
  if (this.root !== dir) {
174
- this._register(dir);
179
+ this._register(dir, 'd');
175
180
  }
176
181
  return true;
177
182
  };
@@ -243,92 +248,109 @@ module.exports = class NodeWatcher extends EventEmitter {
243
248
  if (!file) {
244
249
  this._detectChangedFile(dir, event, actualFile => {
245
250
  if (actualFile) {
246
- this._processChange(dir, event, actualFile);
251
+ this._processChange(dir, event, actualFile).catch(error =>
252
+ this.emit('error', error),
253
+ );
247
254
  }
248
255
  });
249
256
  } else {
250
- this._processChange(dir, event, path.normalize(file));
257
+ this._processChange(dir, event, path.normalize(file)).catch(error =>
258
+ this.emit('error', error),
259
+ );
251
260
  }
252
261
  }
253
262
 
254
263
  /**
255
264
  * Process changes.
256
265
  */
257
- _processChange(dir: string, event: string, file: string) {
266
+ async _processChange(dir: string, event: string, file: string) {
258
267
  const fullPath = path.join(dir, file);
259
268
  const relativePath = path.join(path.relative(this.root, dir), file);
260
269
 
261
- fs.lstat(fullPath, (error, stat) => {
262
- if (error && error.code !== 'ENOENT') {
263
- this.emit('error', error);
264
- } else if (!error && stat.isDirectory()) {
270
+ const registered = this._registered(fullPath);
271
+
272
+ try {
273
+ const stat = await fsPromises.lstat(fullPath);
274
+ if (stat.isDirectory()) {
275
+ // win32 emits usless change events on dirs.
265
276
  if (event === 'change') {
266
- // win32 emits usless change events on dirs.
267
277
  return;
268
278
  }
279
+
269
280
  if (
270
- stat &&
271
- common.isFileIncluded(
281
+ !common.isIncluded(
282
+ 'd',
272
283
  this.globs,
273
284
  this.dot,
274
285
  this.doIgnore,
275
286
  relativePath,
276
287
  )
277
288
  ) {
278
- common.recReaddir(
279
- path.resolve(this.root, relativePath),
280
- (dir, stats) => {
281
- if (this._watchdir(dir)) {
282
- this._emitEvent(ADD_EVENT, path.relative(this.root, dir), {
283
- modifiedTime: stats.mtime.getTime(),
284
- size: stats.size,
285
- type: 'd',
286
- });
287
- }
288
- },
289
- (file, stats) => {
290
- if (this._register(file)) {
291
- this._emitEvent(ADD_EVENT, path.relative(this.root, file), {
292
- modifiedTime: stats.mtime.getTime(),
293
- size: stats.size,
294
- type: 'f',
295
- });
296
- }
297
- },
298
- function endCallback() {},
299
- this._checkedEmitError,
300
- this.ignored,
301
- );
289
+ return;
302
290
  }
291
+ common.recReaddir(
292
+ path.resolve(this.root, relativePath),
293
+ (dir, stats) => {
294
+ if (this._watchdir(dir)) {
295
+ this._emitEvent(ADD_EVENT, path.relative(this.root, dir), {
296
+ modifiedTime: stats.mtime.getTime(),
297
+ size: stats.size,
298
+ type: 'd',
299
+ });
300
+ }
301
+ },
302
+ (file, stats) => {
303
+ if (this._register(file, 'f')) {
304
+ this._emitEvent(ADD_EVENT, path.relative(this.root, file), {
305
+ modifiedTime: stats.mtime.getTime(),
306
+ size: stats.size,
307
+ type: 'f',
308
+ });
309
+ }
310
+ },
311
+ (symlink, stats) => {
312
+ if (this._register(symlink, 'l')) {
313
+ this._rawEmitEvent(ADD_EVENT, path.relative(this.root, symlink), {
314
+ modifiedTime: stats.mtime.getTime(),
315
+ size: stats.size,
316
+ type: 'l',
317
+ });
318
+ }
319
+ },
320
+ function endCallback() {},
321
+ this._checkedEmitError,
322
+ this.ignored,
323
+ );
303
324
  } else {
304
- const registered = this._registered(fullPath);
305
- if (error && error.code === 'ENOENT') {
306
- this._unregister(fullPath);
307
- this._stopWatching(fullPath);
308
- this._unregisterDir(fullPath);
309
- if (registered) {
310
- this._emitEvent(DELETE_EVENT, relativePath);
311
- }
325
+ const type = common.typeFromStat(stat);
326
+ if (type == null) {
327
+ return;
328
+ }
329
+ const metadata = {
330
+ modifiedTime: stat.mtime.getTime(),
331
+ size: stat.size,
332
+ type,
333
+ };
334
+ if (registered) {
335
+ this._emitEvent(CHANGE_EVENT, relativePath, metadata);
312
336
  } else {
313
- const type = common.typeFromStat(stat);
314
- if (type == null) {
315
- return;
316
- }
317
- const metadata = {
318
- modifiedTime: stat.mtime.getTime(),
319
- size: stat.size,
320
- type,
321
- };
322
- if (registered) {
323
- this._emitEvent(CHANGE_EVENT, relativePath, metadata);
324
- } else {
325
- if (this._register(fullPath)) {
326
- this._emitEvent(ADD_EVENT, relativePath, metadata);
327
- }
337
+ if (this._register(fullPath, type)) {
338
+ this._emitEvent(ADD_EVENT, relativePath, metadata);
328
339
  }
329
340
  }
330
341
  }
331
- });
342
+ } catch (error) {
343
+ if (error?.code !== 'ENOENT') {
344
+ this.emit('error', error);
345
+ return;
346
+ }
347
+ this._unregister(fullPath);
348
+ this._stopWatching(fullPath);
349
+ this._unregisterDir(fullPath);
350
+ if (registered) {
351
+ this._emitEvent(DELETE_EVENT, relativePath);
352
+ }
353
+ }
332
354
  }
333
355
 
334
356
  /**
@@ -258,14 +258,25 @@ class WatchmanWatcher extends _events.default {
258
258
  size,
259
259
  } = changeDescriptor;
260
260
  debug(
261
- "Handling change to: %s (new: %s, exists: %s)",
261
+ "Handling change to: %s (new: %s, exists: %s, type: %s)",
262
262
  relativePath,
263
263
  isNew,
264
- exists
264
+ exists,
265
+ type
265
266
  );
267
+
268
+ // Ignore files of an unrecognized type
269
+ if (type != null && !(type === "f" || type === "d" || type === "l")) {
270
+ return;
271
+ }
266
272
  if (
267
- this.hasIgnore &&
268
- !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
273
+ !common.isIncluded(
274
+ type,
275
+ this.globs,
276
+ this.dot,
277
+ this.doIgnore,
278
+ relativePath
279
+ )
269
280
  ) {
270
281
  return;
271
282
  }
@@ -283,10 +294,8 @@ class WatchmanWatcher extends _events.default {
283
294
  size
284
295
  );
285
296
  if (
286
- type === "f" ||
287
- type === "l" ||
288
297
  // Change event on dirs are mostly useless.
289
- (type === "d" && eventType !== CHANGE_EVENT)
298
+ !(type === "d" && eventType === CHANGE_EVENT)
290
299
  ) {
291
300
  self._emitEvent(eventType, relativePath, self.root, {
292
301
  modifiedTime: Number(mtime_ms),
@@ -46,7 +46,6 @@ export default class WatchmanWatcher extends EventEmitter {
46
46
  dot: boolean;
47
47
  doIgnore: string => boolean;
48
48
  globs: $ReadOnlyArray<string>;
49
- hasIgnore: boolean;
50
49
  root: string;
51
50
  subscriptionName: string;
52
51
  watchProjectInfo: ?$ReadOnly<{
@@ -248,15 +247,26 @@ export default class WatchmanWatcher extends EventEmitter {
248
247
  } = changeDescriptor;
249
248
 
250
249
  debug(
251
- 'Handling change to: %s (new: %s, exists: %s)',
250
+ 'Handling change to: %s (new: %s, exists: %s, type: %s)',
252
251
  relativePath,
253
252
  isNew,
254
253
  exists,
254
+ type,
255
255
  );
256
256
 
257
+ // Ignore files of an unrecognized type
258
+ if (type != null && !(type === 'f' || type === 'd' || type === 'l')) {
259
+ return;
260
+ }
261
+
257
262
  if (
258
- this.hasIgnore &&
259
- !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
263
+ !common.isIncluded(
264
+ type,
265
+ this.globs,
266
+ this.dot,
267
+ this.doIgnore,
268
+ relativePath,
269
+ )
260
270
  ) {
261
271
  return;
262
272
  }
@@ -276,10 +286,8 @@ export default class WatchmanWatcher extends EventEmitter {
276
286
  );
277
287
 
278
288
  if (
279
- type === 'f' ||
280
- type === 'l' ||
281
289
  // Change event on dirs are mostly useless.
282
- (type === 'd' && eventType !== CHANGE_EVENT)
290
+ !(type === 'd' && eventType === CHANGE_EVENT)
283
291
  ) {
284
292
  self._emitEvent(eventType, relativePath, self.root, {
285
293
  modifiedTime: Number(mtime_ms),
@@ -25,7 +25,7 @@ exports.assignOptions =
25
25
  exports.ALL_EVENT =
26
26
  exports.ADD_EVENT =
27
27
  void 0;
28
- exports.isFileIncluded = isFileIncluded;
28
+ exports.isIncluded = isIncluded;
29
29
  exports.recReaddir = recReaddir;
30
30
  exports.typeFromStat = typeFromStat;
31
31
  // $FlowFixMe[untyped-import] - Write libdefs for `anymatch`
@@ -72,8 +72,6 @@ const assignOptions = function (watcher, opts) {
72
72
  if (!Array.isArray(watcher.globs)) {
73
73
  watcher.globs = [watcher.globs];
74
74
  }
75
- watcher.hasIgnore =
76
- Boolean(opts.ignored) && !(Array.isArray(opts) && opts.length > 0);
77
75
  watcher.doIgnore =
78
76
  opts.ignored != null && opts.ignored !== false
79
77
  ? anymatch(opts.ignored)
@@ -88,15 +86,18 @@ const assignOptions = function (watcher, opts) {
88
86
  * Checks a file relative path against the globs array.
89
87
  */
90
88
  exports.assignOptions = assignOptions;
91
- function isFileIncluded(globs, dot, doIgnore, relativePath) {
89
+ function isIncluded(type, globs, dot, doIgnore, relativePath) {
92
90
  if (doIgnore(relativePath)) {
93
91
  return false;
94
92
  }
95
- return globs.length
96
- ? micromatch.some(relativePath, globs, {
97
- dot,
98
- })
99
- : dot || micromatch.some(relativePath, "**/*");
93
+ // For non-regular files or if there are no glob matchers, just respect the
94
+ // `dot` option to filter dotfiles if dot === false.
95
+ if (globs.length === 0 || type !== "f") {
96
+ return dot || micromatch.some(relativePath, "**/*");
97
+ }
98
+ return micromatch.some(relativePath, globs, {
99
+ dot,
100
+ });
100
101
  }
101
102
 
102
103
  /**
@@ -106,6 +107,7 @@ function recReaddir(
106
107
  dir,
107
108
  dirCallback,
108
109
  fileCallback,
110
+ symlinkCallback,
109
111
  endCallback,
110
112
  errorCallback,
111
113
  ignored
@@ -114,6 +116,7 @@ function recReaddir(
114
116
  .filterDir((currentDir) => !anymatch(ignored, currentDir))
115
117
  .on("dir", normalizeProxy(dirCallback))
116
118
  .on("file", normalizeProxy(fileCallback))
119
+ .on("symlink", normalizeProxy(symlinkCallback))
117
120
  .on("error", errorCallback)
118
121
  .on("end", () => {
119
122
  if (platform === "win32") {
@@ -49,7 +49,6 @@ interface Watcher {
49
49
  doIgnore: string => boolean;
50
50
  dot: boolean;
51
51
  globs: $ReadOnlyArray<string>;
52
- hasIgnore: boolean;
53
52
  ignored?: ?(boolean | RegExp);
54
53
  watchmanDeferStates: $ReadOnlyArray<string>;
55
54
  watchmanPath?: ?string;
@@ -75,8 +74,6 @@ export const assignOptions = function (
75
74
  if (!Array.isArray(watcher.globs)) {
76
75
  watcher.globs = [watcher.globs];
77
76
  }
78
- watcher.hasIgnore =
79
- Boolean(opts.ignored) && !(Array.isArray(opts) && opts.length > 0);
80
77
  watcher.doIgnore =
81
78
  opts.ignored != null && opts.ignored !== false
82
79
  ? anymatch(opts.ignored)
@@ -92,7 +89,8 @@ export const assignOptions = function (
92
89
  /**
93
90
  * Checks a file relative path against the globs array.
94
91
  */
95
- export function isFileIncluded(
92
+ export function isIncluded(
93
+ type: ?('f' | 'l' | 'd'),
96
94
  globs: $ReadOnlyArray<string>,
97
95
  dot: boolean,
98
96
  doIgnore: string => boolean,
@@ -101,9 +99,12 @@ export function isFileIncluded(
101
99
  if (doIgnore(relativePath)) {
102
100
  return false;
103
101
  }
104
- return globs.length
105
- ? micromatch.some(relativePath, globs, {dot})
106
- : dot || micromatch.some(relativePath, '**/*');
102
+ // For non-regular files or if there are no glob matchers, just respect the
103
+ // `dot` option to filter dotfiles if dot === false.
104
+ if (globs.length === 0 || type !== 'f') {
105
+ return dot || micromatch.some(relativePath, '**/*');
106
+ }
107
+ return micromatch.some(relativePath, globs, {dot});
107
108
  }
108
109
 
109
110
  /**
@@ -113,6 +114,7 @@ export function recReaddir(
113
114
  dir: string,
114
115
  dirCallback: (string, Stats) => void,
115
116
  fileCallback: (string, Stats) => void,
117
+ symlinkCallback: (string, Stats) => void,
116
118
  endCallback: () => void,
117
119
  errorCallback: Error => void,
118
120
  ignored: ?(boolean | RegExp),
@@ -121,6 +123,7 @@ export function recReaddir(
121
123
  .filterDir(currentDir => !anymatch(ignored, currentDir))
122
124
  .on('dir', normalizeProxy(dirCallback))
123
125
  .on('file', normalizeProxy(fileCallback))
126
+ .on('symlink', normalizeProxy(symlinkCallback))
124
127
  .on('error', errorCallback)
125
128
  .on('end', () => {
126
129
  if (platform === 'win32') {