first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

12
node_modules/metro-file-map/README.md generated vendored Normal file
View File

@@ -0,0 +1,12 @@
# \[Experimental\] Metro File Map
🚇 File system crawling, watching and mapping for [Metro](https://metrobundler.dev/).
Originally a fork of [`jest-haste-map`](https://github.com/facebook/jest/tree/main/packages/jest-haste-map).
This entire package should be considered "experimental" for the time being -
the API is considered internal and changes will not be semver-breaking.
If you need to rely on `metro-file-map` APIs directly please
[raise an issue](https://github.com/facebook/metro/issues/new) to discuss your
use case.

37
node_modules/metro-file-map/package.json generated vendored Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "metro-file-map",
"version": "0.83.3",
"description": "[Experimental] - 🚇 File crawling, watching and mapping for Metro",
"main": "src/index.js",
"exports": {
".": "./src/index.js",
"./package.json": "./package.json",
"./private/*": "./src/*.js"
},
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro.git"
},
"scripts": {
"prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src",
"cleanup-release": "test ! -e build && mv src build && mv src.real src"
},
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"fb-watchman": "^2.0.0",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"nullthrows": "^1.1.1",
"walker": "^1.0.7"
},
"devDependencies": {
"slash": "^3.0.0"
},
"engines": {
"node": ">=20.19.4"
}
}

24
node_modules/metro-file-map/src/Watcher.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
export type HealthCheckResult =
| {type: 'error'; timeout: number; error: Error; watcher: string | null}
| {
type: 'success';
timeout: number;
timeElapsed: number;
watcher: string | null;
}
| {
type: 'timeout';
timeout: number;
watcher: string | null;
pauseReason: string | null;
};

265
node_modules/metro-file-map/src/Watcher.js generated vendored Normal file
View File

