FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

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

@@ -0,0 +1,74 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<25fee66c7d26ad53cdd5bbab454fe50b>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/Watcher.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
Console,
CrawlerOptions,
CrawlResult,
PerfLogger,
WatcherBackendChangeEvent,
} from './flow-types';
import EventEmitter from 'events';
type WatcherOptions = {
abortSignal: AbortSignal;
computeSha1: boolean;
console: Console;
enableSymlinks: boolean;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI: boolean;
healthCheckFilePrefix: string;
ignoreForCrawl: (filePath: string) => boolean;
ignorePatternForWatch: RegExp;
previousState: CrawlerOptions['previousState'];
perfLogger: null | undefined | PerfLogger;
roots: ReadonlyArray<string>;
rootDir: string;
useWatchman: boolean;
watch: boolean;
watchmanDeferStates: ReadonlyArray<string>;
};
export type HealthCheckResult =
| {
type: 'error';
timeout: number;
error: Error;
watcher: null | undefined | string;
}
| {
type: 'success';
timeout: number;
timeElapsed: number;
watcher: null | undefined | string;
}
| {
type: 'timeout';
timeout: number;
watcher: null | undefined | string;
pauseReason: null | undefined | string;
};
export declare class Watcher extends EventEmitter {
constructor(options: WatcherOptions);
crawl(): Promise<CrawlResult>;
recrawl(
subpath: string,
currentFileSystem: CrawlerOptions['previousState']['fileSystem'],
): Promise<CrawlResult>;
watch(onChange: (change: WatcherBackendChangeEvent) => void): void;
close(): void;
checkHealth(timeout: number): Promise<HealthCheckResult>;
}

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

@@ -0,0 +1,299 @@
"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 _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:Watcher");
const MAX_WAIT_TIME = 240000;
let nextInstanceId = 0;
class Watcher extends _events.default {
#activeWatcher;
#backends = [];
#instanceId;
#nextHealthCheckId = 0;
#options;
#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 result = await this.#crawl({
previousState: options.previousState,
roots: options.roots,
useWatchman: options.useWatchman,
});
this.#options.perfLogger?.point("crawl_end");
return result;
}
async recrawl(subpath, currentFileSystem) {
return this.#crawl({
previousState: {
clocks: new Map(),
fileSystem: currentFileSystem,
},
roots: [path.join(this.#options.rootDir, subpath)],
subpath,
useWatchman: false,
});
}
async #crawl(crawlOptions) {
const options = this.#options;
const { useWatchman, subpath } = crawlOptions;
const ignoreForCrawl = (filePath) =>
options.ignoreForCrawl(filePath) ||
path.basename(filePath).startsWith(this.#options.healthCheckFilePrefix);
const crawl = 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: crawlOptions.previousState,
rootDir: options.rootDir,
roots: crawlOptions.roots,
subpath,
};
debug("Crawling roots: %s with %s crawler.", crawlOptions.roots, crawler);
let delta;
try {
delta = await crawl(crawlerOptions);
} catch (firstError) {
if (crawl !== _watchman.default) {
throw firstError;
}
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" +
" " +
firstError.toString(),
);
try {
delta = await (0, _node.default)(crawlerOptions);
} catch (retryError) {
throw new Error(
"Crawler retry failed:\n" +
` Original error: ${firstError.message}\n` +
` Retry error: ${retryError.message}\n`,
);
}
}
debug(
'Crawler "%s" returned %d added/modified, %d removed, %d clock(s).',
crawler,
delta.changedFiles.size,
delta.removedFiles.size,
delta.clocks?.size ?? 0,
);
return delta;
}
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;
}
if (change.event === "recrawl" && useWatchman) {
this.#options.console.error(
"metro-file-map: Received unexpected recrawl event while using " +
"Watchman. Watchman recrawls are not implemented.",
);
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;

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

@@ -0,0 +1,344 @@
/**
* 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 {
Console,
CrawlerOptions,
CrawlResult,
Path,
PerfLogger,
WatcherBackend,
WatcherBackendChangeEvent,
} 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 InternalCrawlOptions = Readonly<{
previousState: CrawlerOptions['previousState'],
roots: ReadonlyArray<string>,
subpath?: string,
useWatchman: boolean,
}>;
type WatcherOptions = {
abortSignal: AbortSignal,
computeSha1: boolean,
console: Console,
enableSymlinks: boolean,
extensions: ReadonlyArray<string>,
forceNodeFilesystemAPI: boolean,
healthCheckFilePrefix: string,
ignoreForCrawl: (filePath: 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 {
#activeWatcher: ?string;
#backends: ReadonlyArray<WatcherBackend> = [];
+#instanceId: number;
#nextHealthCheckId: number = 0;
+#options: WatcherOptions;
+#pendingHealthChecks: Map</* basename */ string, /* resolve */ () => void> =
new Map();
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 result = await this.#crawl({
previousState: options.previousState,
roots: options.roots,
useWatchman: options.useWatchman,
});
this.#options.perfLogger?.point('crawl_end');
return result;
}
async recrawl(
subpath: string,
currentFileSystem: CrawlerOptions['previousState']['fileSystem'],
): Promise<CrawlResult> {
return this.#crawl({
previousState: {
clocks: new Map(),
fileSystem: currentFileSystem,
},
roots: [path.join(this.#options.rootDir, subpath)],
subpath,
useWatchman: false,
});
}
async #crawl(crawlOptions: InternalCrawlOptions): Promise<CrawlResult> {
const options = this.#options;
const {useWatchman, subpath} = crawlOptions;
const ignoreForCrawl = (filePath: string) =>
options.ignoreForCrawl(filePath) ||
path.basename(filePath).startsWith(this.#options.healthCheckFilePrefix);
const crawl = 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: crawlOptions.previousState,
rootDir: options.rootDir,
roots: crawlOptions.roots,
subpath,
};
debug('Crawling roots: %s with %s crawler.', crawlOptions.roots, crawler);
let delta: CrawlResult;
try {
delta = await crawl(crawlerOptions);
} catch (firstError) {
if (crawl !== watchmanCrawl) {
throw firstError;
}
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' +
' ' +
firstError.toString(),
);
try {
delta = await nodeCrawl(crawlerOptions);
} catch (retryError) {
throw new Error(
'Crawler retry failed:\n' +
` Original error: ${firstError.message}\n` +
` Retry error: ${retryError.message}\n`,
);
}
}
debug(
'Crawler "%s" returned %d added/modified, %d removed, %d clock(s).',
crawler,
delta.changedFiles.size,
delta.removedFiles.size,
delta.clocks?.size ?? 0,
);
return delta;
}
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;
}
// Watchman handles recrawls internally - receiving a recrawl event
// when using Watchman would indicate a bug. Log an error and ignore.
if (change.event === 'recrawl' && useWatchman) {
this.#options.console.error(
'metro-file-map: Received unexpected recrawl event while using ' +
'Watchman. Watchman recrawls are not implemented.',
);
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,49 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<9cdec2a3b7a46f0a893dd5dc392a5294>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/cache/DiskCacheManager.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
BuildParameters,
CacheData,
CacheManager,
CacheManagerFactoryOptions,
CacheManagerWriteOptions,
} from '../flow-types';
type AutoSaveOptions = Readonly<{debounceMs: number}>;
type DiskCacheConfig = Readonly<{
autoSave?: Partial<AutoSaveOptions> | boolean;
cacheFilePrefix?: null | undefined | string;
cacheDirectory?: null | undefined | string;
}>;
export declare class DiskCacheManager implements CacheManager {
constructor(
$$PARAM_0$$: CacheManagerFactoryOptions,
$$PARAM_1$$: DiskCacheConfig,
);
static getCacheFilePath(
buildParameters: BuildParameters,
cacheFilePrefix?: null | undefined | string,
cacheDirectory?: null | undefined | string,
): string;
getCacheFilePath(): string;
read(): Promise<null | undefined | CacheData>;
write(
getSnapshot: () => CacheData,
$$PARAM_1$$: 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(): Promise<void> {
// 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?.();
}
}

22
node_modules/metro-file-map/src/constants.d.ts generated vendored Normal file
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.
*
* @noformat
* @generated SignedSource<<733fae11203b79438dfb1ee2bbb6473d>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/constants.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {HType} from './flow-types';
declare const $$EXPORT_DEFAULT_DECLARATION$$: HType;
declare type $$EXPORT_DEFAULT_DECLARATION$$ =
typeof $$EXPORT_DEFAULT_DECLARATION$$;
export default $$EXPORT_DEFAULT_DECLARATION$$;

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

@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _default = (exports.default = {
MTIME: 0,
SIZE: 1,
VISITED: 2,
SHA1: 3,
SYMLINK: 4,
PLUGINDATA: 5,
PATH: 0,
TYPE: 1,
MODULE: 0,
PACKAGE: 1,
GENERIC_PLATFORM: "g",
NATIVE_PLATFORM: "native",
});

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

@@ -0,0 +1,42 @@
/**
* 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
*/
/*
* 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';
export default {
/* file map attributes */
MTIME: 0,
SIZE: 1,
VISITED: 2,
SHA1: 3,
SYMLINK: 4,
PLUGINDATA: 5,
/* module map attributes */
PATH: 0,
TYPE: 1,
/* module types */
MODULE: 0,
PACKAGE: 1,
/* platforms */
GENERIC_PLATFORM: 'g',
NATIVE_PLATFORM: 'native',
} as HType;

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,19 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<8b6ff8a24f9156cd7991006c72edd296>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/crawlers/node/hasNativeFindSupport.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
declare function hasNativeFindSupport(): Promise<boolean>;
export default hasNativeFindSupport;

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;
}
}

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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<27109494e4956802ba89ac6fd22aa277>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/crawlers/node/index.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {CrawlerOptions, CrawlResult} from '../../flow-types';
declare function nodeCrawl(options: CrawlerOptions): Promise<CrawlResult>;
export default nodeCrawl;

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

@@ -0,0 +1,230 @@
"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 _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro: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,
null,
]);
}
}
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,
null,
]);
}
if (--count === 0) {
callback(result);
}
});
});
}
});
}
async function nodeCrawl(options) {
const {
console,
previousState,
extensions,
forceNodeFilesystemAPI,
ignore,
rootDir,
includeSymlinks,
perfLogger,
roots,
abortSignal,
subpath,
} = 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, {
subpath,
});
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 {
Console,
CrawlerOptions,
CrawlResult,
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,
null,
]);
}
}
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,
null,
]);
}
if (--count === 0) {
callback(result);
}
});
});
}
});
}
export default async function nodeCrawl(
options: CrawlerOptions,
): Promise<CrawlResult> {
const {
console,
previousState,
extensions,
forceNodeFilesystemAPI,
ignore,
rootDir,
includeSymlinks,
perfLogger,
roots,
abortSignal,
subpath,
} = 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, {
subpath,
});
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,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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<bcfb58810773510450845bc00a93beae>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/crawlers/watchman/index.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {CrawlerOptions, CrawlResult} from '../../flow-types';
declare function watchmanCrawl(
$$PARAM_0$$: CrawlerOptions,
): Promise<CrawlResult>;
export default watchmanCrawl;

View File

@@ -0,0 +1,300 @@
"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 _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const 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, null];
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,364 @@
/**
* 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,
CrawlResult,
FileData,
FileMetadata,
Path,
} 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<CrawlResult> {
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,
null,
];
// 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,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
*/
type WatchmanQuery = {[key: string]: unknown};
type WatchmanQuerySince = unknown;
export declare function planQuery(
args: Readonly<{
since: WatchmanQuerySince;
directoryFilters: ReadonlyArray<string>;
extensions: ReadonlyArray<string>;
includeSha1: boolean;
includeSymlinks: boolean;
}>,
): {
query: WatchmanQuery;
queryGenerator: string;
};

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] as 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};
}

