diff --git a/packages/metro-file-map/src/watchers/FSEventsWatcher.js b/packages/metro-file-map/src/watchers/FSEventsWatcher.js index 8bc39ef04b..9705faee38 100644 --- a/packages/metro-file-map/src/watchers/FSEventsWatcher.js +++ b/packages/metro-file-map/src/watchers/FSEventsWatcher.js @@ -9,13 +9,14 @@ */ import type {ChangeEventMetadata} from '../flow-types'; +import type {Stats} from 'fs'; // $FlowFixMe[cannot-resolve-module] - Optional, Darwin only import type {FSEvents} from 'fsevents'; // $FlowFixMe[untyped-import] - anymatch import anymatch from 'anymatch'; import EventEmitter from 'events'; -import * as fs from 'graceful-fs'; +import {promises as fsPromises} from 'fs'; import * as path from 'path'; // $FlowFixMe[untyped-import] - walker import walker from 'walker'; @@ -66,17 +67,17 @@ export default class FSEventsWatcher extends EventEmitter { } static _normalizeProxy( - callback: (normalizedPath: string, stats: fs.Stats) => void, + callback: (normalizedPath: string, stats: Stats) => void, // $FlowFixMe[cannot-resolve-name] ): (filepath: string, stats: Stats) => void { - return (filepath: string, stats: fs.Stats): void => + return (filepath: string, stats: Stats): void => callback(path.normalize(filepath), stats); } static _recReaddir( dir: string, - dirCallback: (normalizedPath: string, stats: fs.Stats) => void, - fileCallback: (normalizedPath: string, stats: fs.Stats) => void, + dirCallback: (normalizedPath: string, stats: Stats) => void, + fileCallback: (normalizedPath: string, stats: Stats) => void, // $FlowFixMe[unclear-type] Add types for callback endCallback: Function, // $FlowFixMe[unclear-type] Add types for callback @@ -123,9 +124,11 @@ export default class FSEventsWatcher extends EventEmitter { this.root = path.resolve(dir); - this.fsEventsWatchStopper = fsevents.watch(this.root, path => - this._handleEvent(path), - ); + this.fsEventsWatchStopper = fsevents.watch(this.root, path => { + this._handleEvent(path).catch(error => { + this.emit('error', error); + }); + }); debug(`Watching ${this.root}`); @@ -167,30 +170,17 @@ export default class FSEventsWatcher extends EventEmitter { : this.dot || micromatch([relativePath], '**/*').length > 0; } - _handleEvent(filepath: string) { + async _handleEvent(filepath: string) { const relativePath = path.relative(this.root, filepath); if (!this._isFileIncluded(relativePath)) { return; } - fs.lstat(filepath, (error, stat) => { - if (error && error.code !== 'ENOENT') { - this.emit('error', error); - return; - } - - if (error) { - // Ignore files that aren't tracked and don't exist. - if (!this._tracked.has(filepath)) { - return; - } - - this._emit(DELETE_EVENT, relativePath); - this._tracked.delete(filepath); - return; - } - + try { + const stat = await fsPromises.lstat(filepath); const type = typeFromStat(stat); + + // Ignore files of an unrecognized type if (!type) { return; } @@ -206,7 +196,20 @@ export default class FSEventsWatcher extends EventEmitter { this._tracked.add(filepath); this._emit(ADD_EVENT, relativePath, metadata); } - }); + } catch (error) { + if (error?.code !== 'ENOENT') { + this.emit('error', error); + return; + } + + // Ignore files that aren't tracked and don't exist. + if (!this._tracked.has(filepath)) { + return; + } + + this._emit(DELETE_EVENT, relativePath); + this._tracked.delete(filepath); + } } /** diff --git a/packages/metro-file-map/src/watchers/NodeWatcher.js b/packages/metro-file-map/src/watchers/NodeWatcher.js index ebbb3bd72f..6ebf378f11 100644 --- a/packages/metro-file-map/src/watchers/NodeWatcher.js +++ b/packages/metro-file-map/src/watchers/NodeWatcher.js @@ -25,6 +25,8 @@ const fs = require('fs'); const platform = require('os').platform(); const path = require('path'); +const fsPromises = fs.promises; + const CHANGE_EVENT = common.CHANGE_EVENT; const DELETE_EVENT = common.DELETE_EVENT; const ADD_EVENT = common.ADD_EVENT; @@ -243,29 +245,35 @@ module.exports = class NodeWatcher extends EventEmitter { if (!file) { this._detectChangedFile(dir, event, actualFile => { if (actualFile) { - this._processChange(dir, event, actualFile); + this._processChange(dir, event, actualFile).catch(error => + this.emit('error', error), + ); } }); } else { - this._processChange(dir, event, path.normalize(file)); + this._processChange(dir, event, path.normalize(file)).catch(error => + this.emit('error', error), + ); } } /** * Process changes. */ - _processChange(dir: string, event: string, file: string) { + async _processChange(dir: string, event: string, file: string) { const fullPath = path.join(dir, file); const relativePath = path.join(path.relative(this.root, dir), file); - fs.lstat(fullPath, (error, stat) => { - if (error && error.code !== 'ENOENT') { - this.emit('error', error); - } else if (!error && stat.isDirectory()) { + const registered = this._registered(fullPath); + + try { + const stat = await fsPromises.lstat(fullPath); + if (stat.isDirectory()) { + // win32 emits usless change events on dirs. if (event === 'change') { - // win32 emits usless change events on dirs. return; } + if ( stat && common.isFileIncluded( @@ -300,35 +308,37 @@ module.exports = class NodeWatcher extends EventEmitter { this.ignored, ); } + return; } else { - const registered = this._registered(fullPath); - if (error && error.code === 'ENOENT') { - this._unregister(fullPath); - this._stopWatching(fullPath); - this._unregisterDir(fullPath); - if (registered) { - this._emitEvent(DELETE_EVENT, relativePath); - } + const type = common.typeFromStat(stat); + if (type == null) { + return; + } + const metadata = { + modifiedTime: stat.mtime.getTime(), + size: stat.size, + type, + }; + if (registered) { + this._emitEvent(CHANGE_EVENT, relativePath, metadata); } else { - const type = common.typeFromStat(stat); - if (type == null) { - return; - } - const metadata = { - modifiedTime: stat.mtime.getTime(), - size: stat.size, - type, - }; - if (registered) { - this._emitEvent(CHANGE_EVENT, relativePath, metadata); - } else { - if (this._register(fullPath)) { - this._emitEvent(ADD_EVENT, relativePath, metadata); - } + if (this._register(fullPath)) { + this._emitEvent(ADD_EVENT, relativePath, metadata); } } } - }); + } catch (error) { + if (error?.code !== 'ENOENT') { + this.emit('error', error); + return; + } + this._unregister(fullPath); + this._stopWatching(fullPath); + this._unregisterDir(fullPath); + if (registered) { + this._emitEvent(DELETE_EVENT, relativePath); + } + } } /**