@@ -0,0 +1,265 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.Watcher = void 0;
var _node = _interopRequireDefault(require("./crawlers/node"));
var _watchman = _interopRequireDefault(require("./crawlers/watchman"));
var _common = require("./watchers/common");
var _FallbackWatcher = _interopRequireDefault(
require("./watchers/FallbackWatcher"),
);
var _NativeWatcher = _interopRequireDefault(
require("./watchers/NativeWatcher"),
);
var _WatchmanWatcher = _interopRequireDefault(
require("./watchers/WatchmanWatcher"),
);
var _events = _interopRequireDefault(require("events"));
var fs = _interopRequireWildcard(require("fs"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
var path = _interopRequireWildcard(require("path"));
var _perf_hooks = require("perf_hooks");
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:Watcher");
const MAX_WAIT_TIME = 240000;
let nextInstanceId = 0;
class Watcher extends _events.default {
_backends = [];
_nextHealthCheckId = 0;
_pendingHealthChecks = new Map();
constructor(options) {
super();
this._options = options;
this._instanceId = nextInstanceId++;
}
async crawl() {
this._options.perfLogger?.point("crawl_start");
const options = this._options;
const ignoreForCrawl = (filePath) =>
options.ignoreForCrawl(filePath) ||
path.basename(filePath).startsWith(this._options.healthCheckFilePrefix);
const crawl = options.useWatchman ? _watchman.default : _node.default;
let crawler = crawl === _watchman.default ? "watchman" : "node";
options.abortSignal.throwIfAborted();
const crawlerOptions = {
abortSignal: options.abortSignal,
computeSha1: options.computeSha1,
console: options.console,
includeSymlinks: options.enableSymlinks,
extensions: options.extensions,
forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
ignore: ignoreForCrawl,
onStatus: (status) => {
this.emit("status", status);
},
perfLogger: options.perfLogger,
previousState: options.previousState,
rootDir: options.rootDir,
roots: options.roots,
};
const retry = (error) => {
if (crawl === _watchman.default) {
crawler = "node";
options.console.warn(
"metro-file-map: Watchman crawl failed. Retrying once with node " +
"crawler.\n" +
" Usually this happens when watchman isn't running. Create an " +
"empty `.watchmanconfig` file in your project's root folder or " +
"initialize a git or hg repository in your project.\n" +
" " +
error.toString(),
);
return (0, _node.default)(crawlerOptions).catch((e) => {
throw new Error(
"Crawler retry failed:\n" +
` Original error: ${error.message}\n` +
` Retry error: ${e.message}\n`,
);
});
}
throw error;
};
const logEnd = (delta) => {
debug(
'Crawler "%s" returned %d added/modified, %d removed, %d clock(s).',
crawler,
delta.changedFiles.size,
delta.removedFiles.size,
delta.clocks?.size ?? 0,
);
this._options.perfLogger?.point("crawl_end");
return delta;
};
debug('Beginning crawl with "%s".', crawler);
try {
return crawl(crawlerOptions).catch(retry).then(logEnd);
} catch (error) {
return retry(error).then(logEnd);
}
}
async watch(onChange) {
const { extensions, ignorePatternForWatch, useWatchman } = this._options;
const WatcherImpl = useWatchman
? _WatchmanWatcher.default
: _NativeWatcher.default.isSupported()
? _NativeWatcher.default
: _FallbackWatcher.default;
let watcher = "fallback";
if (WatcherImpl === _WatchmanWatcher.default) {
watcher = "watchman";
} else if (WatcherImpl === _NativeWatcher.default) {
watcher = "native";
}
debug(`Using watcher: ${watcher}`);
this._options.perfLogger?.annotate({
string: {
watcher,
},
});
this._activeWatcher = watcher;
const createWatcherBackend = (root) => {
const watcherOptions = {
dot: true,
globs: [
"**/package.json",
"**/" + this._options.healthCheckFilePrefix + "*",
...extensions.map((extension) => "**/*." + extension),
],
ignored: ignorePatternForWatch,
watchmanDeferStates: this._options.watchmanDeferStates,
};
const watcher = new WatcherImpl(root, watcherOptions);
return new Promise(async (resolve, reject) => {
const rejectTimeout = setTimeout(
() => reject(new Error("Failed to start watch mode.")),
MAX_WAIT_TIME,
);
watcher.onFileEvent((change) => {
const basename = path.basename(change.relativePath);
if (basename.startsWith(this._options.healthCheckFilePrefix)) {
if (change.event === _common.TOUCH_EVENT) {
debug(
"Observed possible health check cookie: %s in %s",
change.relativePath,
root,
);
this._handleHealthCheckObservation(basename);
}
return;
}
onChange(change);
});
await watcher.startWatching();
clearTimeout(rejectTimeout);
resolve(watcher);
});
};
this._backends = await Promise.all(
this._options.roots.map(createWatcherBackend),
);
}
_handleHealthCheckObservation(basename) {
const resolveHealthCheck = this._pendingHealthChecks.get(basename);
if (!resolveHealthCheck) {
return;
}
resolveHealthCheck();
}
async close() {
await Promise.all(this._backends.map((watcher) => watcher.stopWatching()));
this._activeWatcher = null;
}
async checkHealth(timeout) {
const healthCheckId = this._nextHealthCheckId++;
if (healthCheckId === Number.MAX_SAFE_INTEGER) {
this._nextHealthCheckId = 0;
}
const watcher = this._activeWatcher;
const basename =
this._options.healthCheckFilePrefix +
"-" +
process.pid +
"-" +
this._instanceId +
"-" +
healthCheckId;
const healthCheckPath = path.join(this._options.rootDir, basename);
let result;
const timeoutPromise = new Promise((resolve) =>
setTimeout(resolve, timeout),
).then(() => {
if (!result) {
result = {
type: "timeout",
pauseReason: this._backends[0]?.getPauseReason(),
timeout,
watcher,
};
}
});
const startTime = _perf_hooks.performance.now();
debug("Creating health check cookie: %s", healthCheckPath);
const creationPromise = fs.promises
.writeFile(healthCheckPath, String(startTime))
.catch((error) => {
if (!result) {
result = {
type: "error",
error,
timeout,
watcher,
};
}
});
const observationPromise = new Promise((resolve) => {
this._pendingHealthChecks.set(basename, resolve);
}).then(() => {
if (!result) {
result = {
type: "success",
timeElapsed: _perf_hooks.performance.now() - startTime,
timeout,
watcher,
};
}
});
await Promise.race([
timeoutPromise,
creationPromise.then(() => observationPromise),
]);
this._pendingHealthChecks.delete(basename);
creationPromise.then(() =>
fs.promises.unlink(healthCheckPath).catch(() => {}),
);
debug("Health check result: %o", result);
return (0, _nullthrows.default)(result);
}
}
exports.Watcher = Watcher;

311
node_modules/metro-file-map/src/Watcher.js.flow generated vendored Normal file
View File

@@ -0,0 +1,311 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {
Console,
CrawlerOptions,
FileData,
Path,
PerfLogger,
WatcherBackend,
WatcherBackendChangeEvent,
WatchmanClocks,
} from './flow-types';
import type {WatcherOptions as WatcherBackendOptions} from './watchers/common';
import nodeCrawl from './crawlers/node';
import watchmanCrawl from './crawlers/watchman';
import {TOUCH_EVENT} from './watchers/common';
import FallbackWatcher from './watchers/FallbackWatcher';
import NativeWatcher from './watchers/NativeWatcher';
import WatchmanWatcher from './watchers/WatchmanWatcher';
import EventEmitter from 'events';
import * as fs from 'fs';
import nullthrows from 'nullthrows';
import * as path from 'path';
import {performance} from 'perf_hooks';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:Watcher');
const MAX_WAIT_TIME = 240000;
type CrawlResult = {
changedFiles: FileData,
clocks?: WatchmanClocks,
removedFiles: Set<Path>,
};
type WatcherOptions = {
abortSignal: AbortSignal,
computeSha1: boolean,
console: Console,
enableSymlinks: boolean,
extensions: $ReadOnlyArray<string>,
forceNodeFilesystemAPI: boolean,
healthCheckFilePrefix: string,
ignoreForCrawl: string => boolean,
ignorePatternForWatch: RegExp,
previousState: CrawlerOptions['previousState'],
perfLogger: ?PerfLogger,
roots: $ReadOnlyArray<string>,
rootDir: string,
useWatchman: boolean,
watch: boolean,
watchmanDeferStates: $ReadOnlyArray<string>,
};
let nextInstanceId = 0;
export type HealthCheckResult =
| {type: 'error', timeout: number, error: Error, watcher: ?string}
| {type: 'success', timeout: number, timeElapsed: number, watcher: ?string}
| {type: 'timeout', timeout: number, watcher: ?string, pauseReason: ?string};
export class Watcher extends EventEmitter {
_options: WatcherOptions;
_backends: $ReadOnlyArray<WatcherBackend> = [];
_instanceId: number;
_nextHealthCheckId: number = 0;
_pendingHealthChecks: Map</* basename */ string, /* resolve */ () => void> =
new Map();
_activeWatcher: ?string;
constructor(options: WatcherOptions) {
super();
this._options = options;
this._instanceId = nextInstanceId++;
}
async crawl(): Promise<CrawlResult> {
this._options.perfLogger?.point('crawl_start');
const options = this._options;
const ignoreForCrawl = (filePath: string) =>
options.ignoreForCrawl(filePath) ||
path.basename(filePath).startsWith(this._options.healthCheckFilePrefix);
const crawl = options.useWatchman ? watchmanCrawl : nodeCrawl;
let crawler = crawl === watchmanCrawl ? 'watchman' : 'node';
options.abortSignal.throwIfAborted();
const crawlerOptions: CrawlerOptions = {
abortSignal: options.abortSignal,
computeSha1: options.computeSha1,
console: options.console,
includeSymlinks: options.enableSymlinks,
extensions: options.extensions,
forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
ignore: ignoreForCrawl,
onStatus: status => {
this.emit('status', status);
},
perfLogger: options.perfLogger,
previousState: options.previousState,
rootDir: options.rootDir,
roots: options.roots,
};
const retry = (error: Error): Promise<CrawlResult> => {
if (crawl === watchmanCrawl) {
crawler = 'node';
options.console.warn(
'metro-file-map: Watchman crawl failed. Retrying once with node ' +
'crawler.\n' +
" Usually this happens when watchman isn't running. Create an " +
"empty `.watchmanconfig` file in your project's root folder or " +
'initialize a git or hg repository in your project.\n' +
' ' +
error.toString(),
);
// $FlowFixMe[incompatible-type] Found when updating Promise type definition
return nodeCrawl(crawlerOptions).catch<CrawlResult>(e => {
throw new Error(
'Crawler retry failed:\n' +
` Original error: ${error.message}\n` +
` Retry error: ${e.message}\n`,
);
});
}
throw error;
};
const logEnd = (delta: CrawlResult): CrawlResult => {
debug(
'Crawler "%s" returned %d added/modified, %d removed, %d clock(s).',
crawler,
delta.changedFiles.size,
delta.removedFiles.size,
delta.clocks?.size ?? 0,
);
this._options.perfLogger?.point('crawl_end');
return delta;
};
debug('Beginning crawl with "%s".', crawler);
try {
// $FlowFixMe[incompatible-type] Found when updating Promise type definition
return crawl(crawlerOptions).catch<CrawlResult>(retry).then(logEnd);
} catch (error) {
return retry(error).then(logEnd);
}
}
async watch(onChange: (change: WatcherBackendChangeEvent) => void) {
const {extensions, ignorePatternForWatch, useWatchman} = this._options;
// WatchmanWatcher > NativeWatcher > FallbackWatcher
const WatcherImpl = useWatchman
? WatchmanWatcher
: NativeWatcher.isSupported()
? NativeWatcher
: FallbackWatcher;
let watcher = 'fallback';
if (WatcherImpl === WatchmanWatcher) {
watcher = 'watchman';
} else if (WatcherImpl === NativeWatcher) {
watcher = 'native';
}
debug(`Using watcher: ${watcher}`);
this._options.perfLogger?.annotate({string: {watcher}});
this._activeWatcher = watcher;
const createWatcherBackend = (root: Path): Promise<WatcherBackend> => {
const watcherOptions: WatcherBackendOptions = {
dot: true,
globs: [
// Ensure we always include package.json files, which are crucial for
/// module resolution.
'**/package.json',
// Ensure we always watch any health check files
'**/' + this._options.healthCheckFilePrefix + '*',
...extensions.map(extension => '**/*.' + extension),
],
ignored: ignorePatternForWatch,
watchmanDeferStates: this._options.watchmanDeferStates,
};
const watcher: WatcherBackend = new WatcherImpl(root, watcherOptions);
return new Promise(async (resolve, reject) => {
const rejectTimeout = setTimeout(
() => reject(new Error('Failed to start watch mode.')),
MAX_WAIT_TIME,
);
watcher.onFileEvent(change => {
const basename = path.basename(change.relativePath);
if (basename.startsWith(this._options.healthCheckFilePrefix)) {
if (change.event === TOUCH_EVENT) {
debug(
'Observed possible health check cookie: %s in %s',
change.relativePath,
root,
);
this._handleHealthCheckObservation(basename);
}
return;
}
onChange(change);
});
await watcher.startWatching();
clearTimeout(rejectTimeout);
resolve(watcher);
});
};
this._backends = await Promise.all(
this._options.roots.map(createWatcherBackend),
);
}
_handleHealthCheckObservation(basename: string) {
const resolveHealthCheck = this._pendingHealthChecks.get(basename);
if (!resolveHealthCheck) {
return;
}
resolveHealthCheck();
}
async close() {
await Promise.all(this._backends.map(watcher => watcher.stopWatching()));
this._activeWatcher = null;
}
async checkHealth(timeout: number): Promise<HealthCheckResult> {
const healthCheckId = this._nextHealthCheckId++;
if (healthCheckId === Number.MAX_SAFE_INTEGER) {
this._nextHealthCheckId = 0;
}
const watcher = this._activeWatcher;
const basename =
this._options.healthCheckFilePrefix +
'-' +
process.pid +
'-' +
this._instanceId +
'-' +
healthCheckId;
const healthCheckPath = path.join(this._options.rootDir, basename);
let result: ?HealthCheckResult;
const timeoutPromise = new Promise(resolve =>
setTimeout(resolve, timeout),
).then(() => {
if (!result) {
result = {
type: 'timeout',
pauseReason: this._backends[0]?.getPauseReason(),
timeout,
watcher,
};
}
});
const startTime = performance.now();
debug('Creating health check cookie: %s', healthCheckPath);
const creationPromise = fs.promises
.writeFile(healthCheckPath, String(startTime))
.catch(error => {
if (!result) {
result = {
type: 'error',
error,
timeout,
watcher,
};
}
});
const observationPromise = new Promise(resolve => {
this._pendingHealthChecks.set(basename, resolve);
}).then(() => {
if (!result) {
result = {
type: 'success',
timeElapsed: performance.now() - startTime,
timeout,
watcher,
};
}
});
await Promise.race([
timeoutPromise,
creationPromise.then(() => observationPromise),
]);
this._pendingHealthChecks.delete(basename);
// Chain a deletion to the creation promise (which may not have even settled yet!),
// don't await it, and swallow errors. This is just best-effort cleanup.
// $FlowFixMe[unused-promise]
creationPromise.then(() =>
fs.promises.unlink(healthCheckPath).catch(() => {}),
);
debug('Health check result: %o', result);
return nullthrows(result);
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {
BuildParameters,
CacheData,
CacheManager,
CacheManagerWriteOptions,
} from '../flow-types';
export interface DiskCacheConfig {
buildParameters: BuildParameters;
cacheFilePrefix?: string | null;
cacheDirectory?: string | null;
}
export class DiskCacheManager implements CacheManager {
constructor(options: DiskCacheConfig);
static getCacheFilePath(
buildParameters: BuildParameters,
cacheFilePrefix?: string | null,
cacheDirectory?: string | null,
): string;
getCacheFilePath(): string;
read(): Promise<CacheData | null>;
write(
getSnapshot: () => CacheData,
opts: CacheManagerWriteOptions,
): Promise<void>;
end(): Promise<void>;
}

View File

@@ -0,0 +1,117 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.DiskCacheManager = void 0;
var _rootRelativeCacheKeys = _interopRequireDefault(
require("../lib/rootRelativeCacheKeys"),
);
var _fs = require("fs");
var _os = require("os");
var _path = _interopRequireDefault(require("path"));
var _timers = require("timers");
var _v = require("v8");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:FileMapCache");
const DEFAULT_PREFIX = "metro-file-map";
const DEFAULT_DIRECTORY = (0, _os.tmpdir)();
const DEFAULT_AUTO_SAVE_DEBOUNCE_MS = 5000;
class DiskCacheManager {
#autoSaveOpts;
#cachePath;
#debounceTimeout = null;
#writePromise = Promise.resolve();
#hasUnwrittenChanges = false;
#tryWrite;
#stopListening;
constructor(
{ buildParameters },
{ autoSave = {}, cacheDirectory, cacheFilePrefix },
) {
this.#cachePath = DiskCacheManager.getCacheFilePath(
buildParameters,
cacheFilePrefix,
cacheDirectory,
);
if (autoSave) {
const { debounceMs = DEFAULT_AUTO_SAVE_DEBOUNCE_MS } =
autoSave === true ? {} : autoSave;
this.#autoSaveOpts = {
debounceMs,
};
}
}
static getCacheFilePath(buildParameters, cacheFilePrefix, cacheDirectory) {
const { rootDirHash, relativeConfigHash } = (0,
_rootRelativeCacheKeys.default)(buildParameters);
return _path.default.join(
cacheDirectory ?? DEFAULT_DIRECTORY,
`${cacheFilePrefix ?? DEFAULT_PREFIX}-${rootDirHash}-${relativeConfigHash}`,
);
}
getCacheFilePath() {
return this.#cachePath;
}
async read() {
try {
return (0, _v.deserialize)(await _fs.promises.readFile(this.#cachePath));
} catch (e) {
if (e?.code === "ENOENT") {
return null;
}
throw e;
}
}
async write(
getSnapshot,
{ changedSinceCacheRead, eventSource, onWriteError },
) {
const tryWrite = (this.#tryWrite = () => {
this.#writePromise = this.#writePromise
.then(async () => {
if (!this.#hasUnwrittenChanges) {
return;
}
const data = getSnapshot();
this.#hasUnwrittenChanges = false;
await _fs.promises.writeFile(
this.#cachePath,
(0, _v.serialize)(data),
);
debug("Written cache to %s", this.#cachePath);
})
.catch(onWriteError);
return this.#writePromise;
});
if (this.#autoSaveOpts) {
const autoSave = this.#autoSaveOpts;
this.#stopListening?.();
this.#stopListening = eventSource.onChange(() => {
this.#hasUnwrittenChanges = true;
if (this.#debounceTimeout) {
this.#debounceTimeout.refresh();
} else {
this.#debounceTimeout = (0, _timers.setTimeout)(
() => tryWrite(),
autoSave.debounceMs,
).unref();
}
});
}
if (changedSinceCacheRead) {
this.#hasUnwrittenChanges = true;
await tryWrite();
}
}
async end() {
if (this.#debounceTimeout) {
(0, _timers.clearTimeout)(this.#debounceTimeout);
}
this.#stopListening?.();
await this.#tryWrite?.();
}
}
exports.DiskCacheManager = DiskCacheManager;

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {
BuildParameters,
CacheData,
CacheManager,
CacheManagerFactoryOptions,
CacheManagerWriteOptions,
} from '../flow-types';
import rootRelativeCacheKeys from '../lib/rootRelativeCacheKeys';
import {promises as fsPromises} from 'fs';
import {tmpdir} from 'os';
import path from 'path';
import {Timeout, clearTimeout, setTimeout} from 'timers';
import {deserialize, serialize} from 'v8';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:FileMapCache');
type AutoSaveOptions = $ReadOnly<{
debounceMs: number,
}>;
type DiskCacheConfig = $ReadOnly<{
autoSave?: Partial<AutoSaveOptions> | boolean,
cacheFilePrefix?: ?string,
cacheDirectory?: ?string,
}>;
const DEFAULT_PREFIX = 'metro-file-map';
const DEFAULT_DIRECTORY = tmpdir();
const DEFAULT_AUTO_SAVE_DEBOUNCE_MS = 5000;
export class DiskCacheManager implements CacheManager {
+#autoSaveOpts: ?AutoSaveOptions;
+#cachePath: string;
#debounceTimeout: ?Timeout = null;
#writePromise: Promise<void> = Promise.resolve();
#hasUnwrittenChanges: boolean = false;
#tryWrite: ?() => Promise<void>;
#stopListening: ?() => void;
constructor(
{buildParameters}: CacheManagerFactoryOptions,
{autoSave = {}, cacheDirectory, cacheFilePrefix}: DiskCacheConfig,
) {
this.#cachePath = DiskCacheManager.getCacheFilePath(
buildParameters,
cacheFilePrefix,
cacheDirectory,
);
// Normalise auto-save options.
if (autoSave) {
const {debounceMs = DEFAULT_AUTO_SAVE_DEBOUNCE_MS} =
autoSave === true ? {} : autoSave;
this.#autoSaveOpts = {debounceMs};
}
}
static getCacheFilePath(
buildParameters: BuildParameters,
cacheFilePrefix?: ?string,
cacheDirectory?: ?string,
): string {
const {rootDirHash, relativeConfigHash} =
rootRelativeCacheKeys(buildParameters);
return path.join(
cacheDirectory ?? DEFAULT_DIRECTORY,
`${
cacheFilePrefix ?? DEFAULT_PREFIX
}-${rootDirHash}-${relativeConfigHash}`,
);
}
getCacheFilePath(): string {
return this.#cachePath;
}
async read(): Promise<?CacheData> {
try {
return deserialize(await fsPromises.readFile(this.#cachePath));
} catch (e) {
if (e?.code === 'ENOENT') {
// Cache file not found - not considered an error.
return null;
}
// Rethrow anything else.
throw e;
}
}
async write(
getSnapshot: () => CacheData,
{
changedSinceCacheRead,
eventSource,
onWriteError,
}: CacheManagerWriteOptions,
): Promise<void> {
// Initialise a writer function using a promise queue to ensure writes are
// sequenced.
const tryWrite = (this.#tryWrite = () => {
this.#writePromise = this.#writePromise
.then(async () => {
if (!this.#hasUnwrittenChanges) {
return;
}
const data = getSnapshot();
this.#hasUnwrittenChanges = false;
await fsPromises.writeFile(this.#cachePath, serialize(data));
debug('Written cache to %s', this.#cachePath);
})
.catch(onWriteError);
return this.#writePromise;
});
// Set up auto-save on changes, if enabled.
if (this.#autoSaveOpts) {
const autoSave = this.#autoSaveOpts;
this.#stopListening?.();
this.#stopListening = eventSource.onChange(() => {
this.#hasUnwrittenChanges = true;
if (this.#debounceTimeout) {
this.#debounceTimeout.refresh();
} else {
this.#debounceTimeout = setTimeout(
() => tryWrite(),
autoSave.debounceMs,
).unref();
}
});
}
// Write immediately if state has changed since the cache was read.
if (changedSinceCacheRead) {
this.#hasUnwrittenChanges = true;
await tryWrite();
}
}
async end() {
// Clear any timers
if (this.#debounceTimeout) {
clearTimeout(this.#debounceTimeout);
}
// Remove event listeners
this.#stopListening?.();
// Flush unwritten changes to disk (no-op if no changes)
await this.#tryWrite?.();
}
}

19
node_modules/metro-file-map/src/constants.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
"use strict";
const constants = {
DEPENDENCY_DELIM: "\0",
MTIME: 0,
SIZE: 1,
VISITED: 2,
DEPENDENCIES: 3,
SHA1: 4,
SYMLINK: 5,
ID: 6,
PATH: 0,
TYPE: 1,
MODULE: 0,
PACKAGE: 1,
GENERIC_PLATFORM: "g",
NATIVE_PLATFORM: "native",
};
module.exports = constants;

54
node_modules/metro-file-map/src/constants.js.flow generated vendored Normal file
View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @noformat - Flow comment syntax
*/
/* eslint-disable import/no-commonjs */
/*
* This file exports a set of constants that are used for Jest's haste map
* serialization. On very large repositories, the haste map cache becomes very
* large to the point where it is the largest overhead in starting up Jest.
*
* This constant key map allows to keep the map smaller without having to build
* a custom serialization library.
*/
/*::
import type {HType} from './flow-types';
*/
'use strict';
const constants/*: HType */ = {
/* dependency serialization */
DEPENDENCY_DELIM: '\0',
/* file map attributes */
MTIME: 0,
SIZE: 1,
VISITED: 2,
DEPENDENCIES: 3,
SHA1: 4,
SYMLINK: 5,
ID: 6,
/* module map attributes */
PATH: 0,
TYPE: 1,
/* module types */
MODULE: 0,
PACKAGE: 1,
/* platforms */
GENERIC_PLATFORM: 'g',
NATIVE_PLATFORM: 'native',
};
module.exports = constants;

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = hasNativeFindSupport;
var _child_process = require("child_process");
async function hasNativeFindSupport() {
try {
return await new Promise((resolve) => {
const args = [
".",
"-type",
"f",
"(",
"-iname",
"*.ts",
"-o",
"-iname",
"*.js",
")",
];
const child = (0, _child_process.spawn)("find", args, {
cwd: __dirname,
});
child.on("error", () => {
resolve(false);
});
child.on("exit", (code) => {
resolve(code === 0);
});
});
} catch {
return false;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import {spawn} from 'child_process';
export default async function hasNativeFindSupport(): Promise<boolean> {
try {
return await new Promise(resolve => {
// Check the find binary supports the non-POSIX -iname parameter wrapped in parens.
const args = [
'.',
'-type',
'f',
'(',
'-iname',
'*.ts',
'-o',
'-iname',
'*.js',
')',
];
const child = spawn('find', args, {cwd: __dirname});
child.on('error', () => {
resolve(false);
});
child.on('exit', code => {
resolve(code === 0);
});
});
} catch {
return false;
}
}

225
node_modules/metro-file-map/src/crawlers/node/index.js generated vendored Normal file
View File

@@ -0,0 +1,225 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = nodeCrawl;
var _RootPathUtils = require("../../lib/RootPathUtils");
var _hasNativeFindSupport = _interopRequireDefault(
require("./hasNativeFindSupport"),
);
var _child_process = require("child_process");
var fs = _interopRequireWildcard(require("graceful-fs"));
var _os = require("os");
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:NodeCrawler");
function find(
roots,
extensions,
ignore,
includeSymlinks,
rootDir,
console,
callback,
) {
const result = new Map();
let activeCalls = 0;
const pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
function search(directory) {
activeCalls++;
fs.readdir(
directory,
{
withFileTypes: true,
},
(err, entries) => {
activeCalls--;
if (err) {
console.warn(
`Error "${err.code ?? err.message}" reading contents of "${directory}", skipping. Add this directory to your ignore list to exclude it.`,
);
} else {
entries.forEach((entry) => {
const file = path.join(directory, entry.name.toString());
if (ignore(file)) {
return;
}
if (entry.isSymbolicLink() && !includeSymlinks) {
return;
}
if (entry.isDirectory()) {
search(file);
return;
}
activeCalls++;
fs.lstat(file, (err, stat) => {
activeCalls--;
if (!err && stat) {
const ext = path.extname(file).substr(1);
if (stat.isSymbolicLink() || extensions.includes(ext)) {
result.set(pathUtils.absoluteToNormal(file), [
stat.mtime.getTime(),
stat.size,
0,
"",
null,
stat.isSymbolicLink() ? 1 : 0,
"",
]);
}
}
if (activeCalls === 0) {
callback(result);
}
});
});
}
if (activeCalls === 0) {
callback(result);
}
},
);
}
if (roots.length > 0) {
roots.forEach(search);
} else {
callback(result);
}
}
function findNative(
roots,
extensions,
ignore,
includeSymlinks,
rootDir,
console,
callback,
) {
const extensionClause = extensions.length
? `( ${extensions.map((ext) => `-iname *.${ext}`).join(" -o ")} )`
: "";
const expression = `( ( -type f ${extensionClause} ) ${includeSymlinks ? "-o -type l " : ""})`;
const pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
const child = (0, _child_process.spawn)(
"find",
roots.concat(expression.split(" ")),
);
let stdout = "";
if (child.stdout == null) {
throw new Error(
"stdout is null - this should never happen. Please open up an issue at https://github.com/facebook/metro",
);
}
child.stdout.setEncoding("utf-8");
child.stdout.on("data", (data) => (stdout += data));
child.stdout.on("close", () => {
const lines = stdout
.trim()
.split("\n")
.filter((x) => !ignore(x));
const result = new Map();
let count = lines.length;
if (!count) {
callback(new Map());
} else {
lines.forEach((path) => {
fs.lstat(path, (err, stat) => {
if (!err && stat) {
result.set(pathUtils.absoluteToNormal(path), [
stat.mtime.getTime(),
stat.size,
0,
"",
null,
stat.isSymbolicLink() ? 1 : 0,
"",
]);
}
if (--count === 0) {
callback(result);
}
});
});
}
});
}
async function nodeCrawl(options) {
const {
console,
previousState,
extensions,
forceNodeFilesystemAPI,
ignore,
rootDir,
includeSymlinks,
perfLogger,
roots,
abortSignal,
} = options;
abortSignal?.throwIfAborted();
perfLogger?.point("nodeCrawl_start");
const useNativeFind =
!forceNodeFilesystemAPI &&
(0, _os.platform)() !== "win32" &&
(await (0, _hasNativeFindSupport.default)());
debug("Using system find: %s", useNativeFind);
return new Promise((resolve, reject) => {
const callback = (fileData) => {
const difference = previousState.fileSystem.getDifference(fileData);
perfLogger?.point("nodeCrawl_end");
try {
abortSignal?.throwIfAborted();
} catch (e) {
reject(e);
}
resolve(difference);
};
if (useNativeFind) {
findNative(
roots,
extensions,
ignore,
includeSymlinks,
rootDir,
console,
callback,
);
} else {
find(
roots,
extensions,
ignore,
includeSymlinks,
rootDir,
console,
callback,
);
}
});
}

View File

@@ -0,0 +1,239 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {
CanonicalPath,
Console,
CrawlerOptions,
FileData,
IgnoreMatcher,
} from '../../flow-types';
import {RootPathUtils} from '../../lib/RootPathUtils';
import hasNativeFindSupport from './hasNativeFindSupport';
import {spawn} from 'child_process';
import * as fs from 'graceful-fs';
import {platform} from 'os';
import * as path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:NodeCrawler');
type Callback = (result: FileData) => void;
function find(
roots: $ReadOnlyArray<string>,
extensions: $ReadOnlyArray<string>,
ignore: IgnoreMatcher,
includeSymlinks: boolean,
rootDir: string,
console: Console,
callback: Callback,
): void {
const result: FileData = new Map();
let activeCalls = 0;
const pathUtils = new RootPathUtils(rootDir);
function search(directory: string): void {
activeCalls++;
fs.readdir(directory, {withFileTypes: true}, (err, entries) => {
activeCalls--;
if (err) {
console.warn(
`Error "${err.code ?? err.message}" reading contents of "${directory}", skipping. Add this directory to your ignore list to exclude it.`,
);
} else {
entries.forEach((entry: fs.Dirent) => {
const file = path.join(directory, entry.name.toString());
if (ignore(file)) {
return;
}
if (entry.isSymbolicLink() && !includeSymlinks) {
return;
}
if (entry.isDirectory()) {
search(file);
return;
}
activeCalls++;
fs.lstat(file, (err, stat) => {
activeCalls--;
if (!err && stat) {
const ext = path.extname(file).substr(1);
if (stat.isSymbolicLink() || extensions.includes(ext)) {
result.set(pathUtils.absoluteToNormal(file), [
stat.mtime.getTime(),
stat.size,
0,
'',
null,
stat.isSymbolicLink() ? 1 : 0,
'',
]);
}
}
if (activeCalls === 0) {
callback(result);
}
});
});
}
if (activeCalls === 0) {
callback(result);
}
});
}
if (roots.length > 0) {
roots.forEach(search);
} else {
callback(result);
}
}
function findNative(
roots: $ReadOnlyArray<string>,
extensions: $ReadOnlyArray<string>,
ignore: IgnoreMatcher,
includeSymlinks: boolean,
rootDir: string,
console: Console,
callback: Callback,
): void {
// Examples:
// ( ( -type f ( -iname *.js ) ) )
// ( ( -type f ( -iname *.js -o -iname *.ts ) ) )
// ( ( -type f ( -iname *.js ) ) -o -type l )
// ( ( -type f ) -o -type l )
const extensionClause = extensions.length
? `( ${extensions.map(ext => `-iname *.${ext}`).join(' -o ')} )`
: ''; // Empty inner expressions eg "( )" are not allowed
const expression = `( ( -type f ${extensionClause} ) ${
includeSymlinks ? '-o -type l ' : ''
})`;
const pathUtils = new RootPathUtils(rootDir);
const child = spawn('find', roots.concat(expression.split(' ')));
let stdout = '';
if (child.stdout == null) {
throw new Error(
'stdout is null - this should never happen. Please open up an issue at https://github.com/facebook/metro',
);
}
child.stdout.setEncoding('utf-8');
child.stdout.on('data', data => (stdout += data));
child.stdout.on('close', () => {
const lines = stdout
.trim()
.split('\n')
.filter(x => !ignore(x));
const result: FileData = new Map();
let count = lines.length;
if (!count) {
callback(new Map());
} else {
lines.forEach(path => {
fs.lstat(path, (err, stat) => {
if (!err && stat) {
result.set(pathUtils.absoluteToNormal(path), [
stat.mtime.getTime(),
stat.size,
0,
'',
null,
stat.isSymbolicLink() ? 1 : 0,
'',
]);
}
if (--count === 0) {
callback(result);
}
});
});
}
});
}
export default async function nodeCrawl(options: CrawlerOptions): Promise<{
removedFiles: Set<CanonicalPath>,
changedFiles: FileData,
}> {
const {
console,
previousState,
extensions,
forceNodeFilesystemAPI,
ignore,
rootDir,
includeSymlinks,
perfLogger,
roots,
abortSignal,
} = options;
abortSignal?.throwIfAborted();
perfLogger?.point('nodeCrawl_start');
const useNativeFind =
!forceNodeFilesystemAPI &&
platform() !== 'win32' &&
(await hasNativeFindSupport());
debug('Using system find: %s', useNativeFind);
return new Promise((resolve, reject) => {
const callback: Callback = fileData => {
const difference = previousState.fileSystem.getDifference(fileData);
perfLogger?.point('nodeCrawl_end');
try {
// TODO: Use AbortSignal.reason directly when Flow supports it
abortSignal?.throwIfAborted();
} catch (e) {
reject(e);
}
resolve(difference);
};
if (useNativeFind) {
findNative(
roots,
extensions,
ignore,
includeSymlinks,
rootDir,
console,
callback,
);
} else {
find(
roots,
extensions,
ignore,
includeSymlinks,
rootDir,
console,
callback,
);
}
});
}

View File

@@ -0,0 +1,296 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = watchmanCrawl;
var _normalizePathSeparatorsToPosix = _interopRequireDefault(
require("../../lib/normalizePathSeparatorsToPosix"),
);
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
require("../../lib/normalizePathSeparatorsToSystem"),
);
var _RootPathUtils = require("../../lib/RootPathUtils");
var _planQuery = require("./planQuery");
var _fbWatchman = _interopRequireDefault(require("fb-watchman"));
var _invariant = _interopRequireDefault(require("invariant"));
var path = _interopRequireWildcard(require("path"));
var _perf_hooks = require("perf_hooks");
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const WATCHMAN_WARNING_INITIAL_DELAY_MILLISECONDS = 10000;
const WATCHMAN_WARNING_INTERVAL_MILLISECONDS = 20000;
const watchmanURL = "https://facebook.github.io/watchman/docs/troubleshooting";
function makeWatchmanError(error) {
error.message =
`Watchman error: ${error.message.trim()}. Make sure watchman ` +
`is running for this project. See ${watchmanURL}.`;
return error;
}
async function watchmanCrawl({
abortSignal,
computeSha1,
extensions,
ignore,
includeSymlinks,
onStatus,
perfLogger,
previousState,
rootDir,
roots,
}) {
abortSignal?.throwIfAborted();
const client = new _fbWatchman.default.Client();
const pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
abortSignal?.addEventListener("abort", () => client.end());
perfLogger?.point("watchmanCrawl_start");
const newClocks = new Map();
let clientError;
client.on("error", (error) => {
clientError = makeWatchmanError(error);
});
const cmd = async (command, ...args) => {
let didLogWatchmanWaitMessage = false;
const startTime = _perf_hooks.performance.now();
const logWatchmanWaitMessage = () => {
didLogWatchmanWaitMessage = true;
onStatus({
type: "watchman_slow_command",
timeElapsed: _perf_hooks.performance.now() - startTime,
command,
});
};
let intervalOrTimeoutId = setTimeout(() => {
logWatchmanWaitMessage();
intervalOrTimeoutId = setInterval(
logWatchmanWaitMessage,
WATCHMAN_WARNING_INTERVAL_MILLISECONDS,
);
}, WATCHMAN_WARNING_INITIAL_DELAY_MILLISECONDS);
try {
const response = await new Promise((resolve, reject) =>
client.command([command, ...args], (error, result) =>
error ? reject(makeWatchmanError(error)) : resolve(result),
),
);
if ("warning" in response) {
onStatus({
type: "watchman_warning",
warning: response.warning,
command,
});
}
return response;
} finally {
clearInterval(intervalOrTimeoutId);
if (didLogWatchmanWaitMessage) {
onStatus({
type: "watchman_slow_command_complete",
timeElapsed: _perf_hooks.performance.now() - startTime,
command,
});
}
}
};
async function getWatchmanRoots(roots) {
perfLogger?.point("watchmanCrawl/getWatchmanRoots_start");
const watchmanRoots = new Map();
await Promise.all(
roots.map(async (root, index) => {
perfLogger?.point(`watchmanCrawl/watchProject_${index}_start`);
const response = await cmd("watch-project", root);
perfLogger?.point(`watchmanCrawl/watchProject_${index}_end`);
const existing = watchmanRoots.get(response.watch);
const canBeFiltered = !existing || existing.directoryFilters.length > 0;
if (canBeFiltered) {
if (response.relative_path) {
watchmanRoots.set(response.watch, {
watcher: response.watcher,
directoryFilters: (existing?.directoryFilters || []).concat(
response.relative_path,
),
});
} else {
watchmanRoots.set(response.watch, {
watcher: response.watcher,
directoryFilters: [],
});
}
}
}),
);
perfLogger?.point("watchmanCrawl/getWatchmanRoots_end");
return watchmanRoots;
}
async function queryWatchmanForDirs(rootProjectDirMappings) {
perfLogger?.point("watchmanCrawl/queryWatchmanForDirs_start");
const results = new Map();
let isFresh = false;
await Promise.all(
Array.from(rootProjectDirMappings).map(
async ([posixSeparatedRoot, { directoryFilters, watcher }], index) => {
const since = previousState.clocks.get(
(0, _normalizePathSeparatorsToPosix.default)(
pathUtils.absoluteToNormal(
(0, _normalizePathSeparatorsToSystem.default)(
posixSeparatedRoot,
),
),
),
);
perfLogger?.annotate({
bool: {
[`watchmanCrawl/query_${index}_has_clock`]: since != null,
},
});
const { query, queryGenerator } = (0, _planQuery.planQuery)({
since,
extensions,
directoryFilters,
includeSha1: computeSha1,
includeSymlinks,
});
perfLogger?.annotate({
string: {
[`watchmanCrawl/query_${index}_watcher`]: watcher ?? "unknown",
[`watchmanCrawl/query_${index}_generator`]: queryGenerator,
},
});
perfLogger?.point(`watchmanCrawl/query_${index}_start`);
const response = await cmd("query", posixSeparatedRoot, query);
perfLogger?.point(`watchmanCrawl/query_${index}_end`);
const isSourceControlQuery =
typeof since !== "string" && since?.scm?.["mergebase-with"] != null;
if (!isSourceControlQuery) {
isFresh = isFresh || response.is_fresh_instance;
}
results.set(posixSeparatedRoot, response);
},
),
);
perfLogger?.point("watchmanCrawl/queryWatchmanForDirs_end");
return {
isFresh,
results,
};
}
let removedFiles = new Set();
let changedFiles = new Map();
let results;
let isFresh = false;
let queryError;
try {
const watchmanRoots = await getWatchmanRoots(roots);
const watchmanFileResults = await queryWatchmanForDirs(watchmanRoots);
results = watchmanFileResults.results;
isFresh = watchmanFileResults.isFresh;
} catch (e) {
queryError = e;
}
client.end();
if (results == null) {
if (clientError) {
perfLogger?.annotate({
string: {
"watchmanCrawl/client_error":
clientError.message ?? "[message missing]",
},
});
}
if (queryError) {
perfLogger?.annotate({
string: {
"watchmanCrawl/query_error":
queryError.message ?? "[message missing]",
},
});
}
perfLogger?.point("watchmanCrawl_end");
abortSignal?.throwIfAborted();
throw (
queryError ?? clientError ?? new Error("Watchman file results missing")
);
}
perfLogger?.point("watchmanCrawl/processResults_start");
const freshFileData = new Map();
for (const [watchRoot, response] of results) {
const fsRoot = (0, _normalizePathSeparatorsToSystem.default)(watchRoot);
const relativeFsRoot = pathUtils.absoluteToNormal(fsRoot);
newClocks.set(
(0, _normalizePathSeparatorsToPosix.default)(relativeFsRoot),
typeof response.clock === "string"
? response.clock
: response.clock.clock,
);
for (const fileData of response.files) {
const filePath =
fsRoot +
path.sep +
(0, _normalizePathSeparatorsToSystem.default)(fileData.name);
const relativeFilePath = pathUtils.absoluteToNormal(filePath);
if (!fileData.exists) {
if (!isFresh) {
removedFiles.add(relativeFilePath);
}
} else if (!ignore(filePath)) {
const { mtime_ms, size } = fileData;
(0, _invariant.default)(
mtime_ms != null && size != null,
"missing file data in watchman response",
);
const mtime =
typeof mtime_ms === "number" ? mtime_ms : mtime_ms.toNumber();
let sha1hex = fileData["content.sha1hex"];
if (typeof sha1hex !== "string" || sha1hex.length !== 40) {
sha1hex = undefined;
}
let symlinkInfo = 0;
if (fileData.type === "l") {
symlinkInfo = fileData["symlink_target"] ?? 1;
}
const nextData = [mtime, size, 0, "", sha1hex ?? null, symlinkInfo, ""];
if (isFresh) {
freshFileData.set(relativeFilePath, nextData);
} else {
changedFiles.set(relativeFilePath, nextData);
}
}
}
}
if (isFresh) {
({ changedFiles, removedFiles } =
previousState.fileSystem.getDifference(freshFileData));
}
perfLogger?.point("watchmanCrawl/processResults_end");
perfLogger?.point("watchmanCrawl_end");
abortSignal?.throwIfAborted();
return {
changedFiles,
removedFiles,
clocks: newClocks,
};
}

View File

@@ -0,0 +1,369 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {WatchmanClockSpec} from '../../flow-types';
import type {
CanonicalPath,
CrawlerOptions,
FileData,
FileMetadata,
Path,
WatchmanClocks,
} from '../../flow-types';
import type {WatchmanQueryResponse, WatchmanWatchResponse} from 'fb-watchman';
import normalizePathSeparatorsToPosix from '../../lib/normalizePathSeparatorsToPosix';
import normalizePathSeparatorsToSystem from '../../lib/normalizePathSeparatorsToSystem';
import {RootPathUtils} from '../../lib/RootPathUtils';
import {planQuery} from './planQuery';
import watchman from 'fb-watchman';
import invariant from 'invariant';
import * as path from 'path';
import {performance} from 'perf_hooks';
type WatchmanRoots = Map<
string, // Posix-separated absolute path
$ReadOnly<{directoryFilters: Array<string>, watcher: string}>,
>;
const WATCHMAN_WARNING_INITIAL_DELAY_MILLISECONDS = 10000;
const WATCHMAN_WARNING_INTERVAL_MILLISECONDS = 20000;
const watchmanURL = 'https://facebook.github.io/watchman/docs/troubleshooting';
function makeWatchmanError(error: Error): Error {
error.message =
`Watchman error: ${error.message.trim()}. Make sure watchman ` +
`is running for this project. See ${watchmanURL}.`;
return error;
}
export default async function watchmanCrawl({
abortSignal,
computeSha1,
extensions,
ignore,
includeSymlinks,
onStatus,
perfLogger,
previousState,
rootDir,
roots,
}: CrawlerOptions): Promise<{
changedFiles: FileData,
removedFiles: Set<CanonicalPath>,
clocks: WatchmanClocks,
}> {
abortSignal?.throwIfAborted();
const client = new watchman.Client();
const pathUtils = new RootPathUtils(rootDir);
abortSignal?.addEventListener('abort', () => client.end());
perfLogger?.point('watchmanCrawl_start');
const newClocks = new Map<Path, WatchmanClockSpec>();
let clientError;
client.on('error', error => {
clientError = makeWatchmanError(error);
});
const cmd = async <T>(
command: 'watch-project' | 'query',
// $FlowFixMe[unclear-type] - Fix to use fb-watchman types
...args: Array<any>
): Promise<T> => {
let didLogWatchmanWaitMessage = false;
const startTime = performance.now();
const logWatchmanWaitMessage = () => {
didLogWatchmanWaitMessage = true;
onStatus({
type: 'watchman_slow_command',
timeElapsed: performance.now() - startTime,
command,
});
};
let intervalOrTimeoutId: TimeoutID | IntervalID = setTimeout(() => {
logWatchmanWaitMessage();
intervalOrTimeoutId = setInterval(
logWatchmanWaitMessage,
WATCHMAN_WARNING_INTERVAL_MILLISECONDS,
);
}, WATCHMAN_WARNING_INITIAL_DELAY_MILLISECONDS);
try {
const response = await new Promise<WatchmanQueryResponse>(
(resolve, reject) =>
// $FlowFixMe[incompatible-type] - dynamic call of command
client.command(
[command, ...args],
(error: ?Error, result: WatchmanQueryResponse) =>
error ? reject(makeWatchmanError(error)) : resolve(result),
),
);
if ('warning' in response) {
onStatus({
type: 'watchman_warning',
warning: response.warning,
command,
});
}
// $FlowFixMe[incompatible-type]
return response;
} finally {
// $FlowFixMe[incompatible-type] clearInterval / clearTimeout are interchangeable
clearInterval(intervalOrTimeoutId);
if (didLogWatchmanWaitMessage) {
onStatus({
type: 'watchman_slow_command_complete',
timeElapsed: performance.now() - startTime,
command,
});
}
}
};
async function getWatchmanRoots(
roots: $ReadOnlyArray<Path>,
): Promise<WatchmanRoots> {
perfLogger?.point('watchmanCrawl/getWatchmanRoots_start');
const watchmanRoots: WatchmanRoots = new Map();
await Promise.all(
roots.map(async (root, index) => {
perfLogger?.point(`watchmanCrawl/watchProject_${index}_start`);
const response = await cmd<WatchmanWatchResponse>(
'watch-project',
root,
);
perfLogger?.point(`watchmanCrawl/watchProject_${index}_end`);
const existing = watchmanRoots.get(response.watch);
// A root can only be filtered if it was never seen with a
// relative_path before.
const canBeFiltered = !existing || existing.directoryFilters.length > 0;
if (canBeFiltered) {
if (response.relative_path) {
watchmanRoots.set(response.watch, {
watcher: response.watcher,
directoryFilters: (existing?.directoryFilters || []).concat(
response.relative_path,
),
});
} else {
// Make the filter directories an empty array to signal that this
// root was already seen and needs to be watched for all files or
// directories.
watchmanRoots.set(response.watch, {
watcher: response.watcher,
directoryFilters: [],
});
}
}
}),
);
perfLogger?.point('watchmanCrawl/getWatchmanRoots_end');
return watchmanRoots;
}
async function queryWatchmanForDirs(rootProjectDirMappings: WatchmanRoots) {
perfLogger?.point('watchmanCrawl/queryWatchmanForDirs_start');
const results = new Map<string, WatchmanQueryResponse>();
let isFresh = false;
await Promise.all(
Array.from(rootProjectDirMappings).map(
async ([posixSeparatedRoot, {directoryFilters, watcher}], index) => {
// Jest is only going to store one type of clock; a string that
// represents a local clock. However, the Watchman crawler supports
// a second type of clock that can be written by automation outside of
// Jest, called an "scm query", which fetches changed files based on
// source control mergebases. The reason this is necessary is because
// local clocks are not portable across systems, but scm queries are.
// By using scm queries, we can create the haste map on a different
// system and import it, transforming the clock into a local clock.
const since = previousState.clocks.get(
normalizePathSeparatorsToPosix(
pathUtils.absoluteToNormal(
normalizePathSeparatorsToSystem(posixSeparatedRoot),
),
),
);
perfLogger?.annotate({
bool: {
[`watchmanCrawl/query_${index}_has_clock`]: since != null,
},
});
const {query, queryGenerator} = planQuery({
since,
extensions,
directoryFilters,
includeSha1: computeSha1,
includeSymlinks,
});
perfLogger?.annotate({
string: {
[`watchmanCrawl/query_${index}_watcher`]: watcher ?? 'unknown',
[`watchmanCrawl/query_${index}_generator`]: queryGenerator,
},
});
perfLogger?.point(`watchmanCrawl/query_${index}_start`);
const response = await cmd<WatchmanQueryResponse>(
'query',
posixSeparatedRoot,
query,
);
perfLogger?.point(`watchmanCrawl/query_${index}_end`);
// When a source-control query is used, we ignore the "is fresh"
// response from Watchman because it will be true despite the query
// being incremental.
const isSourceControlQuery =
typeof since !== 'string' && since?.scm?.['mergebase-with'] != null;
if (!isSourceControlQuery) {
isFresh = isFresh || response.is_fresh_instance;
}
results.set(posixSeparatedRoot, response);
},
),
);
perfLogger?.point('watchmanCrawl/queryWatchmanForDirs_end');
return {
isFresh,
results,
};
}
let removedFiles: Set<CanonicalPath> = new Set();
let changedFiles: FileData = new Map();
let results: Map<string, WatchmanQueryResponse>;
let isFresh = false;
let queryError: ?Error;
try {
const watchmanRoots = await getWatchmanRoots(roots);
const watchmanFileResults = await queryWatchmanForDirs(watchmanRoots);
results = watchmanFileResults.results;
isFresh = watchmanFileResults.isFresh;
} catch (e) {
queryError = e;
}
client.end();
if (results == null) {
if (clientError) {
perfLogger?.annotate({
string: {
'watchmanCrawl/client_error':
clientError.message ?? '[message missing]',
},
});
}
if (queryError) {
perfLogger?.annotate({
string: {
'watchmanCrawl/query_error':
queryError.message ?? '[message missing]',
},
});
}
perfLogger?.point('watchmanCrawl_end');
abortSignal?.throwIfAborted();
throw (
queryError ?? clientError ?? new Error('Watchman file results missing')
);
}
perfLogger?.point('watchmanCrawl/processResults_start');
const freshFileData: FileData = new Map();
for (const [watchRoot, response] of results) {
const fsRoot = normalizePathSeparatorsToSystem(watchRoot);
const relativeFsRoot = pathUtils.absoluteToNormal(fsRoot);
newClocks.set(
normalizePathSeparatorsToPosix(relativeFsRoot),
// Ensure we persist only the local clock.
typeof response.clock === 'string'
? response.clock
: response.clock.clock,
);
for (const fileData of response.files) {
const filePath =
fsRoot + path.sep + normalizePathSeparatorsToSystem(fileData.name);
const relativeFilePath = pathUtils.absoluteToNormal(filePath);
if (!fileData.exists) {
if (!isFresh) {
removedFiles.add(relativeFilePath);
}
// Whether watchman can return exists: false in a fresh instance
// response is unknown, but there's nothing we need to do in that case.
} else if (!ignore(filePath)) {
const {mtime_ms, size} = fileData;
invariant(
mtime_ms != null && size != null,
'missing file data in watchman response',
);
const mtime =
typeof mtime_ms === 'number' ? mtime_ms : mtime_ms.toNumber();
let sha1hex = fileData['content.sha1hex'];
if (typeof sha1hex !== 'string' || sha1hex.length !== 40) {
sha1hex = undefined;
}
let symlinkInfo: 0 | 1 | string = 0;
if (fileData.type === 'l') {
symlinkInfo = fileData['symlink_target'] ?? 1;
}
const nextData: FileMetadata = [
mtime,
size,
0,
'',
sha1hex ?? null,
symlinkInfo,
'',
];
// If watchman is fresh, the removed files map starts with all files
// and we remove them as we verify they still exist.
if (isFresh) {
freshFileData.set(relativeFilePath, nextData);
} else {
changedFiles.set(relativeFilePath, nextData);
}
}
}
}
if (isFresh) {
({changedFiles, removedFiles} =
previousState.fileSystem.getDifference(freshFileData));
}
perfLogger?.point('watchmanCrawl/processResults_end');
perfLogger?.point('watchmanCrawl_end');
abortSignal?.throwIfAborted();
return {
changedFiles,
removedFiles,
clocks: newClocks,
};
}

View File

@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.planQuery = planQuery;
function planQuery({
since,
directoryFilters,
extensions,
includeSha1,
includeSymlinks,
}) {
const fields = ["name", "exists", "mtime_ms", "size"];
if (includeSha1) {
fields.push("content.sha1hex");
}
if (includeSymlinks) {
fields.push("type");
}
const allOfTerms = includeSymlinks
? [
[
"anyof",
["allof", ["type", "f"], ["suffix", extensions]],
["type", "l"],
],
]
: [["type", "f"]];
const query = {
fields,
};
let queryGenerator;
if (since != null) {
query.since = since;
queryGenerator = "since";
if (directoryFilters.length > 0) {
allOfTerms.push([
"anyof",
...directoryFilters.map((dir) => ["dirname", dir]),
]);
}
} else if (directoryFilters.length > 0) {
query.glob = directoryFilters.map((directory) => `${directory}/**`);
query.glob_includedotfiles = true;
queryGenerator = "glob";
} else if (!includeSymlinks) {
query.suffix = extensions;
queryGenerator = "suffix";
} else {
queryGenerator = "all";
}
if (!includeSymlinks && queryGenerator !== "suffix") {
allOfTerms.push(["suffix", extensions]);
}
query.expression =
allOfTerms.length === 1 ? allOfTerms[0] : ["allof", ...allOfTerms];
return {
query,
queryGenerator,
};
}

View File

@@ -0,0 +1,136 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import type {
WatchmanDirnameExpression,
WatchmanExpression,
WatchmanQuery,
WatchmanQuerySince,
} from 'fb-watchman';
export function planQuery({
since,
directoryFilters,
extensions,
includeSha1,
includeSymlinks,
}: $ReadOnly<{
since: ?WatchmanQuerySince,
directoryFilters: $ReadOnlyArray<string>,
extensions: $ReadOnlyArray<string>,
includeSha1: boolean,
includeSymlinks: boolean,
}>): {
query: WatchmanQuery,
queryGenerator: string,
} {
const fields = ['name', 'exists', 'mtime_ms', 'size'];
if (includeSha1) {
fields.push('content.sha1hex');
}
/**
* Note on symlink_target:
*
* Watchman supports requesting the symlink_target field, which is
* *potentially* more efficient if targets can be read from metadata without
* reading/materialising files. However, at the time of writing, Watchman has
* issues reporting symlink_target on some backends[1]. Additionally, though
* the Eden watcher is known to work, it reads links serially[2] on demand[3]
* - less efficiently than we can do ourselves.
* [1] https://github.com/facebook/watchman/issues/1084
* [2] https://github.com/facebook/watchman/blob/v2023.01.02.00/watchman/watcher/eden.cpp#L476-L485
* [3] https://github.com/facebook/watchman/blob/v2023.01.02.00/watchman/watcher/eden.cpp#L433-L434
*/
if (includeSymlinks) {
fields.push('type');
}
const allOfTerms: Array<WatchmanExpression> = includeSymlinks
? [
[
'anyof',
['allof', ['type', 'f'], ['suffix', extensions]],
['type', 'l'],
],
]
: [['type', 'f']];
const query: WatchmanQuery = {fields};
/**
* Watchman "query planner".
*
* Watchman file queries consist of 1 or more generators that feed
* files through the expression evaluator.
*
* Strategy:
* 1. Select the narrowest possible generator so that the expression
* evaluator has fewer candidates to process.
* 2. Evaluate expressions from narrowest to broadest.
* 3. Don't use an expression to recheck a condition that the
* generator already guarantees.
* 4. Compose expressions to avoid combinatorial explosions in the
* number of terms.
*
* The ordering of generators/filters, from narrow to broad, is:
* - since = O(changes)
* - glob / dirname = O(files in a subtree of the repo)
* - suffix = O(files in the repo)
*
* We assume that file extensions are ~uniformly distributed in the
* repo but Haste map projects are focused on a handful of
* directories. Therefore `glob` < `suffix`.
*/
let queryGenerator: ?string;
if (since != null) {
// Prefer the since generator whenever we have a clock
query.since = since;
queryGenerator = 'since';
// Filter on directories using an anyof expression
if (directoryFilters.length > 0) {
allOfTerms.push([
'anyof',
...directoryFilters.map(
dir => (['dirname', dir]: WatchmanDirnameExpression),
),
]);
}
} else if (directoryFilters.length > 0) {
// Use the `glob` generator and filter only by extension.
query.glob = directoryFilters.map(directory => `${directory}/**`);
query.glob_includedotfiles = true;
queryGenerator = 'glob';
} else if (!includeSymlinks) {
// Use the `suffix` generator with no path/extension filtering, as long
// as we don't need (suffixless) directory symlinks.
query.suffix = extensions;
queryGenerator = 'suffix';
} else {
// Fall back to `all` if we need symlinks and don't have a clock or
// directory filters.
queryGenerator = 'all';
}
// `includeSymlinks` implies we need (suffixless) directory links. In the
// case of the `suffix` generator, a suffix expression would be redundant.
if (!includeSymlinks && queryGenerator !== 'suffix') {
allOfTerms.push(['suffix', extensions]);
}
// If we only have one "all of" expression we can use it directly, otherwise
// wrap in ['allof', ...expressions]. By construction we should never have
// length 0.
query.expression =
allOfTerms.length === 1 ? allOfTerms[0] : ['allof', ...allOfTerms];
return {query, queryGenerator};
}

353
node_modules/metro-file-map/src/flow-types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,353 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {PerfLogger, PerfLoggerFactory, RootPerfLogger} from 'metro-config';
export type {PerfLoggerFactory, PerfLogger};
/**
* These inputs affect the internal data collected for a given filesystem
* state, and changes may invalidate a cache.
*/
export type BuildParameters = Readonly<{
computeDependencies: boolean;
computeSha1: boolean;
enableHastePackages: boolean;
enableSymlinks: boolean;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI: boolean;
ignorePattern: RegExp;
mocksPattern: RegExp | null;
platforms: ReadonlyArray<string>;
retainAllFiles: boolean;
rootDir: string;
roots: ReadonlyArray<string>;
skipPackageJson: boolean;
dependencyExtractor: string | null;
hasteImplModulePath: string | null;
cacheBreaker: string;
}>;
export interface BuildResult {
fileSystem: FileSystem;
hasteMap: HasteMap;
}
export interface CacheData {
readonly clocks: WatchmanClocks;
readonly fileSystemData: unknown;
readonly plugins: ReadonlyMap<string, unknown>;
}
export interface CacheManager {
/**
* Called during startup to load initial state, if available. Provided to
* a crawler, which will return the delta between the initial state and the
* current file system state.
*/
read(): Promise<CacheData | null>;
/**
* Called when metro-file-map `build()` has applied changes returned by the
* crawler - i.e. internal state reflects the current file system state.
*
* getSnapshot may be retained and called at any time before end(), such as
* in response to eventSource 'change' events.
*/
write(
getSnapshot: () => CacheData,
opts: CacheManagerWriteOptions,
): Promise<void>;
/**
* The last call that will be made to this CacheManager. Any handles should
* be closed by the time this settles.
*/
end(): Promise<void>;
}
export interface CacheManagerEventSource {
onChange(listener: () => void): () => void /* unsubscribe */;
}
export type CacheManagerFactory = (
options: CacheManagerFactoryOptions,
) => CacheManager;
export type CacheManagerFactoryOptions = {
buildParameters: BuildParameters;
};
export type CacheManagerWriteOptions = {
changedSinceCacheRead: boolean;
eventSource: CacheManagerEventSource;
onWriteError: (e: Error) => void;
};
export interface ChangeEvent {
logger: RootPerfLogger | null;
eventsQueue: EventsQueue;
}
export interface ChangeEventMetadata {
/** Epoch ms */
modifiedTime: number | null;
/** Bytes */
size: number | null;
/** Regular file / Directory / Symlink */
type: 'f' | 'd' | 'l';
}
export type Console = typeof global.console;
export interface CrawlerOptions {
abortSignal: AbortSignal | null;
computeSha1: boolean;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI: boolean;
ignore: IgnoreMatcher;
includeSymlinks: boolean;
perfLogger?: PerfLogger | null;
previousState: Readonly<{
clocks: ReadonlyMap<Path, WatchmanClockSpec>;
files: ReadonlyMap<Path, FileMetadata>;
}>;
rootDir: string;
roots: ReadonlyArray<string>;
onStatus: (status: WatcherStatus) => void;
}
export type WatcherStatus =
| {
type: 'watchman_slow_command';
timeElapsed: number;
command: 'watch-project' | 'query';
}
| {
type: 'watchman_slow_command_complete';
timeElapsed: number;
command: 'watch-project' | 'query';
}
| {
type: 'watchman_warning';
warning: unknown;
command: 'watch-project' | 'query';
};
export type DuplicatesSet = Map<string, /* type */ number>;
export type DuplicatesIndex = Map<string, Map<string, DuplicatesSet>>;
export type EventsQueue = Array<{
filePath: Path;
metadata?: ChangeEventMetadata | null;
type: string;
}>;
export interface HType {
ID: 0;
MTIME: 1;
SIZE: 2;
VISITED: 3;
DEPENDENCIES: 4;
SHA1: 5;
SYMLINK: 6;
PATH: 0;
TYPE: 1;
MODULE: 0;
PACKAGE: 1;
GENERIC_PLATFORM: 'g';
NATIVE_PLATFORM: 'native';
DEPENDENCY_DELIM: '\0';
}
type Values<T> = T[keyof T];
export type HTypeValue = Values<HType>;
export type IgnoreMatcher = (item: string) => boolean;
export type FileData = Map<Path, FileMetadata>;
export type FileMetadata = [
/* id */ string,
/* mtime */ number,
/* size */ number,
/* visited */ 0 | 1,
/* dependencies */ string,
/* sha1 */ string | null,
/* symlink */ 0 | 1 | string, // string specifies target, if known
];
export type FileStats = Readonly<{
fileType: 'f' | 'l';
modifiedTime: number;
}>;
export interface FileSystem {
exists(file: Path): boolean;
getAllFiles(): Path[];
getDependencies(file: Path): string[] | null;
getModuleName(file: Path): string | null;
getSerializableSnapshot(): FileData;
getSha1(file: Path): string | null;
/**
* Given a start path (which need not exist), a subpath and type, and
* optionally a 'breakOnSegment', performs the following:
*
* X = mixedStartPath
* do
* if basename(X) === opts.breakOnSegment
* return null
* if X + subpath exists and has type opts.subpathType
* return {
* absolutePath: realpath(X + subpath)
* containerRelativePath: relative(mixedStartPath, X)
* }
* X = dirname(X)
* while X !== dirname(X)
*
* If opts.invalidatedBy is given, collects all absolute, real paths that if
* added or removed may invalidate this result.
*
* Useful for finding the closest package scope (subpath: package.json,
* type f, breakOnSegment: node_modules) or closest potential package root
* (subpath: node_modules/pkg, type: d) in Node.js resolution.
*/
hierarchicalLookup(
mixedStartPath: string,
subpath: string,
opts: {
breakOnSegment: string | null | undefined;
invalidatedBy: Set<string> | null | undefined;
subpathType: 'f' | 'd';
},
): {
absolutePath: string;
containerRelativePath: string;
} | null;
/**
* Analogous to posix lstat. If the file at `file` is a symlink, return
* information about the symlink without following it.
*/
linkStats(file: Path): FileStats | null;
/**
* Return information about the given path, whether a directory or file.
* Always follow symlinks, and return a real path if it exists.
*/
lookup(mixedPath: Path): LookupResult;
matchFiles(opts: {
/* Filter relative paths against a pattern. */
filter?: RegExp | null;
/* `filter` is applied against absolute paths, vs rootDir-relative. (default: false) */
filterCompareAbsolute?: boolean;
/* `filter` is applied against posix-delimited paths, even on Windows. (default: false) */
filterComparePosix?: boolean;
/* Follow symlinks when enumerating paths. (default: false) */
follow?: boolean;
/* Should search for files recursively. (default: true) */
recursive?: boolean;
/* Match files under a given root, or null for all files */
rootDir?: Path | null;
}): Iterable<Path>;
}
export type Glob = string;
export type LookupResult =
| {
// The node is missing from the FileSystem implementation (note this
// could indicate an unwatched path, or a directory containing no watched
// files).
exists: false;
// The real, normal, absolute paths of any symlinks traversed.
links: ReadonlySet<string>;
// The real, normal, absolute path of the first path segment
// encountered that does not exist, or cannot be navigated through.
missing: string;
}
| {
exists: true;
// The real, normal, absolute paths of any symlinks traversed.
links: ReadonlySet<string>;
// The real, normal, absolute path of the file or directory.
realPath: string;
// Currently lookup always follows symlinks, so can only return
// directories or regular files, but this may be extended.
type: 'd' | 'f';
};
export type HasteConflict = {
id: string;
platform: string | null;
absolutePaths: Array<string>;
type: 'duplicate' | 'shadowing';
};
export interface HasteMap {
getModule(
name: string,
platform?: string | null,
supportsNativePlatform?: boolean | null,
type?: HTypeValue | null,
): Path | null;
getPackage(
name: string,
platform: string | null,
_supportsNativePlatform: boolean | null,
): Path | null;
computeConflicts(): Array<HasteConflict>;
}
export type RawMockMap = {
readonly mocks: Map<string, string>;
readonly duplicates: Map<string, Set<string>>;
};
export type HasteMapData = Map<string, HasteMapItem>;
export interface HasteMapItem {
[platform: string]: HasteMapItemMetadata;
}
export type HasteMapItemMetadata = [/* path */ string, /* type */ number];
export interface MutableFileSystem extends FileSystem {
remove(filePath: Path): void;
addOrModify(filePath: Path, fileMetadata: FileMetadata): void;
bulkAddOrModify(addedOrModifiedFiles: FileData): void;
}
export type Path = string;
export type WatchmanClockSpec =
| string
| Readonly<{scm: Readonly<{'mergebase-with': string}>}>;
export type WatchmanClocks = Map<Path, WatchmanClockSpec>;
export type WorkerMessage = Readonly<{
computeDependencies: boolean;
computeSha1: boolean;
dependencyExtractor?: string | null;
enableHastePackages: boolean;
rootDir: string;
filePath: string;
hasteImplModulePath?: string | null;
}>;
export type WorkerMetadata = Readonly<{
dependencies?: ReadonlyArray<string>;
id?: string | null;
module?: HasteMapItemMetadata | null;
sha1?: string | null;
}>;

1
node_modules/metro-file-map/src/flow-types.js generated vendored Normal file
View File

@@ -0,0 +1 @@
"use strict";

476
node_modules/metro-file-map/src/flow-types.js.flow generated vendored Normal file
View File

@@ -0,0 +1,476 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {PerfLogger, PerfLoggerFactory, RootPerfLogger} from 'metro-config';
export type {PerfLoggerFactory, PerfLogger};
// These inputs affect the internal data collected for a given filesystem
// state, and changes may invalidate a cache.
export type BuildParameters = $ReadOnly<{
computeDependencies: boolean,
computeSha1: boolean,
enableHastePackages: boolean,
enableSymlinks: boolean,
extensions: $ReadOnlyArray<string>,
forceNodeFilesystemAPI: boolean,
ignorePattern: RegExp,
plugins: $ReadOnlyArray<FileMapPlugin<>>,
retainAllFiles: boolean,
rootDir: string,
roots: $ReadOnlyArray<string>,
skipPackageJson: boolean,
// Module paths that should export a 'getCacheKey' method
dependencyExtractor: ?string,
hasteImplModulePath: ?string,
cacheBreaker: string,
}>;
export type BuildResult = {
fileSystem: FileSystem,
hasteMap: HasteMap,
mockMap: ?MockMap,
};
export type CacheData = $ReadOnly<{
clocks: WatchmanClocks,
fileSystemData: mixed,
plugins: $ReadOnlyMap<string, V8Serializable>,
}>;
export interface CacheManager {
/**
* Called during startup to load initial state, if available. Provided to
* a crawler, which will return the delta between the initial state and the
* current file system state.
*/
read(): Promise<?CacheData>;
/**
* Called when metro-file-map `build()` has applied changes returned by the
* crawler - i.e. internal state reflects the current file system state.
*
* getSnapshot may be retained and called at any time before end(), such as
* in response to eventSource 'change' events.
*/
write(
getSnapshot: () => CacheData,
opts: CacheManagerWriteOptions,
): Promise<void>;
/**
* The last call that will be made to this CacheManager. Any handles should
* be closed by the time this settles.
*/
end(): Promise<void>;
}
export interface CacheManagerEventSource {
onChange(listener: () => void): () => void /* unsubscribe */;
}
export type CacheManagerFactory = (
options: CacheManagerFactoryOptions,
) => CacheManager;
export type CacheManagerFactoryOptions = $ReadOnly<{
buildParameters: BuildParameters,
}>;
export type CacheManagerWriteOptions = $ReadOnly<{
changedSinceCacheRead: boolean,
eventSource: CacheManagerEventSource,
onWriteError: Error => void,
}>;
// A path that is
// - Relative to the contextual `rootDir`
// - Normalised (no extraneous '.' or '..')
// - Real (no symlinks in path, though the path itself may be a symlink)
export type CanonicalPath = string;
export type ChangeEvent = {
logger: ?RootPerfLogger,
eventsQueue: EventsQueue,
};
export type ChangeEventMetadata = {
modifiedTime: ?number, // Epoch ms
size: ?number, // Bytes
type: 'f' | 'd' | 'l', // Regular file / Directory / Symlink
};
export type Console = typeof global.console;
export type CrawlerOptions = {
abortSignal: ?AbortSignal,
computeSha1: boolean,
console: Console,
extensions: $ReadOnlyArray<string>,
forceNodeFilesystemAPI: boolean,
ignore: IgnoreMatcher,
includeSymlinks: boolean,
perfLogger?: ?PerfLogger,
previousState: $ReadOnly<{
clocks: $ReadOnlyMap<CanonicalPath, WatchmanClockSpec>,
fileSystem: FileSystem,
}>,
rootDir: string,
roots: $ReadOnlyArray<string>,
onStatus: (status: WatcherStatus) => void,
};
export type DependencyExtractor = {
extract: (
content: string,
absoluteFilePath: string,
defaultExtractor?: DependencyExtractor['extract'],
) => Set<string>,
getCacheKey: () => string,
};
export type WatcherStatus =
| {
type: 'watchman_slow_command',
timeElapsed: number,
command: 'watch-project' | 'query',
}
| {
type: 'watchman_slow_command_complete',
timeElapsed: number,
command: 'watch-project' | 'query',
}
| {
type: 'watchman_warning',
warning: mixed,
command: 'watch-project' | 'query',
};
export type DuplicatesSet = Map<string, /* type */ number>;
export type DuplicatesIndex = Map<string, Map<string, DuplicatesSet>>;
export type EventsQueue = Array<{
filePath: Path,
metadata: ChangeEventMetadata,
type: string,
}>;
export type FileMapDelta = $ReadOnly<{
removed: Iterable<[CanonicalPath, FileMetadata]>,
addedOrModified: Iterable<[CanonicalPath, FileMetadata]>,
}>;
interface FileSystemState {
metadataIterator(
opts: $ReadOnly<{
includeNodeModules: boolean,
includeSymlinks: boolean,
}>,
): Iterable<{
baseName: string,
canonicalPath: string,
metadata: FileMetadata,
}>;
}
export type FileMapPluginInitOptions<SerializableState> = $ReadOnly<{
files: FileSystemState,
pluginState: ?SerializableState,
}>;
type V8Serializable = interface {};
export interface FileMapPlugin<SerializableState = V8Serializable> {
+name: string;
initialize(
initOptions: FileMapPluginInitOptions<SerializableState>,
): Promise<void>;
assertValid(): void;
bulkUpdate(delta: FileMapDelta): Promise<void>;
getSerializableSnapshot(): SerializableState;
onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata): void;
onNewOrModifiedFile(
relativeFilePath: string,
fileMetadata: FileMetadata,
): void;
getCacheKey(): string;
}
export type HType = {
MTIME: 0,
SIZE: 1,
VISITED: 2,
DEPENDENCIES: 3,
SHA1: 4,
SYMLINK: 5,
ID: 6,
PATH: 0,
TYPE: 1,
MODULE: 0,
PACKAGE: 1,
GENERIC_PLATFORM: 'g',
NATIVE_PLATFORM: 'native',
DEPENDENCY_DELIM: '\0',
};
export type HTypeValue = $Values<HType>;
export type IgnoreMatcher = (item: string) => boolean;
export type FileData = Map<CanonicalPath, FileMetadata>;
export type FileMetadata = [
/* mtime */ ?number,
/* size */ number,
/* visited */ 0 | 1,
/* dependencies */ string,
/* sha1 */ ?string,
/* symlink */ 0 | 1 | string, // string specifies target, if known
/* id */ string,
];
export type FileStats = $ReadOnly<{
fileType: 'f' | 'l',
modifiedTime: ?number,
size: ?number,
}>;
export interface FileSystem {
exists(file: Path): boolean;
getAllFiles(): Array<Path>;
getDependencies(file: Path): ?Array<string>;
getDifference(files: FileData): {
changedFiles: FileData,
removedFiles: Set<string>,
};
getModuleName(file: Path): ?string;
getSerializableSnapshot(): CacheData['fileSystemData'];
getSha1(file: Path): ?string;
getOrComputeSha1(file: Path): Promise<?{sha1: string, content?: Buffer}>;
/**
* Given a start path (which need not exist), a subpath and type, and
* optionally a 'breakOnSegment', performs the following:
*
* X = mixedStartPath
* do
* if basename(X) === opts.breakOnSegment
* return null
* if X + subpath exists and has type opts.subpathType
* return {
* absolutePath: realpath(X + subpath)
* containerRelativePath: relative(mixedStartPath, X)
* }
* X = dirname(X)
* while X !== dirname(X)
*
* If opts.invalidatedBy is given, collects all absolute, real paths that if
* added or removed may invalidate this result.
*
* Useful for finding the closest package scope (subpath: package.json,
* type f, breakOnSegment: node_modules) or closest potential package root
* (subpath: node_modules/pkg, type: d) in Node.js resolution.
*/
hierarchicalLookup(
mixedStartPath: string,
subpath: string,
opts: {
breakOnSegment: ?string,
invalidatedBy: ?Set<string>,
subpathType: 'f' | 'd',
},
): ?{
absolutePath: string,
containerRelativePath: string,
};
/**
* Analogous to posix lstat. If the file at `file` is a symlink, return
* information about the symlink without following it.
*/
linkStats(file: Path): ?FileStats;
/**
* Return information about the given path, whether a directory or file.
* Always follow symlinks, and return a real path if it exists.
*/
lookup(mixedPath: Path): LookupResult;
matchFiles(opts: {
/* Filter relative paths against a pattern. */
filter?: RegExp | null,
/* `filter` is applied against absolute paths, vs rootDir-relative. (default: false) */
filterCompareAbsolute?: boolean,
/* `filter` is applied against posix-delimited paths, even on Windows. (default: false) */
filterComparePosix?: boolean,
/* Follow symlinks when enumerating paths. (default: false) */
follow?: boolean,
/* Should search for files recursively. (default: true) */
recursive?: boolean,
/* Match files under a given root, or null for all files */
rootDir?: Path | null,
}): Iterable<Path>;
}
export type Glob = string;
export type LookupResult =
| {
// The node is missing from the FileSystem implementation (note this
// could indicate an unwatched path, or a directory containing no watched
// files).
exists: false,
// The real, normal, absolute paths of any symlinks traversed.
links: $ReadOnlySet<string>,
// The real, normal, absolute path of the first path segment
// encountered that does not exist, or cannot be navigated through.
missing: string,
}
| {
exists: true,
// The real, normal, absolute paths of any symlinks traversed.
links: $ReadOnlySet<string>,
// The real, normal, absolute path of the file or directory.
realPath: string,
// Currently lookup always follows symlinks, so can only return
// directories or regular files, but this may be extended.
type: 'd' | 'f',
};
export interface MockMap {
getMockModule(name: string): ?Path;
}
export type HasteConflict = {
id: string,
platform: string | null,
absolutePaths: Array<string>,
type: 'duplicate' | 'shadowing',
};
export interface HasteMap {
getModule(
name: string,
platform?: ?string,
supportsNativePlatform?: ?boolean,
type?: ?HTypeValue,
): ?Path;
getPackage(
name: string,
platform: ?string,
_supportsNativePlatform: ?boolean,
): ?Path;
computeConflicts(): Array<HasteConflict>;
}
export type HasteMapData = Map<string, HasteMapItem>;
export type HasteMapItem = {
[platform: string]: HasteMapItemMetadata,
__proto__: null,
};
export type HasteMapItemMetadata = [/* path */ string, /* type */ number];
export interface MutableFileSystem extends FileSystem {
remove(filePath: Path): ?FileMetadata;
addOrModify(filePath: Path, fileMetadata: FileMetadata): void;
bulkAddOrModify(addedOrModifiedFiles: FileData): void;
}
export type Path = string;
export type ProcessFileFunction = (
absolutePath: string,
metadata: FileMetadata,
request: $ReadOnly<{computeSha1: boolean}>,
) => ?Buffer;
export type RawMockMap = $ReadOnly<{
duplicates: Map<
string, // posix-separated mock name
Set<string>, // posix-separated, project-relative paths
>,
mocks: Map<
string, // posix-separated mock name
Path, // posix-separated, project-relative pathf
>,
version: number,
}>;
export type ReadOnlyRawMockMap = $ReadOnly<{
duplicates: $ReadOnlyMap<string, $ReadOnlySet<string>>,
mocks: $ReadOnlyMap<string, Path>,
version: number,
}>;
export interface WatcherBackend {
getPauseReason(): ?string;
onError((error: Error) => void): () => void;
onFileEvent((event: WatcherBackendChangeEvent) => void): () => void;
startWatching(): Promise<void>;
stopWatching(): Promise<void>;
}
export type ChangeEventClock = [
string /* absolute watch root */,
string /* opaque clock */,
];
export type WatcherBackendChangeEvent =
| $ReadOnly<{
event: 'touch',
clock?: ChangeEventClock,
relativePath: string,
root: string,
metadata: ChangeEventMetadata,
}>
| $ReadOnly<{
event: 'delete',
clock?: ChangeEventClock,
relativePath: string,
root: string,
metadata?: void,
}>;
export type WatcherBackendOptions = $ReadOnly<{
ignored: ?RegExp,
globs: $ReadOnlyArray<string>,
dot: boolean,
...
}>;
export type WatchmanClockSpec =
| string
| $ReadOnly<{scm: $ReadOnly<{'mergebase-with': string}>}>;
export type WatchmanClocks = Map<Path, WatchmanClockSpec>;
export type WorkerMessage = $ReadOnly<{
computeDependencies: boolean,
computeSha1: boolean,
dependencyExtractor?: ?string,
enableHastePackages: boolean,
filePath: string,
hasteImplModulePath?: ?string,
maybeReturnContent: boolean,
}>;
export type WorkerMetadata = $ReadOnly<{
dependencies?: ?$ReadOnlyArray<string>,
id?: ?string,
sha1?: ?string,
content?: ?Buffer,
}>;
export type WorkerSetupArgs = $ReadOnly<{}>;

97
node_modules/metro-file-map/src/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {
BuildParameters,
BuildResult,
CacheData,
CacheManagerFactory,
ChangeEventMetadata,
Console,
FileData,
FileSystem,
HasteConflict,
HasteMapData,
HasteMapItem,
PerfLoggerFactory,
} from './flow-types';
import type {EventEmitter} from 'events';
export type {
BuildParameters,
CacheData,
ChangeEventMetadata,
FileData,
FileMap,
FileSystem,
HasteMapData,
HasteMapItem,
};
export type InputOptions = Readonly<{
computeDependencies?: boolean | null;
computeSha1?: boolean | null;
enableSymlinks?: boolean | null;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI?: boolean | null;
ignorePattern?: RegExp | null;
mocksPattern?: string | null;
platforms: ReadonlyArray<string>;
retainAllFiles: boolean;
rootDir: string;
roots: ReadonlyArray<string>;
skipPackageJson?: boolean | null;
/** Module paths that should export a 'getCacheKey' method */
dependencyExtractor?: string | null;
hasteImplModulePath?: string | null;
perfLoggerFactory?: PerfLoggerFactory | null;
resetCache?: boolean | null;
maxWorkers: number;
throwOnModuleCollision?: boolean | null;
useWatchman?: boolean | null;
watchmanDeferStates?: ReadonlyArray<string>;
watch?: boolean | null;
console?: Console;
cacheManagerFactory?: CacheManagerFactory | null;
healthCheck: HealthCheckOptions;
}>;
type HealthCheckOptions = Readonly<{
enabled: boolean;
interval: number;
timeout: number;
filePrefix: string;
}>;
export {DiskCacheManager} from './cache/DiskCacheManager';
export {DuplicateHasteCandidatesError} from './lib/DuplicateHasteCandidatesError';
export type {HasteMap} from './flow-types';
export type {HealthCheckResult} from './Watcher';
export type {
CacheManager,
CacheManagerFactory,
ChangeEvent,
WatcherStatus,
} from './flow-types';
export default class FileMap extends EventEmitter {
static create(options: InputOptions): FileMap;
constructor(options: InputOptions);
build(): Promise<BuildResult>;
read(): Promise<CacheData | null>;
}
export class HasteConflictsError extends Error {
constructor(conflicts: ReadonlyArray<HasteConflict>);
getDetailedMessage(pathsRelativeToRoot?: string): string;
}

758
node_modules/metro-file-map/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,758 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
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 _normalizePathSeparatorsToPosix = _interopRequireDefault(
require("./lib/normalizePathSeparatorsToPosix"),
);
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
require("./lib/normalizePathSeparatorsToSystem"),
);
var _RootPathUtils = require("./lib/RootPathUtils");
var _TreeFS = _interopRequireDefault(require("./lib/TreeFS"));
var _HastePlugin = _interopRequireDefault(require("./plugins/HastePlugin"));
var _MockPlugin = _interopRequireDefault(require("./plugins/MockPlugin"));
var _Watcher = require("./Watcher");
var _events = _interopRequireDefault(require("events"));
var _fs = require("fs");
var _invariant = _interopRequireDefault(require("invariant"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
var path = _interopRequireWildcard(require("path"));
var _perf_hooks = require("perf_hooks");
var _DuplicateHasteCandidatesError = require("./plugins/haste/DuplicateHasteCandidatesError");
var _HasteConflictsError = require("./plugins/haste/HasteConflictsError");
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:FileMap");
const CACHE_BREAKER = "10";
const CHANGE_INTERVAL = 30;
const NODE_MODULES = path.sep + "node_modules" + path.sep;
const PACKAGE_JSON = path.sep + "package.json";
const VCS_DIRECTORIES = /[/\\]\.(git|hg)[/\\]/.source;
const WATCHMAN_REQUIRED_CAPABILITIES = [
"field-content.sha1hex",
"relative_root",
"suffix-set",
"wildmatch",
];
class FileMap extends _events.default {
#hastePlugin;
#mockPlugin = null;
#plugins;
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;
const throwOnModuleCollision = Boolean(options.throwOnModuleCollision);
const enableHastePackages = options.enableHastePackages ?? true;
this.#hastePlugin = new _HastePlugin.default({
console: this._console,
enableHastePackages,
perfLogger: this._startupPerfLogger,
platforms: new Set(options.platforms),
rootDir: options.rootDir,
failValidationOnConflicts: throwOnModuleCollision,
});
const plugins = [this.#hastePlugin];
if (options.mocksPattern != null && options.mocksPattern !== "") {
this.#mockPlugin = new _MockPlugin.default({
console: this._console,
mocksPattern: new RegExp(options.mocksPattern),
rootDir: options.rootDir,
throwOnModuleCollision,
});
plugins.push(this.#mockPlugin);
}
this.#plugins = plugins;
const buildParameters = {
computeDependencies:
options.computeDependencies == null
? true
: options.computeDependencies,
computeSha1: options.computeSha1 || false,
dependencyExtractor: options.dependencyExtractor ?? null,
enableHastePackages,
enableSymlinks: options.enableSymlinks || false,
extensions: options.extensions,
forceNodeFilesystemAPI: !!options.forceNodeFilesystemAPI,
hasteImplModulePath: options.hasteImplModulePath,
ignorePattern,
plugins: options.plugins ?? [],
retainAllFiles: options.retainAllFiles,
rootDir: options.rootDir,
roots: Array.from(new Set(options.roots)),
skipPackageJson: !!options.skipPackageJson,
cacheBreaker: CACHE_BREAKER,
};
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({
dependencyExtractor: buildParameters.dependencyExtractor,
enableHastePackages: buildParameters.enableHastePackages,
enableWorkerThreads: options.enableWorkerThreads ?? false,
hasteImplModulePath: buildParameters.hasteImplModulePath,
maxFilesPerWorker: options.maxFilesPerWorker,
maxWorkers: options.maxWorkers,
perfLogger: this._startupPerfLogger,
});
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 = (absolutePath, metadata, opts) => {
const result = this._fileProcessor.processRegularFile(
absolutePath,
metadata,
{
computeSha1: opts.computeSha1,
computeDependencies: false,
maybeReturnContent: true,
},
);
debug("Lazily processed file: %s", absolutePath);
this.emit("metadata");
return result?.content;
};
const fileSystem =
initialData != null
? _TreeFS.default.fromDeserializedSnapshot({
rootDir,
fileSystemData: initialData.fileSystemData,
processFile,
})
: new _TreeFS.default({
rootDir,
processFile,
});
this._startupPerfLogger?.point("constructFileSystem_end");
const plugins = this.#plugins;
const [fileDelta] = await Promise.all([
this._buildFileDelta({
fileSystem,
clocks: initialData?.clocks ?? new Map(),
}),
Promise.all(
plugins.map((plugin) =>
plugin.initialize({
files: fileSystem,
pluginState: initialData?.plugins.get(plugin.name),
}),
),
),
]);
await this._applyFileDelta(fileSystem, plugins, fileDelta);
plugins.forEach((plugin) => plugin.assertValid());
const watchmanClocks = new Map(fileDelta.clocks ?? []);
await this._takeSnapshotAndPersist(
fileSystem,
watchmanClocks,
plugins,
fileDelta.changedFiles,
fileDelta.removedFiles,
);
debug(
"Finished mapping files (%d changes, %d removed).",
fileDelta.changedFiles.size,
fileDelta.removedFiles.size,
);
await this._watch(fileSystem, watchmanClocks, plugins);
return {
fileSystem,
hasteMap: this.#hastePlugin,
mockMap: this.#mockPlugin,
};
})();
}
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,
roots,
rootDir,
useWatchman: await this._shouldUseWatchman(),
watch,
watchmanDeferStates,
});
const watcher = this._watcher;
watcher.on("status", (status) => this.emit("status", status));
return watcher.crawl().then((result) => {
this._startupPerfLogger?.point("buildFileDelta_end");
return result;
});
}
_maybeReadLink(filePath, fileMetadata) {
if (fileMetadata[_constants.default.SYMLINK] === 1) {
return _fs.promises.readlink(filePath).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");
const missingFiles = new Set();
this._startupPerfLogger?.point("applyFileDelta_remove_start");
const removed = [];
for (const relativeFilePath of removedFiles) {
const metadata = fileSystem.remove(relativeFilePath);
if (metadata) {
removed.push([relativeFilePath, metadata]);
}
}
this._startupPerfLogger?.point("applyFileDelta_remove_end");
const readLinkPromises = [];
const readLinkErrors = [];
const filesToProcess = [];
for (const [relativeFilePath, fileData] of changedFiles) {
if (fileData[_constants.default.VISITED] === 1) {
continue;
}
if (
this._options.skipPackageJson &&
relativeFilePath.endsWith(PACKAGE_JSON)
) {
continue;
}
if (
fileData[_constants.default.SYMLINK] === 0 &&
!this._options.computeDependencies &&
!this._options.computeSha1 &&
this._options.hasteImplModulePath == null &&
!(
this._options.enableHastePackages &&
relativeFilePath.endsWith(PACKAGE_JSON)
)
) {
continue;
}
const absolutePath = this._pathUtils.normalToAbsolute(relativeFilePath);
if (fileData[_constants.default.SYMLINK] === 0) {
filesToProcess.push([absolutePath, fileData]);
} else {
const maybeReadLink = this._maybeReadLink(absolutePath, fileData);
if (maybeReadLink) {
readLinkPromises.push(
maybeReadLink.catch((error) =>
readLinkErrors.push({
absolutePath,
error,
}),
),
);
}
}
}
this._startupPerfLogger?.point("applyFileDelta_preprocess_end");
debug(
"Visiting %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,
computeDependencies: this._options.computeDependencies,
maybeReturnContent: false,
}),
Promise.all(readLinkPromises),
]);
this._startupPerfLogger?.point("applyFileDelta_process_end");
this._startupPerfLogger?.point("applyFileDelta_missing_start");
for (const { absolutePath, error } of batchResult.errors.concat(
readLinkErrors,
)) {
if (["ENOENT", "EACCESS"].includes(error.code)) {
missingFiles.add(this._pathUtils.absoluteToNormal(absolutePath));
} else {
throw error;
}
}
for (const relativeFilePath of missingFiles) {
changedFiles.delete(relativeFilePath);
const metadata = fileSystem.remove(relativeFilePath);
if (metadata) {
removed.push([relativeFilePath, metadata]);
}
}
this._startupPerfLogger?.point("applyFileDelta_missing_end");
this._startupPerfLogger?.point("applyFileDelta_add_start");
fileSystem.bulkAddOrModify(changedFiles);
this._startupPerfLogger?.point("applyFileDelta_add_end");
this._startupPerfLogger?.point("applyFileDelta_updatePlugins_start");
await Promise.all([
plugins.map((plugin) =>
plugin.bulkUpdate({
addedOrModified: changedFiles,
removed,
}),
),
]);
this._startupPerfLogger?.point("applyFileDelta_updatePlugins_end");
this._startupPerfLogger?.point("applyFileDelta_end");
}
async _takeSnapshotAndPersist(fileSystem, clocks, plugins, changed, removed) {
this._startupPerfLogger?.point("persist_start");
await this._cacheManager.write(
() => ({
fileSystemData: fileSystem.getSerializableSnapshot(),
clocks: new Map(clocks),
plugins: new Map(
plugins.map((plugin) => [
plugin.name,
plugin.getSerializableSnapshot(),
]),
),
}),
{
changedSinceCacheRead: changed.size + removed.size > 0,
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 changeQueue = Promise.resolve();
let nextEmit = null;
const emitChange = () => {
if (nextEmit == null || nextEmit.eventsQueue.length === 0) {
return;
}
const { eventsQueue, firstEventTimestamp, firstEnqueuedTimestamp } =
nextEmit;
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: {
eventsQueueLength: eventsQueue.length,
},
});
hmrPerfLogger.point("fileChange_start");
}
const changeEvent = {
logger: hmrPerfLogger,
eventsQueue,
};
this.emit("change", changeEvent);
nextEmit = null;
};
const onChange = (change) => {
if (
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 linkStats = fileSystem.linkStats(relativeFilePath);
if (
change.event === "touch" &&
linkStats != null &&
change.metadata.modifiedTime != null &&
linkStats.modifiedTime === change.metadata.modifiedTime
) {
return;
}
const eventTypeToEmit =
change.event === "touch"
? linkStats == null
? "add"
: "change"
: "delete";
const onChangeStartTime =
_perf_hooks.performance.timeOrigin + _perf_hooks.performance.now();
changeQueue = changeQueue
.then(async () => {
if (
nextEmit != null &&
nextEmit.eventsQueue.find(
(event) =>
event.type === eventTypeToEmit &&
event.filePath === absoluteFilePath &&
((!event.metadata && !change.metadata) ||
(event.metadata &&
change.metadata &&
event.metadata.modifiedTime != null &&
change.metadata.modifiedTime != null &&
event.metadata.modifiedTime ===
change.metadata.modifiedTime)),
)
) {
return null;
}
const linkStats = fileSystem.linkStats(relativeFilePath);
const enqueueEvent = (metadata) => {
const event = {
filePath: absoluteFilePath,
metadata,
type: eventTypeToEmit,
};
if (nextEmit == null) {
nextEmit = {
eventsQueue: [event],
firstEventTimestamp: onChangeStartTime,
firstEnqueuedTimestamp:
_perf_hooks.performance.timeOrigin +
_perf_hooks.performance.now(),
};
} else {
nextEmit.eventsQueue.push(event);
}
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,
"",
];
try {
if (change.metadata.type === "l") {
await this._maybeReadLink(absoluteFilePath, fileMetadata);
} else {
await this._fileProcessor.processRegularFile(
absoluteFilePath,
fileMetadata,
{
computeSha1: this._options.computeSha1,
computeDependencies: this._options.computeDependencies,
maybeReturnContent: false,
},
);
}
fileSystem.addOrModify(relativeFilePath, fileMetadata);
this._updateClock(clocks, change.clock);
plugins.forEach((plugin) =>
plugin.onNewOrModifiedFile(relativeFilePath, fileMetadata),
);
enqueueEvent(change.metadata);
} catch (e) {
if (!["ENOENT", "EACCESS"].includes(e.code)) {
throw e;
}
}
} else if (change.event === "delete") {
if (linkStats == null) {
return null;
}
const metadata = (0, _nullthrows.default)(
fileSystem.remove(relativeFilePath),
);
this._updateClock(clocks, change.clock);
plugins.forEach((plugin) =>
plugin.onRemovedFile(relativeFilePath, metadata),
);
enqueueEvent({
modifiedTime: null,
size: null,
type: linkStats.fileType,
});
} 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;

1076
node_modules/metro-file-map/src/index.js.flow generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {DuplicatesSet} from '../flow-types';
export class DuplicateHasteCandidatesError extends Error {
hasteName: string;
platform: string | null;
supportsNativePlatform: boolean;
duplicatesSet: DuplicatesSet;
constructor(
name: string,
platform: string,
supportsNativePlatform: boolean,
duplicatesSet: DuplicatesSet,
);
}

172
node_modules/metro-file-map/src/lib/FileProcessor.js generated vendored Normal file
View File

@@ -0,0 +1,172 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.FileProcessor = void 0;
var _constants = _interopRequireDefault(require("../constants"));
var _worker = require("../worker");
var _jestWorker = require("jest-worker");
var _path = require("path");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:FileMap");
const NODE_MODULES = _path.sep + "node_modules" + _path.sep;
const MAX_FILES_PER_WORKER = 100;
class FileProcessor {
#dependencyExtractor;
#enableHastePackages;
#hasteImplModulePath;
#enableWorkerThreads;
#maxFilesPerWorker;
#maxWorkers;
#perfLogger;
#workerArgs;
#inBandWorker;
constructor(opts) {
this.#dependencyExtractor = opts.dependencyExtractor;
this.#enableHastePackages = opts.enableHastePackages;
this.#enableWorkerThreads = opts.enableWorkerThreads;
this.#hasteImplModulePath = opts.hasteImplModulePath;
this.#maxFilesPerWorker = opts.maxFilesPerWorker ?? MAX_FILES_PER_WORKER;
this.#maxWorkers = opts.maxWorkers;
this.#workerArgs = {};
this.#inBandWorker = new _worker.Worker(this.#workerArgs);
this.#perfLogger = opts.perfLogger;
}
async processBatch(files, req) {
const errors = [];
const numWorkers = Math.min(
this.#maxWorkers,
Math.ceil(files.length / this.#maxFilesPerWorker),
);
const batchWorker = this.#getBatchWorker(numWorkers);
if (req.maybeReturnContent) {
throw new Error(
"Batch processing does not support returning file contents",
);
}
await Promise.all(
files.map(([absolutePath, fileMetadata]) => {
const maybeWorkerInput = this.#getWorkerInput(
absolutePath,
fileMetadata,
req,
);
if (!maybeWorkerInput) {
return null;
}
return batchWorker
.processFile(maybeWorkerInput)
.then((reply) => processWorkerReply(reply, fileMetadata))
.catch((error) =>
errors.push({
absolutePath,
error: normalizeWorkerError(error),
}),
);
}),
);
await batchWorker.end();
return {
errors,
};
}
processRegularFile(absolutePath, fileMetadata, req) {
const workerInput = this.#getWorkerInput(absolutePath, fileMetadata, req);
return workerInput
? {
content: processWorkerReply(
this.#inBandWorker.processFile(workerInput),
fileMetadata,
),
}
: null;
}
#getWorkerInput(absolutePath, fileMetadata, req) {
const computeSha1 =
req.computeSha1 && fileMetadata[_constants.default.SHA1] == null;
const { computeDependencies, maybeReturnContent } = req;
if (absolutePath.includes(NODE_MODULES)) {
if (computeSha1) {
return {
computeDependencies: false,
computeSha1: true,
dependencyExtractor: null,
enableHastePackages: false,
filePath: absolutePath,
hasteImplModulePath: null,
maybeReturnContent,
};
}
return null;
}
return {
computeDependencies,
computeSha1,
dependencyExtractor: this.#dependencyExtractor,
enableHastePackages: this.#enableHastePackages,
filePath: absolutePath,
hasteImplModulePath: this.#hasteImplModulePath,
maybeReturnContent,
};
}
#getBatchWorker(numWorkers) {
if (numWorkers <= 1) {
return {
processFile: async (message) => this.#inBandWorker.processFile(message),
end: async () => {},
};
}
const workerPath = require.resolve("../worker");
debug(
"Creating worker farm of %d worker %s",
numWorkers,
this.#enableWorkerThreads ? "threads" : "processes",
);
this.#perfLogger?.point("initWorkers_start");
const jestWorker = new _jestWorker.Worker(workerPath, {
exposedMethods: ["processFile"],
maxRetries: 3,
numWorkers,
enableWorkerThreads: this.#enableWorkerThreads,
forkOptions: {
execArgv: [],
},
setupArgs: [this.#workerArgs],
});
this.#perfLogger?.point("initWorkers_end");
this.#perfLogger = null;
return jestWorker;
}
async end() {}
}
exports.FileProcessor = FileProcessor;
function processWorkerReply(metadata, fileMetadata) {
fileMetadata[_constants.default.VISITED] = 1;
const metadataId = metadata.id;
if (metadataId != null) {
fileMetadata[_constants.default.ID] = metadataId;
}
fileMetadata[_constants.default.DEPENDENCIES] = metadata.dependencies
? metadata.dependencies.join(_constants.default.DEPENDENCY_DELIM)
: "";
if (metadata.sha1 != null) {
fileMetadata[_constants.default.SHA1] = metadata.sha1;
}
return metadata.content;
}
function normalizeWorkerError(mixedError) {
if (
mixedError == null ||
typeof mixedError !== "object" ||
mixedError.message == null ||
mixedError.stack == null
) {
const error = new Error(mixedError);
error.stack = "";
return error;
}
return mixedError;
}

View File

@@ -0,0 +1,268 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {
FileMetadata,
PerfLogger,
WorkerMessage,
WorkerMetadata,
WorkerSetupArgs,
} from '../flow-types';
import H from '../constants';
import {Worker} from '../worker';
import {Worker as JestWorker} from 'jest-worker';
import {sep} from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:FileMap');
type ProcessFileRequest = $ReadOnly<{
/**
* Populate metadata[H.SHA1] with the SHA1 of the file's contents.
*/
computeSha1: boolean,
/**
* Populate metadata[H.DEPENDENCIES] with unresolved dependency specifiers
* using the dependencyExtractor provided to the constructor.
*/
computeDependencies: boolean,
/**
* Only if processing has already required reading the file's contents, return
* the contents as a Buffer - null otherwise. Not supported for batches.
*/
maybeReturnContent: boolean,
}>;
interface AsyncWorker {
+processFile: WorkerMessage => Promise<WorkerMetadata>;
+end: () => Promise<void>;
}
interface MaybeCodedError extends Error {
code?: string;
}
const NODE_MODULES = sep + 'node_modules' + sep;
const MAX_FILES_PER_WORKER = 100;
export class FileProcessor {
#dependencyExtractor: ?string;
#enableHastePackages: boolean;
#hasteImplModulePath: ?string;
#enableWorkerThreads: boolean;
#maxFilesPerWorker: number;
#maxWorkers: number;
#perfLogger: ?PerfLogger;
#workerArgs: WorkerSetupArgs;
#inBandWorker: Worker;
constructor(
opts: $ReadOnly<{
dependencyExtractor: ?string,
enableHastePackages: boolean,
enableWorkerThreads: boolean,
hasteImplModulePath: ?string,
maxFilesPerWorker?: ?number,
maxWorkers: number,
perfLogger: ?PerfLogger,
}>,
) {
this.#dependencyExtractor = opts.dependencyExtractor;
this.#enableHastePackages = opts.enableHastePackages;
this.#enableWorkerThreads = opts.enableWorkerThreads;
this.#hasteImplModulePath = opts.hasteImplModulePath;
this.#maxFilesPerWorker = opts.maxFilesPerWorker ?? MAX_FILES_PER_WORKER;
this.#maxWorkers = opts.maxWorkers;
this.#workerArgs = {};
this.#inBandWorker = new Worker(this.#workerArgs);
this.#perfLogger = opts.perfLogger;
}
async processBatch(
files: $ReadOnlyArray<[string /*absolutePath*/, FileMetadata]>,
req: ProcessFileRequest,
): Promise<{
errors: Array<{
absolutePath: string,
error: MaybeCodedError,
}>,
}> {
const errors = [];
const numWorkers = Math.min(
this.#maxWorkers,
Math.ceil(files.length / this.#maxFilesPerWorker),
);
const batchWorker = this.#getBatchWorker(numWorkers);
if (req.maybeReturnContent) {
throw new Error(
'Batch processing does not support returning file contents',
);
}
await Promise.all(
files.map(([absolutePath, fileMetadata]) => {
const maybeWorkerInput = this.#getWorkerInput(
absolutePath,
fileMetadata,
req,
);
if (!maybeWorkerInput) {
return null;
}
return batchWorker
.processFile(maybeWorkerInput)
.then(reply => processWorkerReply(reply, fileMetadata))
.catch(error =>
errors.push({absolutePath, error: normalizeWorkerError(error)}),
);
}),
);
await batchWorker.end();
return {errors};
}
processRegularFile(
absolutePath: string,
fileMetadata: FileMetadata,
req: ProcessFileRequest,
): ?{content: ?Buffer} {
const workerInput = this.#getWorkerInput(absolutePath, fileMetadata, req);
return workerInput
? {
content: processWorkerReply(
this.#inBandWorker.processFile(workerInput),
fileMetadata,
),
}
: null;
}
#getWorkerInput(
absolutePath: string,
fileMetadata: FileMetadata,
req: ProcessFileRequest,
): ?WorkerMessage {
const computeSha1 = req.computeSha1 && fileMetadata[H.SHA1] == null;
const {computeDependencies, maybeReturnContent} = req;
// Use a cheaper worker configuration for node_modules files, because we
// never care about extracting dependencies, and they may never be Haste
// modules or packages.
//
// Note that we'd only expect node_modules files to reach this point if
// retainAllFiles is true, or they're touched during watch mode.
if (absolutePath.includes(NODE_MODULES)) {
if (computeSha1) {
return {
computeDependencies: false,
computeSha1: true,
dependencyExtractor: null,
enableHastePackages: false,
filePath: absolutePath,
hasteImplModulePath: null,
maybeReturnContent,
};
}
return null;
}
return {
computeDependencies,
computeSha1,
dependencyExtractor: this.#dependencyExtractor,
enableHastePackages: this.#enableHastePackages,
filePath: absolutePath,
hasteImplModulePath: this.#hasteImplModulePath,
maybeReturnContent,
};
}
/**
* Creates workers or parses files and extracts metadata in-process.
*/
#getBatchWorker(numWorkers: number): AsyncWorker {
if (numWorkers <= 1) {
// In-band worker with the same interface as a Jest worker farm
return {
processFile: async message => this.#inBandWorker.processFile(message),
end: async () => {},
};
}
const workerPath = require.resolve('../worker');
debug(
'Creating worker farm of %d worker %s',
numWorkers,
this.#enableWorkerThreads ? 'threads' : 'processes',
);
this.#perfLogger?.point('initWorkers_start');
const jestWorker = new JestWorker<{
processFile: WorkerMessage => Promise<WorkerMetadata>,
}>(workerPath, {
exposedMethods: ['processFile'],
maxRetries: 3,
numWorkers,
enableWorkerThreads: this.#enableWorkerThreads,
forkOptions: {
// Don't pass Node arguments down to workers. In particular, avoid
// unnecessarily registering Babel when we're running Metro from
// source (our worker is plain CommonJS).
execArgv: [],
},
setupArgs: [this.#workerArgs],
});
this.#perfLogger?.point('initWorkers_end');
// Only log worker init once
this.#perfLogger = null;
return jestWorker;
}
async end(): Promise<void> {}
}
function processWorkerReply(
metadata: WorkerMetadata,
fileMetadata: FileMetadata,
) {
fileMetadata[H.VISITED] = 1;
const metadataId = metadata.id;
if (metadataId != null) {
fileMetadata[H.ID] = metadataId;
}
fileMetadata[H.DEPENDENCIES] = metadata.dependencies
? metadata.dependencies.join(H.DEPENDENCY_DELIM)
: '';
if (metadata.sha1 != null) {
fileMetadata[H.SHA1] = metadata.sha1;
}
return metadata.content;
}
function normalizeWorkerError(mixedError: ?Error | string): MaybeCodedError {
if (
mixedError == null ||
typeof mixedError !== 'object' ||
mixedError.message == null ||
mixedError.stack == null
) {
const error = new Error(mixedError);
error.stack = ''; // Remove stack for stack-less errors.
return error;
}
return mixedError;
}

238
node_modules/metro-file-map/src/lib/RootPathUtils.js generated vendored Normal file
View File

@@ -0,0 +1,238 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.RootPathUtils = void 0;
var _invariant = _interopRequireDefault(require("invariant"));
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const UP_FRAGMENT_SEP = ".." + path.sep;
const SEP_UP_FRAGMENT = path.sep + "..";
const UP_FRAGMENT_SEP_LENGTH = UP_FRAGMENT_SEP.length;
const CURRENT_FRAGMENT = "." + path.sep;
class RootPathUtils {
#rootDir;
#rootDirnames;
#rootParts;
#rootDepth;
constructor(rootDir) {
this.#rootDir = rootDir;
const rootDirnames = [];
for (
let next = rootDir, previous = null;
previous !== next;
previous = next, next = path.dirname(next)
) {
rootDirnames.push(next);
}
this.#rootDirnames = rootDirnames;
this.#rootParts = rootDir.split(path.sep);
this.#rootDepth = rootDirnames.length - 1;
if (this.#rootDepth === 0) {
this.#rootParts.pop();
}
}
getBasenameOfNthAncestor(n) {
return this.#rootParts[this.#rootParts.length - 1 - n];
}
getParts() {
return this.#rootParts;
}
absoluteToNormal(absolutePath) {
let endOfMatchingPrefix = 0;
let lastMatchingPartIdx = 0;
for (
let nextPart = this.#rootParts[0], nextLength = nextPart.length;
nextPart != null &&
absolutePath.startsWith(nextPart, endOfMatchingPrefix) &&
(absolutePath.length === endOfMatchingPrefix + nextLength ||
absolutePath[endOfMatchingPrefix + nextLength] === path.sep);
) {
endOfMatchingPrefix += nextLength + 1;
nextPart = this.#rootParts[++lastMatchingPartIdx];
nextLength = nextPart?.length;
}
const upIndirectionsToPrepend =
this.#rootParts.length - lastMatchingPartIdx;
return (
this.#tryCollapseIndirectionsInSuffix(
absolutePath,
endOfMatchingPrefix,
upIndirectionsToPrepend,
)?.collapsedPath ?? this.#slowAbsoluteToNormal(absolutePath)
);
}
#slowAbsoluteToNormal(absolutePath) {
const endsWithSep = absolutePath.endsWith(path.sep);
const result = path.relative(this.#rootDir, absolutePath);
return endsWithSep && !result.endsWith(path.sep)
? result + path.sep
: result;
}
normalToAbsolute(normalPath) {
let left = this.#rootDir;
let i = 0;
let pos = 0;
while (
normalPath.startsWith(UP_FRAGMENT_SEP, pos) ||
(normalPath.endsWith("..") && normalPath.length === 2 + pos)
) {
left = this.#rootDirnames[i === this.#rootDepth ? this.#rootDepth : ++i];
pos += UP_FRAGMENT_SEP_LENGTH;
}
const right = pos === 0 ? normalPath : normalPath.slice(pos);
if (right.length === 0) {
return left;
}
if (i === this.#rootDepth) {
return left + right;
}
return left + path.sep + right;
}
relativeToNormal(relativePath) {
return (
this.#tryCollapseIndirectionsInSuffix(relativePath, 0, 0)
?.collapsedPath ??
path.relative(this.#rootDir, path.join(this.#rootDir, relativePath))
);
}
getAncestorOfRootIdx(normalPath) {
if (normalPath === "") {
return 0;
}
if (normalPath === "..") {
return 1;
}
if (normalPath.endsWith(SEP_UP_FRAGMENT)) {
return (normalPath.length + 1) / 3;
}
return null;
}
joinNormalToRelative(normalPath, relativePath) {
if (normalPath === "") {
return {
collapsedSegments: 0,
normalPath: relativePath,
};
}
if (relativePath === "") {
return {
collapsedSegments: 0,
normalPath,
};
}
const left = normalPath + path.sep;
const rawPath = left + relativePath;
if (normalPath === ".." || normalPath.endsWith(SEP_UP_FRAGMENT)) {
const collapsed = this.#tryCollapseIndirectionsInSuffix(rawPath, 0, 0);
(0, _invariant.default)(collapsed != null, "Failed to collapse");
return {
collapsedSegments: collapsed.collapsedSegments,
normalPath: collapsed.collapsedPath,
};
}
return {
collapsedSegments: 0,
normalPath: rawPath,
};
}
relative(from, to) {
return path.relative(from, to);
}
#tryCollapseIndirectionsInSuffix(
fullPath,
startOfRelativePart,
implicitUpIndirections,
) {
let totalUpIndirections = implicitUpIndirections;
let collapsedSegments = 0;
for (let pos = startOfRelativePart; ; pos += UP_FRAGMENT_SEP_LENGTH) {
const nextIndirection = fullPath.indexOf(CURRENT_FRAGMENT, pos);
if (nextIndirection === -1) {
while (totalUpIndirections > 0) {
const segmentToMaybeCollapse =
this.#rootParts[this.#rootParts.length - totalUpIndirections];
if (
fullPath.startsWith(segmentToMaybeCollapse, pos) &&
(fullPath.length === segmentToMaybeCollapse.length + pos ||
fullPath[segmentToMaybeCollapse.length + pos] === path.sep)
) {
pos += segmentToMaybeCollapse.length + 1;
collapsedSegments++;
totalUpIndirections--;
} else {
break;
}
}
if (pos >= fullPath.length) {
return {
collapsedPath:
totalUpIndirections > 0
? UP_FRAGMENT_SEP.repeat(totalUpIndirections - 1) +
".." +
fullPath.slice(pos - 1)
: "",
collapsedSegments,
};
}
const right = pos > 0 ? fullPath.slice(pos) : fullPath;
if (
right === ".." &&
totalUpIndirections >= this.#rootParts.length - 1
) {
return {
collapsedPath: UP_FRAGMENT_SEP.repeat(totalUpIndirections).slice(
0,
-1,
),
collapsedSegments,
};
}
if (totalUpIndirections === 0) {
return {
collapsedPath: right,
collapsedSegments,
};
}
return {
collapsedPath: UP_FRAGMENT_SEP.repeat(totalUpIndirections) + right,
collapsedSegments,
};
}
if (totalUpIndirections < this.#rootParts.length - 1) {
totalUpIndirections++;
}
if (nextIndirection !== pos + 1 || fullPath[pos] !== ".") {
return null;
}
}
}
}
exports.RootPathUtils = RootPathUtils;