460
node_modules/metro-file-map/src/flow-types.d.ts generated vendored Normal file
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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<b3646c81d37188726a1fc27777dcbede>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/flow-types.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {PerfLogger, PerfLoggerFactory, RootPerfLogger} from 'metro-config';
export type {PerfLoggerFactory, PerfLogger};
export type BuildParameters = Readonly<{
computeSha1: boolean;
enableSymlinks: boolean;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI: boolean;
ignorePattern: RegExp;
plugins: ReadonlyArray<InputFileMapPlugin>;
retainAllFiles: boolean;
rootDir: string;
roots: ReadonlyArray<string>;
cacheBreaker: string;
}>;
export type BuildResult = {fileSystem: FileSystem};
export type CacheData = Readonly<{
clocks: WatchmanClocks;
fileSystemData: unknown;
plugins: ReadonlyMap<string, void | 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<null | undefined | 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;
}
export type CacheManagerFactory = (
options: CacheManagerFactoryOptions,
) => CacheManager;
export type CacheManagerFactoryOptions = Readonly<{
buildParameters: BuildParameters;
}>;
export type CacheManagerWriteOptions = Readonly<{
changedSinceCacheRead: boolean;
eventSource: CacheManagerEventSource;
onWriteError: (error: Error) => void;
}>;
export type CanonicalPath = string;
export type ChangedFileMetadata = Readonly<{
isSymlink: boolean;
modifiedTime?: null | undefined | number;
}>;
export type ChangeEvent = Readonly<{
logger: null | undefined | RootPerfLogger;
changes: ReadonlyFileSystemChanges<Readonly<ChangedFileMetadata>>;
rootDir: string;
}>;
export type ChangeEventMetadata = {
modifiedTime: null | undefined | number;
size: null | undefined | number;
type: 'f' | 'd' | 'l';
};
export type Console = typeof global.console;
export type CrawlerOptions = {
abortSignal: null | undefined | AbortSignal;
computeSha1: boolean;
console: Console;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI: boolean;
ignore: IgnoreMatcher;
includeSymlinks: boolean;
perfLogger?: null | undefined | PerfLogger;
previousState: Readonly<{
clocks: ReadonlyMap<CanonicalPath, WatchmanClockSpec>;
fileSystem: FileSystem;
}>;
rootDir: string;
roots: ReadonlyArray<string>;
onStatus: (status: WatcherStatus) => void;
subpath?: string;
};
export type CrawlResult =
| {changedFiles: FileData; removedFiles: Set<Path>; clocks: WatchmanClocks}
| {changedFiles: FileData; removedFiles: Set<Path>};
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: unknown;
command: 'watch-project' | 'query';
};
export type DuplicatesSet = Map<string, number>;
export type DuplicatesIndex = Map<string, Map<string, DuplicatesSet>>;
export type FileMapPluginInitOptions<
SerializableState,
PerFileData = void,
> = Readonly<{
files: Readonly<{
fileIterator(
opts: Readonly<{includeNodeModules: boolean; includeSymlinks: boolean}>,
): Iterable<{
baseName: string;
canonicalPath: string;
readonly pluginData: null | undefined | PerFileData;
}>;
lookup(
mixedPath: string,
):
| {exists: false}
| {exists: true; type: 'f'; readonly pluginData: PerFileData}
| {exists: true; type: 'd'};
}>;
pluginState: null | undefined | SerializableState;
}>;
export type FileMapPluginWorker = Readonly<{
worker: Readonly<{modulePath: string; setupArgs: JsonData}>;
filter: ($$PARAM_0$$: {
normalPath: string;
isNodeModules: boolean;
}) => boolean;
}>;
export type V8Serializable =
| string
| number
| boolean
| null
| ReadonlyArray<V8Serializable>
| ReadonlySet<V8Serializable>
| ReadonlyMap<string, V8Serializable>
| Readonly<{[key: string]: V8Serializable}>;
export interface FileMapPlugin<
SerializableState extends void | V8Serializable = void | V8Serializable,
PerFileData extends void | V8Serializable = void | V8Serializable,
> {
readonly name: string;
initialize(
initOptions: FileMapPluginInitOptions<SerializableState, PerFileData>,
): Promise<void>;
assertValid(): void;
onChanged(
changes: ReadonlyFileSystemChanges<null | undefined | PerFileData>,
): void;
getSerializableSnapshot(): void | V8Serializable;
getCacheKey(): string;
getWorker(): null | undefined | FileMapPluginWorker;
}
export type InputFileMapPlugin = FileMapPlugin<
/**
* > 235 | export type InputFileMapPlugin = FileMapPlugin<empty, empty>;
* | ^^^^^ Unsupported feature: Translating "empty type" is currently not supported.
**/
any,
/**
* > 235 | export type InputFileMapPlugin = FileMapPlugin<empty, empty>;
* | ^^^^^ Unsupported feature: Translating "empty type" is currently not supported.
**/
any
>;
export interface MetadataWorker {
processFile(
$$PARAM_0$$: WorkerMessage,
$$PARAM_1$$: Readonly<{getContent: () => Buffer}>,
): V8Serializable;
}
export type HType = {
MTIME: 0;
SIZE: 1;
VISITED: 2;
SHA1: 3;
SYMLINK: 4;
PLUGINDATA: number;
PATH: 0;
TYPE: 1;
MODULE: 0;
PACKAGE: 1;
GENERIC_PLATFORM: 'g';
NATIVE_PLATFORM: 'native';
};
export type HTypeValue = HType[keyof HType];
export type IgnoreMatcher = (item: string) => boolean;
export type FileData = Map<CanonicalPath, FileMetadata>;
export type FileMetadata = [
null | undefined | number,
number,
0 | 1,
null | undefined | string,
0 | 1 | string,
...unknown[],
];
export type FileStats = Readonly<{
fileType: 'f' | 'l';
modifiedTime: null | undefined | number;
size: null | undefined | number;
}>;
export interface FileSystem {
exists(file: Path): boolean;
getAllFiles(): Array<Path>;
/**
* Given a map of files, determine which of them are new or modified
* (changedFiles), and which of them are missing from the input
* (removedFiles), vs the current state of this instance of FileSystem.
*/
getDifference(
files: FileData,
options?: Readonly<{
/**
* Only consider files under this subpath (which should be a directory)
* when computing removedFiles. If not provided, all files in the file
* system are considered.
*/
subpath?: string;
}>,
): {changedFiles: FileData; removedFiles: Set<string>};
getSerializableSnapshot(): CacheData['fileSystemData'];
getSha1(file: Path): null | undefined | string;
getOrComputeSha1(
file: Path,
): Promise<null | undefined | {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: null | undefined | string;
invalidatedBy: null | undefined | Set<string>;
subpathType: 'f' | 'd';
},
): null | undefined | {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): null | undefined | 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?: RegExp | null;
filterCompareAbsolute?: boolean;
filterComparePosix?: boolean;
follow?: boolean;
recursive?: boolean;
rootDir?: Path | null;
}): Iterable<Path>;
}
export type Glob = string;
export type JsonData =
| string
| number
| boolean
| null
| Array<JsonData>
| {[key: string]: JsonData};
export type LookupResult =
| {exists: false; links: ReadonlySet<string>; missing: string}
| {exists: true; links: ReadonlySet<string>; realPath: string; type: 'd'}
| {
exists: true;
links: ReadonlySet<string>;
realPath: string;
type: 'f';
metadata: FileMetadata;
};
export interface MockMap {
getMockModule(name: string): null | undefined | Path;
}
export type HasteConflict = {
id: string;
platform: string | null;
absolutePaths: Array<string>;
type: 'duplicate' | 'shadowing';
};
export interface HasteMap {
getModule(
name: string,
platform?: null | undefined | string,
supportsNativePlatform?: null | undefined | boolean,
type?: null | undefined | HTypeValue,
): null | undefined | Path;
getModuleNameByPath(file: Path): null | undefined | string;
getPackage(
name: string,
platform: null | undefined | string,
_supportsNativePlatform: null | undefined | boolean,
): null | undefined | Path;
computeConflicts(): Array<HasteConflict>;
}
export type HasteMapData = Map<string, HasteMapItem>;
export type HasteMapItem = {
[platform: string]: HasteMapItemMetadata;
};
export type HasteMapItemMetadata = [string, number];
export interface FileSystemListener {
directoryAdded(canonicalPath: CanonicalPath): void;
directoryRemoved(canonicalPath: CanonicalPath): void;
fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void;
fileModified(
canonicalPath: CanonicalPath,
oldData: FileMetadata,
newData: FileMetadata,
): void;
fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void;
}
export interface ReadonlyFileSystemChanges<T = FileMetadata> {
readonly addedDirectories: Iterable<CanonicalPath>;
readonly removedDirectories: Iterable<CanonicalPath>;
readonly addedFiles: Iterable<Readonly<[CanonicalPath, T]>>;
readonly modifiedFiles: Iterable<Readonly<[CanonicalPath, T]>>;
readonly removedFiles: Iterable<Readonly<[CanonicalPath, T]>>;
}
export interface MutableFileSystem extends FileSystem {
remove(filePath: Path, listener?: FileSystemListener): void;
addOrModify(
filePath: Path,
fileMetadata: FileMetadata,
listener?: FileSystemListener,
): void;
bulkAddOrModify(
addedOrModifiedFiles: FileData,
listener?: FileSystemListener,
): void;
}
export type Path = string;
export type ProcessFileFunction = (
normalPath: string,
metadata: FileMetadata,
request: Readonly<{computeSha1: boolean}>,
) => null | undefined | Buffer;
export type RawMockMap = Readonly<{
duplicates: Map<string, Set<string>>;
mocks: Map<string, Path>;
version: number;
}>;
export type ReadOnlyRawMockMap = Readonly<{
duplicates: ReadonlyMap<string, ReadonlySet<string>>;
mocks: ReadonlyMap<string, Path>;
version: number;
}>;
export interface WatcherBackend {
getPauseReason(): null | undefined | string;
onError(listener: (error: Error) => void): () => void;
onFileEvent(listener: (event: WatcherBackendChangeEvent) => void): () => void;
startWatching(): Promise<void>;
stopWatching(): Promise<void>;
}
export type ChangeEventClock = [string, string];
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;
}>
| Readonly<{
event: 'recrawl';
clock?: ChangeEventClock;
relativePath: string;
root: string;
}>;
export type WatcherBackendOptions = Readonly<{
ignored: null | undefined | 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<{
computeSha1: boolean;
filePath: string;
maybeReturnContent: boolean;
pluginsToRun: ReadonlyArray<number>;
}>;
export type WorkerMetadata = Readonly<{
sha1?: null | undefined | string;
content?: null | undefined | Buffer;
pluginData?: ReadonlyArray<V8Serializable>;
}>;
export type WorkerSetupArgs = Readonly<{
plugins?: ReadonlyArray<FileMapPluginWorker['worker']>;
}>;

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

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

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

@@ -0,0 +1,574 @@
/**
* 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<{
computeSha1: boolean,
enableSymlinks: boolean,
extensions: ReadonlyArray<string>,
forceNodeFilesystemAPI: boolean,
ignorePattern: RegExp,
plugins: ReadonlyArray<InputFileMapPlugin>,
retainAllFiles: boolean,
rootDir: string,
roots: ReadonlyArray<string>,
cacheBreaker: string,
}>;
export type BuildResult = {
fileSystem: FileSystem,
};
export type CacheData = Readonly<{
clocks: WatchmanClocks,
fileSystemData: unknown,
plugins: ReadonlyMap<string, void | 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: 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 ChangedFileMetadata = Readonly<{
isSymlink: boolean,
modifiedTime?: ?number,
}>;
export type ChangeEvent = Readonly<{
logger: ?RootPerfLogger,
changes: ReadonlyFileSystemChanges<Readonly<ChangedFileMetadata>>,
rootDir: string,
}>;
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,
// Only consider files under this normalized subdirectory when computing
// removedFiles. If not provided, all files in the file system are considered.
subpath?: string,
};
export type CrawlResult =
| {
changedFiles: FileData,
removedFiles: Set<Path>,
clocks: WatchmanClocks,
}
| {
changedFiles: FileData,
removedFiles: Set<Path>,
};
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: unknown,
command: 'watch-project' | 'query',
};
export type DuplicatesSet = Map<string, /* type */ number>;
export type DuplicatesIndex = Map<string, Map<string, DuplicatesSet>>;
export type FileMapPluginInitOptions<
+SerializableState,
+PerFileData = void,
> = Readonly<{
files: Readonly<{
fileIterator(
opts: Readonly<{
includeNodeModules: boolean,
includeSymlinks: boolean,
}>,
): Iterable<{
baseName: string,
canonicalPath: string,
+pluginData: ?PerFileData,
}>,
lookup(
mixedPath: string,
):
| {exists: false}
| {exists: true, type: 'f', +pluginData: PerFileData}
| {exists: true, type: 'd'},
}>,
pluginState: ?SerializableState,
}>;
export type FileMapPluginWorker = Readonly<{
worker: Readonly<{
modulePath: string,
setupArgs: JsonData,
}>,
filter: ({normalPath: string, isNodeModules: boolean}) => boolean,
}>;
export type V8Serializable =
| string
| number
| boolean
| null
| ReadonlyArray<V8Serializable>
| ReadonlySet<V8Serializable>
| ReadonlyMap<string, V8Serializable>
| Readonly<{[key: string]: V8Serializable}>;
export interface FileMapPlugin<
-SerializableState extends void | V8Serializable = void | V8Serializable,
-PerFileData extends void | V8Serializable = void | V8Serializable,
> {
+name: string;
initialize(
initOptions: FileMapPluginInitOptions<SerializableState, PerFileData>,
): Promise<void>;
assertValid(): void;
onChanged(changes: ReadonlyFileSystemChanges<?PerFileData>): void;
getSerializableSnapshot(): void | V8Serializable;
getCacheKey(): string;
getWorker(): ?FileMapPluginWorker;
}
export type InputFileMapPlugin = FileMapPlugin<empty, empty>;
export interface MetadataWorker {
processFile(
WorkerMessage,
Readonly<{getContent: () => Buffer}>,
): V8Serializable;
}
export type HType = {
MTIME: 0,
SIZE: 1,
VISITED: 2,
SHA1: 3,
SYMLINK: 4,
PLUGINDATA: number,
PATH: 0,
TYPE: 1,
MODULE: 0,
PACKAGE: 1,
GENERIC_PLATFORM: 'g',
NATIVE_PLATFORM: 'native',
};
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,
/* sha1 */ ?string,
/* symlink */ 0 | 1 | string, // string specifies target, if known
/* plugindata */
...
];
export type FileStats = Readonly<{
fileType: 'f' | 'l',
modifiedTime: ?number,
size: ?number,
}>;
export interface FileSystem {
exists(file: Path): boolean;
getAllFiles(): Array<Path>;
/**
* Given a map of files, determine which of them are new or modified
* (changedFiles), and which of them are missing from the input
* (removedFiles), vs the current state of this instance of FileSystem.
*/
getDifference(
files: FileData,
options?: Readonly<{
/**
* Only consider files under this subpath (which should be a directory)
* when computing removedFiles. If not provided, all files in the file
* system are considered.
*/
subpath?: string,
}>,
): {
changedFiles: FileData,
removedFiles: Set<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 JsonData =
| string
| number
| boolean
| null
| Array<JsonData>
| {[key: string]: JsonData};
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 directory.
realPath: string,
// Currently lookup always follows symlinks, so can only return
// directories or regular files, but this may be extended.
type: 'd',
}
| {
exists: true,
// The real, normal, absolute paths of any symlinks traversed.
links: ReadonlySet<string>,
// The real, normal, absolute path of the file.
realPath: string,
// Currently lookup always follows symlinks, so can only return
// directories or regular files, but this may be extended.
type: 'f',
// The file's metadata tuple. Must only be mutated via FileProcessor.
metadata: FileMetadata,
};
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;
getModuleNameByPath(file: Path): ?string;
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 FileSystemListener {
directoryAdded(canonicalPath: CanonicalPath): void;
directoryRemoved(canonicalPath: CanonicalPath): void;
fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void;
fileModified(
canonicalPath: CanonicalPath,
oldData: FileMetadata,
newData: FileMetadata,
): void;
fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void;
}
export interface ReadonlyFileSystemChanges<+T = FileMetadata> {
+addedDirectories: Iterable<CanonicalPath>;
+removedDirectories: Iterable<CanonicalPath>;
+addedFiles: Iterable<Readonly<[CanonicalPath, T]>>;
+modifiedFiles: Iterable<Readonly<[CanonicalPath, T]>>;
+removedFiles: Iterable<Readonly<[CanonicalPath, T]>>;
}
export interface MutableFileSystem extends FileSystem {
remove(filePath: Path, listener?: FileSystemListener): void;
addOrModify(
filePath: Path,
fileMetadata: FileMetadata,
listener?: FileSystemListener,
): void;
bulkAddOrModify(
addedOrModifiedFiles: FileData,
listener?: FileSystemListener,
): void;
}
export type Path = string;
export type ProcessFileFunction = (
normalPath: 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(listener: (error: Error) => void): () => void;
onFileEvent(listener: (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,
}>
| Readonly<{
event: 'recrawl',
clock?: ChangeEventClock,
relativePath: string,
root: string,
}>;
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<{
computeSha1: boolean,
filePath: string,
maybeReturnContent: boolean,
pluginsToRun: ReadonlyArray<number>,
}>;
export type WorkerMetadata = Readonly<{
sha1?: ?string,
content?: ?Buffer,
pluginData?: ReadonlyArray<V8Serializable>,
}>;
export type WorkerSetupArgs = Readonly<{
plugins?: ReadonlyArray<FileMapPluginWorker['worker']>,
}>;

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

@@ -0,0 +1,182 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<806d228988308075b7a911c3dfb513d3>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/index.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
BuildParameters,
BuildResult,
CacheData,
CacheManagerFactory,
ChangeEventMetadata,
Console,
FileData,
FileSystem,
HasteMapData,
HasteMapItem,
HType,
InputFileMapPlugin,
PerfLoggerFactory,
} from './flow-types';
import EventEmitter from 'events';
export type {
BuildParameters,
BuildResult,
CacheData,
ChangeEventMetadata,
FileData,
FileMap,
FileSystem,
HasteMapData,
HasteMapItem,
InputFileMapPlugin,
};
export type InputOptions = Readonly<{
computeSha1?: null | undefined | boolean;
enableSymlinks?: null | undefined | boolean;
extensions: ReadonlyArray<string>;
forceNodeFilesystemAPI?: null | undefined | boolean;
ignorePattern?: null | undefined | RegExp;
plugins?: ReadonlyArray<InputFileMapPlugin>;
retainAllFiles: boolean;
rootDir: string;
roots: ReadonlyArray<string>;
cacheManagerFactory?: null | undefined | CacheManagerFactory;
console?: Console;
healthCheck: HealthCheckOptions;
maxFilesPerWorker?: null | undefined | number;
maxWorkers: number;
perfLoggerFactory?: null | undefined | PerfLoggerFactory;
resetCache?: null | undefined | boolean;
useWatchman?: null | undefined | boolean;
watch?: null | undefined | boolean;
watchmanDeferStates?: ReadonlyArray<string>;
}>;
type HealthCheckOptions = Readonly<{
enabled: boolean;
interval: number;
timeout: number;
filePrefix: string;
}>;
export {DiskCacheManager} from './cache/DiskCacheManager';
export {default as DependencyPlugin} from './plugins/DependencyPlugin';
export type {DependencyPluginOptions} from './plugins/DependencyPlugin';
export {DuplicateHasteCandidatesError} from './plugins/haste/DuplicateHasteCandidatesError';
export {HasteConflictsError} from './plugins/haste/HasteConflictsError';
export {default as HastePlugin} from './plugins/HastePlugin';
export type {HasteMap} from './flow-types';
export type {HealthCheckResult} from './Watcher';
export type {
CacheManager,
CacheManagerFactory,
CacheManagerFactoryOptions,
CacheManagerWriteOptions,
ChangeEvent,
DependencyExtractor,
WatcherStatus,
} from './flow-types';
/**
* FileMap includes a JavaScript implementation of Facebook's haste module system.
*
* This implementation is inspired by https://github.com/facebook/node-haste
* and was built with for high-performance in large code repositories with
* hundreds of thousands of files. This implementation is scalable and provides
* predictable performance.
*
* Because the file map creation and synchronization is critical to startup
* performance and most tasks are blocked by I/O this class makes heavy use of
* synchronous operations. It uses worker processes for parallelizing file
* access and metadata extraction.
*
* The data structures created by `metro-file-map` can be used directly from the
* cache without further processing. The metadata objects in the `files` and
* `map` objects contain cross-references: a metadata object from one can look
* up the corresponding metadata object in the other map. Note that in most
* projects, the number of files will be greater than the number of haste
* modules one module can refer to many files based on platform extensions.
*
* type CacheData = {
* clocks: WatchmanClocks,
* files: {[filepath: string]: FileMetadata},
* map: {[id: string]: HasteMapItem},
* mocks: {[id: string]: string},
* }
*
* // Watchman clocks are used for query synchronization and file system deltas.
* type WatchmanClocks = {[filepath: string]: string};
*
* type FileMetadata = {
* id: ?string, // used to look up module metadata objects in `map`.
* mtime: number, // check for outdated files.
* size: number, // size of the file in bytes.
* visited: boolean, // whether the file has been parsed or not.
* dependencies: Array<string>, // all relative dependencies of this file.
* sha1: ?string, // SHA-1 of the file, if requested via options.
* symlink: ?(1 | 0 | string), // Truthy if symlink, string is target
* };
*
* // Modules can be targeted to a specific platform based on the file name.
* // Example: platform.ios.js and Platform.android.js will both map to the same
* // `Platform` module. The platform should be specified during resolution.
* type HasteMapItem = {[platform: string]: ModuleMetadata};
*
* //
* type ModuleMetadata = {
* path: string, // the path to look up the file object in `files`.
* type: string, // the module type (either `package` or `module`).
* };
*
* Note that the data structures described above are conceptual only. The actual
* implementation uses arrays and constant keys for metadata storage. Instead of
* `{id: 'flatMap', mtime: 3421, size: 42, visited: true, dependencies: []}` the real
* representation is similar to `['flatMap', 3421, 42, 1, []]` to save storage space
* and reduce parse and write time of a big JSON blob.
*
* The FileMap is created as follows:
* 1. read data from the cache or create an empty structure.
*
* 2. crawl the file system.
* * empty cache: crawl the entire file system.
* * cache available:
* * if watchman is available: get file system delta changes.
* * if watchman is unavailable: crawl the entire file system.
* * build metadata objects for every file. This builds the `files` part of
* the `FileMap`.
*
* 3. visit and extract metadata from changed files, including sha1,
* depedendencies, and any plugins.
* * this is done in parallel over worker processes to improve performance.
* * the worst case is to visit all files.
* * the best case is no file system access and retrieving all data from
* the cache.
* * the average case is a small number of changed files.
*
* 4. serialize the new `FileMap` in a cache file.
*
*/
declare class FileMap extends EventEmitter {
static create(options: InputOptions): FileMap;
constructor(options: InputOptions);
build(): Promise<BuildResult>;
/**
* 1. read data from the cache or create an empty structure.
*/
read(): Promise<null | undefined | CacheData>;
end(): Promise<void>;
static H: HType;
}
export default FileMap;

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

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

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

File diff suppressed because it is too large Load Diff

60
node_modules/metro-file-map/src/lib/FileProcessor.d.ts generated vendored Normal file
View File

@@ -0,0 +1,60 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<2ea213f753eef5de14cb8a27f68b9fa2>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/FileProcessor.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
FileMapPluginWorker,
FileMetadata,
PerfLogger,
} from '../flow-types';
type ProcessFileRequest = Readonly<{
/**
* Populate metadata[H.SHA1] with the SHA1 of the file's contents.
*/
computeSha1: 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 MaybeCodedError extends Error {
code?: string;
}
export declare class FileProcessor {
constructor(
opts: Readonly<{
maxFilesPerWorker?: null | undefined | number;
maxWorkers: number;
pluginWorkers: null | undefined | ReadonlyArray<FileMapPluginWorker>;
perfLogger: null | undefined | PerfLogger;
rootDir: string;
}>,
);
processBatch(
files: ReadonlyArray<[string, FileMetadata]>,
req: ProcessFileRequest,
): Promise<{
errors: Array<{normalFilePath: string; error: MaybeCodedError}>;
}>;
processRegularFile(
normalPath: string,
fileMetadata: FileMetadata,
req: ProcessFileRequest,
): null | undefined | {content: null | undefined | Buffer};
end(): Promise<void>;
}

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

@@ -0,0 +1,194 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.FileProcessor = void 0;
var _constants = _interopRequireDefault(require("../constants"));
var _worker = require("../worker");
var _RootPathUtils = require("./RootPathUtils");
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_SEP = "node_modules" + _path.sep;
const MAX_FILES_PER_WORKER = 100;
class FileProcessor {
#maxFilesPerWorker;
#maxWorkers;
#perfLogger;
#pluginWorkers;
#inBandWorker;
#rootPathUtils;
constructor(opts) {
this.#maxFilesPerWorker = opts.maxFilesPerWorker ?? MAX_FILES_PER_WORKER;
this.#maxWorkers = opts.maxWorkers;
this.#pluginWorkers = opts.pluginWorkers ?? [];
this.#inBandWorker = new _worker.Worker({
plugins: this.#pluginWorkers.map((plugin) => plugin.worker),
});
this.#perfLogger = opts.perfLogger;
this.#rootPathUtils = new _RootPathUtils.RootPathUtils(opts.rootDir);
}
async processBatch(files, req) {
const errors = [];
const workerJobs = files
.map(([normalFilePath, fileMetadata]) => {
const maybeWorkerInput = this.#getWorkerInput(
normalFilePath,
fileMetadata,
req,
);
if (!maybeWorkerInput) {
return null;
}
return [maybeWorkerInput, fileMetadata];
})
.filter(Boolean);
const numWorkers = Math.min(
this.#maxWorkers,
Math.ceil(workerJobs.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(
workerJobs.map(([workerInput, fileMetadata]) => {
return batchWorker
.processFile(workerInput)
.then((reply) =>
processWorkerReply(reply, workerInput.pluginsToRun, fileMetadata),
)
.catch((error) =>
errors.push({
normalFilePath: this.#rootPathUtils.absoluteToNormal(
workerInput.filePath,
),
error: normalizeWorkerError(error),
}),
);
}),
);
await batchWorker.end();
return {
errors,
};
}
processRegularFile(normalPath, fileMetadata, req) {
const workerInput = this.#getWorkerInput(normalPath, fileMetadata, req);
return workerInput
? {
content: processWorkerReply(
this.#inBandWorker.processFile(workerInput),
workerInput.pluginsToRun,
fileMetadata,
),
}
: null;
}
#getWorkerInput(normalPath, fileMetadata, req) {
if (fileMetadata[_constants.default.SYMLINK] !== 0) {
return null;
}
const computeSha1 =
req.computeSha1 && fileMetadata[_constants.default.SHA1] == null;
const { maybeReturnContent } = req;
const nodeModulesIdx = normalPath.indexOf(NODE_MODULES_SEP);
const isNodeModules =
nodeModulesIdx === 0 ||
(nodeModulesIdx > 0 && normalPath[nodeModulesIdx - 1] === _path.sep);
const pluginsToRun =
this.#pluginWorkers?.reduce((prev, plugin, idx) => {
if (
plugin.filter({
isNodeModules,
normalPath,
})
) {
prev.push(idx);
}
return prev;
}, []) ?? [];
if (!computeSha1 && pluginsToRun.length === 0) {
return null;
}
if (isNodeModules) {
if (computeSha1) {
return {
computeSha1: true,
filePath: this.#rootPathUtils.normalToAbsolute(normalPath),
maybeReturnContent,
pluginsToRun,
};
}
return null;
}
return {
computeSha1,
filePath: this.#rootPathUtils.normalToAbsolute(normalPath),
maybeReturnContent,
pluginsToRun,
};
}
#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 threads", numWorkers);
this.#perfLogger?.point("initWorkers_start");
const jestWorker = new _jestWorker.Worker(workerPath, {
exposedMethods: ["processFile"],
maxRetries: 3,
numWorkers,
enableWorkerThreads: true,
forkOptions: {
execArgv: [],
},
setupArgs: [
{
plugins: this.#pluginWorkers.map((plugin) => plugin.worker),
},
],
});
this.#perfLogger?.point("initWorkers_end");
this.#perfLogger = null;
return jestWorker;
}
async end() {}
}
exports.FileProcessor = FileProcessor;
function processWorkerReply(metadata, pluginsRun, fileMetadata) {
fileMetadata[_constants.default.VISITED] = 1;
const pluginData = metadata.pluginData;
if (pluginData) {
for (const [i, pluginIdx] of pluginsRun.entries()) {
fileMetadata[_constants.default.PLUGINDATA + pluginIdx] = pluginData[i];
}
}
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,289 @@
/**
* 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 {
FileMapPluginWorker,
FileMetadata,
PerfLogger,
WorkerMessage,
WorkerMetadata,
WorkerSetupArgs,
} from '../flow-types';
import H from '../constants';
import {Worker} from '../worker';
import {RootPathUtils} from './RootPathUtils';
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,
/**
* 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 {
#maxFilesPerWorker: number;
#maxWorkers: number;
#perfLogger: ?PerfLogger;
#pluginWorkers: ReadonlyArray<FileMapPluginWorker>;
#inBandWorker: Worker;
#rootPathUtils: RootPathUtils;
constructor(
opts: Readonly<{
maxFilesPerWorker?: ?number,
maxWorkers: number,
pluginWorkers: ?ReadonlyArray<FileMapPluginWorker>,
perfLogger: ?PerfLogger,
rootDir: string,
}>,
) {
this.#maxFilesPerWorker = opts.maxFilesPerWorker ?? MAX_FILES_PER_WORKER;
this.#maxWorkers = opts.maxWorkers;
this.#pluginWorkers = opts.pluginWorkers ?? [];
this.#inBandWorker = new Worker({
plugins: this.#pluginWorkers.map(plugin => plugin.worker),
});
this.#perfLogger = opts.perfLogger;
this.#rootPathUtils = new RootPathUtils(opts.rootDir);
}
async processBatch(
files: ReadonlyArray<[string /*relativePath*/, FileMetadata]>,
req: ProcessFileRequest,
): Promise<{
errors: Array<{
normalFilePath: string,
error: MaybeCodedError,
}>,
}> {
const errors = [];
const workerJobs = files
.map(([normalFilePath, fileMetadata]) => {
const maybeWorkerInput = this.#getWorkerInput(
normalFilePath,
fileMetadata,
req,
);
if (!maybeWorkerInput) {
return null;
}
return [maybeWorkerInput, fileMetadata];
})
.filter(Boolean);
const numWorkers = Math.min(
this.#maxWorkers,
Math.ceil(workerJobs.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(
workerJobs.map(([workerInput, fileMetadata]) => {
return batchWorker
.processFile(workerInput)
.then(reply =>
processWorkerReply(reply, workerInput.pluginsToRun, fileMetadata),
)
.catch(error =>
errors.push({
normalFilePath: this.#rootPathUtils.absoluteToNormal(
workerInput.filePath,
),
error: normalizeWorkerError(error),
}),
);
}),
);
await batchWorker.end();
return {errors};
}
processRegularFile(
normalPath: string,
fileMetadata: FileMetadata,
req: ProcessFileRequest,
): ?{content: ?Buffer} {
const workerInput = this.#getWorkerInput(normalPath, fileMetadata, req);
return workerInput
? {
content: processWorkerReply(
this.#inBandWorker.processFile(workerInput),
workerInput.pluginsToRun,
fileMetadata,
),
}
: null;
}
#getWorkerInput(
normalPath: string,
fileMetadata: FileMetadata,
req: ProcessFileRequest,
): ?WorkerMessage {
if (fileMetadata[H.SYMLINK] !== 0) {
// Only process regular files
return null;
}
const computeSha1 = req.computeSha1 && fileMetadata[H.SHA1] == null;
const {maybeReturnContent} = req;
const nodeModulesIdx = normalPath.indexOf(NODE_MODULES_SEP);
// Path may begin 'node_modules/' or contain '/node_modules/'.
const isNodeModules =
nodeModulesIdx === 0 ||
(nodeModulesIdx > 0 && normalPath[nodeModulesIdx - 1] === sep);
// Indices of plugins with a passing filter
const pluginsToRun =
this.#pluginWorkers?.reduce((prev, plugin, idx) => {
if (plugin.filter({isNodeModules, normalPath})) {
prev.push(idx);
}
return prev;
}, [] as Array<number>) ?? [];
if (!computeSha1 && pluginsToRun.length === 0) {
// Nothing to process
return null;
}
// Use a cheaper worker configuration for node_modules files, because
// 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 (isNodeModules) {
if (computeSha1) {
return {
computeSha1: true,
filePath: this.#rootPathUtils.normalToAbsolute(normalPath),
maybeReturnContent,
pluginsToRun,
};
}
return null;
}
return {
computeSha1,
filePath: this.#rootPathUtils.normalToAbsolute(normalPath),
maybeReturnContent,
pluginsToRun,
};
}
/**
* 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 threads', numWorkers);
this.#perfLogger?.point('initWorkers_start');
const jestWorker = new JestWorker<{
processFile: WorkerMessage => Promise<WorkerMetadata>,
}>(workerPath, {
exposedMethods: ['processFile'],
maxRetries: 3,
numWorkers,
enableWorkerThreads: true,
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: [
{
plugins: this.#pluginWorkers.map(plugin => plugin.worker),
} as WorkerSetupArgs,
],
});
this.#perfLogger?.point('initWorkers_end');
// Only log worker init once
this.#perfLogger = null;
return jestWorker;
}
async end(): Promise<void> {}
}
function processWorkerReply(
metadata: WorkerMetadata,
pluginsRun: ReadonlyArray<number>,
fileMetadata: FileMetadata,
) {
fileMetadata[H.VISITED] = 1;
const pluginData = metadata.pluginData;
if (pluginData) {
for (const [i, pluginIdx] of pluginsRun.entries()) {
// $FlowFixMe[invalid-tuple-index]
fileMetadata[H.PLUGINDATA + pluginIdx] = pluginData[i];
}
}
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;
}

View File

@@ -0,0 +1,40 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<5feda1b197530a9a5fdbc57200633ac5>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/FileSystemChangeAggregator.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
CanonicalPath,
FileMetadata,
FileSystemListener,
ReadonlyFileSystemChanges,
} from '../flow-types';
export declare class FileSystemChangeAggregator implements FileSystemListener {
directoryAdded(canonicalPath: CanonicalPath): void;
directoryRemoved(canonicalPath: CanonicalPath): void;
fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void;
fileModified(
canonicalPath: CanonicalPath,
oldData: FileMetadata,
newData: FileMetadata,
): void;
fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void;
getSize(): number;
getView(): ReadonlyFileSystemChanges<FileMetadata>;
getMappedView<T>(
metadataMapFn: (metadata: FileMetadata) => T,
): ReadonlyFileSystemChanges<T>;
}

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.FileSystemChangeAggregator = void 0;
class FileSystemChangeAggregator {
#addedDirectories = new Set();
#removedDirectories = new Set();
#addedFiles = new Map();
#modifiedFiles = new Map();
#removedFiles = new Map();
#initialMetadata = new Map();
directoryAdded(canonicalPath) {
if (!this.#removedDirectories.delete(canonicalPath)) {
this.#addedDirectories.add(canonicalPath);
}
}
directoryRemoved(canonicalPath) {
if (!this.#addedDirectories.delete(canonicalPath)) {
this.#removedDirectories.add(canonicalPath);
}
}
fileAdded(canonicalPath, data) {
if (this.#removedFiles.delete(canonicalPath)) {
this.#modifiedFiles.set(canonicalPath, data);
} else {
this.#addedFiles.set(canonicalPath, data);
}
}
fileModified(canonicalPath, oldData, newData) {
if (this.#addedFiles.has(canonicalPath)) {
this.#addedFiles.set(canonicalPath, newData);
} else {
if (!this.#initialMetadata.has(canonicalPath)) {
this.#initialMetadata.set(canonicalPath, oldData);
}
this.#modifiedFiles.set(canonicalPath, newData);
}
}
fileRemoved(canonicalPath, data) {
if (!this.#addedFiles.delete(canonicalPath)) {
let initialData = this.#initialMetadata.get(canonicalPath);
if (!initialData) {
initialData = data;
this.#initialMetadata.set(canonicalPath, initialData);
}
this.#modifiedFiles.delete(canonicalPath);
this.#removedFiles.set(canonicalPath, initialData);
}
}
getSize() {
return (
this.#addedDirectories.size +
this.#removedDirectories.size +
this.#addedFiles.size +
this.#modifiedFiles.size +
this.#removedFiles.size
);
}
getView() {
return {
addedDirectories: this.#addedDirectories,
removedDirectories: this.#removedDirectories,
addedFiles: this.#addedFiles,
modifiedFiles: this.#modifiedFiles,
removedFiles: this.#removedFiles,
};
}
getMappedView(metadataMapFn) {
return {
addedDirectories: this.#addedDirectories,
removedDirectories: this.#removedDirectories,
addedFiles: mapIterable(this.#addedFiles, metadataMapFn),
modifiedFiles: mapIterable(this.#modifiedFiles, metadataMapFn),
removedFiles: mapIterable(this.#removedFiles, metadataMapFn),
};
}
}
exports.FileSystemChangeAggregator = FileSystemChangeAggregator;
function mapIterable(map, metadataMapFn) {
return {
*[Symbol.iterator]() {
for (const [path, metadata] of map) {
yield [path, metadataMapFn(metadata)];
}
},
};
}

View File

@@ -0,0 +1,143 @@
/**
* 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,
FileMetadata,
FileSystemListener,
ReadonlyFileSystemChanges,
} from '../flow-types';
export class FileSystemChangeAggregator implements FileSystemListener {
// Mutually exclusive with removedDirectories
+#addedDirectories: Set<CanonicalPath> = new Set();
// Mutually exclusive with addedDirectories
+#removedDirectories: Set<CanonicalPath> = new Set();
// Mutually exclusive with modified and removed files
+#addedFiles: Map<CanonicalPath, FileMetadata> = new Map();
// Mutually exclusive with added and removed files
+#modifiedFiles: Map<CanonicalPath, FileMetadata> = new Map();
// Mutually exclusive with added and modified files
+#removedFiles: Map<CanonicalPath, FileMetadata> = new Map();
// Removed files must be paired with the file's metadata the last time it was
// observable by consumers - ie, immediately *before* this batch. To report
// this accurately with minimal overhead, we'll note the current metadata of
// a file the first time it is modified or removed within a batch. If it is
// re-added, modified and removed again, we still have the initial metadata.
// This is particularly important if, say, a regular file is replaced by a
// symlink, or vice-versa.
+#initialMetadata: Map<CanonicalPath, FileMetadata> = new Map();
directoryAdded(canonicalPath: CanonicalPath): void {
// Only add to newDirectories if this directory wasn't previously removed
// (i.e., it's truly new). If it was removed and re-added, the net effect
// is no directory change.
if (!this.#removedDirectories.delete(canonicalPath)) {
this.#addedDirectories.add(canonicalPath);
}
}
directoryRemoved(canonicalPath: CanonicalPath): void {
if (!this.#addedDirectories.delete(canonicalPath)) {
this.#removedDirectories.add(canonicalPath);
}
}
fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void {
if (this.#removedFiles.delete(canonicalPath)) {
// File was removed then re-added in the same batch - treat as modification
this.#modifiedFiles.set(canonicalPath, data);
} else {
// New file
this.#addedFiles.set(canonicalPath, data);
}
}
fileModified(
canonicalPath: CanonicalPath,
oldData: FileMetadata,
newData: FileMetadata,
): void {
if (this.#addedFiles.has(canonicalPath)) {
// File did not exist before this batch. Further modification only
// updates metadata
this.#addedFiles.set(canonicalPath, newData);
} else {
if (!this.#initialMetadata.has(canonicalPath)) {
this.#initialMetadata.set(canonicalPath, oldData);
}
this.#modifiedFiles.set(canonicalPath, newData);
}
}
fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void {
// Check if this file was added in the same batch
if (!this.#addedFiles.delete(canonicalPath)) {
let initialData = this.#initialMetadata.get(canonicalPath);
if (!initialData) {
initialData = data;
this.#initialMetadata.set(canonicalPath, initialData);
}
// File was not added in this batch, so add to removed with last metadata
this.#modifiedFiles.delete(canonicalPath);
this.#removedFiles.set(canonicalPath, initialData);
}
// else: File was added then removed in the same batch - no net change
}
getSize(): number {
return (
this.#addedDirectories.size +
this.#removedDirectories.size +
this.#addedFiles.size +
this.#modifiedFiles.size +
this.#removedFiles.size
);
}
getView(): ReadonlyFileSystemChanges<FileMetadata> {
return {
addedDirectories: this.#addedDirectories,
removedDirectories: this.#removedDirectories,
addedFiles: this.#addedFiles,
modifiedFiles: this.#modifiedFiles,
removedFiles: this.#removedFiles,
};
}
getMappedView<T>(
metadataMapFn: (metadata: FileMetadata) => T,
): ReadonlyFileSystemChanges<T> {
return {
addedDirectories: this.#addedDirectories,
removedDirectories: this.#removedDirectories,
addedFiles: mapIterable(this.#addedFiles, metadataMapFn),
modifiedFiles: mapIterable(this.#modifiedFiles, metadataMapFn),
removedFiles: mapIterable(this.#removedFiles, metadataMapFn),
};
}
}
function mapIterable<T>(
map: Map<CanonicalPath, FileMetadata>,
metadataMapFn: (metadata: FileMetadata) => T,
): Iterable<Readonly<[CanonicalPath, T]>> {
return {
*[Symbol.iterator](): Iterator<Readonly<[CanonicalPath, T]>> {
for (const [path, metadata] of map) {
yield [path, metadataMapFn(metadata)];
}
},
};
}

30
node_modules/metro-file-map/src/lib/RootPathUtils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<5ecdb559fce5f5c6ed50df6e4eaebf20>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/RootPathUtils.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
export declare class RootPathUtils {
constructor(rootDir: string);
getBasenameOfNthAncestor(n: number): string;
getParts(): ReadonlyArray<string>;
absoluteToNormal(absolutePath: string): string;
normalToAbsolute(normalPath: string): string;
relativeToNormal(relativePath: string): string;
getAncestorOfRootIdx(normalPath: string): null | undefined | number;
joinNormalToRelative(
normalPath: string,
relativePath: string,
): {normalPath: string; collapsedSegments: number};
relative(from: string, to: string): string;
}

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

@@ -0,0 +1,242 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.RootPathUtils = void 0;
var _invariant = _interopRequireDefault(require("invariant"));
var path = _interopRequireWildcard(require("path"));
function _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const 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.
*
* @flow strict
* @format
*/
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;
}
}
}
}

