- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
814 lines
26 KiB
JavaScript
814 lines
26 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true,
|
|
});
|
|
Object.defineProperty(exports, "DependencyPlugin", {
|
|
enumerable: true,
|
|
get: function () {
|
|
return _DependencyPlugin.default;
|
|
},
|
|
});
|
|
Object.defineProperty(exports, "DiskCacheManager", {
|
|
enumerable: true,
|
|
get: function () {
|
|
return _DiskCacheManager.DiskCacheManager;
|
|
},
|
|
});
|
|
Object.defineProperty(exports, "DuplicateHasteCandidatesError", {
|
|
enumerable: true,
|
|
get: function () {
|
|
return _DuplicateHasteCandidatesError.DuplicateHasteCandidatesError;
|
|
},
|
|
});
|
|
Object.defineProperty(exports, "HasteConflictsError", {
|
|
enumerable: true,
|
|
get: function () {
|
|
return _HasteConflictsError.HasteConflictsError;
|
|
},
|
|
});
|
|
Object.defineProperty(exports, "HastePlugin", {
|
|
enumerable: true,
|
|
get: function () {
|
|
return _HastePlugin.default;
|
|
},
|
|
});
|
|
exports.default = void 0;
|
|
var _DiskCacheManager = require("./cache/DiskCacheManager");
|
|
var _constants = _interopRequireDefault(require("./constants"));
|
|
var _checkWatchmanCapabilities = _interopRequireDefault(
|
|
require("./lib/checkWatchmanCapabilities"),
|
|
);
|
|
var _FileProcessor = require("./lib/FileProcessor");
|
|
var _FileSystemChangeAggregator = require("./lib/FileSystemChangeAggregator");
|
|
var _normalizePathSeparatorsToPosix = _interopRequireDefault(
|
|
require("./lib/normalizePathSeparatorsToPosix"),
|
|
);
|
|
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
|
|
require("./lib/normalizePathSeparatorsToSystem"),
|
|
);
|
|
var _RootPathUtils = require("./lib/RootPathUtils");
|
|
var _TreeFS = _interopRequireDefault(require("./lib/TreeFS"));
|
|
var _Watcher = require("./Watcher");
|
|
var _events = _interopRequireDefault(require("events"));
|
|
var _fs = require("fs");
|
|
var _invariant = _interopRequireDefault(require("invariant"));
|
|
var path = _interopRequireWildcard(require("path"));
|
|
var _perf_hooks = require("perf_hooks");
|
|
var _DependencyPlugin = _interopRequireDefault(
|
|
require("./plugins/DependencyPlugin"),
|
|
);
|
|
var _DuplicateHasteCandidatesError = require("./plugins/haste/DuplicateHasteCandidatesError");
|
|
var _HasteConflictsError = require("./plugins/haste/HasteConflictsError");
|
|
var _HastePlugin = _interopRequireDefault(require("./plugins/HastePlugin"));
|
|
function _interopRequireWildcard(e, t) {
|
|
if ("function" == typeof WeakMap)
|
|
var r = new WeakMap(),
|
|
n = new WeakMap();
|
|
return (_interopRequireWildcard = function (e, t) {
|
|
if (!t && e && e.__esModule) return e;
|
|
var o,
|
|
i,
|
|
f = { __proto__: null, default: e };
|
|
if (null === e || ("object" != typeof e && "function" != typeof e))
|
|
return f;
|
|
if ((o = t ? n : r)) {
|
|
if (o.has(e)) return o.get(e);
|
|
o.set(e, f);
|
|
}
|
|
for (const t in e)
|
|
"default" !== t &&
|
|
{}.hasOwnProperty.call(e, t) &&
|
|
((i =
|
|
(o = Object.defineProperty) &&
|
|
Object.getOwnPropertyDescriptor(e, t)) &&
|
|
(i.get || i.set)
|
|
? o(f, t, i)
|
|
: (f[t] = e[t]));
|
|
return f;
|
|
})(e, t);
|
|
}
|
|
function _interopRequireDefault(e) {
|
|
return e && e.__esModule ? e : { default: e };
|
|
}
|
|
const debug = require("debug")("Metro:FileMap");
|
|
const CACHE_BREAKER = "11";
|
|
const CHANGE_INTERVAL = 30;
|
|
const NODE_MODULES = path.sep + "node_modules" + path.sep;
|
|
const VCS_DIRECTORIES = /[/\\]\.(git|hg)[/\\]/.source;
|
|
const WATCHMAN_REQUIRED_CAPABILITIES = [
|
|
"field-content.sha1hex",
|
|
"relative_root",
|
|
"suffix-set",
|
|
"wildmatch",
|
|
];
|
|
class FileMap extends _events.default {
|
|
#buildPromise;
|
|
#cacheManager;
|
|
#canUseWatchmanPromise;
|
|
#changeID;
|
|
#changeInterval;
|
|
#console;
|
|
#crawlerAbortController;
|
|
#fileProcessor;
|
|
#healthCheckInterval;
|
|
#options;
|
|
#pathUtils;
|
|
#plugins;
|
|
#startupPerfLogger;
|
|
#watcher;
|
|
static create(options) {
|
|
return new FileMap(options);
|
|
}
|
|
constructor(options) {
|
|
super();
|
|
if (options.perfLoggerFactory) {
|
|
this.#startupPerfLogger =
|
|
options.perfLoggerFactory?.("START_UP").subSpan("fileMap") ?? null;
|
|
this.#startupPerfLogger?.point("constructor_start");
|
|
}
|
|
let ignorePattern;
|
|
if (options.ignorePattern) {
|
|
const inputIgnorePattern = options.ignorePattern;
|
|
if (inputIgnorePattern instanceof RegExp) {
|
|
ignorePattern = new RegExp(
|
|
inputIgnorePattern.source.concat("|" + VCS_DIRECTORIES),
|
|
inputIgnorePattern.flags,
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
"metro-file-map: the `ignorePattern` option must be a RegExp",
|
|
);
|
|
}
|
|
} else {
|
|
ignorePattern = new RegExp(VCS_DIRECTORIES);
|
|
}
|
|
this.#console = options.console || global.console;
|
|
let dataSlot = _constants.default.PLUGINDATA;
|
|
const indexedPlugins = [];
|
|
const pluginWorkers = [];
|
|
const plugins = options.plugins ?? [];
|
|
for (const plugin of plugins) {
|
|
const maybeWorker = plugin.getWorker();
|
|
indexedPlugins.push({
|
|
plugin,
|
|
dataIdx: maybeWorker != null ? dataSlot++ : null,
|
|
});
|
|
if (maybeWorker != null) {
|
|
pluginWorkers.push(maybeWorker);
|
|
}
|
|
}
|
|
this.#plugins = indexedPlugins;
|
|
const buildParameters = {
|
|
cacheBreaker: CACHE_BREAKER,
|
|
computeSha1: options.computeSha1 || false,
|
|
enableSymlinks: options.enableSymlinks || false,
|
|
extensions: options.extensions,
|
|
forceNodeFilesystemAPI: !!options.forceNodeFilesystemAPI,
|
|
ignorePattern,
|
|
plugins,
|
|
retainAllFiles: options.retainAllFiles,
|
|
rootDir: options.rootDir,
|
|
roots: Array.from(new Set(options.roots)),
|
|
};
|
|
this.#options = {
|
|
...buildParameters,
|
|
healthCheck: options.healthCheck,
|
|
perfLoggerFactory: options.perfLoggerFactory,
|
|
resetCache: options.resetCache,
|
|
useWatchman: options.useWatchman == null ? true : options.useWatchman,
|
|
watch: !!options.watch,
|
|
watchmanDeferStates: options.watchmanDeferStates ?? [],
|
|
};
|
|
const cacheFactoryOptions = {
|
|
buildParameters,
|
|
};
|
|
this.#cacheManager = options.cacheManagerFactory
|
|
? options.cacheManagerFactory.call(null, cacheFactoryOptions)
|
|
: new _DiskCacheManager.DiskCacheManager(cacheFactoryOptions, {});
|
|
this.#fileProcessor = new _FileProcessor.FileProcessor({
|
|
maxFilesPerWorker: options.maxFilesPerWorker,
|
|
maxWorkers: options.maxWorkers,
|
|
perfLogger: this.#startupPerfLogger,
|
|
pluginWorkers,
|
|
rootDir: options.rootDir,
|
|
});
|
|
this.#buildPromise = null;
|
|
this.#pathUtils = new _RootPathUtils.RootPathUtils(options.rootDir);
|
|
this.#startupPerfLogger?.point("constructor_end");
|
|
this.#crawlerAbortController = new AbortController();
|
|
this.#changeID = 0;
|
|
}
|
|
build() {
|
|
this.#startupPerfLogger?.point("build_start");
|
|
if (!this.#buildPromise) {
|
|
this.#buildPromise = (async () => {
|
|
let initialData;
|
|
if (this.#options.resetCache !== true) {
|
|
initialData = await this.read();
|
|
}
|
|
if (!initialData) {
|
|
debug("Not using a cache");
|
|
} else {
|
|
debug("Cache loaded (%d clock(s))", initialData.clocks.size);
|
|
}
|
|
const rootDir = this.#options.rootDir;
|
|
this.#startupPerfLogger?.point("constructFileSystem_start");
|
|
const processFile = (normalPath, metadata, opts) => {
|
|
const result = this.#fileProcessor.processRegularFile(
|
|
normalPath,
|
|
metadata,
|
|
{
|
|
computeSha1: opts.computeSha1,
|
|
maybeReturnContent: true,
|
|
},
|
|
);
|
|
debug("Lazily processed file: %s", normalPath);
|
|
this.emit("metadata");
|
|
return result?.content;
|
|
};
|
|
const fileSystem =
|
|
initialData != null
|
|
? _TreeFS.default.fromDeserializedSnapshot({
|
|
fileSystemData: initialData.fileSystemData,
|
|
processFile,
|
|
rootDir,
|
|
})
|
|
: new _TreeFS.default({
|
|
processFile,
|
|
rootDir,
|
|
});
|
|
this.#startupPerfLogger?.point("constructFileSystem_end");
|
|
const plugins = this.#plugins;
|
|
const [fileDelta] = await Promise.all([
|
|
this.#buildFileDelta({
|
|
clocks: initialData?.clocks ?? new Map(),
|
|
fileSystem,
|
|
}),
|
|
Promise.all(
|
|
plugins.map(({ plugin, dataIdx }) =>
|
|
plugin.initialize({
|
|
files: {
|
|
lookup: (mixedPath) => {
|
|
const result = fileSystem.lookup(mixedPath);
|
|
if (!result.exists) {
|
|
return {
|
|
exists: false,
|
|
};
|
|
}
|
|
if (result.type === "d") {
|
|
return {
|
|
exists: true,
|
|
type: "d",
|
|
};
|
|
}
|
|
return {
|
|
exists: true,
|
|
type: "f",
|
|
pluginData:
|
|
dataIdx != null ? result.metadata[dataIdx] : null,
|
|
};
|
|
},
|
|
fileIterator: (opts) =>
|
|
mapIterable(
|
|
fileSystem.metadataIterator(opts),
|
|
({ baseName, canonicalPath, metadata }) => ({
|
|
baseName,
|
|
canonicalPath,
|
|
pluginData: dataIdx != null ? metadata[dataIdx] : null,
|
|
}),
|
|
),
|
|
},
|
|
pluginState: initialData?.plugins.get(plugin.name),
|
|
}),
|
|
),
|
|
),
|
|
]);
|
|
const actualChanges = await this.#applyFileDelta(
|
|
fileSystem,
|
|
plugins,
|
|
fileDelta,
|
|
);
|
|
const changeSize = actualChanges.getSize();
|
|
plugins.forEach(({ plugin }) => plugin.assertValid());
|
|
const watchmanClocks = new Map(fileDelta.clocks ?? []);
|
|
await this.#takeSnapshotAndPersist(
|
|
fileSystem,
|
|
watchmanClocks,
|
|
plugins,
|
|
changeSize > 0,
|
|
);
|
|
debug("Finished mapping files (%d changes).", changeSize);
|
|
await this.#watch(fileSystem, watchmanClocks, plugins);
|
|
return {
|
|
fileSystem,
|
|
};
|
|
})();
|
|
}
|
|
return this.#buildPromise.then((result) => {
|
|
this.#startupPerfLogger?.point("build_end");
|
|
return result;
|
|
});
|
|
}
|
|
async read() {
|
|
let data;
|
|
this.#startupPerfLogger?.point("read_start");
|
|
try {
|
|
data = await this.#cacheManager.read();
|
|
} catch (e) {
|
|
this.#console.warn(
|
|
"Error while reading cache, falling back to a full crawl:\n",
|
|
e,
|
|
);
|
|
this.#startupPerfLogger?.annotate({
|
|
string: {
|
|
cacheReadError: e.toString(),
|
|
},
|
|
});
|
|
}
|
|
this.#startupPerfLogger?.point("read_end");
|
|
return data;
|
|
}
|
|
async #buildFileDelta(previousState) {
|
|
this.#startupPerfLogger?.point("buildFileDelta_start");
|
|
const {
|
|
computeSha1,
|
|
enableSymlinks,
|
|
extensions,
|
|
forceNodeFilesystemAPI,
|
|
ignorePattern,
|
|
retainAllFiles,
|
|
roots,
|
|
rootDir,
|
|
watch,
|
|
watchmanDeferStates,
|
|
} = this.#options;
|
|
this.#watcher = new _Watcher.Watcher({
|
|
abortSignal: this.#crawlerAbortController.signal,
|
|
computeSha1,
|
|
console: this.#console,
|
|
enableSymlinks,
|
|
extensions,
|
|
forceNodeFilesystemAPI,
|
|
healthCheckFilePrefix: this.#options.healthCheck.filePrefix,
|
|
ignoreForCrawl: (filePath) => {
|
|
const ignoreMatched = ignorePattern.test(filePath);
|
|
return (
|
|
ignoreMatched || (!retainAllFiles && filePath.includes(NODE_MODULES))
|
|
);
|
|
},
|
|
ignorePatternForWatch: ignorePattern,
|
|
perfLogger: this.#startupPerfLogger,
|
|
previousState,
|
|
rootDir,
|
|
roots,
|
|
useWatchman: await this.#shouldUseWatchman(),
|
|
watch,
|
|
watchmanDeferStates,
|
|
});
|
|
const watcher = this.#watcher;
|
|
watcher.on("status", (status) => this.emit("status", status));
|
|
const result = await watcher.crawl();
|
|
this.#startupPerfLogger?.point("buildFileDelta_end");
|
|
return result;
|
|
}
|
|
#maybeReadLink(normalPath, fileMetadata) {
|
|
if (fileMetadata[_constants.default.SYMLINK] === 1) {
|
|
return _fs.promises
|
|
.readlink(this.#pathUtils.normalToAbsolute(normalPath))
|
|
.then((symlinkTarget) => {
|
|
fileMetadata[_constants.default.VISITED] = 1;
|
|
fileMetadata[_constants.default.SYMLINK] = symlinkTarget;
|
|
});
|
|
}
|
|
return null;
|
|
}
|
|
async #applyFileDelta(fileSystem, plugins, delta) {
|
|
this.#startupPerfLogger?.point("applyFileDelta_start");
|
|
const { changedFiles, removedFiles } = delta;
|
|
this.#startupPerfLogger?.point("applyFileDelta_preprocess_start");
|
|
this.#startupPerfLogger?.point("applyFileDelta_remove_start");
|
|
const changeAggregator =
|
|
new _FileSystemChangeAggregator.FileSystemChangeAggregator();
|
|
for (const relativeFilePath of removedFiles) {
|
|
fileSystem.remove(relativeFilePath, changeAggregator);
|
|
}
|
|
this.#startupPerfLogger?.point("applyFileDelta_remove_end");
|
|
const readLinkPromises = [];
|
|
const readLinkErrors = [];
|
|
const filesToProcess = [];
|
|
for (const [normalFilePath, fileData] of changedFiles) {
|
|
if (fileData[_constants.default.VISITED] === 1) {
|
|
continue;
|
|
}
|
|
if (fileData[_constants.default.SYMLINK] === 0) {
|
|
filesToProcess.push([normalFilePath, fileData]);
|
|
} else {
|
|
const maybeReadLink = this.#maybeReadLink(normalFilePath, fileData);
|
|
if (maybeReadLink) {
|
|
readLinkPromises.push(
|
|
maybeReadLink.catch((error) => {
|
|
readLinkErrors.push({
|
|
normalFilePath,
|
|
error,
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
this.#startupPerfLogger?.point("applyFileDelta_preprocess_end");
|
|
debug(
|
|
"Found %d added/modified files and %d symlinks.",
|
|
filesToProcess.length,
|
|
readLinkPromises.length,
|
|
);
|
|
this.#startupPerfLogger?.point("applyFileDelta_process_start");
|
|
const [batchResult] = await Promise.all([
|
|
this.#fileProcessor.processBatch(filesToProcess, {
|
|
computeSha1: this.#options.computeSha1,
|
|
maybeReturnContent: false,
|
|
}),
|
|
Promise.all(readLinkPromises),
|
|
]);
|
|
this.#startupPerfLogger?.point("applyFileDelta_process_end");
|
|
this.#startupPerfLogger?.point("applyFileDelta_missing_start");
|
|
for (const { normalFilePath, error } of batchResult.errors.concat(
|
|
readLinkErrors,
|
|
)) {
|
|
if (["ENOENT", "EACCESS"].includes(error.code)) {
|
|
delta.changedFiles.delete(normalFilePath);
|
|
fileSystem.remove(normalFilePath, changeAggregator);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
this.#startupPerfLogger?.point("applyFileDelta_missing_end");
|
|
this.#startupPerfLogger?.point("applyFileDelta_add_start");
|
|
fileSystem.bulkAddOrModify(changedFiles, changeAggregator);
|
|
this.#startupPerfLogger?.point("applyFileDelta_add_end");
|
|
this.#startupPerfLogger?.point("applyFileDelta_updatePlugins_start");
|
|
this.#plugins.forEach(({ plugin, dataIdx }) => {
|
|
plugin.onChanged(
|
|
changeAggregator.getMappedView(
|
|
dataIdx != null ? (metadata) => metadata[dataIdx] : () => null,
|
|
),
|
|
);
|
|
});
|
|
this.#startupPerfLogger?.point("applyFileDelta_updatePlugins_end");
|
|
this.#startupPerfLogger?.point("applyFileDelta_end");
|
|
return changeAggregator;
|
|
}
|
|
async #takeSnapshotAndPersist(
|
|
fileSystem,
|
|
clocks,
|
|
plugins,
|
|
changedSinceCacheRead,
|
|
) {
|
|
this.#startupPerfLogger?.point("persist_start");
|
|
await this.#cacheManager.write(
|
|
() => ({
|
|
clocks: new Map(clocks),
|
|
fileSystemData: fileSystem.getSerializableSnapshot(),
|
|
plugins: new Map(
|
|
plugins.map(({ plugin }) => [
|
|
plugin.name,
|
|
plugin.getSerializableSnapshot(),
|
|
]),
|
|
),
|
|
}),
|
|
{
|
|
changedSinceCacheRead,
|
|
eventSource: {
|
|
onChange: (cb) => {
|
|
this.on("change", cb);
|
|
this.on("metadata", cb);
|
|
return () => {
|
|
this.removeListener("change", cb);
|
|
this.removeListener("metadata", cb);
|
|
};
|
|
},
|
|
},
|
|
onWriteError: (error) => {
|
|
this.#console.warn("[metro-file-map] Cache write error\n:", error);
|
|
},
|
|
},
|
|
);
|
|
this.#startupPerfLogger?.point("persist_end");
|
|
}
|
|
async #watch(fileSystem, clocks, plugins) {
|
|
this.#startupPerfLogger?.point("watch_start");
|
|
if (!this.#options.watch) {
|
|
this.#startupPerfLogger?.point("watch_end");
|
|
return;
|
|
}
|
|
const hasWatchedExtension = (filePath) =>
|
|
this.#options.extensions.some((ext) => filePath.endsWith(ext));
|
|
let nextEmit = null;
|
|
const emitChange = () => {
|
|
if (nextEmit == null) {
|
|
return;
|
|
}
|
|
const { events, firstEventTimestamp, firstEnqueuedTimestamp } = nextEmit;
|
|
const changeAggregator =
|
|
new _FileSystemChangeAggregator.FileSystemChangeAggregator();
|
|
for (const event of events) {
|
|
const { relativeFilePath, clock } = event;
|
|
if (event.type === "delete") {
|
|
fileSystem.remove(relativeFilePath, changeAggregator);
|
|
} else {
|
|
fileSystem.addOrModify(
|
|
relativeFilePath,
|
|
event.metadata,
|
|
changeAggregator,
|
|
);
|
|
}
|
|
this.#updateClock(clocks, clock);
|
|
}
|
|
const changeSize = changeAggregator.getSize();
|
|
if (changeSize === 0) {
|
|
nextEmit = null;
|
|
return;
|
|
}
|
|
const _netChange = changeAggregator.getView();
|
|
this.#plugins.forEach(({ plugin, dataIdx }) => {
|
|
plugin.onChanged(
|
|
changeAggregator.getMappedView(
|
|
dataIdx != null ? (metadata) => metadata[dataIdx] : () => null,
|
|
),
|
|
);
|
|
});
|
|
const toPublicMetadata = (metadata) => ({
|
|
isSymlink: metadata[_constants.default.SYMLINK] !== 0,
|
|
modifiedTime: metadata[_constants.default.MTIME] ?? null,
|
|
});
|
|
const changesWithMetadata =
|
|
changeAggregator.getMappedView(toPublicMetadata);
|
|
const hmrPerfLogger = this.#options.perfLoggerFactory?.("HMR", {
|
|
key: this.#getNextChangeID(),
|
|
});
|
|
if (hmrPerfLogger != null) {
|
|
hmrPerfLogger.start({
|
|
timestamp: firstEventTimestamp,
|
|
});
|
|
hmrPerfLogger.point("waitingForChangeInterval_start", {
|
|
timestamp: firstEnqueuedTimestamp,
|
|
});
|
|
hmrPerfLogger.point("waitingForChangeInterval_end");
|
|
hmrPerfLogger.annotate({
|
|
int: {
|
|
changeSize,
|
|
},
|
|
});
|
|
hmrPerfLogger.point("fileChange_start");
|
|
}
|
|
const changeEvent = {
|
|
changes: changesWithMetadata,
|
|
logger: hmrPerfLogger,
|
|
rootDir: this.#options.rootDir,
|
|
};
|
|
this.emit("change", changeEvent);
|
|
nextEmit = null;
|
|
};
|
|
let changeQueue = Promise.resolve();
|
|
const onChange = (change) => {
|
|
if (
|
|
change.event !== "recrawl" &&
|
|
change.metadata &&
|
|
(change.metadata.type === "d" ||
|
|
(change.metadata.type === "f" &&
|
|
!hasWatchedExtension(change.relativePath)) ||
|
|
(!this.#options.enableSymlinks && change.metadata?.type === "l"))
|
|
) {
|
|
return;
|
|
}
|
|
const absoluteFilePath = path.join(
|
|
change.root,
|
|
(0, _normalizePathSeparatorsToSystem.default)(change.relativePath),
|
|
);
|
|
if (this.#options.ignorePattern.test(absoluteFilePath)) {
|
|
return;
|
|
}
|
|
const relativeFilePath =
|
|
this.#pathUtils.absoluteToNormal(absoluteFilePath);
|
|
const onChangeStartTime =
|
|
_perf_hooks.performance.timeOrigin + _perf_hooks.performance.now();
|
|
const enqueueEvent = (event) => {
|
|
nextEmit ??= {
|
|
events: [],
|
|
firstEnqueuedTimestamp:
|
|
_perf_hooks.performance.timeOrigin + _perf_hooks.performance.now(),
|
|
firstEventTimestamp: onChangeStartTime,
|
|
};
|
|
nextEmit.events.push(event);
|
|
};
|
|
changeQueue = changeQueue
|
|
.then(async () => {
|
|
if (
|
|
nextEmit != null &&
|
|
nextEmit.events.find(
|
|
(event) =>
|
|
event.type === change.event &&
|
|
event.relativeFilePath === relativeFilePath &&
|
|
((!event.metadata && !change.metadata) ||
|
|
(event.metadata &&
|
|
change.metadata &&
|
|
event.metadata[_constants.default.MTIME] != null &&
|
|
change.metadata.modifiedTime != null &&
|
|
event.metadata[_constants.default.MTIME] ===
|
|
change.metadata.modifiedTime)),
|
|
)
|
|
) {
|
|
return null;
|
|
}
|
|
if (change.event === "touch") {
|
|
(0, _invariant.default)(
|
|
change.metadata.size != null,
|
|
"since the file exists or changed, it should have known size",
|
|
);
|
|
const fileMetadata = [
|
|
change.metadata.modifiedTime,
|
|
change.metadata.size,
|
|
0,
|
|
null,
|
|
change.metadata.type === "l" ? 1 : 0,
|
|
null,
|
|
];
|
|
try {
|
|
if (change.metadata.type === "l") {
|
|
await this.#maybeReadLink(relativeFilePath, fileMetadata);
|
|
} else {
|
|
await this.#fileProcessor.processRegularFile(
|
|
relativeFilePath,
|
|
fileMetadata,
|
|
{
|
|
computeSha1: this.#options.computeSha1,
|
|
maybeReturnContent: false,
|
|
},
|
|
);
|
|
}
|
|
enqueueEvent({
|
|
clock: change.clock,
|
|
relativeFilePath,
|
|
metadata: fileMetadata,
|
|
type: change.event,
|
|
});
|
|
} catch (e) {
|
|
if (!["ENOENT", "EACCESS"].includes(e.code)) {
|
|
throw e;
|
|
}
|
|
}
|
|
} else if (change.event === "delete") {
|
|
enqueueEvent({
|
|
clock: change.clock,
|
|
relativeFilePath,
|
|
type: "delete",
|
|
});
|
|
} else if (change.event === "recrawl") {
|
|
emitChange();
|
|
const absoluteDirPath = path.join(
|
|
change.root,
|
|
(0, _normalizePathSeparatorsToSystem.default)(
|
|
change.relativePath,
|
|
),
|
|
);
|
|
const subpath = this.#pathUtils.absoluteToNormal(absoluteDirPath);
|
|
const watcher = this.#watcher;
|
|
(0, _invariant.default)(
|
|
watcher != null,
|
|
"Watcher must be initialized",
|
|
);
|
|
const crawlResult = await watcher.recrawl(subpath, fileSystem);
|
|
if (
|
|
crawlResult.changedFiles.size === 0 &&
|
|
crawlResult.removedFiles.size === 0
|
|
) {
|
|
return null;
|
|
}
|
|
const recrawlChangeAggregator = await this.#applyFileDelta(
|
|
fileSystem,
|
|
this.#plugins,
|
|
crawlResult,
|
|
);
|
|
this.#updateClock(clocks, change.clock);
|
|
if (recrawlChangeAggregator.getSize() === 0) {
|
|
return null;
|
|
}
|
|
const toPublicMetadata = (metadata) => ({
|
|
isSymlink: metadata[_constants.default.SYMLINK] !== 0,
|
|
modifiedTime: metadata[_constants.default.MTIME] ?? null,
|
|
});
|
|
const changesWithMetadata =
|
|
recrawlChangeAggregator.getMappedView(toPublicMetadata);
|
|
const changeEvent = {
|
|
changes: changesWithMetadata,
|
|
logger: null,
|
|
rootDir: this.#options.rootDir,
|
|
};
|
|
this.emit("change", changeEvent);
|
|
} else {
|
|
throw new Error(
|
|
`metro-file-map: Unrecognized event type from watcher: ${change.event}`,
|
|
);
|
|
}
|
|
return null;
|
|
})
|
|
.catch((error) => {
|
|
this.#console.error(
|
|
`metro-file-map: watch error:\n ${error.stack}\n`,
|
|
);
|
|
});
|
|
};
|
|
this.#changeInterval = setInterval(emitChange, CHANGE_INTERVAL);
|
|
(0, _invariant.default)(
|
|
this.#watcher != null,
|
|
"Expected #watcher to have been initialised by build()",
|
|
);
|
|
await this.#watcher.watch(onChange);
|
|
if (this.#options.healthCheck.enabled) {
|
|
const performHealthCheck = () => {
|
|
if (!this.#watcher) {
|
|
return;
|
|
}
|
|
this.#watcher
|
|
.checkHealth(this.#options.healthCheck.timeout)
|
|
.then((result) => {
|
|
this.emit("healthCheck", result);
|
|
});
|
|
};
|
|
performHealthCheck();
|
|
this.#healthCheckInterval = setInterval(
|
|
performHealthCheck,
|
|
this.#options.healthCheck.interval,
|
|
);
|
|
}
|
|
this.#startupPerfLogger?.point("watch_end");
|
|
}
|
|
async end() {
|
|
if (this.#changeInterval) {
|
|
clearInterval(this.#changeInterval);
|
|
}
|
|
if (this.#healthCheckInterval) {
|
|
clearInterval(this.#healthCheckInterval);
|
|
}
|
|
this.#crawlerAbortController.abort();
|
|
await Promise.all([
|
|
this.#fileProcessor.end(),
|
|
this.#watcher?.close(),
|
|
this.#cacheManager.end(),
|
|
]);
|
|
}
|
|
async #shouldUseWatchman() {
|
|
if (!this.#options.useWatchman) {
|
|
return false;
|
|
}
|
|
if (!this.#canUseWatchmanPromise) {
|
|
this.#canUseWatchmanPromise = (0, _checkWatchmanCapabilities.default)(
|
|
WATCHMAN_REQUIRED_CAPABILITIES,
|
|
)
|
|
.then(({ version }) => {
|
|
this.#startupPerfLogger?.annotate({
|
|
string: {
|
|
watchmanVersion: version,
|
|
},
|
|
});
|
|
return true;
|
|
})
|
|
.catch((e) => {
|
|
this.#startupPerfLogger?.annotate({
|
|
string: {
|
|
watchmanFailedCapabilityCheck: e?.message ?? "[missing]",
|
|
},
|
|
});
|
|
return false;
|
|
});
|
|
}
|
|
return this.#canUseWatchmanPromise;
|
|
}
|
|
#getNextChangeID() {
|
|
if (this.#changeID >= Number.MAX_SAFE_INTEGER) {
|
|
this.#changeID = 0;
|
|
}
|
|
return ++this.#changeID;
|
|
}
|
|
#updateClock(clocks, newClock) {
|
|
if (newClock == null) {
|
|
return;
|
|
}
|
|
const [absoluteWatchRoot, clockSpec] = newClock;
|
|
const relativeFsRoot = this.#pathUtils.absoluteToNormal(absoluteWatchRoot);
|
|
clocks.set(
|
|
(0, _normalizePathSeparatorsToPosix.default)(relativeFsRoot),
|
|
clockSpec,
|
|
);
|
|
}
|
|
static H = _constants.default;
|
|
}
|
|
exports.default = FileMap;
|
|
const mapIterable = (it, fn) =>
|
|
(function* mapped() {
|
|
for (const item of it) {
|
|
yield fn(item);
|
|
}
|
|
})();
|