View File

@@ -0,0 +1,316 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import invariant from 'invariant';
import * as path from 'path';
/**
* This module provides path utility functions - similar to `node:path` -
* optimised for Metro's use case (many paths, few roots) under assumptions
* typically safe to make within Metro - namely:
*
* - All input path separators must be system-native.
* - Double/redundant separators like '/foo//bar' are not supported.
* - All characters except separators are assumed to be valid in path segments.
*
* - A "well-formed" path is any path following the rules above.
* - A "normal" path is a root-relative well-formed path with no redundant
* indirections. Normal paths have no leading './`, and the normal path of
* the root is the empty string.
*
* Output and input paths are at least well-formed (normal where indicated by
* naming).
*
* Trailing path separators are preserved, except for fs roots in
* normalToAbsolute (fs roots always have a trailing separator), and the
* project root in absoluteToNormal and relativeToNormal (the project root is
* always the empty string, and is always a directory, so a trailing separator
* is redundant).
*
* As of Node 20, absoluteToNormal is ~8x faster than `path.relative` and
* `normalToAbsolute` is ~20x faster than `path.resolve`, benchmarked on the
* real inputs from building FB's product graph. Some well-formed inputs
* (e.g., /project/./foo/../bar), are handled but not optimised, and we fall
* back to `node:path` equivalents in those cases.
*/
const UP_FRAGMENT_SEP = '..' + path.sep;
const SEP_UP_FRAGMENT = path.sep + '..';
const UP_FRAGMENT_SEP_LENGTH = UP_FRAGMENT_SEP.length;
const CURRENT_FRAGMENT = '.' + path.sep;
export class RootPathUtils {
#rootDir: string;
#rootDirnames: $ReadOnlyArray<string>;
#rootParts: $ReadOnlyArray<string>;
#rootDepth: number;
constructor(rootDir: string) {
this.#rootDir = rootDir;
const rootDirnames = [];
for (
let next = rootDir, previous = null;
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
previous !== next;
previous = next, next = path.dirname(next)
) {
rootDirnames.push(next);
}
this.#rootDirnames = rootDirnames;
this.#rootParts = rootDir.split(path.sep);
this.#rootDepth = rootDirnames.length - 1;
// If rootDir is a filesystem root (C:\ or /), it will end in a separator and
// give a spurious empty entry at the end of rootParts.
if (this.#rootDepth === 0) {
this.#rootParts.pop();
}
}
getBasenameOfNthAncestor(n: number): string {
return this.#rootParts[this.#rootParts.length - 1 - n];
}
getParts(): $ReadOnlyArray<string> {
return this.#rootParts;
}
// absolutePath may be any well-formed absolute path.
absoluteToNormal(absolutePath: string): string {
let endOfMatchingPrefix = 0;
let lastMatchingPartIdx = 0;
for (
let nextPart = this.#rootParts[0], nextLength = nextPart.length;
nextPart != null &&
// Check that absolutePath is equal to nextPart + '/' or ends with
// nextPart, starting from endOfMatchingPrefix.
absolutePath.startsWith(nextPart, endOfMatchingPrefix) &&
(absolutePath.length === endOfMatchingPrefix + nextLength ||
absolutePath[endOfMatchingPrefix + nextLength] === path.sep);
) {
// Move our matching pointer forward and load the next part.
endOfMatchingPrefix += nextLength + 1;
nextPart = this.#rootParts[++lastMatchingPartIdx];
nextLength = nextPart?.length;
}
// If our root is /project/root and we're given /project/bar/foo.js, we
// have matched up to '/project', and will need to return a path
// beginning '../' (one prepended indirection, to go up from 'root').
//
// If we're given /project/../project2/otherroot, we have one level of
// indirection up to prepend in the same way as above. There's another
// explicit indirection already present in the input - we'll account for
// that in tryCollapseIndirectionsInSuffix.
const upIndirectionsToPrepend =
this.#rootParts.length - lastMatchingPartIdx;
return (
this.#tryCollapseIndirectionsInSuffix(
absolutePath,
endOfMatchingPrefix,
upIndirectionsToPrepend,
)?.collapsedPath ?? this.#slowAbsoluteToNormal(absolutePath)
);
}
#slowAbsoluteToNormal(absolutePath: string): string {
const endsWithSep = absolutePath.endsWith(path.sep);
const result = path.relative(this.#rootDir, absolutePath);
return endsWithSep && !result.endsWith(path.sep)
? result + path.sep
: result;
}
// `normalPath` is assumed to be normal (root-relative, no redundant
// indirection), per the definition above.
normalToAbsolute(normalPath: string): string {
let left = this.#rootDir;
let i = 0;
let pos = 0;
while (
normalPath.startsWith(UP_FRAGMENT_SEP, pos) ||
(normalPath.endsWith('..') && normalPath.length === 2 + pos)
) {
left = this.#rootDirnames[i === this.#rootDepth ? this.#rootDepth : ++i];
pos += UP_FRAGMENT_SEP_LENGTH;
}
const right = pos === 0 ? normalPath : normalPath.slice(pos);
if (right.length === 0) {
return left;
}
// left may already end in a path separator only if it is a filesystem root,
// '/' or 'X:\'.
if (i === this.#rootDepth) {
return left + right;
}
return left + path.sep + right;
}
relativeToNormal(relativePath: string): string {
return (
this.#tryCollapseIndirectionsInSuffix(relativePath, 0, 0)
?.collapsedPath ??
path.relative(this.#rootDir, path.join(this.#rootDir, relativePath))
);
}
// If a path is a direct ancestor of the project root (or the root itself),
// return a number with the degrees of separation, e.g. root=0, parent=1,..
// or null otherwise.
getAncestorOfRootIdx(normalPath: string): ?number {
if (normalPath === '') {
return 0;
}
if (normalPath === '..') {
return 1;
}
// Otherwise a *normal* path is only a root ancestor if it is a sequence of
// '../' segments followed by '..', so the length tells us the number of
// up fragments.
if (normalPath.endsWith(SEP_UP_FRAGMENT)) {
return (normalPath.length + 1) / 3;
}
return null;
}
// Takes a normal and relative path, and joins them efficiently into a normal
// path, including collapsing trailing '..' in the first part with leading
// project root segments in the relative part.
joinNormalToRelative(
normalPath: string,
relativePath: string,
): {normalPath: string, collapsedSegments: number} {
if (normalPath === '') {
return {collapsedSegments: 0, normalPath: relativePath};
}
if (relativePath === '') {
return {collapsedSegments: 0, normalPath};
}
const left = normalPath + path.sep;
const rawPath = left + relativePath;
if (normalPath === '..' || normalPath.endsWith(SEP_UP_FRAGMENT)) {
const collapsed = this.#tryCollapseIndirectionsInSuffix(rawPath, 0, 0);
invariant(collapsed != null, 'Failed to collapse');
return {
collapsedSegments: collapsed.collapsedSegments,
normalPath: collapsed.collapsedPath,
};
}
return {
collapsedSegments: 0,
normalPath: rawPath,
};
}
relative(from: string, to: string): string {
return path.relative(from, to);
}
// Internal: Tries to collapse sequences like `../root/foo` for root
// `/project/root` down to the normal 'foo'.
#tryCollapseIndirectionsInSuffix(
fullPath: string, // A string ending with the relative path to process
startOfRelativePart: number, // Index of the start of part to process
implicitUpIndirections: number, // 0=root-relative, 1=dirname(root)-relative...
): ?{collapsedPath: string, collapsedSegments: number} {
let totalUpIndirections = implicitUpIndirections;
let collapsedSegments = 0;
// Allow any sequence of indirection fragments at the start of the
// unmatched suffix e.g /project/[../../foo], but bail out to Node's
// path.relative if we find a possible indirection after any later segment,
// or on any "./" that isn't a "../".
for (let pos = startOfRelativePart; ; pos += UP_FRAGMENT_SEP_LENGTH) {
const nextIndirection = fullPath.indexOf(CURRENT_FRAGMENT, pos);
if (nextIndirection === -1) {
// If we have any indirections, they may "collapse" if a subsequent
// segment re-enters a directory we had previously exited, e.g:
// /project/root/../root/foo should collapse to /project/root/foo' and
// return foo, not ../root/foo.
//
// We match each segment following redirections, in turn, against the
// part of the root path they may collapse into, and break on the first
// mismatch.
while (totalUpIndirections > 0) {
const segmentToMaybeCollapse =
this.#rootParts[this.#rootParts.length - totalUpIndirections];
if (
fullPath.startsWith(segmentToMaybeCollapse, pos) &&
// The following character should be either a separator or end of
// string
(fullPath.length === segmentToMaybeCollapse.length + pos ||
fullPath[segmentToMaybeCollapse.length + pos] === path.sep)
) {
pos += segmentToMaybeCollapse.length + 1;
collapsedSegments++;
totalUpIndirections--;
} else {
break;
}
}
// After collapsing we may have no more segments remaining (following
// '..' indirections). Ensure that we don't drop or add a trailing
// separator in this case by taking .slice(pos-1). In any other case,
// we know that fullPath[pos] is a separator.
if (pos >= fullPath.length) {
return {
collapsedPath:
totalUpIndirections > 0
? UP_FRAGMENT_SEP.repeat(totalUpIndirections - 1) +
'..' +
fullPath.slice(pos - 1)
: '',
collapsedSegments,
};
}
const right = pos > 0 ? fullPath.slice(pos) : fullPath;
if (
right === '..' &&
totalUpIndirections >= this.#rootParts.length - 1
) {
// If we have no right side (or an indirection that would take us
// below the root), just ensure we don't include a trailing separtor.
return {
collapsedPath: UP_FRAGMENT_SEP.repeat(totalUpIndirections).slice(
0,
-1,
),
collapsedSegments,
};
}
// Optimisation for the common case, saves a concatenation.
if (totalUpIndirections === 0) {
return {collapsedPath: right, collapsedSegments};
}
return {
collapsedPath: UP_FRAGMENT_SEP.repeat(totalUpIndirections) + right,
collapsedSegments,
};
}
// Cap the number of indirections at the total number of root segments.
// File systems treat '..' at the root as '.'.
if (totalUpIndirections < this.#rootParts.length - 1) {
totalUpIndirections++;
}
if (
nextIndirection !== pos + 1 || // Fallback when ./ later in the path, or leading
fullPath[pos] !== '.' // and for anything other than a leading ../
) {
return null;
}
}
}
}