174
node_modules/metro-file-map/src/lib/TreeFS.d.ts generated vendored Normal file
View File

@@ -0,0 +1,174 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<65a3c4140d459a56b8c949e52b32ea1b>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/TreeFS.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
CacheData,
FileData,
FileMetadata,
FileStats,
FileSystemListener,
LookupResult,
MutableFileSystem,
Path,
ProcessFileFunction,
} from '../flow-types';
type DirectoryNode = Map<string, MixedNode>;
type FileNode = FileMetadata;
type MixedNode = FileNode | DirectoryNode;
type DeserializedSnapshotInput = {
rootDir: string;
fileSystemData: DirectoryNode;
processFile: ProcessFileFunction;
};
type TreeFSOptions = {
rootDir: Path;
files?: FileData;
processFile: ProcessFileFunction;
};
type MatchFilesOptions = Readonly<{
filter?: null | undefined | RegExp;
filterCompareAbsolute?: boolean;
filterComparePosix?: boolean;
follow?: boolean;
recursive?: boolean;
rootDir?: null | undefined | Path;
}>;
type MetadataIteratorOptions = Readonly<{
includeSymlinks: boolean;
includeNodeModules: boolean;
}>;
/**
* OVERVIEW:
*
* TreeFS is Metro's in-memory representation of the file system. It is
* structured as a tree of non-empty maps and leaves (tuples), with the root
* node representing the given `rootDir`, typically Metro's _project root_
* (not a filesystem root). Map keys are path segments, and branches outside
* the project root are accessed via `'..'`.
*
* EXAMPLE:
*
* For a root dir '/data/project', the file '/data/other/app/index.js' would
* have metadata at #rootNode.get('..').get('other').get('app').get('index.js')
*
* SERIALISATION:
*
* #rootNode is designed to be directly serialisable and directly portable (for
* a given project) between different root directories and operating systems.
*
* SYMLINKS:
*
* Symlinks are represented as nodes whose metadata contains their literal
* target. Literal targets are resolved to normal paths at runtime, and cached.
* If a symlink is encountered during traversal, we restart traversal at the
* root node targeting join(normal symlink target, remaining path suffix).
*
* NODE TYPES:
*
* - A directory (including a parent directory at '..') is represented by a
* `Map` of basenames to any other node type.
* - A file is represented by an `Array` (tuple) of metadata, of which:
* - A regular file has node[H.SYMLINK] === 0
* - A symlink has node[H.SYMLINK] === 1 or
* typeof node[H.SYMLINK] === 'string', where a string is the literal
* content of the symlink (i.e. from readlink), if known.
*
* TERMINOLOGY:
*
* - mixedPath
* A root-relative or absolute path
* - relativePath
* A root-relative path
* - normalPath
* A root-relative, normalised path (no extraneous '.' or '..'), may have a
* single trailing slash
* - canonicalPath
* A root-relative, normalised, real path (no symlinks in dirname), never has
* a trailing slash
*/
declare class TreeFS implements MutableFileSystem {
constructor(opts: TreeFSOptions);
getSerializableSnapshot(): CacheData['fileSystemData'];
static fromDeserializedSnapshot(args: DeserializedSnapshotInput): TreeFS;
getSize(mixedPath: Path): null | undefined | number;
getDifference(
files: FileData,
options?: Readonly<{subpath?: string}>,
): {changedFiles: FileData; removedFiles: Set<string>};
getSha1(mixedPath: Path): null | undefined | string;
getOrComputeSha1(
mixedPath: Path,
): Promise<null | undefined | {sha1: string; content?: Buffer}>;
exists(mixedPath: Path): boolean;
lookup(mixedPath: Path): LookupResult;
getAllFiles(): Array<Path>;
linkStats(mixedPath: Path): null | undefined | FileStats;
/**
* Given a search context, return a list of file paths matching the query.
* The query matches against normalized paths which start with `./`,
* for example: `a/b.js` -> `./a/b.js`
*/
matchFiles(opts: MatchFilesOptions): Iterable<Path>;
addOrModify(
mixedPath: Path,
metadata: FileMetadata,
changeListener?: FileSystemListener,
): void;
bulkAddOrModify(
addedOrModifiedFiles: FileData,
changeListener?: FileSystemListener,
): void;
remove(mixedPath: Path, changeListener?: FileSystemListener): void;
/**
* 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: null | undefined | string;
invalidatedBy: null | undefined | Set<string>;
subpathType: 'f' | 'd';
},
): null | undefined | {absolutePath: string; containerRelativePath: string};
metadataIterator(opts: MetadataIteratorOptions): Iterator<{
baseName: string;
canonicalPath: string;
metadata: FileMetadata;
}>;
}
export default TreeFS;

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

@@ -0,0 +1,881 @@
"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();
#pathUtils;
#processFile;
#rootDir;
#rootNode = new Map();
constructor(opts) {
const { rootDir, files, processFile } = opts;
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(args) {
const { rootDir, fileSystemData, processFile } = args;
const tfs = new TreeFS({
processFile,
rootDir,
});
tfs.#rootNode = fileSystemData;
return tfs;
}
getSize(mixedPath) {
const fileMetadata = this.#getFileData(mixedPath);
return (fileMetadata && fileMetadata[_constants.default.SIZE]) ?? null;
}
getDifference(files, options) {
const changedFiles = new Map(files);
const removedFiles = new Set();
const subpath = options?.subpath;
let rootNode = this.#rootNode;
let prefix = "";
if (subpath != null && subpath !== "") {
const lookupResult = this.#lookupByNormalPath(subpath, {
followLeaf: true,
});
if (!lookupResult.exists || !isDirectory(lookupResult.node)) {
return {
changedFiles,
removedFiles,
};
}
rootNode = lookupResult.node;
prefix = lookupResult.canonicalPath;
}
for (const { canonicalPath, metadata } of this.#metadataIterator(
rootNode,
{
includeNodeModules: true,
includeSymlinks: true,
},
prefix,
)) {
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 maybeContent = await this.#processFile(canonicalPath, 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",
canonicalPath,
);
return maybeContent
? {
content: maybeContent,
sha1,
}
: {
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 realPath = this.#pathUtils.normalToAbsolute(canonicalPath);
if (isDirectory(node)) {
return {
exists: true,
links,
realPath,
type: "d",
};
}
(0, _invariant.default)(
isRegularFile(node),
"lookup follows symlinks, so should never return one (%s -> %s)",
mixedPath,
canonicalPath,
);
return {
exists: true,
links,
realPath,
type: "f",
metadata: node,
};
}
getAllFiles() {
return Array.from(
this.metadataIterator({
includeNodeModules: true,
includeSymlinks: false,
}),
({ 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(opts) {
const {
filter = null,
filterCompareAbsolute = false,
filterComparePosix = false,
follow = false,
recursive = true,
rootDir = null,
} = opts;
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, changeListener) {
const normalPath = this.#normalizePath(mixedPath);
const parentDirNode = this.#lookupByNormalPath(
_path.default.dirname(normalPath),
{
changeListener,
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]]), changeListener);
}
bulkAddOrModify(addedOrModifiedFiles, changeListener) {
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, {
changeListener,
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;
}
if (changeListener != null) {
const existingNode = directoryNode.get(basename);
if (existingNode != null) {
(0, _invariant.default)(
!isDirectory(existingNode),
"Detected addition or modification of file %s, but it is tracked as a non-empty directory",
normalPath,
);
changeListener.fileModified(normalPath, existingNode, metadata);
} else {
changeListener.fileAdded(normalPath, metadata);
}
}
directoryNode.set(basename, metadata);
}
}
remove(mixedPath, changeListener) {
const normalPath = this.#normalizePath(mixedPath);
const result = this.#lookupByNormalPath(normalPath, {
followLeaf: false,
});
if (!result.exists) {
return;
}
const { parentNode, canonicalPath, node } = result;
if (isDirectory(node) && node.size > 0) {
for (const basename of node.keys()) {
this.remove(
canonicalPath + _path.default.sep + basename,
changeListener,
);
}
return;
}
if (parentNode != null) {
if (changeListener != null) {
if (isDirectory(node)) {
changeListener.directoryRemoved(canonicalPath);
} else {
changeListener.fileRemoved(canonicalPath, node);
}
}
parentNode.delete(_path.default.basename(canonicalPath));
if (parentNode.size === 0 && parentNode !== this.#rootNode) {
this.remove(_path.default.dirname(canonicalPath), changeListener);
}
}
}
#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, changeListener } = opts;
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) {
if (changeListener != null) {
const canonicalPath = isLastSegment
? targetNormalPath
: targetNormalPath.slice(0, fromIdx - 1);
changeListener.directoryAdded(canonicalPath);
}
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 {
baseName: name,
canonicalPath: prefixedName,
metadata: node,
};
}
}
}
#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;

1272
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,20 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<f72d8f0c4d8f513383584a02f36795ef>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/checkWatchmanCapabilities.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
declare function checkWatchmanCapabilities(
requiredCapabilities: ReadonlyArray<string>,
): Promise<{version: string}>;
export default checkWatchmanCapabilities;

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.
*
* @flow strict
* @format
*/
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) as unknown;
} 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,14 @@
/**
* 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
*/
declare const dependencyExtractor: {
extract: (code: string) => Set<string>;
};
export = dependencyExtractor;

View File

@@ -0,0 +1,20 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<30b5e6d2308dde108c136f95a59e3740>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/normalizePathSeparatorsToPosix.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
declare const $$EXPORT_DEFAULT_DECLARATION$$: (filePath: string) => string;
declare type $$EXPORT_DEFAULT_DECLARATION$$ =
typeof $$EXPORT_DEFAULT_DECLARATION$$;
export default $$EXPORT_DEFAULT_DECLARATION$$;

View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var path = _interopRequireWildcard(require("path"));
function _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
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.
*
* @flow strict
* @format
*/
import * as path from 'path';
let normalizePathSeparatorsToPosix;
if (path.sep === '/') {
normalizePathSeparatorsToPosix = (filePath: string): string => filePath;
} else {
normalizePathSeparatorsToPosix = (filePath: string): string =>
filePath.replace(/\\/g, '/');
}
export default normalizePathSeparatorsToPosix as (filePath: string) => string;

View File

@@ -0,0 +1,20 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<719a82b7670f09ecb97e007293fddfc6>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/normalizePathSeparatorsToSystem.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
declare const $$EXPORT_DEFAULT_DECLARATION$$: (filePath: string) => string;
declare type $$EXPORT_DEFAULT_DECLARATION$$ =
typeof $$EXPORT_DEFAULT_DECLARATION$$;
export default $$EXPORT_DEFAULT_DECLARATION$$;