835
node_modules/metro-file-map/src/lib/TreeFS.js generated vendored Normal file
View File

@@ -0,0 +1,835 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _constants = _interopRequireDefault(require("../constants"));
var _RootPathUtils = require("./RootPathUtils");
var _invariant = _interopRequireDefault(require("invariant"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function isDirectory(node) {
return node instanceof Map;
}
function isRegularFile(node) {
return node[_constants.default.SYMLINK] === 0;
}
class TreeFS {
#cachedNormalSymlinkTargets = new WeakMap();
#rootDir;
#rootNode = new Map();
#pathUtils;
#processFile;
constructor({ rootDir, files, processFile }) {
this.#rootDir = rootDir;
this.#pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
this.#processFile = processFile;
if (files != null) {
this.bulkAddOrModify(files);
}
}
getSerializableSnapshot() {
return this._cloneTree(this.#rootNode);
}
static fromDeserializedSnapshot({ rootDir, fileSystemData, processFile }) {
const tfs = new TreeFS({
rootDir,
processFile,
});
tfs.#rootNode = fileSystemData;
return tfs;
}
getModuleName(mixedPath) {
const fileMetadata = this._getFileData(mixedPath);
return (fileMetadata && fileMetadata[_constants.default.ID]) ?? null;
}
getSize(mixedPath) {
const fileMetadata = this._getFileData(mixedPath);
return (fileMetadata && fileMetadata[_constants.default.SIZE]) ?? null;
}
getDependencies(mixedPath) {
const fileMetadata = this._getFileData(mixedPath);
if (fileMetadata) {
return fileMetadata[_constants.default.DEPENDENCIES]
? fileMetadata[_constants.default.DEPENDENCIES].split(
_constants.default.DEPENDENCY_DELIM,
)
: [];
} else {
return null;
}
}
getDifference(files) {
const changedFiles = new Map(files);
const removedFiles = new Set();
for (const { canonicalPath, metadata } of this.metadataIterator({
includeSymlinks: true,
includeNodeModules: true,
})) {
const newMetadata = files.get(canonicalPath);
if (newMetadata) {
if (isRegularFile(newMetadata) !== isRegularFile(metadata)) {
continue;
}
if (
newMetadata[_constants.default.MTIME] != null &&
newMetadata[_constants.default.MTIME] != 0 &&
newMetadata[_constants.default.MTIME] ===
metadata[_constants.default.MTIME]
) {
changedFiles.delete(canonicalPath);
} else if (
newMetadata[_constants.default.SHA1] != null &&
newMetadata[_constants.default.SHA1] ===
metadata[_constants.default.SHA1] &&
metadata[_constants.default.VISITED] === 1
) {
const updatedMetadata = [...metadata];
updatedMetadata[_constants.default.MTIME] =
newMetadata[_constants.default.MTIME];
changedFiles.set(canonicalPath, updatedMetadata);
}
} else {
removedFiles.add(canonicalPath);
}
}
return {
changedFiles,
removedFiles,
};
}
getSha1(mixedPath) {
const fileMetadata = this._getFileData(mixedPath);
return (fileMetadata && fileMetadata[_constants.default.SHA1]) ?? null;
}
async getOrComputeSha1(mixedPath) {
const normalPath = this._normalizePath(mixedPath);
const result = this._lookupByNormalPath(normalPath, {
followLeaf: true,
});
if (!result.exists || isDirectory(result.node)) {
return null;
}
const { canonicalPath, node: fileMetadata } = result;
const existing = fileMetadata[_constants.default.SHA1];
if (existing != null && existing.length > 0) {
return {
sha1: existing,
};
}
const absolutePath = this.#pathUtils.normalToAbsolute(canonicalPath);
const maybeContent = await this.#processFile(absolutePath, fileMetadata, {
computeSha1: true,
});
const sha1 = fileMetadata[_constants.default.SHA1];
(0, _invariant.default)(
sha1 != null && sha1.length > 0,
"File processing didn't populate a SHA-1 hash for %s",
absolutePath,
);
return maybeContent
? {
sha1,
content: maybeContent,
}
: {
sha1,
};
}
exists(mixedPath) {
const result = this._getFileData(mixedPath);
return result != null;
}
lookup(mixedPath) {
const normalPath = this._normalizePath(mixedPath);
const links = new Set();
const result = this._lookupByNormalPath(normalPath, {
collectLinkPaths: links,
followLeaf: true,
});
if (!result.exists) {
const { canonicalMissingPath } = result;
return {
exists: false,
links,
missing: this.#pathUtils.normalToAbsolute(canonicalMissingPath),
};
}
const { canonicalPath, node } = result;
const type = isDirectory(node) ? "d" : isRegularFile(node) ? "f" : "l";
(0, _invariant.default)(
type !== "l",
"lookup follows symlinks, so should never return one (%s -> %s)",
mixedPath,
canonicalPath,
);
return {
exists: true,
links,
realPath: this.#pathUtils.normalToAbsolute(canonicalPath),
type,
};
}
getAllFiles() {
return Array.from(
this.metadataIterator({
includeSymlinks: false,
includeNodeModules: true,
}),
({ canonicalPath }) => this.#pathUtils.normalToAbsolute(canonicalPath),
);
}
linkStats(mixedPath) {
const fileMetadata = this._getFileData(mixedPath, {
followLeaf: false,
});
if (fileMetadata == null) {
return null;
}
const fileType = isRegularFile(fileMetadata) ? "f" : "l";
return {
fileType,
modifiedTime: fileMetadata[_constants.default.MTIME],
size: fileMetadata[_constants.default.SIZE],
};
}
*matchFiles({
filter = null,
filterCompareAbsolute = false,
filterComparePosix = false,
follow = false,
recursive = true,
rootDir = null,
}) {
const normalRoot = rootDir == null ? "" : this._normalizePath(rootDir);
const contextRootResult = this._lookupByNormalPath(normalRoot);
if (!contextRootResult.exists) {
return;
}
const {
ancestorOfRootIdx,
canonicalPath: rootRealPath,
node: contextRoot,
parentNode: contextRootParent,
} = contextRootResult;
if (!isDirectory(contextRoot)) {
return;
}
const contextRootAbsolutePath =
rootRealPath === ""
? this.#rootDir
: _path.default.join(this.#rootDir, rootRealPath);
const prefix = filterComparePosix ? "./" : "." + _path.default.sep;
const contextRootAbsolutePathForComparison =
filterComparePosix && _path.default.sep !== "/"
? contextRootAbsolutePath.replaceAll(_path.default.sep, "/")
: contextRootAbsolutePath;
for (const relativePathForComparison of this._pathIterator(
contextRoot,
contextRootParent,
ancestorOfRootIdx,
{
alwaysYieldPosix: filterComparePosix,
canonicalPathOfRoot: rootRealPath,
follow,
recursive,
subtreeOnly: rootDir != null,
},
)) {
if (
filter == null ||
filter.test(
filterCompareAbsolute === true
? _path.default.join(
contextRootAbsolutePathForComparison,
relativePathForComparison,
)
: prefix + relativePathForComparison,
)
) {
const relativePath =
filterComparePosix === true && _path.default.sep !== "/"
? relativePathForComparison.replaceAll("/", _path.default.sep)
: relativePathForComparison;
yield _path.default.join(contextRootAbsolutePath, relativePath);
}
}
}
addOrModify(mixedPath, metadata) {
const normalPath = this._normalizePath(mixedPath);
const parentDirNode = this._lookupByNormalPath(
_path.default.dirname(normalPath),
{
makeDirectories: true,
},
);
if (!parentDirNode.exists) {
throw new Error(
`TreeFS: Failed to make parent directory entry for ${mixedPath}`,
);
}
const canonicalPath = this._normalizePath(
parentDirNode.canonicalPath +
_path.default.sep +
_path.default.basename(normalPath),
);
this.bulkAddOrModify(new Map([[canonicalPath, metadata]]));
}
bulkAddOrModify(addedOrModifiedFiles) {
let lastDir;
let directoryNode;
for (const [normalPath, metadata] of addedOrModifiedFiles) {
const lastSepIdx = normalPath.lastIndexOf(_path.default.sep);
const dirname = lastSepIdx === -1 ? "" : normalPath.slice(0, lastSepIdx);
const basename =
lastSepIdx === -1 ? normalPath : normalPath.slice(lastSepIdx + 1);
if (directoryNode == null || dirname !== lastDir) {
const lookup = this._lookupByNormalPath(dirname, {
followLeaf: false,
makeDirectories: true,
});
if (!lookup.exists) {
throw new Error(
`TreeFS: Unexpected error adding ${normalPath}.\nMissing: ` +
lookup.canonicalMissingPath,
);
}
if (!isDirectory(lookup.node)) {
throw new Error(
`TreeFS: Could not add directory ${dirname}, adding ${normalPath}. ` +
`${dirname} already exists in the file map as a file.`,
);
}
lastDir = dirname;
directoryNode = lookup.node;
}
directoryNode.set(basename, metadata);
}
}
remove(mixedPath) {
const normalPath = this._normalizePath(mixedPath);
const result = this._lookupByNormalPath(normalPath, {
followLeaf: false,
});
if (!result.exists) {
return null;
}
const { parentNode, canonicalPath, node } = result;
if (isDirectory(node) && node.size > 0) {
throw new Error(
`TreeFS: remove called on a non-empty directory: ${mixedPath}`,
);
}
if (parentNode != null) {
parentNode.delete(_path.default.basename(canonicalPath));
if (parentNode.size === 0 && parentNode !== this.#rootNode) {
this.remove(_path.default.dirname(canonicalPath));
}
}
return isDirectory(node) ? null : node;
}
_lookupByNormalPath(
requestedNormalPath,
opts = {
followLeaf: true,
makeDirectories: false,
},
) {
let targetNormalPath = requestedNormalPath;
let seen;
let fromIdx = opts.start?.pathIdx ?? 0;
let parentNode = opts.start?.node ?? this.#rootNode;
let ancestorOfRootIdx = opts.start?.ancestorOfRootIdx ?? 0;
const collectAncestors = opts.collectAncestors;
let unseenPathFromIdx = 0;
while (targetNormalPath.length > fromIdx) {
const nextSepIdx = targetNormalPath.indexOf(_path.default.sep, fromIdx);
const isLastSegment = nextSepIdx === -1;
const segmentName = isLastSegment
? targetNormalPath.slice(fromIdx)
: targetNormalPath.slice(fromIdx, nextSepIdx);
const isUnseen = fromIdx >= unseenPathFromIdx;
fromIdx = !isLastSegment ? nextSepIdx + 1 : targetNormalPath.length;
if (segmentName === ".") {
continue;
}
let segmentNode = parentNode.get(segmentName);
if (segmentName === ".." && ancestorOfRootIdx != null) {
ancestorOfRootIdx++;
} else if (segmentNode != null) {
ancestorOfRootIdx = null;
}
if (segmentNode == null) {
if (opts.makeDirectories !== true && segmentName !== "..") {
return {
canonicalMissingPath: isLastSegment
? targetNormalPath
: targetNormalPath.slice(0, fromIdx - 1),
exists: false,
missingSegmentName: segmentName,
};
}
segmentNode = new Map();
if (opts.makeDirectories === true) {
parentNode.set(segmentName, segmentNode);
}
}
if (
(nextSepIdx === targetNormalPath.length - 1 &&
isDirectory(segmentNode)) ||
(isLastSegment &&
(isDirectory(segmentNode) ||
isRegularFile(segmentNode) ||
opts.followLeaf === false))
) {
return {
ancestorOfRootIdx,
canonicalPath: isLastSegment
? targetNormalPath
: targetNormalPath.slice(0, -1),
exists: true,
node: segmentNode,
parentNode,
};
}
if (isDirectory(segmentNode)) {
parentNode = segmentNode;
if (collectAncestors && isUnseen) {
const currentPath = isLastSegment
? targetNormalPath
: targetNormalPath.slice(0, fromIdx - 1);
collectAncestors.push({
ancestorOfRootIdx,
node: segmentNode,
normalPath: currentPath,
segmentName,
});
}
} else {
const currentPath = isLastSegment
? targetNormalPath
: targetNormalPath.slice(0, fromIdx - 1);
if (isRegularFile(segmentNode)) {
return {
canonicalMissingPath: currentPath,
exists: false,
missingSegmentName: segmentName,
};
}
const normalSymlinkTarget = this._resolveSymlinkTargetToNormalPath(
segmentNode,
currentPath,
);
if (opts.collectLinkPaths) {
opts.collectLinkPaths.add(
this.#pathUtils.normalToAbsolute(currentPath),
);
}
const remainingTargetPath = isLastSegment
? ""
: targetNormalPath.slice(fromIdx);
const joinedResult = this.#pathUtils.joinNormalToRelative(
normalSymlinkTarget.normalPath,
remainingTargetPath,
);
targetNormalPath = joinedResult.normalPath;
if (
collectAncestors &&
!isLastSegment &&
(normalSymlinkTarget.ancestorOfRootIdx === 0 ||
joinedResult.collapsedSegments > 0)
) {
let node = this.#rootNode;
let collapsedPath = "";
const reverseAncestors = [];
for (
let i = 0;
i <= joinedResult.collapsedSegments && isDirectory(node);
i++
) {
if (
i > 0 ||
normalSymlinkTarget.ancestorOfRootIdx === 0 ||
joinedResult.collapsedSegments > 0
) {
reverseAncestors.push({
ancestorOfRootIdx: i,
node,
normalPath: collapsedPath,
segmentName: this.#pathUtils.getBasenameOfNthAncestor(i),
});
}
node = node.get("..") ?? new Map();
collapsedPath =
collapsedPath === ""
? ".."
: collapsedPath + _path.default.sep + "..";
}
collectAncestors.push(...reverseAncestors.reverse());
}
unseenPathFromIdx = normalSymlinkTarget.startOfBasenameIdx;
if (seen == null) {
seen = new Set([requestedNormalPath]);
}
if (seen.has(targetNormalPath)) {
return {
canonicalMissingPath: targetNormalPath,
exists: false,
missingSegmentName: segmentName,
};
}
seen.add(targetNormalPath);
fromIdx = 0;
parentNode = this.#rootNode;
ancestorOfRootIdx = 0;
}
}
(0, _invariant.default)(
parentNode === this.#rootNode,
"Unexpectedly escaped traversal",
);
return {
ancestorOfRootIdx: 0,
canonicalPath: targetNormalPath,
exists: true,
node: this.#rootNode,
parentNode: null,
};
}
hierarchicalLookup(mixedStartPath, subpath, opts) {
const ancestorsOfInput = [];
const normalPath = this._normalizePath(mixedStartPath);
const invalidatedBy = opts.invalidatedBy;
const closestLookup = this._lookupByNormalPath(normalPath, {
collectAncestors: ancestorsOfInput,
collectLinkPaths: invalidatedBy,
});
if (closestLookup.exists && isDirectory(closestLookup.node)) {
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(
closestLookup.canonicalPath,
subpath,
opts.subpathType,
invalidatedBy,
null,
);
if (maybeAbsolutePathMatch != null) {
return {
absolutePath: maybeAbsolutePathMatch,
containerRelativePath: "",
};
}
} else {
if (
invalidatedBy &&
(!closestLookup.exists || !isDirectory(closestLookup.node))
) {
invalidatedBy.add(
this.#pathUtils.normalToAbsolute(
closestLookup.exists
? closestLookup.canonicalPath
: closestLookup.canonicalMissingPath,
),
);
}
if (
opts.breakOnSegment != null &&
!closestLookup.exists &&
closestLookup.missingSegmentName === opts.breakOnSegment
) {
return null;
}
}
let commonRoot = this.#rootNode;
let commonRootDepth = 0;
if (closestLookup.exists && closestLookup.ancestorOfRootIdx != null) {
commonRootDepth = closestLookup.ancestorOfRootIdx;
(0, _invariant.default)(
isDirectory(closestLookup.node),
"ancestors of the root must be directories",
);
commonRoot = closestLookup.node;
} else {
for (const ancestor of ancestorsOfInput) {
if (ancestor.ancestorOfRootIdx == null) {
break;
}
commonRootDepth = ancestor.ancestorOfRootIdx;
commonRoot = ancestor.node;
}
}
for (
let candidateIdx = ancestorsOfInput.length - 1;
candidateIdx >= commonRootDepth;
--candidateIdx
) {
const candidate = ancestorsOfInput[candidateIdx];
if (candidate.segmentName === opts.breakOnSegment) {
return null;
}
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(
candidate.normalPath,
subpath,
opts.subpathType,
invalidatedBy,
{
ancestorOfRootIdx: candidate.ancestorOfRootIdx,
node: candidate.node,
pathIdx:
candidate.normalPath.length > 0
? candidate.normalPath.length + 1
: 0,
},
);
if (maybeAbsolutePathMatch != null) {
let prefixLength = commonRootDepth * 3;
for (let i = commonRootDepth; i <= candidateIdx; i++) {
prefixLength = normalPath.indexOf(
_path.default.sep,
prefixLength + 1,
);
}
const containerRelativePath = normalPath.slice(prefixLength + 1);
return {
absolutePath: maybeAbsolutePathMatch,
containerRelativePath,
};
}
}
let candidateNormalPath =
commonRootDepth > 0 ? normalPath.slice(0, 3 * commonRootDepth - 1) : "";
const remainingNormalPath = normalPath.slice(commonRootDepth * 3);
let nextNode = commonRoot;
let depthBelowCommonRoot = 0;
while (isDirectory(nextNode)) {
const maybeAbsolutePathMatch = this.#checkCandidateHasSubpath(
candidateNormalPath,
subpath,
opts.subpathType,
invalidatedBy,
null,
);
if (maybeAbsolutePathMatch != null) {
const rootDirParts = this.#pathUtils.getParts();
const relativeParts =
depthBelowCommonRoot > 0
? rootDirParts.slice(
-(depthBelowCommonRoot + commonRootDepth),
commonRootDepth > 0 ? -commonRootDepth : undefined,
)
: [];
if (remainingNormalPath !== "") {
relativeParts.push(remainingNormalPath);
}
return {
absolutePath: maybeAbsolutePathMatch,
containerRelativePath: relativeParts.join(_path.default.sep),
};
}
depthBelowCommonRoot++;
candidateNormalPath =
candidateNormalPath === ""
? ".."
: candidateNormalPath + _path.default.sep + "..";
nextNode = nextNode.get("..");
}
return null;
}
#checkCandidateHasSubpath(
normalCandidatePath,
subpath,
subpathType,
invalidatedBy,
start,
) {
const lookupResult = this._lookupByNormalPath(
this.#pathUtils.joinNormalToRelative(normalCandidatePath, subpath)
.normalPath,
{
collectLinkPaths: invalidatedBy,
},
);
if (
lookupResult.exists &&
isDirectory(lookupResult.node) === (subpathType === "d")
) {
return this.#pathUtils.normalToAbsolute(lookupResult.canonicalPath);
} else if (invalidatedBy) {
invalidatedBy.add(
this.#pathUtils.normalToAbsolute(
lookupResult.exists
? lookupResult.canonicalPath
: lookupResult.canonicalMissingPath,
),
);
}
return null;
}
*metadataIterator(opts) {
yield* this._metadataIterator(this.#rootNode, opts);
}
*_metadataIterator(rootNode, opts, prefix = "") {
for (const [name, node] of rootNode) {
if (
!opts.includeNodeModules &&
isDirectory(node) &&
name === "node_modules"
) {
continue;
}
const prefixedName =
prefix === "" ? name : prefix + _path.default.sep + name;
if (isDirectory(node)) {
yield* this._metadataIterator(node, opts, prefixedName);
} else if (isRegularFile(node) || opts.includeSymlinks) {
yield {
canonicalPath: prefixedName,
metadata: node,
baseName: name,
};
}
}
}
_normalizePath(relativeOrAbsolutePath) {
return _path.default.isAbsolute(relativeOrAbsolutePath)
? this.#pathUtils.absoluteToNormal(relativeOrAbsolutePath)
: this.#pathUtils.relativeToNormal(relativeOrAbsolutePath);
}
*#directoryNodeIterator(node, parent, ancestorOfRootIdx) {
if (ancestorOfRootIdx != null && ancestorOfRootIdx > 0 && parent) {
yield [
this.#pathUtils.getBasenameOfNthAncestor(ancestorOfRootIdx - 1),
parent,
];
}
yield* node.entries();
}
*_pathIterator(
iterationRootNode,
iterationRootParentNode,
ancestorOfRootIdx,
opts,
pathPrefix = "",
followedLinks = new Set(),
) {
const pathSep = opts.alwaysYieldPosix ? "/" : _path.default.sep;
const prefixWithSep = pathPrefix === "" ? pathPrefix : pathPrefix + pathSep;
for (const [name, node] of this.#directoryNodeIterator(
iterationRootNode,
iterationRootParentNode,
ancestorOfRootIdx,
)) {
if (opts.subtreeOnly && name === "..") {
continue;
}
const nodePath = prefixWithSep + name;
if (!isDirectory(node)) {
if (isRegularFile(node)) {
yield nodePath;
} else {
const nodePathWithSystemSeparators =
pathSep === _path.default.sep
? nodePath
: nodePath.replaceAll(pathSep, _path.default.sep);
const normalPathOfSymlink = _path.default.join(
opts.canonicalPathOfRoot,
nodePathWithSystemSeparators,
);
const resolved = this._lookupByNormalPath(normalPathOfSymlink, {
followLeaf: true,
});
if (!resolved.exists) {
continue;
}
const target = resolved.node;
if (!isDirectory(target)) {
yield nodePath;
} else if (
opts.recursive &&
opts.follow &&
!followedLinks.has(node)
) {
yield* this._pathIterator(
target,
resolved.parentNode,
resolved.ancestorOfRootIdx,
opts,
nodePath,
new Set([...followedLinks, node]),
);
}
}
} else if (opts.recursive) {
yield* this._pathIterator(
node,
iterationRootParentNode,
ancestorOfRootIdx != null && ancestorOfRootIdx > 0
? ancestorOfRootIdx - 1
: null,
opts,
nodePath,
followedLinks,
);
}
}
}
_resolveSymlinkTargetToNormalPath(symlinkNode, canonicalPathOfSymlink) {
const cachedResult = this.#cachedNormalSymlinkTargets.get(symlinkNode);
if (cachedResult != null) {
return cachedResult;
}
const literalSymlinkTarget = symlinkNode[_constants.default.SYMLINK];
(0, _invariant.default)(
typeof literalSymlinkTarget === "string",
"Expected symlink target to be populated.",
);
const absoluteSymlinkTarget = _path.default.resolve(
this.#rootDir,
canonicalPathOfSymlink,
"..",
literalSymlinkTarget,
);
const normalSymlinkTarget = _path.default.relative(
this.#rootDir,
absoluteSymlinkTarget,
);
const result = {
ancestorOfRootIdx:
this.#pathUtils.getAncestorOfRootIdx(normalSymlinkTarget),
normalPath: normalSymlinkTarget,
startOfBasenameIdx:
normalSymlinkTarget.lastIndexOf(_path.default.sep) + 1,
};
this.#cachedNormalSymlinkTargets.set(symlinkNode, result);
return result;
}
_getFileData(
filePath,
opts = {
followLeaf: true,
},
) {
const normalPath = this._normalizePath(filePath);
const result = this._lookupByNormalPath(normalPath, {
followLeaf: opts.followLeaf,
});
if (!result.exists || isDirectory(result.node)) {
return null;
}
return result.node;
}
_cloneTree(root) {
const clone = new Map();
for (const [name, node] of root) {
if (isDirectory(node)) {
clone.set(name, this._cloneTree(node));
} else {
clone.set(name, [...node]);
}
}
return clone;
}
}
exports.default = TreeFS;

1219
node_modules/metro-file-map/src/lib/TreeFS.js.flow generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = checkWatchmanCapabilities;
var _child_process = require("child_process");
var _util = require("util");
async function checkWatchmanCapabilities(requiredCapabilities) {
const execFilePromise = (0, _util.promisify)(_child_process.execFile);
let rawResponse;
try {
const result = await execFilePromise("watchman", [
"list-capabilities",
"--output-encoding=json",
"--no-pretty",
"--no-spawn",
]);
rawResponse = result.stdout;
} catch (e) {
if (e?.code === "ENOENT") {
throw new Error("Watchman is not installed or not available on PATH");
}
throw e;
}
let parsedResponse;
try {
parsedResponse = JSON.parse(rawResponse);
} catch {
throw new Error(
"Failed to parse response from `watchman list-capabilities`",
);
}
if (
parsedResponse == null ||
typeof parsedResponse !== "object" ||
typeof parsedResponse.version !== "string" ||
!Array.isArray(parsedResponse.capabilities)
) {
throw new Error("Unexpected response from `watchman list-capabilities`");
}
const version = parsedResponse.version;
const capabilities = new Set(parsedResponse.capabilities);
const missingCapabilities = requiredCapabilities.filter(
(requiredCapability) => !capabilities.has(requiredCapability),
);
if (missingCapabilities.length > 0) {
throw new Error(
`The installed version of Watchman (${version}) is missing required capabilities: ${missingCapabilities.join(", ")}`,
);
}
return {
version,
};
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import {execFile} from 'child_process';
import {promisify} from 'util';
export default async function checkWatchmanCapabilities(
requiredCapabilities: $ReadOnlyArray<string>,
): Promise<{version: string}> {
const execFilePromise: (
cmd: string,
args: $ReadOnlyArray<string>,
) => Promise<{stdout: string}> = promisify(execFile);
let rawResponse;
try {
const result = await execFilePromise('watchman', [
'list-capabilities',
'--output-encoding=json',
'--no-pretty',
'--no-spawn', // The client can answer this, so don't spawn a server
]);
rawResponse = result.stdout;
} catch (e) {
if (e?.code === 'ENOENT') {
throw new Error('Watchman is not installed or not available on PATH');
}
throw e;
}
let parsedResponse;
try {
parsedResponse = (JSON.parse(rawResponse): mixed);
} catch {
throw new Error(
'Failed to parse response from `watchman list-capabilities`',
);
}
if (
parsedResponse == null ||
typeof parsedResponse !== 'object' ||
typeof parsedResponse.version !== 'string' ||
!Array.isArray(parsedResponse.capabilities)
) {
throw new Error('Unexpected response from `watchman list-capabilities`');
}
const version = parsedResponse.version;
const capabilities = new Set(parsedResponse.capabilities);
const missingCapabilities = requiredCapabilities.filter(
requiredCapability => !capabilities.has(requiredCapability),
);
if (missingCapabilities.length > 0) {
throw new Error(
`The installed version of Watchman (${version}) is missing required capabilities: ${missingCapabilities.join(
', ',
)}`,
);
}
return {version};
}

View File

@@ -0,0 +1,73 @@
"use strict";
const NOT_A_DOT = "(?<!\\.\\s*)";
const CAPTURE_STRING_LITERAL = (pos) => `([\`'"])([^'"\`]*?)(?:\\${pos})`;
const WORD_SEPARATOR = "\\b";
const LEFT_PARENTHESIS = "\\(";
const RIGHT_PARENTHESIS = "\\)";
const WHITESPACE = "\\s*";
const OPTIONAL_COMMA = "(:?,\\s*)?";
function createRegExp(parts, flags) {
return new RegExp(parts.join(""), flags);
}
function alternatives(...parts) {
return `(?:${parts.join("|")})`;
}
function functionCallStart(...names) {
return [
NOT_A_DOT,
WORD_SEPARATOR,
alternatives(...names),
WHITESPACE,
LEFT_PARENTHESIS,
WHITESPACE,
];
}
const BLOCK_COMMENT_RE = /\/\*[^]*?\*\//g;
const LINE_COMMENT_RE = /\/\/.*/g;
const REQUIRE_OR_DYNAMIC_IMPORT_RE = createRegExp(
[
...functionCallStart("require", "import"),
CAPTURE_STRING_LITERAL(1),
WHITESPACE,
OPTIONAL_COMMA,
RIGHT_PARENTHESIS,
],
"g",
);
const IMPORT_OR_EXPORT_RE = createRegExp(
[
"\\b(?:import|export)\\s+(?!type(?:of)?\\s+)(?:[^'\"]+\\s+from\\s+)?",
CAPTURE_STRING_LITERAL(1),
],
"g",
);
const JEST_EXTENSIONS_RE = createRegExp(
[
...functionCallStart(
"jest\\s*\\.\\s*(?:requireActual|requireMock|genMockFromModule|createMockFromModule)",
),
CAPTURE_STRING_LITERAL(1),
WHITESPACE,
OPTIONAL_COMMA,
RIGHT_PARENTHESIS,
],
"g",
);
function extract(code) {
const dependencies = new Set();
const addDependency = (match, _, dep) => {
dependencies.add(dep);
return match;
};
code
.replace(BLOCK_COMMENT_RE, "")
.replace(LINE_COMMENT_RE, "")
.replace(IMPORT_OR_EXPORT_RE, addDependency)
.replace(REQUIRE_OR_DYNAMIC_IMPORT_RE, addDependency)
.replace(JEST_EXTENSIONS_RE, addDependency);
return dependencies;
}
module.exports = {
extract,
};

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
// Required by worker, must be commonjs
/* eslint-disable import/no-commonjs */
'use strict';
const NOT_A_DOT = '(?<!\\.\\s*)';
const CAPTURE_STRING_LITERAL = (pos /*: number */) =>
`([\`'"])([^'"\`]*?)(?:\\${pos})`;
const WORD_SEPARATOR = '\\b';
const LEFT_PARENTHESIS = '\\(';
const RIGHT_PARENTHESIS = '\\)';
const WHITESPACE = '\\s*';
const OPTIONAL_COMMA = '(:?,\\s*)?';
function createRegExp(
parts /*: $ReadOnlyArray<string> */,
flags /*: string */,
) {
return new RegExp(parts.join(''), flags);
}
function alternatives(...parts /*: $ReadOnlyArray<string> */) {
return `(?:${parts.join('|')})`;
}
function functionCallStart(...names /*: $ReadOnlyArray<string> */) {
return [
NOT_A_DOT,
WORD_SEPARATOR,
alternatives(...names),
WHITESPACE,
LEFT_PARENTHESIS,
WHITESPACE,
];
}
const BLOCK_COMMENT_RE = /\/\*[^]*?\*\//g;
const LINE_COMMENT_RE = /\/\/.*/g;
const REQUIRE_OR_DYNAMIC_IMPORT_RE = createRegExp(
[
...functionCallStart('require', 'import'),
CAPTURE_STRING_LITERAL(1),
WHITESPACE,
OPTIONAL_COMMA,
RIGHT_PARENTHESIS,
],
'g',
);
const IMPORT_OR_EXPORT_RE = createRegExp(
[
'\\b(?:import|export)\\s+(?!type(?:of)?\\s+)(?:[^\'"]+\\s+from\\s+)?',
CAPTURE_STRING_LITERAL(1),
],
'g',
);
const JEST_EXTENSIONS_RE = createRegExp(
[
...functionCallStart(
'jest\\s*\\.\\s*(?:requireActual|requireMock|genMockFromModule|createMockFromModule)',
),
CAPTURE_STRING_LITERAL(1),
WHITESPACE,
OPTIONAL_COMMA,
RIGHT_PARENTHESIS,
],
'g',
);
function extract(code /*: string */) /*: Set<string> */ {
const dependencies /*: Set<string> */ = new Set();
const addDependency = (
match /*: string */,
_ /*: string */,
dep /*: string */,
) => {
dependencies.add(dep);
return match;
};
code
.replace(BLOCK_COMMENT_RE, '')
.replace(LINE_COMMENT_RE, '')
.replace(IMPORT_OR_EXPORT_RE, addDependency)
.replace(REQUIRE_OR_DYNAMIC_IMPORT_RE, addDependency)
.replace(JEST_EXTENSIONS_RE, addDependency);
return dependencies;
}
module.exports = {extract};

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
let normalizePathSeparatorsToPosix;
if (path.sep === "/") {
normalizePathSeparatorsToPosix = (filePath) => filePath;
} else {
normalizePathSeparatorsToPosix = (filePath) => filePath.replace(/\\/g, "/");
}
var _default = (exports.default = normalizePathSeparatorsToPosix);

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import * as path from 'path';
let normalizePathSeparatorsToPosix: (string: string) => string;
if (path.sep === '/') {
normalizePathSeparatorsToPosix = (filePath: string): string => filePath;
} else {
normalizePathSeparatorsToPosix = (filePath: string): string =>
filePath.replace(/\\/g, '/');
}
export default normalizePathSeparatorsToPosix;

View File

@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
let normalizePathSeparatorsToSystem;
if (path.sep === "/") {
normalizePathSeparatorsToSystem = (filePath) => filePath;
} else {
normalizePathSeparatorsToSystem = (filePath) =>
filePath.replace(/\//g, path.sep);
}
var _default = (exports.default = normalizePathSeparatorsToSystem);

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import * as path from 'path';
let normalizePathSeparatorsToSystem: (string: string) => string;
if (path.sep === '/') {
normalizePathSeparatorsToSystem = (filePath: string): string => filePath;
} else {
normalizePathSeparatorsToSystem = (filePath: string): string =>
filePath.replace(/\//g, path.sep);
}
export default normalizePathSeparatorsToSystem;

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = rootRelativeCacheKeys;
var _normalizePathSeparatorsToPosix = _interopRequireDefault(
require("./normalizePathSeparatorsToPosix"),
);
var _RootPathUtils = require("./RootPathUtils");
var _crypto = require("crypto");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function moduleCacheKey(modulePath) {
if (modulePath == null) {
return null;
}
const moduleExports = require(modulePath);
if (typeof moduleExports?.getCacheKey !== "function") {
console.warn(
`metro-file-map: Expected \`${modulePath}\` to export ` +
"`getCacheKey: () => string`",
);
return null;
}
return moduleExports.getCacheKey();
}
function rootRelativeCacheKeys(buildParameters) {
const { rootDir, plugins, ...otherParameters } = buildParameters;
const rootDirHash = (0, _crypto.createHash)("md5")
.update((0, _normalizePathSeparatorsToPosix.default)(rootDir))
.digest("hex");
const pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
const cacheComponents = Object.keys(otherParameters)
.sort()
.map((key) => {
switch (key) {
case "roots":
return buildParameters[key].map((root) =>
(0, _normalizePathSeparatorsToPosix.default)(
pathUtils.absoluteToNormal(root),
),
);
case "cacheBreaker":
case "extensions":
case "computeDependencies":
case "computeSha1":
case "enableHastePackages":
case "enableSymlinks":
case "forceNodeFilesystemAPI":
case "retainAllFiles":
case "skipPackageJson":
return buildParameters[key] ?? null;
case "ignorePattern":
return buildParameters[key].toString();
case "hasteImplModulePath":
case "dependencyExtractor":
return moduleCacheKey(buildParameters[key]);
default:
key;
throw new Error("Unrecognised key in build parameters: " + key);
}
});
for (const plugin of plugins) {
cacheComponents.push(plugin.getCacheKey());
}
const relativeConfigHash = (0, _crypto.createHash)("md5")
.update(JSON.stringify(cacheComponents))
.digest("hex");
return {
rootDirHash,
relativeConfigHash,
};
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {BuildParameters} from '../flow-types';
import normalizePathSeparatorsToPosix from './normalizePathSeparatorsToPosix';
import {RootPathUtils} from './RootPathUtils';
import {createHash} from 'crypto';
function moduleCacheKey(modulePath: ?string) {
if (modulePath == null) {
return null;
}
// $FlowFixMe[unsupported-syntax] - Dynamic require
const moduleExports = require(modulePath);
if (typeof moduleExports?.getCacheKey !== 'function') {
console.warn(
`metro-file-map: Expected \`${modulePath}\` to export ` +
'`getCacheKey: () => string`',
);
return null;
}
return moduleExports.getCacheKey();
}
export default function rootRelativeCacheKeys(
buildParameters: BuildParameters,
): {
rootDirHash: string,
relativeConfigHash: string,
} {
const {rootDir, plugins, ...otherParameters} = buildParameters;
const rootDirHash = createHash('md5')
.update(normalizePathSeparatorsToPosix(rootDir))
.digest('hex');
const pathUtils = new RootPathUtils(rootDir);
const cacheComponents = Object.keys(otherParameters)
.sort()
.map(key => {
switch (key) {
case 'roots':
return buildParameters[key].map(root =>
normalizePathSeparatorsToPosix(pathUtils.absoluteToNormal(root)),
);
case 'cacheBreaker':
case 'extensions':
case 'computeDependencies':
case 'computeSha1':
case 'enableHastePackages':
case 'enableSymlinks':
case 'forceNodeFilesystemAPI':
case 'retainAllFiles':
case 'skipPackageJson':
return buildParameters[key] ?? null;
case 'ignorePattern':
return buildParameters[key].toString();
case 'hasteImplModulePath':
case 'dependencyExtractor':
return moduleCacheKey(buildParameters[key]);
default:
(key: empty);
throw new Error('Unrecognised key in build parameters: ' + key);
}
});
for (const plugin of plugins) {
cacheComponents.push(plugin.getCacheKey());
}
// JSON.stringify is stable here because we only deal in (nested) arrays of
// primitives. Use a different approach if this is expanded to include
// objects/Sets/Maps, etc.
const relativeConfigHash = createHash('md5')
.update(JSON.stringify(cacheComponents))
.digest('hex');
return {
rootDirHash,
relativeConfigHash,
};
}

27
node_modules/metro-file-map/src/lib/sorting.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.chainComparators = chainComparators;
exports.compareStrings = compareStrings;
function compareStrings(a, b) {
if (a == null) {
return b == null ? 0 : -1;
}
if (b == null) {
return 1;
}
return a.localeCompare(b);
}
function chainComparators(...comparators) {
return (a, b) => {
for (const comparator of comparators) {
const result = comparator(a, b);
if (result !== 0) {
return result;
}
}
return 0;
};
}

35
node_modules/metro-file-map/src/lib/sorting.js.flow generated vendored Normal file
View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
// Utilities for working with Array.prototype.sort
export function compareStrings(a: null | string, b: null | string): number {
if (a == null) {
return b == null ? 0 : -1;
}
if (b == null) {
return 1;
}
return a.localeCompare(b);
}
export function chainComparators<T>(
...comparators: Array<(a: T, b: T) => number>
): (a: T, b: T) => number {
return (a, b) => {
for (const comparator of comparators) {
const result = comparator(a, b);
if (result !== 0) {
return result;
}
}
return 0;
};
}

358
node_modules/metro-file-map/src/plugins/HastePlugin.js generated vendored Normal file
View File

@@ -0,0 +1,358 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _constants = _interopRequireDefault(require("../constants"));
var _RootPathUtils = require("../lib/RootPathUtils");
var _sorting = require("../lib/sorting");
var _DuplicateHasteCandidatesError = require("./haste/DuplicateHasteCandidatesError");
var _getPlatformExtension = _interopRequireDefault(
require("./haste/getPlatformExtension"),
);
var _HasteConflictsError = require("./haste/HasteConflictsError");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const EMPTY_OBJ = {};
const EMPTY_MAP = new Map();
const YIELD_EVERY_NUM_HASTE_FILES = 10000;
class HastePlugin {
name = "haste";
#rootDir;
#map = new Map();
#duplicates = new Map();
#console;
#enableHastePackages;
#perfLogger;
#pathUtils;
#platforms;
#failValidationOnConflicts;
constructor(options) {
this.#console = options.console ?? null;
this.#enableHastePackages = options.enableHastePackages;
this.#perfLogger = options.perfLogger;
this.#platforms = options.platforms;
this.#rootDir = options.rootDir;
this.#pathUtils = new _RootPathUtils.RootPathUtils(options.rootDir);
this.#failValidationOnConflicts = options.failValidationOnConflicts;
}
async initialize({ files }) {
this.#perfLogger?.point("constructHasteMap_start");
let hasteFiles = 0;
for (const { baseName, canonicalPath, metadata } of files.metadataIterator({
includeNodeModules: false,
includeSymlinks: false,
})) {
if (metadata[_constants.default.ID]) {
this.setModule(metadata[_constants.default.ID], [
canonicalPath,
this.#enableHastePackages && baseName === "package.json"
? _constants.default.PACKAGE
: _constants.default.MODULE,
]);
if (++hasteFiles % YIELD_EVERY_NUM_HASTE_FILES === 0) {
await new Promise(setImmediate);
}
}
}
this.#perfLogger?.point("constructHasteMap_end");
this.#perfLogger?.annotate({
int: {
hasteFiles,
},
});
}
getSerializableSnapshot() {
return null;
}
getModule(name, platform, supportsNativePlatform, type) {
const module = this._getModuleMetadata(
name,
platform,
!!supportsNativePlatform,
);
if (
module &&
module[_constants.default.TYPE] === (type ?? _constants.default.MODULE)
) {
const modulePath = module[_constants.default.PATH];
return modulePath && this.#pathUtils.normalToAbsolute(modulePath);
}
return null;
}
getPackage(name, platform, _supportsNativePlatform) {
return this.getModule(name, platform, null, _constants.default.PACKAGE);
}
_getModuleMetadata(name, platform, supportsNativePlatform) {
const map = this.#map.get(name) || EMPTY_OBJ;
const dupMap = this.#duplicates.get(name) || EMPTY_MAP;
if (platform != null) {
this._assertNoDuplicates(
name,
platform,
supportsNativePlatform,
dupMap.get(platform),
);
if (map[platform] != null) {
return map[platform];
}
}
if (supportsNativePlatform) {
this._assertNoDuplicates(
name,
_constants.default.NATIVE_PLATFORM,
supportsNativePlatform,
dupMap.get(_constants.default.NATIVE_PLATFORM),
);
if (map[_constants.default.NATIVE_PLATFORM]) {
return map[_constants.default.NATIVE_PLATFORM];
}
}
this._assertNoDuplicates(
name,
_constants.default.GENERIC_PLATFORM,
supportsNativePlatform,
dupMap.get(_constants.default.GENERIC_PLATFORM),
);
if (map[_constants.default.GENERIC_PLATFORM]) {
return map[_constants.default.GENERIC_PLATFORM];
}
return null;
}
_assertNoDuplicates(name, platform, supportsNativePlatform, relativePathSet) {
if (relativePathSet == null) {
return;
}
const duplicates = new Map();
for (const [relativePath, type] of relativePathSet) {
const duplicatePath = this.#pathUtils.normalToAbsolute(relativePath);
duplicates.set(duplicatePath, type);
}
throw new _DuplicateHasteCandidatesError.DuplicateHasteCandidatesError(
name,
platform,
supportsNativePlatform,
duplicates,
);
}
async bulkUpdate(delta) {
for (const [normalPath, metadata] of delta.removed) {
this.onRemovedFile(normalPath, metadata);
}
for (const [normalPath, metadata] of delta.addedOrModified) {
this.onNewOrModifiedFile(normalPath, metadata);
}
}
onNewOrModifiedFile(relativeFilePath, fileMetadata) {
const id = fileMetadata[_constants.default.ID] || null;
if (id == null) {
return;
}
const module = [
relativeFilePath,
this.#enableHastePackages &&
_path.default.basename(relativeFilePath) === "package.json"
? _constants.default.PACKAGE
: _constants.default.MODULE,
];
this.setModule(id, module);
}
setModule(id, module) {
let hasteMapItem = this.#map.get(id);
if (!hasteMapItem) {
hasteMapItem = Object.create(null);
this.#map.set(id, hasteMapItem);
}
const platform =
(0, _getPlatformExtension.default)(
module[_constants.default.PATH],
this.#platforms,
) || _constants.default.GENERIC_PLATFORM;
const existingModule = hasteMapItem[platform];
if (
existingModule &&
existingModule[_constants.default.PATH] !==
module[_constants.default.PATH]
) {
if (this.#console) {
this.#console.warn(
[
"metro-file-map: Haste module naming collision: " + id,
" The following files share their name; please adjust your hasteImpl:",
" * <rootDir>" +
_path.default.sep +
existingModule[_constants.default.PATH],
" * <rootDir>" +
_path.default.sep +
module[_constants.default.PATH],
"",
].join("\n"),
);
}
delete hasteMapItem[platform];
if (Object.keys(hasteMapItem).length === 0) {
this.#map.delete(id);
}
let dupsByPlatform = this.#duplicates.get(id);
if (dupsByPlatform == null) {
dupsByPlatform = new Map();
this.#duplicates.set(id, dupsByPlatform);
}
const dups = new Map([
[module[_constants.default.PATH], module[_constants.default.TYPE]],
[
existingModule[_constants.default.PATH],
existingModule[_constants.default.TYPE],
],
]);
dupsByPlatform.set(platform, dups);
return;
}
const dupsByPlatform = this.#duplicates.get(id);
if (dupsByPlatform != null) {
const dups = dupsByPlatform.get(platform);
if (dups != null) {
dups.set(
module[_constants.default.PATH],
module[_constants.default.TYPE],
);
}
return;
}
hasteMapItem[platform] = module;
}
onRemovedFile(relativeFilePath, fileMetadata) {
const moduleName = fileMetadata[_constants.default.ID] || null;
if (moduleName == null) {
return;
}
const platform =
(0, _getPlatformExtension.default)(relativeFilePath, this.#platforms) ||
_constants.default.GENERIC_PLATFORM;
const hasteMapItem = this.#map.get(moduleName);
if (hasteMapItem != null) {
delete hasteMapItem[platform];
if (Object.keys(hasteMapItem).length === 0) {
this.#map.delete(moduleName);
} else {
this.#map.set(moduleName, hasteMapItem);
}
}
this._recoverDuplicates(moduleName, relativeFilePath);
}
assertValid() {
if (!this.#failValidationOnConflicts) {
return;
}
const conflicts = this.computeConflicts();
if (conflicts.length > 0) {
throw new _HasteConflictsError.HasteConflictsError(conflicts);
}
}
_recoverDuplicates(moduleName, relativeFilePath) {
let dupsByPlatform = this.#duplicates.get(moduleName);
if (dupsByPlatform == null) {
return;
}
const platform =
(0, _getPlatformExtension.default)(relativeFilePath, this.#platforms) ||
_constants.default.GENERIC_PLATFORM;
let dups = dupsByPlatform.get(platform);
if (dups == null) {
return;
}
dupsByPlatform = new Map(dupsByPlatform);
this.#duplicates.set(moduleName, dupsByPlatform);
dups = new Map(dups);
dupsByPlatform.set(platform, dups);
dups.delete(relativeFilePath);
if (dups.size !== 1) {
return;
}
const uniqueModule = dups.entries().next().value;
if (!uniqueModule) {
return;
}
let dedupMap = this.#map.get(moduleName);
if (dedupMap == null) {
dedupMap = Object.create(null);
this.#map.set(moduleName, dedupMap);
}
dedupMap[platform] = uniqueModule;
dupsByPlatform.delete(platform);
if (dupsByPlatform.size === 0) {
this.#duplicates.delete(moduleName);
}
}
computeConflicts() {
const conflicts = [];
for (const [id, dupsByPlatform] of this.#duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform:
platform === _constants.default.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map((modulePath) => this.#pathUtils.normalToAbsolute(modulePath))
.sort(),
type: "duplicate",
});
}
}
for (const [id, data] of this.#map) {
const conflictPaths = new Set();
const basePaths = [];
for (const basePlatform of [
_constants.default.NATIVE_PLATFORM,
_constants.default.GENERIC_PLATFORM,
]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = _path.default.dirname(basePath);
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === _constants.default.GENERIC_PLATFORM
) {
continue;
}
const platformPath = data[platform][0];
if (_path.default.dirname(platformPath) !== basePathDir) {
conflictPaths.add(platformPath);
}
}
}
if (conflictPaths.size) {
conflicts.push({
id,
platform: null,
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map((modulePath) => this.#pathUtils.normalToAbsolute(modulePath))
.sort(),
type: "shadowing",
});
}
}
conflicts.sort(
(0, _sorting.chainComparators)(
(a, b) => (0, _sorting.compareStrings)(a.type, b.type),
(a, b) => (0, _sorting.compareStrings)(a.id, b.id),
(a, b) => (0, _sorting.compareStrings)(a.platform, b.platform),
),
);
return conflicts;
}
getCacheKey() {
return JSON.stringify([
this.#enableHastePackages,
[...this.#platforms].sort(),
]);
}
}
exports.default = HastePlugin;

View File

@@ -0,0 +1,460 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {
Console,
DuplicatesIndex,
DuplicatesSet,
FileMapDelta,
FileMapPlugin,
FileMapPluginInitOptions,
FileMetadata,
HasteConflict,
HasteMap,
HasteMapItem,
HasteMapItemMetadata,
HTypeValue,
Path,
PerfLogger,
} from '../flow-types';
import H from '../constants';
import {RootPathUtils} from '../lib/RootPathUtils';
import {chainComparators, compareStrings} from '../lib/sorting';
import {DuplicateHasteCandidatesError} from './haste/DuplicateHasteCandidatesError';
import getPlatformExtension from './haste/getPlatformExtension';
import {HasteConflictsError} from './haste/HasteConflictsError';
import path from 'path';
const EMPTY_OBJ: $ReadOnly<{[string]: HasteMapItemMetadata}> = {};
const EMPTY_MAP: $ReadOnlyMap<string, DuplicatesSet> = new Map();
// Periodically yield to the event loop to allow parallel I/O, etc.
// Based on 200k files taking up to 800ms => max 40ms between yields.
const YIELD_EVERY_NUM_HASTE_FILES = 10000;
type HasteMapOptions = $ReadOnly<{
console?: ?Console,
enableHastePackages: boolean,
perfLogger: ?PerfLogger,
platforms: $ReadOnlySet<string>,
rootDir: Path,
failValidationOnConflicts: boolean,
}>;
export default class HastePlugin implements HasteMap, FileMapPlugin<null> {
+name = 'haste';
+#rootDir: Path;
+#map: Map<string, HasteMapItem> = new Map();
+#duplicates: DuplicatesIndex = new Map();
+#console: ?Console;
+#enableHastePackages: boolean;
+#perfLogger: ?PerfLogger;
+#pathUtils: RootPathUtils;
+#platforms: $ReadOnlySet<string>;
+#failValidationOnConflicts: boolean;
constructor(options: HasteMapOptions) {
this.#console = options.console ?? null;
this.#enableHastePackages = options.enableHastePackages;
this.#perfLogger = options.perfLogger;
this.#platforms = options.platforms;
this.#rootDir = options.rootDir;
this.#pathUtils = new RootPathUtils(options.rootDir);
this.#failValidationOnConflicts = options.failValidationOnConflicts;
}
async initialize({files}: FileMapPluginInitOptions<null>): Promise<void> {
this.#perfLogger?.point('constructHasteMap_start');
let hasteFiles = 0;
for (const {baseName, canonicalPath, metadata} of files.metadataIterator({
// Symlinks and node_modules are never Haste modules or packages.
includeNodeModules: false,
includeSymlinks: false,
})) {
if (metadata[H.ID]) {
this.setModule(metadata[H.ID], [
canonicalPath,
this.#enableHastePackages && baseName === 'package.json'
? H.PACKAGE
: H.MODULE,
]);
if (++hasteFiles % YIELD_EVERY_NUM_HASTE_FILES === 0) {
await new Promise(setImmediate);
}
}
}
this.#perfLogger?.point('constructHasteMap_end');
this.#perfLogger?.annotate({int: {hasteFiles}});
}
getSerializableSnapshot(): null {
// Haste is not serialised, but built from traversing the file metadata
// on each run. This turns out to have comparable performance to
// serialisation, at least when Haste is dense, and makes for a much
// smaller cache.
return null;
}
getModule(
name: string,
platform?: ?string,
supportsNativePlatform?: ?boolean,
type?: ?HTypeValue,
): ?Path {
const module = this._getModuleMetadata(
name,
platform,
!!supportsNativePlatform,
);
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/4oq3zi07. */
if (module && module[H.TYPE] === (type ?? H.MODULE)) {
const modulePath = module[H.PATH];
return modulePath && this.#pathUtils.normalToAbsolute(modulePath);
}
return null;
}
getPackage(
name: string,
platform: ?string,
_supportsNativePlatform?: ?boolean,
): ?Path {
return this.getModule(name, platform, null, H.PACKAGE);
}
/**
* When looking up a module's data, we walk through each eligible platform for
* the query. For each platform, we want to check if there are known
* duplicates for that name+platform pair. The duplication logic normally
* removes elements from the `map` object, but we want to check upfront to be
* extra sure. If metadata exists both in the `duplicates` object and the
* `map`, this would be a bug.
*/
_getModuleMetadata(
name: string,
platform: ?string,
supportsNativePlatform: boolean,
): HasteMapItemMetadata | null {
const map = this.#map.get(name) || EMPTY_OBJ;
const dupMap = this.#duplicates.get(name) || EMPTY_MAP;
if (platform != null) {
this._assertNoDuplicates(
name,
platform,
supportsNativePlatform,
dupMap.get(platform),
);
if (map[platform] != null) {
return map[platform];
}
}
if (supportsNativePlatform) {
this._assertNoDuplicates(
name,
H.NATIVE_PLATFORM,
supportsNativePlatform,
dupMap.get(H.NATIVE_PLATFORM),
);
if (map[H.NATIVE_PLATFORM]) {
return map[H.NATIVE_PLATFORM];
}
}
this._assertNoDuplicates(
name,
H.GENERIC_PLATFORM,
supportsNativePlatform,
dupMap.get(H.GENERIC_PLATFORM),
);
if (map[H.GENERIC_PLATFORM]) {
return map[H.GENERIC_PLATFORM];
}
return null;
}
_assertNoDuplicates(
name: string,
platform: string,
supportsNativePlatform: boolean,
relativePathSet: ?DuplicatesSet,
): void {
if (relativePathSet == null) {
return;
}
const duplicates = new Map<string, number>();
for (const [relativePath, type] of relativePathSet) {
const duplicatePath = this.#pathUtils.normalToAbsolute(relativePath);
duplicates.set(duplicatePath, type);
}
throw new DuplicateHasteCandidatesError(
name,
platform,
supportsNativePlatform,
duplicates,
);
}
async bulkUpdate(delta: FileMapDelta): Promise<void> {
// Process removals first so that moves aren't treated as duplicates.
for (const [normalPath, metadata] of delta.removed) {
this.onRemovedFile(normalPath, metadata);
}
for (const [normalPath, metadata] of delta.addedOrModified) {
this.onNewOrModifiedFile(normalPath, metadata);
}
}
onNewOrModifiedFile(relativeFilePath: string, fileMetadata: FileMetadata) {
const id = fileMetadata[H.ID] || null; // Empty string indicates no module
if (id == null) {
return;
}
const module: HasteMapItemMetadata = [
relativeFilePath,
this.#enableHastePackages &&
path.basename(relativeFilePath) === 'package.json'
? H.PACKAGE
: H.MODULE,
];
this.setModule(id, module);
}
setModule(id: string, module: HasteMapItemMetadata) {
let hasteMapItem = this.#map.get(id);
if (!hasteMapItem) {
// $FlowFixMe[unclear-type] - Add type coverage
hasteMapItem = (Object.create(null): any);
this.#map.set(id, hasteMapItem);
}
const platform =
getPlatformExtension(module[H.PATH], this.#platforms) ||
H.GENERIC_PLATFORM;
const existingModule = hasteMapItem[platform];
if (existingModule && existingModule[H.PATH] !== module[H.PATH]) {
if (this.#console) {
this.#console.warn(
[
'metro-file-map: Haste module naming collision: ' + id,
' The following files share their name; please adjust your hasteImpl:',
' * <rootDir>' + path.sep + existingModule[H.PATH],
' * <rootDir>' + path.sep + module[H.PATH],
'',
].join('\n'),
);
}
// We do NOT want consumers to use a module that is ambiguous.
delete hasteMapItem[platform];
if (Object.keys(hasteMapItem).length === 0) {
this.#map.delete(id);
}
let dupsByPlatform = this.#duplicates.get(id);
if (dupsByPlatform == null) {
dupsByPlatform = new Map();
this.#duplicates.set(id, dupsByPlatform);
}
const dups = new Map([
[module[H.PATH], module[H.TYPE]],
[existingModule[H.PATH], existingModule[H.TYPE]],
]);
dupsByPlatform.set(platform, dups);
return;
}
const dupsByPlatform = this.#duplicates.get(id);
if (dupsByPlatform != null) {
const dups = dupsByPlatform.get(platform);
if (dups != null) {
dups.set(module[H.PATH], module[H.TYPE]);
}
return;
}
hasteMapItem[platform] = module;
}
onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata) {
const moduleName = fileMetadata[H.ID] || null; // Empty string indicates no module
if (moduleName == null) {
return;
}
const platform =
getPlatformExtension(relativeFilePath, this.#platforms) ||
H.GENERIC_PLATFORM;
const hasteMapItem = this.#map.get(moduleName);
if (hasteMapItem != null) {
delete hasteMapItem[platform];
if (Object.keys(hasteMapItem).length === 0) {
this.#map.delete(moduleName);
} else {
this.#map.set(moduleName, hasteMapItem);
}
}
this._recoverDuplicates(moduleName, relativeFilePath);
}
assertValid(): void {
if (!this.#failValidationOnConflicts) {
return;
}
const conflicts = this.computeConflicts();
if (conflicts.length > 0) {
throw new HasteConflictsError(conflicts);
}
}
/**
* This function should be called when the file under `filePath` is removed
* or changed. When that happens, we want to figure out if that file was
* part of a group of files that had the same ID. If it was, we want to
* remove it from the group. Furthermore, if there is only one file
* remaining in the group, then we want to restore that single file as the
* correct resolution for its ID, and cleanup the duplicates index.
*/
_recoverDuplicates(moduleName: string, relativeFilePath: string) {
let dupsByPlatform = this.#duplicates.get(moduleName);
if (dupsByPlatform == null) {
return;
}
const platform =
getPlatformExtension(relativeFilePath, this.#platforms) ||
H.GENERIC_PLATFORM;
let dups = dupsByPlatform.get(platform);
if (dups == null) {
return;
}
dupsByPlatform = new Map(dupsByPlatform);
this.#duplicates.set(moduleName, dupsByPlatform);
dups = new Map(dups);
dupsByPlatform.set(platform, dups);
dups.delete(relativeFilePath);
if (dups.size !== 1) {
return;
}
const uniqueModule = dups.entries().next().value;
if (!uniqueModule) {
return;
}
let dedupMap: ?HasteMapItem = this.#map.get(moduleName);
if (dedupMap == null) {
dedupMap = (Object.create(null): HasteMapItem);
this.#map.set(moduleName, dedupMap);
}
dedupMap[platform] = uniqueModule;
dupsByPlatform.delete(platform);
if (dupsByPlatform.size === 0) {
this.#duplicates.delete(moduleName);
}
}
computeConflicts(): Array<HasteConflict> {
const conflicts: Array<HasteConflict> = [];
// Add literal duplicates tracked in the #duplicates map
for (const [id, dupsByPlatform] of this.#duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform: platform === H.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map(modulePath => this.#pathUtils.normalToAbsolute(modulePath))
// Sort for ease of testing
.sort(),
type: 'duplicate',
});
}
}
// Add cases of "shadowing at a distance": a module with a platform suffix and
// a module with a lower priority platform suffix (or no suffix), in different
// directories.
for (const [id, data] of this.#map) {
const conflictPaths = new Set<string>();
const basePaths = [];
for (const basePlatform of [H.NATIVE_PLATFORM, H.GENERIC_PLATFORM]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = path.dirname(basePath);
// Find all platforms that can shadow basePlatform
// Given that X.(specific platform).js > x.native.js > X.js
// and basePlatform is either 'native' or generic (no platform).
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === H.GENERIC_PLATFORM /* lowest priority */
) {
continue;
}
const platformPath = data[platform][0];
if (path.dirname(platformPath) !== basePathDir) {
conflictPaths.add(platformPath);
}
}
}
if (conflictPaths.size) {
conflicts.push({
id,
platform: null,
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map(modulePath => this.#pathUtils.normalToAbsolute(modulePath))
// Sort for ease of testing
.sort(),
type: 'shadowing',
});
}
}
// Sort for ease of testing
conflicts.sort(
chainComparators(
(a, b) => compareStrings(a.type, b.type),
(a, b) => compareStrings(a.id, b.id),
(a, b) => compareStrings(a.platform, b.platform),
),
);
return conflicts;
}
getCacheKey(): string {
return JSON.stringify([
this.#enableHastePackages,
[...this.#platforms].sort(),
]);
}
}