View File

@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var path = _interopRequireWildcard(require("path"));
function _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ("object" != typeof e && "function" != typeof e))
return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
"default" !== t &&
{}.hasOwnProperty.call(e, t) &&
((i =
(o = Object.defineProperty) &&
Object.getOwnPropertyDescriptor(e, t)) &&
(i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
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.
*
* @flow strict
* @format
*/
import * as path from 'path';
let normalizePathSeparatorsToSystem;
if (path.sep === '/') {
normalizePathSeparatorsToSystem = (filePath: string): string => filePath;
} else {
normalizePathSeparatorsToSystem = (filePath: string): string =>
filePath.replace(/\//g, path.sep);
}
export default normalizePathSeparatorsToSystem as (filePath: string) => string;

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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<f82cf1eeac38c409c5bf891686c2e828>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/rootRelativeCacheKeys.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {BuildParameters} from '../flow-types';
declare function rootRelativeCacheKeys(buildParameters: BuildParameters): {
rootDirHash: string;
relativeConfigHash: string;
};
export default rootRelativeCacheKeys;

View File

@@ -0,0 +1,55 @@
"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 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 "computeSha1":
case "enableSymlinks":
case "forceNodeFilesystemAPI":
case "retainAllFiles":
return buildParameters[key] ?? null;
case "ignorePattern":
return buildParameters[key].toString();
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,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.
*
* @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';
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 'computeSha1':
case 'enableSymlinks':
case 'forceNodeFilesystemAPI':
case 'retainAllFiles':
return buildParameters[key] ?? null;
case 'ignorePattern':
return buildParameters[key].toString();
default:
key as 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,
};
}

23
node_modules/metro-file-map/src/lib/sorting.d.ts generated vendored Normal file
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.
*
* @noformat
* @generated SignedSource<<8805bc71542c6b43e940f8c5761ff187>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/lib/sorting.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
export declare function compareStrings(
a: null | string,
b: null | string,
): number;
export declare function chainComparators<T>(
...comparators: Array<(a: T, b: T) => number>
): (a: T, b: T) => number;

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;
};
}