179
node_modules/metro-file-map/src/plugins/MockPlugin.js generated vendored Normal file
View File

@@ -0,0 +1,179 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = exports.CACHE_VERSION = void 0;
var _normalizePathSeparatorsToPosix = _interopRequireDefault(
require("../lib/normalizePathSeparatorsToPosix"),
);
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
require("../lib/normalizePathSeparatorsToSystem"),
);
var _RootPathUtils = require("../lib/RootPathUtils");
var _getMockName = _interopRequireDefault(require("./mocks/getMockName"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const CACHE_VERSION = (exports.CACHE_VERSION = 2);
class MockPlugin {
name = "mocks";
#mocksPattern;
#raw;
#rootDir;
#pathUtils;
#console;
#throwOnModuleCollision;
constructor({
console,
mocksPattern,
rawMockMap = {
mocks: new Map(),
duplicates: new Map(),
version: CACHE_VERSION,
},
rootDir,
throwOnModuleCollision,
}) {
this.#mocksPattern = mocksPattern;
if (rawMockMap.version !== CACHE_VERSION) {
throw new Error("Incompatible state passed to MockPlugin");
}
this.#raw = rawMockMap;
this.#rootDir = rootDir;
this.#console = console;
this.#pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
this.#throwOnModuleCollision = throwOnModuleCollision;
}
async initialize({ files, pluginState }) {
if (pluginState != null && pluginState.version === this.#raw.version) {
this.#raw = pluginState;
} else {
await this.bulkUpdate({
addedOrModified: [
...files.metadataIterator({
includeNodeModules: false,
includeSymlinks: false,
}),
].map(({ canonicalPath, metadata }) => [canonicalPath, metadata]),
removed: [],
});
}
}
getMockModule(name) {
const mockPosixRelativePath =
this.#raw.mocks.get(name) || this.#raw.mocks.get(name + "/index");
if (typeof mockPosixRelativePath !== "string") {
return null;
}
return this.#pathUtils.normalToAbsolute(
(0, _normalizePathSeparatorsToSystem.default)(mockPosixRelativePath),
);
}
async bulkUpdate(delta) {
for (const [relativeFilePath] of delta.removed) {
this.onRemovedFile(relativeFilePath);
}
for (const [relativeFilePath] of delta.addedOrModified) {
this.onNewOrModifiedFile(relativeFilePath);
}
}
onNewOrModifiedFile(relativeFilePath) {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = (0, _getMockName.default)(absoluteFilePath);
const posixRelativePath = (0, _normalizePathSeparatorsToPosix.default)(
relativeFilePath,
);
const existingMockPosixPath = this.#raw.mocks.get(mockName);
if (existingMockPosixPath != null) {
if (existingMockPosixPath !== posixRelativePath) {
let duplicates = this.#raw.duplicates.get(mockName);
if (duplicates == null) {
duplicates = new Set([existingMockPosixPath, posixRelativePath]);
this.#raw.duplicates.set(mockName, duplicates);
} else {
duplicates.add(posixRelativePath);
}
this.#console.warn(this.#getMessageForDuplicates(mockName, duplicates));
}
}
this.#raw.mocks.set(mockName, posixRelativePath);
}
onRemovedFile(relativeFilePath) {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = (0, _getMockName.default)(absoluteFilePath);
const duplicates = this.#raw.duplicates.get(mockName);
if (duplicates != null) {
const posixRelativePath = (0, _normalizePathSeparatorsToPosix.default)(
relativeFilePath,
);
duplicates.delete(posixRelativePath);
if (duplicates.size === 1) {
this.#raw.duplicates.delete(mockName);
}
const remaining = (0, _nullthrows.default)(
duplicates.values().next().value,
);
this.#raw.mocks.set(mockName, remaining);
} else {
this.#raw.mocks.delete(mockName);
}
}
getSerializableSnapshot() {
return {
mocks: new Map(this.#raw.mocks),
duplicates: new Map(
[...this.#raw.duplicates].map(([k, v]) => [k, new Set(v)]),
),
version: this.#raw.version,
};
}
assertValid() {
if (!this.#throwOnModuleCollision) {
return;
}
const errors = [];
for (const [mockName, relativePosixPaths] of this.#raw.duplicates) {
errors.push(this.#getMessageForDuplicates(mockName, relativePosixPaths));
}
if (errors.length > 0) {
throw new Error(
`Mock map has ${errors.length} error${errors.length > 1 ? "s" : ""}:\n${errors.join("\n")}`,
);
}
}
#getMessageForDuplicates(mockName, relativePosixPaths) {
return (
"Duplicate manual mock found for `" +
mockName +
"`:\n" +
[...relativePosixPaths]
.map(
(relativePosixPath) =>
" * <rootDir>" +
_path.default.sep +
this.#pathUtils.absoluteToNormal(
(0, _normalizePathSeparatorsToSystem.default)(relativePosixPath),
) +
"\n",
)
.join("")
);
}
getCacheKey() {
return (
this.#mocksPattern.source.replaceAll("\\\\", "\\/") +
"," +
this.#mocksPattern.flags
);
}
}
exports.default = MockPlugin;

View File

@@ -0,0 +1,216 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {
FileMapDelta,
FileMapPlugin,
FileMapPluginInitOptions,
MockMap as IMockMap,
Path,
RawMockMap,
} from '../flow-types';
import normalizePathSeparatorsToPosix from '../lib/normalizePathSeparatorsToPosix';
import normalizePathSeparatorsToSystem from '../lib/normalizePathSeparatorsToSystem';
import {RootPathUtils} from '../lib/RootPathUtils';
import getMockName from './mocks/getMockName';
import nullthrows from 'nullthrows';
import path from 'path';
export const CACHE_VERSION = 2;
export default class MockPlugin implements FileMapPlugin<RawMockMap>, IMockMap {
+name = 'mocks';
+#mocksPattern: RegExp;
#raw: RawMockMap;
+#rootDir: Path;
+#pathUtils: RootPathUtils;
+#console: typeof console;
#throwOnModuleCollision: boolean;
constructor({
console,
mocksPattern,
rawMockMap = {
mocks: new Map(),
duplicates: new Map(),
version: CACHE_VERSION,
},
rootDir,
throwOnModuleCollision,
}: $ReadOnly<{
console: typeof console,
mocksPattern: RegExp,
rawMockMap?: RawMockMap,
rootDir: Path,
throwOnModuleCollision: boolean,
}>) {
this.#mocksPattern = mocksPattern;
if (rawMockMap.version !== CACHE_VERSION) {
throw new Error('Incompatible state passed to MockPlugin');
}
this.#raw = rawMockMap;
this.#rootDir = rootDir;
this.#console = console;
this.#pathUtils = new RootPathUtils(rootDir);
this.#throwOnModuleCollision = throwOnModuleCollision;
}
async initialize({
files,
pluginState,
}: FileMapPluginInitOptions<RawMockMap>): Promise<void> {
if (pluginState != null && pluginState.version === this.#raw.version) {
// Use cached state directly if available
this.#raw = pluginState;
} else {
// Otherwise, traverse all files to rebuild
await this.bulkUpdate({
addedOrModified: [
...files.metadataIterator({
includeNodeModules: false,
includeSymlinks: false,
}),
].map(({canonicalPath, metadata}) => [canonicalPath, metadata]),
removed: [],
});
}
}
getMockModule(name: string): ?Path {
const mockPosixRelativePath =
this.#raw.mocks.get(name) || this.#raw.mocks.get(name + '/index');
if (typeof mockPosixRelativePath !== 'string') {
return null;
}
return this.#pathUtils.normalToAbsolute(
normalizePathSeparatorsToSystem(mockPosixRelativePath),
);
}
async bulkUpdate(delta: FileMapDelta): Promise<void> {
// Process removals first so that moves aren't treated as duplicates.
for (const [relativeFilePath] of delta.removed) {
this.onRemovedFile(relativeFilePath);
}
for (const [relativeFilePath] of delta.addedOrModified) {
this.onNewOrModifiedFile(relativeFilePath);
}
}
onNewOrModifiedFile(relativeFilePath: Path): void {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = getMockName(absoluteFilePath);
const posixRelativePath = normalizePathSeparatorsToPosix(relativeFilePath);
const existingMockPosixPath = this.#raw.mocks.get(mockName);
if (existingMockPosixPath != null) {
if (existingMockPosixPath !== posixRelativePath) {
let duplicates = this.#raw.duplicates.get(mockName);
if (duplicates == null) {
duplicates = new Set([existingMockPosixPath, posixRelativePath]);
this.#raw.duplicates.set(mockName, duplicates);
} else {
duplicates.add(posixRelativePath);
}
this.#console.warn(this.#getMessageForDuplicates(mockName, duplicates));
}
}
// If there are duplicates and we don't throw, the latest mock wins.
// This is to preserve backwards compatibility, but it's unpredictable.
this.#raw.mocks.set(mockName, posixRelativePath);
}
onRemovedFile(relativeFilePath: Path): void {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = getMockName(absoluteFilePath);
const duplicates = this.#raw.duplicates.get(mockName);
if (duplicates != null) {
const posixRelativePath =
normalizePathSeparatorsToPosix(relativeFilePath);
duplicates.delete(posixRelativePath);
if (duplicates.size === 1) {
this.#raw.duplicates.delete(mockName);
}
// Set the mock to a remaining duplicate. Should never be empty.
const remaining = nullthrows(duplicates.values().next().value);
this.#raw.mocks.set(mockName, remaining);
} else {
this.#raw.mocks.delete(mockName);
}
}
getSerializableSnapshot(): RawMockMap {
return {
mocks: new Map(this.#raw.mocks),
duplicates: new Map(
[...this.#raw.duplicates].map(([k, v]) => [k, new Set(v)]),
),
version: this.#raw.version,
};
}
assertValid(): void {
if (!this.#throwOnModuleCollision) {
return;
}
// Throw an aggregate error for each duplicate.
const errors = [];
for (const [mockName, relativePosixPaths] of this.#raw.duplicates) {
errors.push(this.#getMessageForDuplicates(mockName, relativePosixPaths));
}
if (errors.length > 0) {
throw new Error(
`Mock map has ${errors.length} error${errors.length > 1 ? 's' : ''}:\n${errors.join('\n')}`,
);
}
}
#getMessageForDuplicates(
mockName: string,
relativePosixPaths: $ReadOnlySet<string>,
): string {
return (
'Duplicate manual mock found for `' +
mockName +
'`:\n' +
[...relativePosixPaths]
.map(
relativePosixPath =>
' * <rootDir>' +
path.sep +
this.#pathUtils.absoluteToNormal(
normalizePathSeparatorsToSystem(relativePosixPath),
) +
'\n',
)
.join('')
);
}
getCacheKey(): string {
return (
this.#mocksPattern.source.replaceAll('\\\\', '\\/') +
',' +
this.#mocksPattern.flags
);
}
}

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.DuplicateHasteCandidatesError = void 0;
var _constants = _interopRequireDefault(require("../../constants"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class DuplicateHasteCandidatesError extends Error {
constructor(name, platform, supportsNativePlatform, duplicatesSet) {
const platformMessage = getPlatformMessage(platform);
super(
`The name \`${name}\` was looked up in the Haste module map. It ` +
"cannot be resolved, because there exists several different " +
"files, or packages, that provide a module for " +
`that particular name and platform. ${platformMessage} You must ` +
"delete or exclude files until there remains only one of these:\n\n" +
Array.from(duplicatesSet)
.map(
([dupFilePath, dupFileType]) =>
` * \`${dupFilePath}\` (${getTypeMessage(dupFileType)})\n`,
)
.sort()
.join(""),
);
this.hasteName = name;
this.platform = platform;
this.supportsNativePlatform = supportsNativePlatform;
this.duplicatesSet = duplicatesSet;
}
}
exports.DuplicateHasteCandidatesError = DuplicateHasteCandidatesError;
function getPlatformMessage(platform) {
if (platform === _constants.default.GENERIC_PLATFORM) {
return "The platform is generic (no extension).";
}
return `The platform extension is \`${platform}\`.`;
}
function getTypeMessage(type) {
switch (type) {
case _constants.default.MODULE:
return "module";
case _constants.default.PACKAGE:
return "package";
}
return "unknown";
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {DuplicatesSet} from '../../flow-types';
import H from '../../constants';
export class DuplicateHasteCandidatesError extends Error {
hasteName: string;
platform: string | null;
supportsNativePlatform: boolean;
duplicatesSet: DuplicatesSet;
constructor(
name: string,
platform: string,
supportsNativePlatform: boolean,
duplicatesSet: DuplicatesSet,
) {
const platformMessage = getPlatformMessage(platform);
super(
`The name \`${name}\` was looked up in the Haste module map. It ` +
'cannot be resolved, because there exists several different ' +
'files, or packages, that provide a module for ' +
`that particular name and platform. ${platformMessage} You must ` +
'delete or exclude files until there remains only one of these:\n\n' +
Array.from(duplicatesSet)
.map(
([dupFilePath, dupFileType]) =>
` * \`${dupFilePath}\` (${getTypeMessage(dupFileType)})\n`,
)
.sort()
.join(''),
);
this.hasteName = name;
this.platform = platform;
this.supportsNativePlatform = supportsNativePlatform;
this.duplicatesSet = duplicatesSet;
}
}
function getPlatformMessage(platform: string) {
if (platform === H.GENERIC_PLATFORM) {
return 'The platform is generic (no extension).';
}
return `The platform extension is \`${platform}\`.`;
}
function getTypeMessage(type: number) {
switch (type) {
case H.MODULE:
return 'module';
case H.PACKAGE:
return 'package';
}
return 'unknown';
}

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.HasteConflictsError = void 0;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class HasteConflictsError extends Error {
#conflicts;
constructor(conflicts) {
super(
`Found ${conflicts.length} Haste conflict(s). Haste module IDs must be globally unique in the codebase.`,
);
this.#conflicts = conflicts;
}
getDetailedMessage(pathsRelativeToRoot) {
const messages = [];
const conflicts = this.#conflicts;
if (conflicts.some((conflict) => conflict.type === "duplicate")) {
messages.push(
'Advice: Resolve conflicts of type "duplicate" by renaming one or both of the conflicting modules, or by excluding conflicting paths from Haste.',
);
}
if (conflicts.some((conflict) => conflict.type === "shadowing")) {
messages.push(
'Advice: Resolve conflicts of type "shadowing" by moving the modules to the same folder, or by excluding conflicting paths from Haste.',
);
}
let index = 0;
for (const conflict of conflicts) {
const itemHeader = index + 1 + ". ";
const indent = " ".repeat(itemHeader.length + 2);
messages.push(
"\n" +
itemHeader +
conflict.id +
(conflict.platform != null ? `.${conflict.platform}` : "") +
` (${conflict.type})`,
);
for (const modulePath of conflict.absolutePaths) {
messages.push(
indent +
(pathsRelativeToRoot != null
? _path.default.relative(pathsRelativeToRoot, modulePath)
: modulePath),
);
}
++index;
}
return messages.join("\n");
}
}
exports.HasteConflictsError = HasteConflictsError;

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {HasteConflict} from '../../flow-types';
import path from 'path';
export class HasteConflictsError extends Error {
#conflicts: $ReadOnlyArray<HasteConflict>;
constructor(conflicts: $ReadOnlyArray<HasteConflict>) {
super(
`Found ${conflicts.length} Haste conflict(s). Haste module IDs must be globally unique in the codebase.`,
);
this.#conflicts = conflicts;
}
getDetailedMessage(pathsRelativeToRoot: ?string): string {
const messages: Array<string> = [];
const conflicts = this.#conflicts;
if (conflicts.some(conflict => conflict.type === 'duplicate')) {
messages.push(
'Advice: Resolve conflicts of type "duplicate" by renaming one or both of the conflicting modules, or by excluding conflicting paths from Haste.',
);
}
if (conflicts.some(conflict => conflict.type === 'shadowing')) {
messages.push(
'Advice: Resolve conflicts of type "shadowing" by moving the modules to the same folder, or by excluding conflicting paths from Haste.',
);
}
let index = 0;
for (const conflict of conflicts) {
const itemHeader = index + 1 + '. ';
const indent = ' '.repeat(itemHeader.length + 2);
messages.push(
'\n' +
itemHeader +
conflict.id +
(conflict.platform != null ? `.${conflict.platform}` : '') +
` (${conflict.type})`,
);
for (const modulePath of conflict.absolutePaths) {
messages.push(
indent +
(pathsRelativeToRoot != null
? path.relative(pathsRelativeToRoot, modulePath)
: modulePath),
);
}
++index;
}
return messages.join('\n');
}
}

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.computeHasteConflicts = computeHasteConflicts;
var _constants = _interopRequireDefault(require("../../constants"));
var _sorting = require("../../lib/sorting");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function computeHasteConflicts({ duplicates, map, rootDir }) {
const conflicts = [];
for (const [id, dupsByPlatform] of duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform:
platform === _constants.default.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map((modulePath) => _path.default.resolve(rootDir, modulePath))
.sort(),
type: "duplicate",
});
}
}
for (const [id, data] of map) {
const conflictPaths = new Set();
const basePaths = [];
for (const basePlatform of [
_constants.default.NATIVE_PLATFORM,
_constants.default.GENERIC_PLATFORM,
]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = _path.default.dirname(basePath);
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === _constants.default.GENERIC_PLATFORM
) {
continue;
}
const platformPath = data[platform][0];
if (_path.default.dirname(platformPath) !== basePathDir) {
conflictPaths.add(platformPath);
}
}
}
if (conflictPaths.size) {
conflicts.push({
id,
platform: null,
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map((modulePath) => _path.default.resolve(rootDir, modulePath))
.sort(),
type: "shadowing",
});
}
}
conflicts.sort(
(0, _sorting.chainComparators)(
(a, b) => (0, _sorting.compareStrings)(a.type, b.type),
(a, b) => (0, _sorting.compareStrings)(a.id, b.id),
(a, b) => (0, _sorting.compareStrings)(a.platform, b.platform),
),
);
return conflicts;
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {HasteMapItem} from '../../flow-types';
import H from '../../constants';
import {chainComparators, compareStrings} from '../../lib/sorting';
import path from 'path';
type Conflict = {
id: string,
platform: string | null,
absolutePaths: Array<string>,
type: 'duplicate' | 'shadowing',
};
export function computeHasteConflicts({
duplicates,
map,
rootDir,
}: $ReadOnly<{
duplicates: $ReadOnlyMap<
string,
$ReadOnlyMap<string, $ReadOnlyMap<string, number>>,
>,
map: $ReadOnlyMap<string, HasteMapItem>,
rootDir: string,
}>): Array<Conflict> {
const conflicts: Array<Conflict> = [];
// Add duplicates reported by metro-file-map
for (const [id, dupsByPlatform] of duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform: platform === H.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map(modulePath => path.resolve(rootDir, modulePath))
// Sort for ease of testing
.sort(),
type: 'duplicate',
});
}
}
// Add cases of "shadowing at a distance": a module with a platform suffix and
// a module with a lower priority platform suffix (or no suffix), in different
// directories.
for (const [id, data] of map) {
const conflictPaths = new Set<string>();
const basePaths = [];
for (const basePlatform of [H.NATIVE_PLATFORM, H.GENERIC_PLATFORM]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = path.dirname(basePath);
// Find all platforms that can shadow basePlatform
// Given that X.(specific platform).js > x.native.js > X.js
// and basePlatform is either 'native' or generic (no platform).
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === H.GENERIC_PLATFORM /* lowest priority */
) {
continue;
}
const platformPath = data[platform][0];
if (path.dirname(platformPath) !== basePathDir) {
conflictPaths.add(platformPath);
}
}
}
if (conflictPaths.size) {
conflicts.push({
id,
platform: null,
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map(modulePath => path.resolve(rootDir, modulePath))
// Sort for ease of testing
.sort(),
type: 'shadowing',
});
}
}
// Sort for ease of testing
conflicts.sort(
chainComparators(
(a, b) => compareStrings(a.type, b.type),
(a, b) => compareStrings(a.id, b.id),
(a, b) => compareStrings(a.platform, b.platform),
),
);
return conflicts;
}

View File

@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getPlatformExtension;
function getPlatformExtension(file, platforms) {
const last = file.lastIndexOf(".");
const secondToLast = file.lastIndexOf(".", last - 1);
if (secondToLast === -1) {
return null;
}
const platform = file.substring(secondToLast + 1, last);
return platforms.has(platform) ? platform : null;
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
// Extract platform extension: index.ios.js -> ios
export default function getPlatformExtension(
file: string,
platforms: $ReadOnlySet<string>,
): ?string {
const last = file.lastIndexOf('.');
const secondToLast = file.lastIndexOf('.', last - 1);
if (secondToLast === -1) {
return null;
}
const platform = file.substring(secondToLast + 1, last);
return platforms.has(platform) ? platform : null;
}

View File

@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const MOCKS_PATTERN = path.sep + "__mocks__" + path.sep;
const getMockName = (filePath) => {
const mockPath = filePath.split(MOCKS_PATTERN)[1];
return mockPath
.substring(0, mockPath.lastIndexOf(path.extname(mockPath)))
.replaceAll("\\", "/");
};
var _default = (exports.default = getMockName);

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import * as path from 'path';
const MOCKS_PATTERN = path.sep + '__mocks__' + path.sep;
const getMockName = (filePath: string): string => {
const mockPath = filePath.split(MOCKS_PATTERN)[1];
return mockPath
.substring(0, mockPath.lastIndexOf(path.extname(mockPath)))
.replaceAll('\\', '/');
};
export default getMockName;

View File

@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.AbstractWatcher = void 0;
var _common = require("./common");
var _events = _interopRequireDefault(require("events"));
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class AbstractWatcher {
#emitter = new _events.default();
constructor(dir, { ignored, globs, dot }) {
this.dot = dot || false;
this.ignored = ignored;
this.globs = globs;
this.doIgnore = ignored
? (filePath) => (0, _common.posixPathMatchesPattern)(ignored, filePath)
: () => false;
this.root = path.resolve(dir);
}
onFileEvent(listener) {
this.#emitter.on("fileevent", listener);
return () => {
this.#emitter.removeListener("fileevent", listener);
};
}
onError(listener) {
this.#emitter.on("error", listener);
return () => {
this.#emitter.removeListener("error", listener);
};
}
async startWatching() {}
async stopWatching() {
this.#emitter.removeAllListeners();
}
emitFileEvent(event) {
this.#emitter.emit("fileevent", {
...event,
root: this.root,
});
}
emitError(error) {
this.#emitter.emit("error", error);
}
getPauseReason() {
return null;
}
}
exports.AbstractWatcher = AbstractWatcher;

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {
WatcherBackend,
WatcherBackendChangeEvent,
WatcherBackendOptions,
} from '../flow-types';
import {posixPathMatchesPattern} from './common';
import EventEmitter from 'events';
import * as path from 'path';
export type Listeners = $ReadOnly<{
onFileEvent: (event: WatcherBackendChangeEvent) => void,
onError: (error: Error) => void,
}>;
export class AbstractWatcher implements WatcherBackend {
+root: string;
+ignored: ?RegExp;
+globs: $ReadOnlyArray<string>;
+dot: boolean;
+doIgnore: (path: string) => boolean;
#emitter: EventEmitter = new EventEmitter();
constructor(dir: string, {ignored, globs, dot}: WatcherBackendOptions) {
this.dot = dot || false;
this.ignored = ignored;
this.globs = globs;
this.doIgnore = ignored
? (filePath: string) => posixPathMatchesPattern(ignored, filePath)
: () => false;
this.root = path.resolve(dir);
}
onFileEvent(
listener: (event: WatcherBackendChangeEvent) => void,
): () => void {
this.#emitter.on('fileevent', listener);
return () => {
this.#emitter.removeListener('fileevent', listener);
};
}
onError(listener: (error: Error) => void): () => void {
this.#emitter.on('error', listener);
return () => {
this.#emitter.removeListener('error', listener);
};
}
async startWatching(): Promise<void> {
// Must be implemented by subclasses
}
async stopWatching() {
this.#emitter.removeAllListeners();
}
emitFileEvent(event: Omit<WatcherBackendChangeEvent, 'root'>) {
this.#emitter.emit('fileevent', {
...event,
root: this.root,
});
}
emitError(error: Error) {
this.#emitter.emit('error', error);
}
getPauseReason(): ?string {
return null;
}
}

View File

@@ -0,0 +1,359 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _AbstractWatcher = require("./AbstractWatcher");
var common = _interopRequireWildcard(require("./common"));
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _walker = _interopRequireDefault(require("walker"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const platform = _os.default.platform();
const fsPromises = _fs.default.promises;
const TOUCH_EVENT = common.TOUCH_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;
const DEBOUNCE_MS = 100;
class FallbackWatcher extends _AbstractWatcher.AbstractWatcher {
_changeTimers = new Map();
_dirRegistry = Object.create(null);
watched = Object.create(null);
async startWatching() {
this._watchdir(this.root);
await new Promise((resolve) => {
recReaddir(
this.root,
(dir) => {
this._watchdir(dir);
},
(filename) => {
this._register(filename, "f");
},
(symlink) => {
this._register(symlink, "l");
},
() => {
resolve();
},
this._checkedEmitError,
this.ignored,
);
});
}
_register(filepath, type) {
const dir = _path.default.dirname(filepath);
const filename = _path.default.basename(filepath);
if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
return false;
}
const relativePath = _path.default.relative(this.root, filepath);
if (
this.doIgnore(relativePath) ||
(type === "f" &&
!common.includedByGlob("f", this.globs, this.dot, relativePath))
) {
return false;
}
if (!this._dirRegistry[dir]) {
this._dirRegistry[dir] = Object.create(null);
}
this._dirRegistry[dir][filename] = true;
return true;
}
_unregister(filepath) {
const dir = _path.default.dirname(filepath);
if (this._dirRegistry[dir]) {
const filename = _path.default.basename(filepath);
delete this._dirRegistry[dir][filename];
}
}
_unregisterDir(dirpath) {
if (this._dirRegistry[dirpath]) {
delete this._dirRegistry[dirpath];
}
}
_registered(fullpath) {
const dir = _path.default.dirname(fullpath);
return !!(
this._dirRegistry[fullpath] ||
(this._dirRegistry[dir] &&
this._dirRegistry[dir][_path.default.basename(fullpath)])
);
}
_checkedEmitError = (error) => {
if (!isIgnorableFileError(error)) {
this.emitError(error);
}
};
_watchdir = (dir) => {
if (this.watched[dir]) {
return false;
}
const watcher = _fs.default.watch(
dir,
{
persistent: true,
},
(event, filename) => this._normalizeChange(dir, event, filename),
);
this.watched[dir] = watcher;
watcher.on("error", this._checkedEmitError);
if (this.root !== dir) {
this._register(dir, "d");
}
return true;
};
async _stopWatching(dir) {
if (this.watched[dir]) {
await new Promise((resolve) => {
this.watched[dir].once("close", () => process.nextTick(resolve));
this.watched[dir].close();
delete this.watched[dir];
});
}
}
async stopWatching() {
await super.stopWatching();
const promises = Object.keys(this.watched).map((dir) =>
this._stopWatching(dir),
);
await Promise.all(promises);
}
_detectChangedFile(dir, event, callback) {
if (!this._dirRegistry[dir]) {
return;
}
let found = false;
let closest = null;
let c = 0;
Object.keys(this._dirRegistry[dir]).forEach((file, i, arr) => {
_fs.default.lstat(_path.default.join(dir, file), (error, stat) => {
if (found) {
return;
}
if (error) {
if (isIgnorableFileError(error)) {
found = true;
callback(file);
} else {
this.emitError(error);
}
} else {
if (closest == null || stat.mtime > closest.mtime) {
closest = {
file,
mtime: stat.mtime,
};
}
if (arr.length === ++c) {
callback(closest.file);
}
}
});
});
}
_normalizeChange(dir, event, file) {
if (!file) {
this._detectChangedFile(dir, event, (actualFile) => {
if (actualFile) {
this._processChange(dir, event, actualFile).catch((error) =>
this.emitError(error),
);
}
});
} else {
this._processChange(dir, event, _path.default.normalize(file)).catch(
(error) => this.emitError(error),
);
}
}
async _processChange(dir, event, file) {
const fullPath = _path.default.join(dir, file);
const relativePath = _path.default.join(
_path.default.relative(this.root, dir),
file,
);
const registered = this._registered(fullPath);
try {
const stat = await fsPromises.lstat(fullPath);
if (stat.isDirectory()) {
if (event === "change") {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob("d", this.globs, this.dot, relativePath)
) {
return;
}
recReaddir(
_path.default.resolve(this.root, relativePath),
(dir, stats) => {
if (this._watchdir(dir)) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: _path.default.relative(this.root, dir),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: "d",
},
});
}
},
(file, stats) => {
if (this._register(file, "f")) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: _path.default.relative(this.root, file),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: "f",
},
});
}
},
(symlink, stats) => {
if (this._register(symlink, "l")) {
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath: _path.default.relative(this.root, symlink),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: "l",
},
});
}
},
function endCallback() {},
this._checkedEmitError,
this.ignored,
);
} else {
const type = common.typeFromStat(stat);
if (type == null) {
return;
}
const metadata = {
modifiedTime: stat.mtime.getTime(),
size: stat.size,
type,
};
if (registered) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath,
metadata,
});
} else {
if (this._register(fullPath, type)) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath,
metadata,
});
}
}
}
} catch (error) {
if (!isIgnorableFileError(error)) {
this.emitError(error);
return;
}
this._unregister(fullPath);
this._unregisterDir(fullPath);
if (registered) {
this._emitEvent({
event: DELETE_EVENT,
relativePath,
});
}
await this._stopWatching(fullPath);
}
}
_emitEvent(change) {
const { event, relativePath } = change;
const key = event + "-" + relativePath;
const existingTimer = this._changeTimers.get(key);
if (existingTimer) {
clearTimeout(existingTimer);
}
this._changeTimers.set(
key,
setTimeout(() => {
this._changeTimers.delete(key);
this.emitFileEvent(change);
}, DEBOUNCE_MS),
);
}
getPauseReason() {
return null;
}
}
exports.default = FallbackWatcher;
function isIgnorableFileError(error) {
return (
error.code === "ENOENT" || (error.code === "EPERM" && platform === "win32")
);
}
function recReaddir(
dir,
dirCallback,
fileCallback,
symlinkCallback,
endCallback,
errorCallback,
ignored,
) {
const walk = (0, _walker.default)(dir);
if (ignored) {
walk.filterDir(
(currentDir) => !common.posixPathMatchesPattern(ignored, currentDir),
);
}
walk
.on("dir", normalizeProxy(dirCallback))
.on("file", normalizeProxy(fileCallback))
.on("symlink", normalizeProxy(symlinkCallback))
.on("error", errorCallback)
.on("end", () => {
if (platform === "win32") {
setTimeout(endCallback, 1000);
} else {
endCallback();
}
});
}
function normalizeProxy(callback) {
return (filepath, stats) =>
callback(_path.default.normalize(filepath), stats);
}

View File

@@ -0,0 +1,446 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/**
* Originally vendored from https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/node_watcher.js
*/
import type {
ChangeEventMetadata,
WatcherBackendChangeEvent,
} from '../flow-types';
import type {FSWatcher, Stats} from 'fs';
import {AbstractWatcher} from './AbstractWatcher';
import * as common from './common';
import fs from 'fs';
import os from 'os';
import path from 'path';
// $FlowFixMe[untyped-import] - Write libdefs for `walker`
import walker from 'walker';
const platform = os.platform();
const fsPromises = fs.promises;
const TOUCH_EVENT = common.TOUCH_EVENT;
const DELETE_EVENT = common.DELETE_EVENT;
/**
* This setting delays all events. It suppresses 'change' events that
* immediately follow an 'add', and debounces successive 'change' events to
* only emit the latest.
*/
const DEBOUNCE_MS = 100;
export default class FallbackWatcher extends AbstractWatcher {
+_changeTimers: Map<string, TimeoutID> = new Map();
+_dirRegistry: {
[directory: string]: {[file: string]: true, __proto__: null},
__proto__: null,
} = Object.create(null);
+watched: {[key: string]: FSWatcher, __proto__: null} = Object.create(null);
async startWatching() {
this._watchdir(this.root);
await new Promise(resolve => {
recReaddir(
this.root,
dir => {
this._watchdir(dir);
},
filename => {
this._register(filename, 'f');
},
symlink => {
this._register(symlink, 'l');
},
() => {
resolve();
},
this._checkedEmitError,
this.ignored,
);
});
}
/**
* Register files that matches our globs to know what to type of event to
* emit in the future.
*
* Registry looks like the following:
*
* dirRegister => Map {
* dirpath => Map {
* filename => true
* }
* }
*
* Return false if ignored or already registered.
*/
_register(filepath: string, type: ChangeEventMetadata['type']): boolean {
const dir = path.dirname(filepath);
const filename = path.basename(filepath);
if (this._dirRegistry[dir] && this._dirRegistry[dir][filename]) {
return false;
}
const relativePath = path.relative(this.root, filepath);
if (
this.doIgnore(relativePath) ||
(type === 'f' &&
!common.includedByGlob('f', this.globs, this.dot, relativePath))
) {
return false;
}
if (!this._dirRegistry[dir]) {
this._dirRegistry[dir] = Object.create(null);
}
this._dirRegistry[dir][filename] = true;
return true;
}
/**
* Removes a file from the registry.
*/
_unregister(filepath: string) {
const dir = path.dirname(filepath);
if (this._dirRegistry[dir]) {
const filename = path.basename(filepath);
delete this._dirRegistry[dir][filename];
}
}
/**
* Removes a dir from the registry.
*/
_unregisterDir(dirpath: string): void {
if (this._dirRegistry[dirpath]) {
delete this._dirRegistry[dirpath];
}
}
/**
* Checks if a file or directory exists in the registry.
*/
_registered(fullpath: string): boolean {
const dir = path.dirname(fullpath);
return !!(
this._dirRegistry[fullpath] ||
(this._dirRegistry[dir] &&
this._dirRegistry[dir][path.basename(fullpath)])
);
}
/**
* Emit "error" event if it's not an ignorable event
*/
_checkedEmitError: (error: Error) => void = error => {
if (!isIgnorableFileError(error)) {
this.emitError(error);
}
};
/**
* Watch a directory.
*/
_watchdir: string => boolean = (dir: string) => {
if (this.watched[dir]) {
return false;
}
const watcher = fs.watch(dir, {persistent: true}, (event, filename) =>
this._normalizeChange(dir, event, filename),
);
this.watched[dir] = watcher;
watcher.on('error', this._checkedEmitError);
if (this.root !== dir) {
this._register(dir, 'd');
}
return true;
};
/**
* Stop watching a directory.
*/
async _stopWatching(dir: string): Promise<void> {
if (this.watched[dir]) {
await new Promise(resolve => {
this.watched[dir].once('close', () => process.nextTick(resolve));
this.watched[dir].close();
delete this.watched[dir];
});
}
}
/**
* End watching.
*/
async stopWatching(): Promise<void> {
await super.stopWatching();
const promises = Object.keys(this.watched).map(dir =>
this._stopWatching(dir),
);
await Promise.all(promises);
}
/**
* On some platforms, as pointed out on the fs docs (most likely just win32)
* the file argument might be missing from the fs event. Try to detect what
* change by detecting if something was deleted or the most recent file change.
*/
_detectChangedFile(
dir: string,
event: string,
callback: (file: string) => void,
) {
if (!this._dirRegistry[dir]) {
return;
}
let found = false;
let closest: ?$ReadOnly<{file: string, mtime: Stats['mtime']}> = null;
let c = 0;
Object.keys(this._dirRegistry[dir]).forEach((file, i, arr) => {
fs.lstat(path.join(dir, file), (error, stat) => {
if (found) {
return;
}
if (error) {
if (isIgnorableFileError(error)) {
found = true;
callback(file);
} else {
this.emitError(error);
}
} else {
if (closest == null || stat.mtime > closest.mtime) {
closest = {file, mtime: stat.mtime};
}
if (arr.length === ++c) {
callback(closest.file);
}
}
});
});
}
/**
* Normalize fs events and pass it on to be processed.
*/
_normalizeChange(dir: string, event: string, file: string) {
if (!file) {
this._detectChangedFile(dir, event, actualFile => {
if (actualFile) {
this._processChange(dir, event, actualFile).catch(error =>
this.emitError(error),
);
}
});
} else {
this._processChange(dir, event, path.normalize(file)).catch(error =>
this.emitError(error),
);
}
}
/**
* Process changes.
*/
async _processChange(dir: string, event: string, file: string) {
const fullPath = path.join(dir, file);
const relativePath = path.join(path.relative(this.root, dir), file);
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') {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob('d', this.globs, this.dot, relativePath)
) {
return;
}
recReaddir(
path.resolve(this.root, relativePath),
(dir, stats) => {
if (this._watchdir(dir)) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: path.relative(this.root, dir),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: 'd',
},
});
}
},
(file, stats) => {
if (this._register(file, 'f')) {
this._emitEvent({
event: TOUCH_EVENT,
relativePath: path.relative(this.root, file),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: 'f',
},
});
}
},
(symlink, stats) => {
if (this._register(symlink, 'l')) {
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath: path.relative(this.root, symlink),
metadata: {
modifiedTime: stats.mtime.getTime(),
size: stats.size,
type: 'l',
},
});
}
},
function endCallback() {},
this._checkedEmitError,
this.ignored,
);
} else {
const type = common.typeFromStat(stat);
if (type == null) {
return;
}
const metadata: ChangeEventMetadata = {
modifiedTime: stat.mtime.getTime(),
size: stat.size,
type,
};
if (registered) {
this._emitEvent({event: TOUCH_EVENT, relativePath, metadata});
} else {
if (this._register(fullPath, type)) {
this._emitEvent({event: TOUCH_EVENT, relativePath, metadata});
}
}
}
} catch (error) {
if (!isIgnorableFileError(error)) {
this.emitError(error);
return;
}
this._unregister(fullPath);
this._unregisterDir(fullPath);
if (registered) {
this._emitEvent({event: DELETE_EVENT, relativePath});
}
await this._stopWatching(fullPath);
}
}
/**
* Emits the given event after debouncing, to emit only the latest
* information when we receive several events in quick succession. E.g.,
* Linux emits two events for every new file.
*
* See also note above for DEBOUNCE_MS.
*/
_emitEvent(change: Omit<WatcherBackendChangeEvent, 'root'>) {
const {event, relativePath} = change;
const key = event + '-' + relativePath;
const existingTimer = this._changeTimers.get(key);
if (existingTimer) {
clearTimeout(existingTimer);
}
this._changeTimers.set(
key,
setTimeout(() => {
this._changeTimers.delete(key);
this.emitFileEvent(change);
}, DEBOUNCE_MS),
);
}
getPauseReason(): ?string {
return null;
}
}
/**
* Determine if a given FS error can be ignored
*/
function isIgnorableFileError(error: Error | {code: string}) {
return (
error.code === 'ENOENT' ||
// Workaround Windows EPERM on watched folder deletion, and when
// reading locked files (pending further writes or pending deletion).
// In such cases, we'll receive a subsequent event when the file is
// deleted or ready to read.
// https://github.com/facebook/metro/issues/1001
// https://github.com/nodejs/node-v0.x-archive/issues/4337
(error.code === 'EPERM' && platform === 'win32')
);
}
/**
* Traverse a directory recursively calling `callback` on every directory.
*/
function recReaddir(
dir: string,
dirCallback: (string, Stats) => void,
fileCallback: (string, Stats) => void,
symlinkCallback: (string, Stats) => void,
endCallback: () => void,
errorCallback: Error => void,
ignored: ?RegExp,
) {
const walk = walker(dir);
if (ignored) {
walk.filterDir(
(currentDir: string) =>
!common.posixPathMatchesPattern(ignored, currentDir),
);
}
walk
.on('dir', normalizeProxy(dirCallback))
.on('file', normalizeProxy(fileCallback))
.on('symlink', normalizeProxy(symlinkCallback))
.on('error', errorCallback)
.on('end', () => {
if (platform === 'win32') {
setTimeout(endCallback, 1000);
} else {
endCallback();
}
});
}
/**
* Returns a callback that when called will normalize a path and call the
* original callback
*/
function normalizeProxy<T>(
callback: (filepath: string, stats: Stats) => T,
): (string, Stats) => T {
return (filepath: string, stats: Stats) =>
callback(path.normalize(filepath), stats);
}

View File

@@ -0,0 +1,109 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _AbstractWatcher = require("./AbstractWatcher");
var _common = require("./common");
var _fs = require("fs");
var _os = require("os");
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const debug = require("debug")("Metro:NativeWatcher");
const TOUCH_EVENT = "touch";
const DELETE_EVENT = "delete";
class NativeWatcher extends _AbstractWatcher.AbstractWatcher {
#fsWatcher;
static isSupported() {
return (0, _os.platform)() === "darwin";
}
constructor(dir, opts) {
if (!NativeWatcher.isSupported) {
throw new Error("This watcher can only be used on macOS");
}
super(dir, opts);
}
async startWatching() {
this.#fsWatcher = (0, _fs.watch)(
this.root,
{
persistent: false,
recursive: true,
},
(_event, relativePath) => {
this._handleEvent(relativePath).catch((error) => {
this.emitError(error);
});
},
);
debug("Watching %s", this.root);
}
async stopWatching() {
await super.stopWatching();
if (this.#fsWatcher) {
this.#fsWatcher.close();
}
}
async _handleEvent(relativePath) {
const absolutePath = path.resolve(this.root, relativePath);
if (this.doIgnore(relativePath)) {
debug("Ignoring event on %s (root: %s)", relativePath, this.root);
return;
}
debug("Handling event on %s (root: %s)", relativePath, this.root);
try {
const stat = await _fs.promises.lstat(absolutePath);
const type = (0, _common.typeFromStat)(stat);
if (!type) {
return;
}
if (
!(0, _common.includedByGlob)(type, this.globs, this.dot, relativePath)
) {
return;
}
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath,
metadata: {
type,
modifiedTime: stat.mtime.getTime(),
size: stat.size,
},
});
} catch (error) {
if (error?.code !== "ENOENT") {
this.emitError(error);
return;
}
this.emitFileEvent({
event: DELETE_EVENT,
relativePath,
});
}
}
}
exports.default = NativeWatcher;