View File

@@ -0,0 +1,52 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<e07a9c061b0224fc44191d956461bd6f>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/DependencyPlugin.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
Path,
} from '../flow-types';
export type DependencyPluginOptions = Readonly<{
/** Path to custom dependency extractor module */
dependencyExtractor: null | undefined | string;
/** Whether to compute dependencies (performance optimization) */
computeDependencies: boolean;
rootDir: Path;
}>;
declare class DependencyPlugin
implements FileMapPlugin<null, ReadonlyArray<string> | null>
{
readonly name: 'dependencies';
constructor(options: DependencyPluginOptions);
initialize(
initOptions: FileMapPluginInitOptions<null, ReadonlyArray<string> | null>,
): Promise<void>;
getSerializableSnapshot(): null;
onChanged(): void;
assertValid(): void;
getCacheKey(): string;
getWorker(): FileMapPluginWorker;
/**
* Get the list of dependencies for a given file.
* @param mixedPath Absolute or project-relative path to the file
* @returns Array of dependency module names, or null if the file doesn't exist
*/
getDependencies(mixedPath: Path): null | undefined | ReadonlyArray<string>;
}
export default DependencyPlugin;

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class DependencyPlugin {
name = "dependencies";
#dependencyExtractor;
#computeDependencies;
#getDependencies;
#rootDir;
constructor(options) {
this.#dependencyExtractor = options.dependencyExtractor;
this.#computeDependencies = options.computeDependencies;
this.#rootDir = options.rootDir;
}
async initialize(initOptions) {
const { files } = initOptions;
this.#getDependencies = (mixedPath) => {
const result = files.lookup(mixedPath);
if (result.exists && result.type === "f") {
return result.pluginData ?? [];
}
return null;
};
}
getSerializableSnapshot() {
return null;
}
onChanged() {}
assertValid() {}
getCacheKey() {
if (this.#dependencyExtractor != null) {
const extractor = require(this.#dependencyExtractor);
return JSON.stringify({
extractorKey: extractor.getCacheKey?.() ?? null,
extractorPath: this.#dependencyExtractor,
});
}
return "default-dependency-extractor";
}
getWorker() {
const excludedExtensions = require("../workerExclusionList");
return {
worker: {
modulePath: require.resolve("./dependencies/worker.js"),
setupArgs: {
dependencyExtractor: this.#dependencyExtractor ?? null,
},
},
filter: ({ normalPath, isNodeModules }) => {
if (!this.#computeDependencies) {
return false;
}
if (isNodeModules) {
return false;
}
const ext = normalPath.substr(normalPath.lastIndexOf("."));
return !excludedExtensions.has(ext);
},
};
}
getDependencies(mixedPath) {
if (this.#getDependencies == null) {
throw new Error(
"DependencyPlugin has not been initialized before getDependencies",
);
}
return this.#getDependencies(mixedPath);
}
}
exports.default = DependencyPlugin;

View File