View File

@@ -0,0 +1,137 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {FSWatcher} from 'fs';
import {AbstractWatcher} from './AbstractWatcher';
import {includedByGlob, typeFromStat} from './common';
import {promises as fsPromises, watch} from 'fs';
import {platform} from 'os';
import * as path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:NativeWatcher');
const TOUCH_EVENT = 'touch';
const DELETE_EVENT = 'delete';
/**
* NativeWatcher uses Node's native fs.watch API with recursive: true.
*
* Supported on macOS (and potentially Windows), because both natively have a
* concept of recurisve watching, via FSEvents and ReadDirectoryChangesW
* respectively. Notably Linux lacks this capability at the OS level.
*
* Node.js has at times supported the `recursive` option to fs.watch on Linux
* by walking the directory tree and creating a watcher on each directory, but
* this fits poorly with the synchronous `watch` API - either it must block for
* arbitrarily large IO, or it may drop changes after `watch` returns. See:
* https://github.com/nodejs/node/issues/48437
*
* Therefore, we retain a fallback to our own application-level recursive
* FallbackWatcher for Linux, which has async `startWatching`.
*
* On Windows, this watcher could be used in principle, but needs work around
* some Windows-specific edge cases handled in FallbackWatcher, like
* deduping file change events, ignoring directory changes, and handling EPERM.
*/
export default class NativeWatcher extends AbstractWatcher {
#fsWatcher: ?FSWatcher;
static isSupported(): boolean {
return platform() === 'darwin';
}
constructor(
dir: string,
opts: $ReadOnly<{
ignored: ?RegExp,
globs: $ReadOnlyArray<string>,
dot: boolean,
...
}>,
) {
if (!NativeWatcher.isSupported) {
throw new Error('This watcher can only be used on macOS');
}
super(dir, opts);
}
async startWatching(): Promise<void> {
this.#fsWatcher = watch(
this.root,
{
// Don't hold the process open if we forget to close()
persistent: false,
// FSEvents or ReadDirectoryChangesW should mean this is cheap and
// ~instant on macOS or Windows.
recursive: true,
},
(_event, relativePath) => {
// _event is always 'rename' on macOS, so we don't use it.
this._handleEvent(relativePath).catch(error => {
this.emitError(error);
});
},
);
debug('Watching %s', this.root);
}
/**
* End watching.
*/
async stopWatching(): Promise<void> {
await super.stopWatching();
if (this.#fsWatcher) {
this.#fsWatcher.close();
}
}
async _handleEvent(relativePath: string) {
const absolutePath = path.resolve(this.root, relativePath);
if (this.doIgnore(relativePath)) {
debug('Ignoring event on %s (root: %s)', relativePath, this.root);
return;
}
debug('Handling event on %s (root: %s)', relativePath, this.root);
try {
const stat = await fsPromises.lstat(absolutePath);
const type = typeFromStat(stat);
// Ignore files of an unrecognized type
if (!type) {
return;
}
if (!includedByGlob(type, this.globs, this.dot, relativePath)) {
return;
}
this.emitFileEvent({
event: TOUCH_EVENT,
relativePath,
metadata: {
type,
modifiedTime: stat.mtime.getTime(),
size: stat.size,
},
});
} catch (error) {
if (error?.code !== 'ENOENT') {
this.emitError(error);
return;
}
this.emitFileEvent({event: DELETE_EVENT, relativePath});
}
}
}

View File

@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class RecrawlWarning {
static RECRAWL_WARNINGS = [];
static REGEXP =
/Recrawled this watch (\d+) times?, most recently because:\n([^:]+)/;
constructor(root, count) {
this.root = root;
this.count = count;
}
static findByRoot(root) {
for (let i = 0; i < this.RECRAWL_WARNINGS.length; i++) {
const warning = this.RECRAWL_WARNINGS[i];
if (warning.root === root) {
return warning;
}
}
return undefined;
}
static isRecrawlWarningDupe(warningMessage) {
if (typeof warningMessage !== "string") {
return false;
}
const match = warningMessage.match(this.REGEXP);
if (!match) {
return false;
}
const count = Number(match[1]);
const root = match[2];
const warning = this.findByRoot(root);
if (warning) {
if (warning.count >= count) {
return true;
} else {
warning.count = count;
return false;
}
} else {
this.RECRAWL_WARNINGS.push(new RecrawlWarning(root, count));
return false;
}
}
}
exports.default = RecrawlWarning;

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
/**
* Originally vendored from
* https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/utils/recrawl-warning-dedupe.js
*/
export default class RecrawlWarning {
static RECRAWL_WARNINGS: Array<RecrawlWarning> = [];
static REGEXP: RegExp =
/Recrawled this watch (\d+) times?, most recently because:\n([^:]+)/;
root: string;
count: number;
constructor(root: string, count: number) {
this.root = root;
this.count = count;
}
static findByRoot(root: string): ?RecrawlWarning {
for (let i = 0; i < this.RECRAWL_WARNINGS.length; i++) {
const warning = this.RECRAWL_WARNINGS[i];
if (warning.root === root) {
return warning;
}
}
return undefined;
}
static isRecrawlWarningDupe(warningMessage: mixed): boolean {
if (typeof warningMessage !== 'string') {
return false;
}
const match = warningMessage.match(this.REGEXP);
if (!match) {
return false;
}
const count = Number(match[1]);
const root = match[2];
const warning = this.findByRoot(root);
if (warning) {
// only keep the highest count, assume count to either stay the same or
// increase.
if (warning.count >= count) {
return true;
} else {
// update the existing warning to the latest (highest) count
warning.count = count;
return false;
}
} else {
this.RECRAWL_WARNINGS.push(new RecrawlWarning(root, count));
return false;
}
}
}

View File

@@ -0,0 +1,294 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
require("../lib/normalizePathSeparatorsToSystem"),
);
var _AbstractWatcher = require("./AbstractWatcher");
var common = _interopRequireWildcard(require("./common"));
var _RecrawlWarning = _interopRequireDefault(require("./RecrawlWarning"));
var _assert = _interopRequireDefault(require("assert"));
var _crypto = require("crypto");
var _fbWatchman = _interopRequireDefault(require("fb-watchman"));
var _invariant = _interopRequireDefault(require("invariant"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:WatchmanWatcher");
const DELETE_EVENT = common.DELETE_EVENT;
const TOUCH_EVENT = common.TOUCH_EVENT;
const SUB_PREFIX = "metro-file-map";
class WatchmanWatcher extends _AbstractWatcher.AbstractWatcher {
#deferringStates = null;
constructor(dir, { watchmanDeferStates, ...opts }) {
super(dir, opts);
this.watchmanDeferStates = watchmanDeferStates;
const watchKey = (0, _crypto.createHash)("md5")
.update(this.root)
.digest("hex");
const readablePath = this.root
.replace(/[\/\\]/g, "-")
.replace(/[^\-\w]/g, "");
this.subscriptionName = `${SUB_PREFIX}-${process.pid}-${readablePath}-${watchKey}`;
}
async startWatching() {
await new Promise((resolve, reject) => this._init(resolve, reject));
}
_init(onReady, onError) {
if (this.client) {
this.client.removeAllListeners();
}
const self = this;
this.client = new _fbWatchman.default.Client();
this.client.on("error", (error) => {
this.emitError(error);
});
this.client.on("subscription", (changeEvent) =>
this._handleChangeEvent(changeEvent),
);
this.client.on("end", () => {
console.warn(
"[metro-file-map] Warning: Lost connection to Watchman, reconnecting..",
);
self._init(
() => {},
(error) => self.emitError(error),
);
});
this.watchProjectInfo = null;
function getWatchRoot() {
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
}
function onWatchProject(error, resp) {
if (error) {
onError(error);
return;
}
debug("Received watch-project response: %s", resp.relative_path);
handleWarning(resp);
self.watchProjectInfo = {
relativePath: resp.relative_path
? (0, _normalizePathSeparatorsToSystem.default)(resp.relative_path)
: "",
root: (0, _normalizePathSeparatorsToSystem.default)(resp.watch),
};
self.client.command(["clock", getWatchRoot()], onClock);
}
function onClock(error, resp) {
if (error) {
onError(error);
return;
}
debug("Received clock response: %s", resp.clock);
const watchProjectInfo = self.watchProjectInfo;
(0, _invariant.default)(
watchProjectInfo != null,
"watch-project response should have been set before clock response",
);
handleWarning(resp);
const options = {
fields: ["name", "exists", "new", "type", "size", "mtime_ms"],
since: resp.clock,
defer: self.watchmanDeferStates,
relative_root: watchProjectInfo.relativePath,
};
if (self.globs.length === 0 && !self.dot) {
options.expression = [
"match",
"**",
"wholename",
{
includedotfiles: false,
},
];
}
self.client.command(
["subscribe", getWatchRoot(), self.subscriptionName, options],
onSubscribe,
);
}
const onSubscribe = (error, resp) => {
if (error) {
onError(error);
return;
}
debug("Received subscribe response: %s", resp.subscribe);
handleWarning(resp);
if (resp["asserted-states"] != null) {
this.#deferringStates = new Set(resp["asserted-states"]);
}
onReady();
};
self.client.command(["watch-project", getWatchRoot()], onWatchProject);
}
_handleChangeEvent(resp) {
debug(
"Received subscription response: %s (fresh: %s, files: %s, enter: %s, leave: %s, clock: %s)",
resp.subscription,
resp.is_fresh_instance,
resp.files?.length,
resp["state-enter"],
resp["state-leave"],
resp.clock,
);
_assert.default.equal(
resp.subscription,
this.subscriptionName,
"Invalid subscription event.",
);
if (Array.isArray(resp.files)) {
resp.files.forEach((change) =>
this._handleFileChange(change, resp.clock),
);
}
const { "state-enter": stateEnter, "state-leave": stateLeave } = resp;
if (
stateEnter != null &&
(this.watchmanDeferStates ?? []).includes(stateEnter)
) {
this.#deferringStates?.add(stateEnter);
debug(
'Watchman reports "%s" just started. Filesystem notifications are paused.',
stateEnter,
);
}
if (
stateLeave != null &&
(this.watchmanDeferStates ?? []).includes(stateLeave)
) {
this.#deferringStates?.delete(stateLeave);
debug(
'Watchman reports "%s" ended. Filesystem notifications resumed.',
stateLeave,
);
}
}
_handleFileChange(changeDescriptor, rawClock) {
const self = this;
const watchProjectInfo = self.watchProjectInfo;
(0, _invariant.default)(
watchProjectInfo != null,
"watch-project response should have been set before receiving subscription events",
);
const {
name: relativePosixPath,
new: isNew = false,
exists = false,
type,
mtime_ms,
size,
} = changeDescriptor;
const relativePath = (0, _normalizePathSeparatorsToSystem.default)(
relativePosixPath,
);
debug(
"Handling change to: %s (new: %s, exists: %s, type: %s)",
relativePath,
isNew,
exists,
type,
);
if (type != null && !(type === "f" || type === "d" || type === "l")) {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob(type, this.globs, this.dot, relativePath)
) {
return;
}
const clock =
typeof rawClock === "string" && this.watchProjectInfo != null
? [this.watchProjectInfo.root, rawClock]
: undefined;
if (!exists) {
self.emitFileEvent({
event: DELETE_EVENT,
clock,
relativePath,
});
} else {
(0, _invariant.default)(
type != null && mtime_ms != null && size != null,
'Watchman file change event for "%s" missing some requested metadata. ' +
"Got type: %s, mtime_ms: %s, size: %s",
relativePath,
type,
mtime_ms,
size,
);
if (!(type === "d" && !isNew)) {
const mtime = Number(mtime_ms);
self.emitFileEvent({
event: TOUCH_EVENT,
clock,
relativePath,
metadata: {
modifiedTime: mtime !== 0 ? mtime : null,
size,
type,
},
});
}
}
}
async stopWatching() {
await super.stopWatching();
if (this.client) {
this.client.removeAllListeners();
this.client.end();
}
this.#deferringStates = null;
}
getPauseReason() {
if (this.#deferringStates == null || this.#deferringStates.size === 0) {
return null;
}
const states = [...this.#deferringStates];
if (states.length === 1) {
return `The watch is in the '${states[0]}' state.`;
}
return `The watch is in the ${states
.slice(0, -1)
.map((s) => `'${s}'`)
.join(", ")} and '${states[states.length - 1]}' states.`;
}
}
exports.default = WatchmanWatcher;
function handleWarning(resp) {
if ("warning" in resp) {
if (_RecrawlWarning.default.isRecrawlWarningDupe(resp.warning)) {
return true;
}
console.warn(resp.warning);
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,353 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {WatcherOptions} from './common';
import type {
Client,
WatchmanClockResponse,
WatchmanFileChange,
WatchmanQuery,
WatchmanSubscribeResponse,
WatchmanSubscriptionEvent,
WatchmanWatchResponse,
} from 'fb-watchman';
import normalizePathSeparatorsToSystem from '../lib/normalizePathSeparatorsToSystem';
import {AbstractWatcher} from './AbstractWatcher';
import * as common from './common';
import RecrawlWarning from './RecrawlWarning';
import assert from 'assert';
import {createHash} from 'crypto';
import watchman from 'fb-watchman';
import invariant from 'invariant';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:WatchmanWatcher');
const DELETE_EVENT = common.DELETE_EVENT;
const TOUCH_EVENT = common.TOUCH_EVENT;
const SUB_PREFIX = 'metro-file-map';
/**
* Watches `dir`.
*/
export default class WatchmanWatcher extends AbstractWatcher {
client: Client;
+subscriptionName: string;
watchProjectInfo: ?$ReadOnly<{
relativePath: string,
root: string,
}>;
+watchmanDeferStates: $ReadOnlyArray<string>;
#deferringStates: ?Set<string> = null;
constructor(dir: string, {watchmanDeferStates, ...opts}: WatcherOptions) {
super(dir, opts);
this.watchmanDeferStates = watchmanDeferStates;
// Use a unique subscription name per process per watched directory
const watchKey = createHash('md5').update(this.root).digest('hex');
const readablePath = this.root
.replace(/[\/\\]/g, '-') // \ and / to -
.replace(/[^\-\w]/g, ''); // Remove non-word/hyphen
this.subscriptionName = `${SUB_PREFIX}-${process.pid}-${readablePath}-${watchKey}`;
}
async startWatching() {
await new Promise((resolve, reject) => this._init(resolve, reject));
}
/**
* Run the watchman `watch` command on the root and subscribe to changes.
*/
_init(onReady: () => void, onError: (error: Error) => void) {
if (this.client) {
this.client.removeAllListeners();
}
const self = this;
this.client = new watchman.Client();
this.client.on('error', error => {
this.emitError(error);
});
this.client.on('subscription', changeEvent =>
this._handleChangeEvent(changeEvent),
);
this.client.on('end', () => {
console.warn(
'[metro-file-map] Warning: Lost connection to Watchman, reconnecting..',
);
self._init(
() => {},
error => self.emitError(error),
);
});
this.watchProjectInfo = null;
function getWatchRoot() {
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
}
function onWatchProject(error: ?Error, resp: WatchmanWatchResponse) {
if (error) {
onError(error);
return;
}
debug('Received watch-project response: %s', resp.relative_path);
handleWarning(resp);
// NB: Watchman outputs posix-separated paths even on Windows, convert
// them to system-native separators.
self.watchProjectInfo = {
relativePath: resp.relative_path
? normalizePathSeparatorsToSystem(resp.relative_path)
: '',
root: normalizePathSeparatorsToSystem(resp.watch),
};
self.client.command(['clock', getWatchRoot()], onClock);
}
function onClock(error: ?Error, resp: WatchmanClockResponse) {
if (error) {
onError(error);
return;
}
debug('Received clock response: %s', resp.clock);
const watchProjectInfo = self.watchProjectInfo;
invariant(
watchProjectInfo != null,
'watch-project response should have been set before clock response',
);
handleWarning(resp);
const options: WatchmanQuery = {
fields: ['name', 'exists', 'new', 'type', 'size', 'mtime_ms'],
since: resp.clock,
defer: self.watchmanDeferStates,
relative_root: watchProjectInfo.relativePath,
};
// Make sure we honor the dot option if even we're not using globs.
if (self.globs.length === 0 && !self.dot) {
options.expression = [
'match',
'**',
'wholename',
{
includedotfiles: false,
},
];
}
self.client.command(
['subscribe', getWatchRoot(), self.subscriptionName, options],
onSubscribe,
);
}
const onSubscribe = (error: ?Error, resp: WatchmanSubscribeResponse) => {
if (error) {
onError(error);
return;
}
debug('Received subscribe response: %s', resp.subscribe);
handleWarning(resp);
if (resp['asserted-states'] != null) {
this.#deferringStates = new Set(resp['asserted-states']);
}
onReady();
};
self.client.command(['watch-project', getWatchRoot()], onWatchProject);
}
/**
* Handles a change event coming from the subscription.
*/
_handleChangeEvent(resp: WatchmanSubscriptionEvent) {
debug(
'Received subscription response: %s (fresh: %s, files: %s, enter: %s, leave: %s, clock: %s)',
resp.subscription,
resp.is_fresh_instance,
resp.files?.length,
resp['state-enter'],
resp['state-leave'],
resp.clock,
);
assert.equal(
resp.subscription,
this.subscriptionName,
'Invalid subscription event.',
);
if (Array.isArray(resp.files)) {
resp.files.forEach(change => this._handleFileChange(change, resp.clock));
}
const {'state-enter': stateEnter, 'state-leave': stateLeave} = resp;
if (
stateEnter != null &&
(this.watchmanDeferStates ?? []).includes(stateEnter)
) {
this.#deferringStates?.add(stateEnter);
debug(
'Watchman reports "%s" just started. Filesystem notifications are paused.',
stateEnter,
);
}
if (
stateLeave != null &&
(this.watchmanDeferStates ?? []).includes(stateLeave)
) {
this.#deferringStates?.delete(stateLeave);
debug(
'Watchman reports "%s" ended. Filesystem notifications resumed.',
stateLeave,
);
}
}
/**
* Handles a single change event record.
*/
_handleFileChange(
changeDescriptor: WatchmanFileChange,
rawClock: WatchmanSubscriptionEvent['clock'],
) {
const self = this;
const watchProjectInfo = self.watchProjectInfo;
invariant(
watchProjectInfo != null,
'watch-project response should have been set before receiving subscription events',
);
const {
name: relativePosixPath,
new: isNew = false,
exists = false,
type,
mtime_ms,
size,
} = changeDescriptor;
// Watchman emits posix-separated paths on Windows, which is inconsistent
// with other watchers. Normalize to system-native separators.
const relativePath = normalizePathSeparatorsToSystem(relativePosixPath);
debug(
'Handling change to: %s (new: %s, exists: %s, type: %s)',
relativePath,
isNew,
exists,
type,
);
// Ignore files of an unrecognized type
if (type != null && !(type === 'f' || type === 'd' || type === 'l')) {
return;
}
if (
this.doIgnore(relativePath) ||
!common.includedByGlob(type, this.globs, this.dot, relativePath)
) {
return;
}
const clock =
typeof rawClock === 'string' && this.watchProjectInfo != null
? ([this.watchProjectInfo.root, rawClock]: [string, string])
: undefined;
if (!exists) {
self.emitFileEvent({event: DELETE_EVENT, clock, relativePath});
} else {
invariant(
type != null && mtime_ms != null && size != null,
'Watchman file change event for "%s" missing some requested metadata. ' +
'Got type: %s, mtime_ms: %s, size: %s',
relativePath,
type,
mtime_ms,
size,
);
if (
// Change event on dirs are mostly useless.
!(type === 'd' && !isNew)
) {
const mtime = Number(mtime_ms);
self.emitFileEvent({
event: TOUCH_EVENT,
clock,
relativePath,
metadata: {
modifiedTime: mtime !== 0 ? mtime : null,
size,
type,
},
});
}
}
}
/**
* Closes the watcher.
*/
async stopWatching() {
await super.stopWatching();
if (this.client) {
this.client.removeAllListeners();
this.client.end();
}
this.#deferringStates = null;
}
getPauseReason(): ?string {
if (this.#deferringStates == null || this.#deferringStates.size === 0) {
return null;
}
const states = [...this.#deferringStates];
if (states.length === 1) {
return `The watch is in the '${states[0]}' state.`;
}
return `The watch is in the ${states
.slice(0, -1)
.map(s => `'${s}'`)
.join(', ')} and '${states[states.length - 1]}' states.`;
}
}
/**
* Handles a warning in the watchman resp object.
*/
function handleWarning(resp: $ReadOnly<{warning?: mixed, ...}>) {
if ('warning' in resp) {
if (RecrawlWarning.isRecrawlWarningDupe(resp.warning)) {
return true;
}
console.warn(resp.warning);
return true;
} else {
return false;
}
}

42
node_modules/metro-file-map/src/watchers/common.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.TOUCH_EVENT = exports.DELETE_EVENT = exports.ALL_EVENT = void 0;
exports.includedByGlob = includedByGlob;
exports.posixPathMatchesPattern = void 0;
exports.typeFromStat = typeFromStat;
var _micromatch = _interopRequireDefault(require("micromatch"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const DELETE_EVENT = (exports.DELETE_EVENT = "delete");
const TOUCH_EVENT = (exports.TOUCH_EVENT = "touch");
const ALL_EVENT = (exports.ALL_EVENT = "all");
function includedByGlob(type, globs, dot, relativePath) {
if (globs.length === 0 || type !== "f") {
return dot || _micromatch.default.some(relativePath, "**/*");
}
return _micromatch.default.some(relativePath, globs, {
dot,
});
}
const posixPathMatchesPattern = (exports.posixPathMatchesPattern =
_path.default.sep === "/"
? (pattern, filePath) => pattern.test(filePath)
: (pattern, filePath) =>
pattern.test(filePath.replaceAll(_path.default.sep, "/")));
function typeFromStat(stat) {
if (stat.isSymbolicLink()) {
return "l";
}
if (stat.isDirectory()) {
return "d";
}
if (stat.isFile()) {
return "f";
}
return null;
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/**
* Originally vendored from
* https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/common.js
*/
import type {ChangeEventMetadata} from '../flow-types';
import type {Stats} from 'fs';
// $FlowFixMe[untyped-import] - Write libdefs for `micromatch`
import micromatch from 'micromatch';
import path from 'path';
/**
* Constants
*/
export const DELETE_EVENT = 'delete';
export const TOUCH_EVENT = 'touch';
export const ALL_EVENT = 'all';
export type WatcherOptions = $ReadOnly<{
globs: $ReadOnlyArray<string>,
dot: boolean,
ignored: ?RegExp,
watchmanDeferStates: $ReadOnlyArray<string>,
watchman?: mixed,
watchmanPath?: string,
}>;
/**
* Checks a file relative path against the globs array.
*/
export function includedByGlob(
type: ?('f' | 'l' | 'd'),
globs: $ReadOnlyArray<string>,
dot: boolean,
relativePath: string,
): boolean {
// For non-regular files or if there are no glob matchers, just respect the
// `dot` option to filter dotfiles if dot === false.
if (globs.length === 0 || type !== 'f') {
return dot || micromatch.some(relativePath, '**/*');
}
return micromatch.some(relativePath, globs, {dot});
}
/**
* Whether the given filePath matches the given RegExp, after converting
* (on Windows only) system separators to posix separators.
*
* Conversion to posix is for backwards compatibility with the previous
* anymatch matcher, which normlises all inputs[1]. This may not be consistent
* with other parts of metro-file-map.
*
* [1]: https://github.com/micromatch/anymatch/blob/3.1.1/index.js#L50
*/
export const posixPathMatchesPattern: (
pattern: RegExp,
filePath: string,
) => boolean =
path.sep === '/'
? (pattern, filePath) => pattern.test(filePath)
: (pattern, filePath) => pattern.test(filePath.replaceAll(path.sep, '/'));
export function typeFromStat(stat: Stats): ?ChangeEventMetadata['type'] {
// Note: These tests are not mutually exclusive - a symlink passes isFile
if (stat.isSymbolicLink()) {
return 'l';
}
if (stat.isDirectory()) {
return 'd';
}
if (stat.isFile()) {
return 'f'; // "Regular" file
}
return null;
}

108
node_modules/metro-file-map/src/worker.js generated vendored Normal file
View File

@@ -0,0 +1,108 @@
"use strict";
const defaultDependencyExtractor = require("./lib/dependencyExtractor");
const excludedExtensions = require("./workerExclusionList");
const { createHash } = require("crypto");
const fs = require("graceful-fs");
const path = require("path");
const PACKAGE_JSON = path.sep + "package.json";
let hasteImpl = null;
let hasteImplModulePath = null;
function getHasteImpl(requestedModulePath) {
if (hasteImpl) {
if (requestedModulePath !== hasteImplModulePath) {
throw new Error("metro-file-map: hasteImplModulePath changed");
}
return hasteImpl;
}
hasteImplModulePath = requestedModulePath;
hasteImpl = require(hasteImplModulePath);
return hasteImpl;
}
function sha1hex(content) {
return createHash("sha1").update(content).digest("hex");
}
class Worker {
constructor(args) {}
processFile(data) {
let content;
let dependencies;
let id;
let sha1;
const { computeDependencies, computeSha1, enableHastePackages, filePath } =
data;
const getContent = () => {
if (content == null) {
content = fs.readFileSync(filePath);
}
return content;
};
if (enableHastePackages && filePath.endsWith(PACKAGE_JSON)) {
try {
const fileData = JSON.parse(getContent().toString());
if (fileData.name) {
id = fileData.name;
}
} catch (err) {
throw new Error(`Cannot parse ${filePath} as JSON: ${err.message}`);
}
} else if (
(data.hasteImplModulePath != null || computeDependencies) &&
!excludedExtensions.has(filePath.substr(filePath.lastIndexOf(".")))
) {
if (data.hasteImplModulePath != null) {
id = getHasteImpl(data.hasteImplModulePath).getHasteName(filePath);
}
if (computeDependencies) {
const dependencyExtractor =
data.dependencyExtractor != null
? require(data.dependencyExtractor)
: null;
dependencies = Array.from(
dependencyExtractor != null
? dependencyExtractor.extract(
getContent().toString(),
filePath,
defaultDependencyExtractor.extract,
)
: defaultDependencyExtractor.extract(getContent().toString()),
);
}
}
if (computeSha1) {
sha1 = sha1hex(getContent());
}
return content && data.maybeReturnContent
? {
content,
dependencies,
id,
sha1,
}
: {
dependencies,
id,
sha1,
};
}
}
let singletonWorker;
function setup(args) {
if (singletonWorker) {
throw new Error("metro-file-map: setup() should only be called once");
}
singletonWorker = new Worker(args);
}
function processFile(data) {
if (!singletonWorker) {
throw new Error(
"metro-file-map: setup() must be called before processFile()",
);
}
return singletonWorker.processFile(data);
}
module.exports = {
setup,
processFile,
Worker,
};

156
node_modules/metro-file-map/src/worker.js.flow generated vendored Normal file
View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
/* eslint-disable import/no-commonjs */
/*::
import type {
DependencyExtractor,
WorkerMessage,
WorkerMetadata,
WorkerSetupArgs,
} from './flow-types';
*/
'use strict';
const defaultDependencyExtractor = require('./lib/dependencyExtractor');
const excludedExtensions = require('./workerExclusionList');
const {createHash} = require('crypto');
const fs = require('graceful-fs');
const path = require('path');
const PACKAGE_JSON = path.sep + 'package.json';
let hasteImpl /*: ?{getHasteName: string => ?string} */ = null;
let hasteImplModulePath /*: ?string */ = null;
function getHasteImpl(
requestedModulePath /*: string */,
) /*: {getHasteName: string => ?string} */ {
if (hasteImpl) {
if (requestedModulePath !== hasteImplModulePath) {
throw new Error('metro-file-map: hasteImplModulePath changed');
}
return hasteImpl;
}
hasteImplModulePath = requestedModulePath;
// $FlowFixMe[unsupported-syntax] - dynamic require
hasteImpl = require(hasteImplModulePath);
return hasteImpl;
}
function sha1hex(content /*: string | Buffer */) /*: string */ {
return createHash('sha1').update(content).digest('hex');
}
class Worker {
constructor(args /*: WorkerSetupArgs */) {}
processFile(data /*: WorkerMessage */) /*: WorkerMetadata */ {
let content /*: ?Buffer */;
let dependencies /*: WorkerMetadata['dependencies'] */;
let id /*: WorkerMetadata['id'] */;
let sha1 /*: WorkerMetadata['sha1'] */;
const {computeDependencies, computeSha1, enableHastePackages, filePath} =
data;
const getContent = () /*: Buffer */ => {
if (content == null) {
content = fs.readFileSync(filePath);
}
return content;
};
if (enableHastePackages && filePath.endsWith(PACKAGE_JSON)) {
// Process a package.json that is returned as a PACKAGE type with its name.
try {
const fileData = JSON.parse(getContent().toString());
if (fileData.name) {
id = fileData.name;
}
} catch (err) {
throw new Error(`Cannot parse ${filePath} as JSON: ${err.message}`);
}
} else if (
(data.hasteImplModulePath != null || computeDependencies) &&
!excludedExtensions.has(filePath.substr(filePath.lastIndexOf('.')))
) {
// Process a random file that is returned as a MODULE.
if (data.hasteImplModulePath != null) {
id = getHasteImpl(data.hasteImplModulePath).getHasteName(filePath);
}
if (computeDependencies) {
const dependencyExtractor /*: ?DependencyExtractor */ =
data.dependencyExtractor != null
? // $FlowFixMe[unsupported-syntax] - dynamic require
require(data.dependencyExtractor)
: null;
dependencies = Array.from(
dependencyExtractor != null
? dependencyExtractor.extract(
getContent().toString(),
filePath,
defaultDependencyExtractor.extract,
)
: defaultDependencyExtractor.extract(getContent().toString()),
);
}
}
// If a SHA-1 is requested on update, compute it.
if (computeSha1) {
sha1 = sha1hex(getContent());
}
return content && data.maybeReturnContent
? {content, dependencies, id, sha1}
: {dependencies, id, sha1};
}
}
let singletonWorker;
function setup(args /*: WorkerSetupArgs */) /*: void */ {
if (singletonWorker) {
throw new Error('metro-file-map: setup() should only be called once');
}
singletonWorker = new Worker(args);
}
function processFile(data /*: WorkerMessage */) /*: WorkerMetadata */ {
if (!singletonWorker) {
throw new Error(
'metro-file-map: setup() must be called before processFile()',
);
}
return singletonWorker.processFile(data);
}
module.exports = {
/**
* Called automatically by jest-worker before the first call to `worker` when
* this module is used as worker thread or child process.
*/
setup,
/**
* Called by jest-worker with each workload
*/
processFile,
/**
* Exposed for use outside a jest-worker context, ie when processing in-band.
*/
Worker,
};

37
node_modules/metro-file-map/src/workerExclusionList.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
"use strict";
const extensions = new Set([
".json",
".bmp",
".gif",
".ico",
".jpeg",
".jpg",
".png",
".svg",
".tiff",
".tif",
".webp",
".avi",
".mp4",
".mpeg",
".mpg",
".ogv",
".webm",
".3gp",
".3g2",
".aac",
".midi",
".mid",
".mp3",
".oga",
".wav",
".3gp",
".3g2",
".eot",
".otf",
".ttf",
".woff",
".woff2",
]);
module.exports = extensions;

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
// Imported directly in CJS workers - must be CommonJS
/* eslint-disable import/no-commonjs */
// This list is compiled after the MDN list of the most common MIME types (see
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/
// Complete_list_of_MIME_types).
//
// Only MIME types starting with "image/", "video/", "audio/" and "font/" are
// reflected in the list. Adding "application/" is too risky since some text
// file formats (like ".js" and ".json") have an "application/" MIME type.
//
// Feel free to add any extensions that cannot be a Haste module.
'use strict';
const extensions /*: $ReadOnlySet<string> */ = new Set([
// JSONs are never haste modules, except for "package.json", which is handled.
'.json',
// Image extensions.
'.bmp',
'.gif',
'.ico',
'.jpeg',
'.jpg',
'.png',
'.svg',
'.tiff',
'.tif',
'.webp',
// Video extensions.
'.avi',
'.mp4',
'.mpeg',
'.mpg',
'.ogv',
'.webm',
'.3gp',
'.3g2',
// Audio extensions.
'.aac',
'.midi',
'.mid',
'.mp3',
'.oga',
'.wav',
'.3gp',
'.3g2',
// Font extensions.
'.eot',
'.otf',
'.ttf',
'.woff',
'.woff2',
]);
module.exports = extensions;