@@ -0,0 +1,129 @@
/**
* 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 {
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
Path,
} from '../flow-types';
export type DependencyPluginOptions = Readonly<{
/** Path to custom dependency extractor module */
dependencyExtractor: ?string,
/** Whether to compute dependencies (performance optimization) */
computeDependencies: boolean,
rootDir: Path,
}>;
export default class DependencyPlugin
implements FileMapPlugin<null, ReadonlyArray<string> | null>
{
+name: 'dependencies' = 'dependencies';
#dependencyExtractor: ?string;
#computeDependencies: boolean;
#getDependencies: Path => ?ReadonlyArray<string>;
#rootDir: Path;
constructor(options: DependencyPluginOptions) {
this.#dependencyExtractor = options.dependencyExtractor;
this.#computeDependencies = options.computeDependencies;
this.#rootDir = options.rootDir;
}
async initialize(
initOptions: FileMapPluginInitOptions<null, ReadonlyArray<string> | null>,
): Promise<void> {
const {files} = initOptions;
// Create closure to access dependencies from file metadata plugin data
this.#getDependencies = (mixedPath: Path) => {
const result = files.lookup(mixedPath);
if (result.exists && result.type === 'f') {
// Backwards compatibility: distinguish an extant file that we've not
// run the worker on (probably because it fails the extension filter)
// from a missing file. Non-source files are expected to have empty
// dependencies.
return result.pluginData ?? [];
}
return null;
};
}
getSerializableSnapshot(): null {
// Dependencies stored in plugin data, no separate serialization needed
return null;
}
onChanged(): void {
// No-op: Worker already populated plugin data
// Plugin data is write-only from worker
}
assertValid(): void {
// No validation needed
}
getCacheKey(): string {
if (this.#dependencyExtractor != null) {
// Dynamic require to get extractor's cache key
// $FlowFixMe[unsupported-syntax] - dynamic require
const extractor = require(this.#dependencyExtractor);
return JSON.stringify({
extractorKey: extractor.getCacheKey?.() ?? null,
extractorPath: this.#dependencyExtractor,
});
}
return 'default-dependency-extractor';
}
getWorker(): FileMapPluginWorker {
const excludedExtensions = require('../workerExclusionList');
return {
worker: {
modulePath: require.resolve('./dependencies/worker.js'),
setupArgs: {
dependencyExtractor: this.#dependencyExtractor ?? null,
},
},
filter: ({normalPath, isNodeModules}) => {
// Respect computeDependencies flag
if (!this.#computeDependencies) {
return false;
}
// Never process node_modules
if (isNodeModules) {
return false;
}
// Skip excluded extensions
const ext = normalPath.substr(normalPath.lastIndexOf('.'));
return !excludedExtensions.has(ext);
},
};
}
/**
* Get the list of dependencies for a given file.
* @param mixedPath Absolute or project-relative path to the file
* @returns Array of dependency module names, or null if the file doesn't exist
*/
getDependencies(mixedPath: Path): ?ReadonlyArray<string> {
if (this.#getDependencies == null) {
throw new Error(
'DependencyPlugin has not been initialized before getDependencies',
);
}
return this.#getDependencies(mixedPath);
}
}

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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<3d1462ab2325a09553e02b69b5de84eb>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/HastePlugin.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
Console,
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
HasteConflict,
HasteMap,
HasteMapItemMetadata,
HTypeValue,
Path,
PerfLogger,
ReadonlyFileSystemChanges,
} from '../flow-types';
export type HasteMapOptions = Readonly<{
console?: null | undefined | Console;
enableHastePackages: boolean;
hasteImplModulePath: null | undefined | string;
perfLogger?: null | undefined | PerfLogger;
platforms: ReadonlySet<string>;
rootDir: Path;
failValidationOnConflicts: boolean;
}>;
declare class HastePlugin
implements HasteMap, FileMapPlugin<null, string | null>
{
readonly name: 'haste';
constructor(options: HasteMapOptions);
initialize(
$$PARAM_0$$: FileMapPluginInitOptions<null, string | null>,
): Promise<void>;
getSerializableSnapshot(): null;
getModule(
name: string,
platform?: null | undefined | string,
supportsNativePlatform?: null | undefined | boolean,
type?: null | undefined | HTypeValue,
): null | undefined | Path;
getModuleNameByPath(mixedPath: Path): null | undefined | string;
getPackage(
name: string,
platform: null | undefined | string,
_supportsNativePlatform?: null | undefined | boolean,
): null | undefined | Path;
onChanged(delta: ReadonlyFileSystemChanges<null | undefined | string>): void;
setModule(id: string, module: HasteMapItemMetadata): void;
assertValid(): void;
computeConflicts(): Array<HasteConflict>;
getCacheKey(): string;
getWorker(): FileMapPluginWorker;
}
export default HastePlugin;

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

@@ -0,0 +1,403 @@
"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 PACKAGE_JSON = /(?:[/\\]|^)package\.json$/;
const YIELD_EVERY_NUM_HASTE_FILES = 10000;
class HastePlugin {
name = "haste";
#console;
#duplicates = new Map();
#enableHastePackages;
#failValidationOnConflicts;
#getModuleNameByPath;
#hasteImplModulePath;
#map = new Map();
#pathUtils;
#perfLogger;
#platforms;
#rootDir;
constructor(options) {
this.#console = options.console ?? global.console;
this.#enableHastePackages = options.enableHastePackages;
this.#hasteImplModulePath = options.hasteImplModulePath;
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,
pluginData: hasteId,
} of files.fileIterator({
includeNodeModules: false,
includeSymlinks: false,
})) {
if (hasteId == null) {
continue;
}
this.setModule(hasteId, [
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.#getModuleNameByPath = (mixedPath) => {
const result = files.lookup(mixedPath);
return result.exists &&
result.type === "f" &&
typeof result.pluginData === "string"
? result.pluginData
: null;
};
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;
}
getModuleNameByPath(mixedPath) {
if (this.#getModuleNameByPath == null) {
throw new Error(
"HastePlugin has not been initialized before getModuleNameByPath",
);
}
return this.#getModuleNameByPath(mixedPath) ?? 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,
);
}
onChanged(delta) {
for (const [canonicalPath, maybeHasteId] of delta.removedFiles) {
this.#onRemovedFile(canonicalPath, maybeHasteId);
}
for (const [canonicalPath, maybeHasteId] of delta.addedFiles) {
this.#onNewFile(canonicalPath, maybeHasteId);
}
}
#onNewFile(canonicalPath, id) {
if (id == null) {
return;
}
const module = [
canonicalPath,
this.#enableHastePackages &&
_path.default.basename(canonicalPath) === "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(canonicalPath, moduleName) {
if (moduleName == null) {
return;
}
const platform =
(0, _getPlatformExtension.default)(canonicalPath, 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, canonicalPath);
}
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({
absolutePaths: [...conflictingModules.keys()]
.map((modulePath) => this.#pathUtils.normalToAbsolute(modulePath))
.sort(),
id,
platform:
platform === _constants.default.GENERIC_PLATFORM ? null : platform,
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({
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map((modulePath) => this.#pathUtils.normalToAbsolute(modulePath))
.sort(),
id,
platform: null,
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.#hasteImplModulePath != null
? require(this.#hasteImplModulePath).getCacheKey()
: null,
[...this.#platforms].sort(),
]);
}
getWorker() {
return {
worker: {
modulePath: require.resolve("./haste/worker.js"),
setupArgs: {
hasteImplModulePath: this.#hasteImplModulePath ?? null,
},
},
filter: ({ isNodeModules, normalPath }) => {
if (isNodeModules) {
return false;
}
if (PACKAGE_JSON.test(normalPath)) {
return this.#enableHastePackages;
}
return this.#hasteImplModulePath != null;
},
};
}
}
exports.default = HastePlugin;

View File

@@ -0,0 +1,514 @@
/**
* 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,
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
HasteConflict,
HasteMap,
HasteMapItem,
HasteMapItemMetadata,
HTypeValue,
Path,
PerfLogger,
ReadonlyFileSystemChanges,
} 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();
const PACKAGE_JSON = /(?:[/\\]|^)package\.json$/;
// 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;
export type HasteMapOptions = Readonly<{
console?: ?Console,
enableHastePackages: boolean,
hasteImplModulePath: ?string,
perfLogger?: ?PerfLogger,
platforms: ReadonlySet<string>,
rootDir: Path,
failValidationOnConflicts: boolean,
}>;
export default class HastePlugin
implements HasteMap, FileMapPlugin<null, string | null>
{
+name: 'haste' = 'haste';
+#console: ?Console;
+#duplicates: DuplicatesIndex = new Map();
+#enableHastePackages: boolean;
+#failValidationOnConflicts: boolean;
#getModuleNameByPath: string => ?string;
+#hasteImplModulePath: ?string;
+#map: Map<string, HasteMapItem> = new Map();
+#pathUtils: RootPathUtils;
+#perfLogger: ?PerfLogger;
+#platforms: ReadonlySet<string>;
+#rootDir: Path;
constructor(options: HasteMapOptions) {
this.#console = options.console ?? global.console;
this.#enableHastePackages = options.enableHastePackages;
this.#hasteImplModulePath = options.hasteImplModulePath;
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, string | null>): Promise<void> {
this.#perfLogger?.point('constructHasteMap_start');
let hasteFiles = 0;
for (const {
baseName,
canonicalPath,
pluginData: hasteId,
} of files.fileIterator({
// Symlinks and node_modules are never Haste modules or packages.
includeNodeModules: false,
includeSymlinks: false,
})) {
if (hasteId == null) {
continue;
}
this.setModule(hasteId, [
canonicalPath,
this.#enableHastePackages && baseName === 'package.json'
? H.PACKAGE
: H.MODULE,
]);
if (++hasteFiles % YIELD_EVERY_NUM_HASTE_FILES === 0) {
await new Promise(setImmediate);
}
}
this.#getModuleNameByPath = mixedPath => {
const result = files.lookup(mixedPath);
return result.exists &&
result.type === 'f' &&
typeof result.pluginData === 'string'
? result.pluginData
: null;
};
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;
}
getModuleNameByPath(mixedPath: Path): ?string {
if (this.#getModuleNameByPath == null) {
throw new Error(
'HastePlugin has not been initialized before getModuleNameByPath',
);
}
return this.#getModuleNameByPath(mixedPath) ?? 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,
);
}
onChanged(delta: ReadonlyFileSystemChanges<?string>): void {
// Process removals first so that moves aren't treated as duplicates.
for (const [canonicalPath, maybeHasteId] of delta.removedFiles) {
this.#onRemovedFile(canonicalPath, maybeHasteId);
}
for (const [canonicalPath, maybeHasteId] of delta.addedFiles) {
this.#onNewFile(canonicalPath, maybeHasteId);
}
}
#onNewFile(canonicalPath: string, id: ?string) {
if (id == null) {
// Not a Haste module or package
return;
}
const module: HasteMapItemMetadata = [
canonicalPath,
this.#enableHastePackages &&
path.basename(canonicalPath) === '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) as 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(canonicalPath: string, moduleName: ?string) {
if (moduleName == null) {
// Not a Haste module or package
return;
}
const platform =
getPlatformExtension(canonicalPath, 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, canonicalPath);
}
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) as 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({
absolutePaths: [...conflictingModules.keys()]
.map(modulePath => this.#pathUtils.normalToAbsolute(modulePath))
// Sort for ease of testing
.sort(),
id,
platform: platform === H.GENERIC_PLATFORM ? null : platform,
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({
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map(modulePath => this.#pathUtils.normalToAbsolute(modulePath))
// Sort for ease of testing
.sort(),
id,
platform: null,
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.#hasteImplModulePath != null
? // $FlowFixMe[unsupported-syntax] - dynamic require
require(this.#hasteImplModulePath).getCacheKey()
: null,
[...this.#platforms].sort(),
]);
}
getWorker(): FileMapPluginWorker {
return {
worker: {
modulePath: require.resolve('./haste/worker.js'),
setupArgs: {
hasteImplModulePath: this.#hasteImplModulePath ?? null,
},
},
filter: ({isNodeModules, normalPath}) => {
if (isNodeModules) {
return false;
}
if (PACKAGE_JSON.test(normalPath)) {
return this.#enableHastePackages;
}
return this.#hasteImplModulePath != null;
},
};
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<d9402d4670982b1e675e1edd9201cf75>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/MockPlugin.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
MockMap as IMockMap,
Path,
RawMockMap,
ReadonlyFileSystemChanges,
} from '../flow-types';
export declare const CACHE_VERSION: 2;
export declare type CACHE_VERSION = typeof CACHE_VERSION;
export type MockMapOptions = Readonly<{
console: typeof console;
mocksPattern: RegExp;
rawMockMap?: RawMockMap;
rootDir: Path;
throwOnModuleCollision: boolean;
}>;
declare class MockPlugin implements FileMapPlugin<RawMockMap, void>, IMockMap {
readonly name: 'mocks';
constructor($$PARAM_0$$: MockMapOptions);
initialize($$PARAM_0$$: FileMapPluginInitOptions<RawMockMap>): Promise<void>;
getMockModule(name: string): null | undefined | Path;
onChanged(delta: ReadonlyFileSystemChanges<null | undefined | void>): void;
getSerializableSnapshot(): RawMockMap;
assertValid(): void;
getCacheKey(): string;
getWorker(): null | undefined | FileMapPluginWorker;
}
export default MockPlugin;

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 = {
duplicates: new Map(),
mocks: 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 {
for (const { canonicalPath } of files.fileIterator({
includeNodeModules: false,
includeSymlinks: false,
})) {
this.#onFileAdded(canonicalPath);
}
}
}
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),
);
}
onChanged(delta) {
for (const [canonicalPath] of delta.removedFiles) {
this.#onFileRemoved(canonicalPath);
}
for (const [canonicalPath] of delta.addedFiles) {
this.#onFileAdded(canonicalPath);
}
}
#onFileAdded(canonicalPath) {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(canonicalPath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = (0, _getMockName.default)(absoluteFilePath);
const posixRelativePath = (0, _normalizePathSeparatorsToPosix.default)(
canonicalPath,
);
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);
}
#onFileRemoved(canonicalPath) {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(canonicalPath);
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)(
canonicalPath,
);
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 {
duplicates: new Map(
[...this.#raw.duplicates].map(([k, v]) => [k, new Set(v)]),
),
mocks: new Map(this.#raw.mocks),
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
);
}
getWorker() {
return null;
}
}
exports.default = MockPlugin;

View File

@@ -0,0 +1,221 @@
/**
* 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 {
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
MockMap as IMockMap,
Path,
RawMockMap,
ReadonlyFileSystemChanges,
} 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 type MockMapOptions = Readonly<{
console: typeof console,
mocksPattern: RegExp,
rawMockMap?: RawMockMap,
rootDir: Path,
throwOnModuleCollision: boolean,
}>;
export default class MockPlugin
implements FileMapPlugin<RawMockMap, void>, IMockMap
{
+name: 'mocks' = 'mocks';
+#mocksPattern: RegExp;
#raw: RawMockMap;
+#rootDir: Path;
+#pathUtils: RootPathUtils;
+#console: typeof console;
#throwOnModuleCollision: boolean;
constructor({
console,
mocksPattern,
rawMockMap = {
duplicates: new Map(),
mocks: new Map(),
version: CACHE_VERSION,
},
rootDir,
throwOnModuleCollision,
}: MockMapOptions) {
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
for (const {canonicalPath} of files.fileIterator({
includeNodeModules: false,
includeSymlinks: false,
})) {
this.#onFileAdded(canonicalPath);
}
}
}
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),
);
}
onChanged(delta: ReadonlyFileSystemChanges<?void>): void {
// Process removals first so that moves aren't treated as duplicates.
for (const [canonicalPath] of delta.removedFiles) {
this.#onFileRemoved(canonicalPath);
}
for (const [canonicalPath] of delta.addedFiles) {
this.#onFileAdded(canonicalPath);
}
}
#onFileAdded(canonicalPath: Path): void {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(canonicalPath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = getMockName(absoluteFilePath);
const posixRelativePath = normalizePathSeparatorsToPosix(canonicalPath);
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);
}
#onFileRemoved(canonicalPath: Path): void {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(canonicalPath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = getMockName(absoluteFilePath);
const duplicates = this.#raw.duplicates.get(mockName);
if (duplicates != null) {
const posixRelativePath = normalizePathSeparatorsToPosix(canonicalPath);
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 {
duplicates: new Map(
[...this.#raw.duplicates].map(([k, v]) => [k, new Set(v)]),
),
mocks: new Map(this.#raw.mocks),
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
);
}
getWorker(): ?FileMapPluginWorker {
return null;
}
}

View File

@@ -0,0 +1,14 @@
/**
* 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.
*
* @generated by js1 build metro-ts-defs / yarn run build-ts-defs
*/
declare const dependencyExtractor: {
extract(code: string): Set<string>;
};
export = dependencyExtractor;

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,101 @@
/**
* 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,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 {
MetadataWorker,
V8Serializable,
WorkerMessage,
} from '../../flow-types';
declare class DependencyExtractorWorker implements MetadataWorker {
constructor(opts: Readonly<{dependencyExtractor: null | undefined | string}>);
processFile(
data: WorkerMessage,
utils: Readonly<{getContent: () => Buffer}>,
): V8Serializable;
}
export = DependencyExtractorWorker;

View File

@@ -0,0 +1,24 @@
"use strict";
const defaultDependencyExtractor = require("./dependencyExtractor");
module.exports = class DependencyExtractorWorker {
#dependencyExtractor;
constructor({ dependencyExtractor }) {
if (dependencyExtractor != null) {
this.#dependencyExtractor = require(dependencyExtractor);
}
}
processFile(data, utils) {
const content = utils.getContent().toString();
const { filePath } = data;
const dependencies =
this.#dependencyExtractor != null
? this.#dependencyExtractor.extract(
content,
filePath,
defaultDependencyExtractor.extract,
)
: defaultDependencyExtractor.extract(content);
return Array.from(dependencies);
}
};

View File

@@ -0,0 +1,53 @@
/**
* 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
*/
/* eslint-disable import/no-commonjs */
'use strict';
const defaultDependencyExtractor = require('./dependencyExtractor');
/*::
import type {MetadataWorker, WorkerMessage, V8Serializable, DependencyExtractor} from '../../flow-types';
*/
module.exports = class DependencyExtractorWorker /*:: implements MetadataWorker */ {
/*:: + */ #dependencyExtractor /*: ?DependencyExtractor */;
constructor(
{dependencyExtractor} /*: Readonly<{dependencyExtractor: ?string}> */,
) {
if (dependencyExtractor != null) {
// $FlowFixMe[unsupported-syntax] - dynamic require
this.#dependencyExtractor = require(dependencyExtractor);
}
}
processFile(
data /*: WorkerMessage */,
utils /*: Readonly<{getContent: () => Buffer}> */,
) /*: V8Serializable */ {
const content = utils.getContent().toString();
const {filePath} = data;
const dependencies =
this.#dependencyExtractor != null
? this.#dependencyExtractor.extract(
content,
filePath,
defaultDependencyExtractor.extract,
)
: defaultDependencyExtractor.extract(content);
// Return as array (PerFileData type)
return Array.from(dependencies);
}
};

View File

@@ -0,0 +1,31 @@
/**
* 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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<2c991103bc4a71a81ef04de0884de576>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/haste/DuplicateHasteCandidatesError.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {DuplicatesSet} from '../../flow-types';
export declare class DuplicateHasteCandidatesError extends Error {
hasteName: string;
platform: string | null;
supportsNativePlatform: boolean;
duplicatesSet: DuplicatesSet;
constructor(
name: string,
platform: string,
supportsNativePlatform: boolean,
duplicatesSet: DuplicatesSet,
);
}

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,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.
*
* @noformat
* @oncall react_native
* @generated SignedSource<<53c103ffe2115282c4d72593f47018aa>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/haste/HasteConflictsError.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {HasteConflict} from '../../flow-types';
export declare class HasteConflictsError extends Error {
constructor(conflicts: ReadonlyArray<HasteConflict>);
getDetailedMessage(pathsRelativeToRoot: null | undefined | string): string;
}

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,34 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<f56c9fdb2fc1c692fa880c61a14ba1e3>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/haste/computeConflicts.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
import type {HasteMapItem} from '../../flow-types';
type Conflict = {
id: string;
platform: string | null;
absolutePaths: Array<string>;
type: 'duplicate' | 'shadowing';
};
export declare function computeHasteConflicts(
options: Readonly<{
duplicates: ReadonlyMap<
string,
ReadonlyMap<string, ReadonlyMap<string, number>>
>;
map: ReadonlyMap<string, HasteMapItem>;
rootDir: string;
}>,
): Array<Conflict>;

View File

@@ -0,0 +1,74 @@
"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(options) {
const { duplicates, map, rootDir } = options;
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,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.
*
* @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(
options: Readonly<{
duplicates: ReadonlyMap<
string,
ReadonlyMap<string, ReadonlyMap<string, number>>,
>,
map: ReadonlyMap<string, HasteMapItem>,
rootDir: string,
}>,
): Array<Conflict> {
const {duplicates, map, rootDir} = options;
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,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.
*
* @noformat
* @generated SignedSource<<3d628d7c2b6149348fcdc5782fc24bb7>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/haste/getPlatformExtension.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
declare function getPlatformExtension(
file: string,
platforms: ReadonlySet<string>,
): null | undefined | string;
export default getPlatformExtension;

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.
*
* @flow strict
* @format
*/
// 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,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
*/
import type {
MetadataWorker,
V8Serializable,
WorkerMessage,
} from '../../flow-types';
declare class Worker implements MetadataWorker {
constructor(opts: Readonly<{hasteImplModulePath: null | undefined | string}>);
processFile(
data: WorkerMessage,
utils: Readonly<{getContent: () => Buffer}>,
): V8Serializable;
}
export = Worker;

View File

@@ -0,0 +1,35 @@
"use strict";
const excludedExtensions = require("../../workerExclusionList");
const path = require("path");
const PACKAGE_JSON = path.sep + "package.json";
module.exports = class Worker {
#hasteImpl = null;
constructor({ hasteImplModulePath }) {
if (hasteImplModulePath != null) {
this.#hasteImpl = require(hasteImplModulePath);
}
}
processFile(data, utils) {
let hasteName = null;
const { filePath } = data;
if (filePath.endsWith(PACKAGE_JSON)) {
try {
const fileData = JSON.parse(utils.getContent().toString());
if (fileData.name) {
hasteName = fileData.name;
}
} catch (err) {
throw new Error(`Cannot parse ${filePath} as JSON: ${err.message}`);
}
} else if (
!excludedExtensions.has(filePath.substr(filePath.lastIndexOf(".")))
) {
if (!this.#hasteImpl) {
throw new Error("computeHaste is true but hasteImplModulePath not set");
}
hasteName = this.#hasteImpl.getHasteName(filePath) || null;
}
return hasteName;
}
};

View File

@@ -0,0 +1,64 @@
/**
* 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 */
'use strict';
const excludedExtensions = require('../../workerExclusionList');
const path = require('path');
/*::
import type {MetadataWorker, WorkerMessage, V8Serializable} from '../../flow-types';
*/
const PACKAGE_JSON = path.sep + 'package.json';
module.exports = class Worker /*:: implements MetadataWorker */ {
/*:: + */ #hasteImpl /*: ?Readonly<{getHasteName: string => ?string}> */ =
null;
constructor(
{hasteImplModulePath} /*: Readonly<{hasteImplModulePath: ?string}> */,
) {
if (hasteImplModulePath != null) {
// $FlowFixMe[unsupported-syntax] - dynamic require
this.#hasteImpl = require(hasteImplModulePath);
}
}
processFile(
data /*: WorkerMessage */,
utils /*: Readonly<{getContent: () => Buffer }> */,
) /*: V8Serializable */ {
let hasteName /*: string | null */ = null;
const {filePath} = data;
if (filePath.endsWith(PACKAGE_JSON)) {
// Process a package.json that is returned as a PACKAGE type with its name.
try {
const fileData = JSON.parse(utils.getContent().toString());
if (fileData.name) {
hasteName = fileData.name;
}
} catch (err) {
throw new Error(`Cannot parse ${filePath} as JSON: ${err.message}`);
}
} else if (
!excludedExtensions.has(filePath.substr(filePath.lastIndexOf('.')))
) {
if (!this.#hasteImpl) {
throw new Error('computeHaste is true but hasteImplModulePath not set');
}
// Process a random file that is returned as a MODULE.
hasteName = this.#hasteImpl.getHasteName(filePath) || null;
}
return hasteName;
}
};

View File

@@ -0,0 +1,20 @@
/**
* 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.
*
* @noformat
* @generated SignedSource<<1c1794b89fa69eff13b6cd80bf0ab42d>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-file-map/src/plugins/mocks/getMockName.js
* To regenerate, run:
* js1 build metro-ts-defs (internal) OR
* yarn run build-ts-defs (OSS)
*/
declare const $$EXPORT_DEFAULT_DECLARATION$$: (filePath: string) => string;
declare type $$EXPORT_DEFAULT_DECLARATION$$ =
typeof $$EXPORT_DEFAULT_DECLARATION$$;
export default $$EXPORT_DEFAULT_DECLARATION$$;

View File

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

View File

@@ -0,0 +1,20 @@
/**
* 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
*/
import * as path from 'path';
const MOCKS_PATTERN = path.sep + '__mocks__' + path.sep;
export default (filePath: string): string => {
const mockPath = filePath.split(MOCKS_PATTERN)[1];
return mockPath
.substring(0, mockPath.lastIndexOf(path.extname(mockPath)))
.replaceAll('\\', '/');
};

Some files were not shown because too many files have changed in this diff Show More