feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,3 @@
# metro-resolver
🚇 [Metro](https://metrobundler.dev/) resolution logic

View File

@@ -0,0 +1,24 @@
{
"name": "metro-resolver",
"version": "0.80.12",
"description": "🚇 Implementation of Metro's resolution logic.",
"main": "src",
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro.git"
},
"scripts": {
"prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src",
"cleanup-release": "test ! -e build && mv src build && mv src.real src"
},
"license": "MIT",
"engines": {
"node": ">=18"
},
"devDependencies": {
"metro": "0.80.12"
},
"dependencies": {
"flow-enums-runtime": "^0.0.6"
}
}

View File

@@ -0,0 +1,283 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.isSubpathDefinedInExports = isSubpathDefinedInExports;
exports.resolvePackageTargetFromExports = resolvePackageTargetFromExports;
var _InvalidPackageConfigurationError = _interopRequireDefault(
require("./errors/InvalidPackageConfigurationError")
);
var _PackagePathNotExportedError = _interopRequireDefault(
require("./errors/PackagePathNotExportedError")
);
var _resolveAsset = _interopRequireDefault(require("./resolveAsset"));
var _isAssetFile = _interopRequireDefault(require("./utils/isAssetFile"));
var _toPosixPath = _interopRequireDefault(require("./utils/toPosixPath"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function resolvePackageTargetFromExports(
context,
packagePath,
modulePath,
packageRelativePath,
exportsField,
platform
) {
const createConfigError = (reason) => {
return new _InvalidPackageConfigurationError.default({
reason,
packagePath,
});
};
const subpath = getExportsSubpath(packageRelativePath);
const exportMap = normalizeExportsField(exportsField, createConfigError);
if (!isSubpathDefinedInExports(exportMap, subpath)) {
throw new _PackagePathNotExportedError.default(
`Attempted to import the module "${modulePath}" which is not listed ` +
`in the "exports" of "${packagePath}" under the requested subpath ` +
`"${subpath}".`
);
}
const { target, patternMatch } = matchSubpathFromExports(
context,
subpath,
exportMap,
platform,
createConfigError
);
if (target != null) {
const invalidSegmentInTarget = findInvalidPathSegment(target.slice(2));
if (invalidSegmentInTarget != null) {
throw createConfigError(
`The target for "${subpath}" defined in "exports" is "${target}", ` +
"however this value is an invalid subpath or subpath pattern " +
`because it includes "${invalidSegmentInTarget}".`
);
}
const filePath = _path.default.join(
packagePath,
patternMatch != null ? target.replace("*", patternMatch) : target
);
if ((0, _isAssetFile.default)(filePath, context.assetExts)) {
const assetResult = (0, _resolveAsset.default)(context, filePath);
if (assetResult != null) {
return assetResult;
}
}
if (context.unstable_fileSystemLookup != null) {
const lookupResult = context.unstable_fileSystemLookup(filePath);
if (lookupResult.exists && lookupResult.type === "f") {
return {
type: "sourceFile",
filePath: lookupResult.realPath,
};
}
} else if (context.doesFileExist(filePath)) {
return {
type: "sourceFile",
filePath,
};
}
throw createConfigError(
`The resolution for "${modulePath}" defined in "exports" is ${filePath}, ` +
"however this file does not exist."
);
}
throw new _PackagePathNotExportedError.default(
`Attempted to import the module "${modulePath}" which is listed in the ` +
`"exports" of "${packagePath}", however no match was resolved for this ` +
`request (platform = ${platform ?? "null"}).`
);
}
function getExportsSubpath(packageSubpath) {
return packageSubpath === ""
? "."
: "./" + (0, _toPosixPath.default)(packageSubpath);
}
const _normalizedExportsFields = new WeakMap();
function normalizeExportsField(exportsField, createConfigError) {
let rootValue;
if (typeof exportsField === "string") {
return new Map([[".", exportsField]]);
}
const cachedValue = _normalizedExportsFields.get(exportsField);
if (cachedValue) {
return cachedValue;
}
if (Array.isArray(exportsField)) {
if (exportsField.every((value) => typeof value === "string")) {
rootValue = exportsField.find((value) => value.startsWith("./"));
} else {
rootValue = exportsField[0];
}
} else {
rootValue = exportsField;
}
if (rootValue == null || Array.isArray(rootValue)) {
throw createConfigError(
'Could not parse non-standard array value at root of "exports" field.'
);
}
if (typeof rootValue === "string") {
const result = new Map([[".", rootValue]]);
_normalizedExportsFields.set(exportsField, result);
return result;
}
const firstLevelKeys = Object.keys(rootValue);
const subpathKeys = firstLevelKeys.filter((subpathOrCondition) =>
subpathOrCondition.startsWith(".")
);
if (subpathKeys.length === firstLevelKeys.length) {
const result = new Map(
Object.entries(flattenLegacySubpathValues(rootValue, createConfigError))
);
_normalizedExportsFields.set(exportsField, result);
return result;
}
if (subpathKeys.length !== 0) {
throw createConfigError(
'The "exports" field cannot have keys which are both subpaths and ' +
"condition names at the same level."
);
}
const result = new Map([
[".", flattenLegacySubpathValues(rootValue, createConfigError)],
]);
_normalizedExportsFields.set(exportsField, result);
return result;
}
function flattenLegacySubpathValues(exportMap, createConfigError) {
return Object.entries(exportMap).reduce((result, [subpath, value]) => {
if (Array.isArray(value)) {
if (!value.length || Array.isArray(value[0])) {
throw createConfigError(
'Could not parse non-standard array value in "exports" field.'
);
}
result[subpath] = value[0];
} else {
result[subpath] = value;
}
return result;
}, {});
}
function isSubpathDefinedInExports(exportMap, subpath) {
if (exportMap.has(subpath)) {
return true;
}
for (const key of exportMap.keys()) {
if (
key.split("*").length === 2 &&
matchSubpathPattern(key, subpath) != null
) {
return true;
}
}
return false;
}
function matchSubpathFromExports(
context,
subpath,
exportMap,
platform,
createConfigError
) {
const conditionNames = new Set([
"default",
...context.unstable_conditionNames,
...(platform != null
? context.unstable_conditionsByPlatform[platform] ?? []
: []),
]);
const exportMapAfterConditions = reduceExportMap(
exportMap,
conditionNames,
createConfigError
);
let target = exportMapAfterConditions.get(subpath);
let patternMatch = null;
if (target == null) {
const expansionKeys = [...exportMapAfterConditions.keys()]
.filter((key) => key.includes("*"))
.sort((key) => key.split("*")[0].length)
.reverse();
for (const key of expansionKeys) {
const value = exportMapAfterConditions.get(key);
if (typeof value === "string" && value.split("*").length !== 2) {
break;
}
patternMatch = matchSubpathPattern(key, subpath);
if (patternMatch != null) {
target = value;
break;
}
}
}
return {
target: target ?? null,
patternMatch,
};
}
function reduceExportMap(exportMap, conditionNames, createConfigError) {
const result = new Map();
for (const [subpath, value] of exportMap) {
const subpathValue = reduceConditionalExport(value, conditionNames);
if (subpathValue !== "no-match") {
result.set(subpath, subpathValue);
}
}
for (const value of result.values()) {
if (value != null && !value.startsWith("./")) {
throw createConfigError(
'One or more mappings for subpaths defined in "exports" are invalid. ' +
'All values must begin with "./".'
);
}
}
return result;
}
function reduceConditionalExport(subpathValue, conditionNames) {
let reducedValue = subpathValue;
while (reducedValue != null && typeof reducedValue !== "string") {
let match;
if ("default" in reducedValue) {
match = "no-match";
} else {
match = null;
}
for (const conditionName in reducedValue) {
if (conditionNames.has(conditionName)) {
match = reducedValue[conditionName];
break;
}
}
reducedValue = match;
}
return reducedValue;
}
function matchSubpathPattern(subpathPattern, subpath) {
const [patternBase, patternTrailer] = subpathPattern.split("*");
if (subpath.startsWith(patternBase) && subpath.endsWith(patternTrailer)) {
return subpath.substring(
patternBase.length,
subpath.length - patternTrailer.length
);
}
return null;
}
function findInvalidPathSegment(subpath) {
for (const segment of subpath.split(/[\\/]/)) {
if (
segment === "" ||
segment === "." ||
segment === ".." ||
segment === "node_modules"
) {
return segment;
}
}
return null;
}

View File

@@ -0,0 +1,469 @@
/**
* 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 {
ExportMap,
ExportMapWithFallbacks,
ExportsField,
FileResolution,
ResolutionContext,
} from './types';
import InvalidPackageConfigurationError from './errors/InvalidPackageConfigurationError';
import PackagePathNotExportedError from './errors/PackagePathNotExportedError';
import resolveAsset from './resolveAsset';
import isAssetFile from './utils/isAssetFile';
import toPosixPath from './utils/toPosixPath';
import path from 'path';
type NormalizedExporthMap = Map<
string /* subpath */,
null | string | ExportMap,
>;
/**
* Resolve a package subpath based on the entry points defined in the package's
* "exports" field. If there is no match for the given subpath (which may be
* augmented by resolution of conditional exports for the passed `context`),
* throws a `PackagePathNotExportedError`.
*
* Implements modern package resolution behaviour based on the [Package Entry
* Points spec](https://nodejs.org/docs/latest-v19.x/api/packages.html#package-entry-points).
*
* @throws {InvalidPackageConfigurationError} Raised if configuration specified
* by `exportsField` is invalid.
* @throws {InvalidModuleSpecifierError} Raised if the resolved module specifier
* is invalid.
* @throws {PackagePathNotExportedError} Raised when the requested subpath is
* not exported.
*/
export function resolvePackageTargetFromExports(
context: ResolutionContext,
/**
* The path to the containing npm package directory.
*/
packagePath: string,
/**
* The unresolved absolute path to the target module. This will be converted
* to a package-relative subpath for comparison.
*/
modulePath: string,
packageRelativePath: string,
exportsField: ExportsField,
platform: string | null,
): FileResolution {
const createConfigError = (reason: string) => {
return new InvalidPackageConfigurationError({
reason,
packagePath,
});
};
const subpath = getExportsSubpath(packageRelativePath);
const exportMap = normalizeExportsField(exportsField, createConfigError);
if (!isSubpathDefinedInExports(exportMap, subpath)) {
throw new PackagePathNotExportedError(
`Attempted to import the module "${modulePath}" which is not listed ` +
`in the "exports" of "${packagePath}" under the requested subpath ` +
`"${subpath}".`,
);
}
const {target, patternMatch} = matchSubpathFromExports(
context,
subpath,
exportMap,
platform,
createConfigError,
);
if (target != null) {
const invalidSegmentInTarget = findInvalidPathSegment(target.slice(2));
if (invalidSegmentInTarget != null) {
throw createConfigError(
`The target for "${subpath}" defined in "exports" is "${target}", ` +
'however this value is an invalid subpath or subpath pattern ' +
`because it includes "${invalidSegmentInTarget}".`,
);
}
const filePath = path.join(
packagePath,
patternMatch != null ? target.replace('*', patternMatch) : target,
);
if (isAssetFile(filePath, context.assetExts)) {
const assetResult = resolveAsset(context, filePath);
if (assetResult != null) {
return assetResult;
}
}
if (context.unstable_fileSystemLookup != null) {
const lookupResult = context.unstable_fileSystemLookup(filePath);
if (lookupResult.exists && lookupResult.type === 'f') {
return {
type: 'sourceFile',
filePath: lookupResult.realPath,
};
}
} else if (context.doesFileExist(filePath)) {
return {
type: 'sourceFile',
filePath,
};
}
throw createConfigError(
`The resolution for "${modulePath}" defined in "exports" is ${filePath}, ` +
'however this file does not exist.',
);
}
throw new PackagePathNotExportedError(
`Attempted to import the module "${modulePath}" which is listed in the ` +
`"exports" of "${packagePath}", however no match was resolved for this ` +
`request (platform = ${platform ?? 'null'}).`,
);
}
/**
* Convert a module path to the package-relative subpath key to attempt for
* "exports" field lookup.
*/
function getExportsSubpath(packageSubpath: string): string {
return packageSubpath === '' ? '.' : './' + toPosixPath(packageSubpath);
}
/**
* Maintain a WeakMap cache of the results of normalizedExportsField.
* Particularly in a large project, many source files depend on the same
* packages (eg @babel/runtime), and this avoids normalising the same JSON
* many times. Note that ExportsField is immutable, and the upstream package
* cache gives us a stable reference.
*
* The case where ExportsField is a string (not weakly referencable) has to be
* excluded, but those are very cheap to process anyway.
*
* (Ultimately this should be coupled more closely to the package cache, so that
* we can clean up immediately rather than on GC.)
*/
type ExcludeString<T> = T extends string ? empty : T;
const _normalizedExportsFields: WeakMap<
ExcludeString<ExportsField>,
NormalizedExporthMap,
> = new WeakMap();
/**
* Normalise an "exports"-like field by parsing string shorthand and conditions
* shorthand at root, and flattening any legacy Node.js <13.7 array values.
*
* See https://nodejs.org/docs/latest-v19.x/api/packages.html#exports-sugar.
*/
function normalizeExportsField(
exportsField: ExportsField,
createConfigError: (reason: string) => Error,
): NormalizedExporthMap {
let rootValue;
if (typeof exportsField === 'string') {
return new Map([['.', exportsField]]);
}
const cachedValue = _normalizedExportsFields.get(exportsField);
if (cachedValue) {
return cachedValue;
}
if (Array.isArray(exportsField)) {
// If an array of strings, use first value with valid specifier (root shorthand)
if (exportsField.every(value => typeof value === 'string')) {
// $FlowIssue[incompatible-call] exportsField is refined to `string[]`
rootValue = exportsField.find((value: string) => value.startsWith('./'));
} else {
// Otherwise, should be a condition map and fallback string (Node.js <13.7)
rootValue = exportsField[0];
}
} else {
rootValue = exportsField;
}
if (rootValue == null || Array.isArray(rootValue)) {
throw createConfigError(
'Could not parse non-standard array value at root of "exports" field.',
);
}
if (typeof rootValue === 'string') {
const result: NormalizedExporthMap = new Map([['.', rootValue]]);
_normalizedExportsFields.set(exportsField, result);
return result;
}
const firstLevelKeys = Object.keys(rootValue);
const subpathKeys = firstLevelKeys.filter(subpathOrCondition =>
subpathOrCondition.startsWith('.'),
);
if (subpathKeys.length === firstLevelKeys.length) {
const result: NormalizedExporthMap = new Map(
Object.entries(flattenLegacySubpathValues(rootValue, createConfigError)),
);
_normalizedExportsFields.set(exportsField, result);
return result;
}
if (subpathKeys.length !== 0) {
throw createConfigError(
'The "exports" field cannot have keys which are both subpaths and ' +
'condition names at the same level.',
);
}
const result: NormalizedExporthMap = new Map([
['.', flattenLegacySubpathValues(rootValue, createConfigError)],
]);
_normalizedExportsFields.set(exportsField, result);
return result;
}
/**
* Flatten legacy Node.js <13.7 array subpath values in an exports mapping.
*/
function flattenLegacySubpathValues(
exportMap: ExportMap | ExportMapWithFallbacks,
createConfigError: (reason: string) => Error,
): ExportMap {
return Object.entries(exportMap).reduce(
(result, [subpath, value]) => {
// We do not support empty or nested arrays (non-standard)
if (Array.isArray(value)) {
if (!value.length || Array.isArray(value[0])) {
throw createConfigError(
'Could not parse non-standard array value in "exports" field.',
);
}
result[subpath] = value[0];
} else {
result[subpath] = value;
}
return result;
},
{} as {[subpathOrCondition: string]: string | ExportMap | null},
);
}
/**
* Identifies whether the given subpath is defined in the given "exports"-like
* mapping. Does not reduce exports conditions (therefore does not identify
* whether the subpath is mapped to a value).
*/
export function isSubpathDefinedInExports(
exportMap: NormalizedExporthMap,
subpath: string,
): boolean {
if (exportMap.has(subpath)) {
return true;
}
// Attempt to match after expanding any subpath pattern keys
for (const key of exportMap.keys()) {
if (
key.split('*').length === 2 &&
matchSubpathPattern(key, subpath) != null
) {
return true;
}
}
return false;
}
/**
* Get the mapped replacement for the given subpath.
*
* Implements modern package resolution behaviour based on the [Package Entry
* Points spec](https://nodejs.org/docs/latest-v19.x/api/packages.html#package-entry-points).
*/
function matchSubpathFromExports(
context: ResolutionContext,
/**
* The package-relative subpath (beginning with '.') to match against either
* an exact subpath key or subpath pattern key in "exports".
*/
subpath: string,
exportMap: NormalizedExporthMap,
platform: string | null,
createConfigError: (reason: string) => Error,
): $ReadOnly<{
target: string | null,
patternMatch: string | null,
}> {
const conditionNames = new Set([
'default',
...context.unstable_conditionNames,
...(platform != null
? context.unstable_conditionsByPlatform[platform] ?? []
: []),
]);
const exportMapAfterConditions = reduceExportMap(
exportMap,
conditionNames,
createConfigError,
);
let target = exportMapAfterConditions.get(subpath);
let patternMatch = null;
// Attempt to match after expanding any subpath pattern keys
if (target == null) {
// Gather keys which are subpath patterns in descending order of specificity
const expansionKeys = [...exportMapAfterConditions.keys()]
.filter(key => key.includes('*'))
.sort(key => key.split('*')[0].length)
.reverse();
for (const key of expansionKeys) {
const value = exportMapAfterConditions.get(key);
// Skip invalid values (must include a single '*' or be `null`)
if (typeof value === 'string' && value.split('*').length !== 2) {
break;
}
patternMatch = matchSubpathPattern(key, subpath);
if (patternMatch != null) {
target = value;
break;
}
}
}
return {target: target ?? null, patternMatch};
}
type FlattenedExportMap = $ReadOnlyMap<string /* subpath */, string | null>;
/**
* Reduce an "exports"-like mapping to a flat subpath mapping after resolving
* conditional exports.
*/
function reduceExportMap(
exportMap: NormalizedExporthMap,
conditionNames: $ReadOnlySet<string>,
createConfigError: (reason: string) => Error,
): FlattenedExportMap {
const result = new Map<string, string | null>();
for (const [subpath, value] of exportMap) {
const subpathValue = reduceConditionalExport(value, conditionNames);
// If a subpath has no resolution for the passed `conditionNames`, do not
// include it in the result. (This includes only explicit `null` values,
// which may conditionally hide higher-specificity subpath patterns.)
if (subpathValue !== 'no-match') {
result.set(subpath, subpathValue);
}
}
for (const value of result.values()) {
if (value != null && !value.startsWith('./')) {
throw createConfigError(
'One or more mappings for subpaths defined in "exports" are invalid. ' +
'All values must begin with "./".',
);
}
}
return result;
}
/**
* Reduce an "exports"-like subpath value after asserting the passed
* `conditionNames` in any nested conditions.
*
* Returns `'no-match'` in the case that none of the asserted `conditionNames`
* are matched.
*
* See https://nodejs.org/docs/latest-v19.x/api/packages.html#conditional-exports.
*/
function reduceConditionalExport(
subpathValue: $Values<ExportMap>,
conditionNames: $ReadOnlySet<string>,
): string | null | 'no-match' {
let reducedValue = subpathValue;
while (reducedValue != null && typeof reducedValue !== 'string') {
let match: typeof subpathValue | 'no-match';
// when conditions are present and default is not specified
// the default condition is implicitly set to null, to allow
// for restricting access to unexported internals of a package.
if ('default' in reducedValue) {
match = 'no-match';
} else {
match = null;
}
for (const conditionName in reducedValue) {
if (conditionNames.has(conditionName)) {
match = reducedValue[conditionName];
break;
}
}
reducedValue = match;
}
return reducedValue;
}
/**
* If a subpath pattern expands to the passed subpath, return the subpath match
* (value to substitute for '*'). Otherwise, return `null`.
*
* See https://nodejs.org/docs/latest-v19.x/api/packages.html#subpath-patterns.
*/
function matchSubpathPattern(
subpathPattern: string,
subpath: string,
): string | null {
const [patternBase, patternTrailer] = subpathPattern.split('*');
if (subpath.startsWith(patternBase) && subpath.endsWith(patternTrailer)) {
return subpath.substring(
patternBase.length,
subpath.length - patternTrailer.length,
);
}
return null;
}
function findInvalidPathSegment(subpath: string): ?string {
for (const segment of subpath.split(/[\\/]/)) {
if (
segment === '' ||
segment === '.' ||
segment === '..' ||
segment === 'node_modules'
) {
return segment;
}
}
return null;
}

View File

@@ -0,0 +1,99 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.getPackageEntryPoint = getPackageEntryPoint;
exports.redirectModulePath = redirectModulePath;
var _toPosixPath = _interopRequireDefault(require("./utils/toPosixPath"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function getPackageEntryPoint(context, packageInfo, platform) {
const { mainFields } = context;
const pkg = packageInfo.packageJson;
let main = "index";
for (const name of mainFields) {
if (typeof pkg[name] === "string" && pkg[name].length) {
main = pkg[name];
break;
}
}
const variants = [
main,
main.slice(0, 2) === "./" ? main.slice(2) : "./" + main,
].flatMap((variant) => [
variant,
variant + ".js",
variant + ".json",
variant.replace(/(\.js|\.json)$/, ""),
]);
const replacement = matchSubpathFromMainFields(variants, pkg, mainFields);
if (typeof replacement === "string") {
return replacement;
}
return main;
}
function redirectModulePath(context, modulePath) {
const { getPackageForModule, mainFields, originModulePath } = context;
const isModulePathAbsolute = _path.default.isAbsolute(modulePath);
const containingPackage = getPackageForModule(
isModulePathAbsolute ? modulePath : originModulePath
);
if (containingPackage == null) {
return modulePath;
}
let redirectedPath;
if (modulePath.startsWith(".") || isModulePathAbsolute) {
const packageRelativeModulePath = isModulePathAbsolute
? containingPackage.packageRelativePath
: _path.default.join(
_path.default.dirname(containingPackage.packageRelativePath),
modulePath
);
redirectedPath = matchSubpathFromMainFields(
"./" + (0, _toPosixPath.default)(packageRelativeModulePath),
containingPackage.packageJson,
mainFields
);
if (typeof redirectedPath === "string") {
redirectedPath = _path.default.resolve(
containingPackage.rootPath,
redirectedPath
);
}
} else {
redirectedPath = matchSubpathFromMainFields(
modulePath,
containingPackage.packageJson,
mainFields
);
}
if (redirectedPath != null) {
return redirectedPath;
}
return modulePath;
}
function matchSubpathFromMainFields(subpath, pkg, mainFields) {
const fieldValues = mainFields
.map((name) => pkg[name])
.filter((value) => value != null && typeof value !== "string");
if (!fieldValues.length) {
return null;
}
const replacements = Object.assign({}, ...fieldValues.reverse());
const variants = Array.isArray(subpath)
? subpath
: expandSubpathVariants(subpath);
for (const variant of variants) {
const replacement = replacements[variant];
if (replacement != null) {
return replacement;
}
}
return null;
}
function expandSubpathVariants(subpath) {
return [subpath, subpath + ".js", subpath + ".json"];
}

View File

@@ -0,0 +1,208 @@
/**
* 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 {PackageInfo, PackageJson, ResolutionContext} from './types';
import toPosixPath from './utils/toPosixPath';
import path from 'path';
/**
* Resolve the main entry point subpath for a package.
*
* Implements legacy (non-exports) package resolution behaviour based on the
* ["browser" field spec](https://github.com/defunctzombie/package-browser-field-spec).
*/
export function getPackageEntryPoint(
context: ResolutionContext,
packageInfo: PackageInfo,
platform: string | null,
): string {
const {mainFields} = context;
const pkg = packageInfo.packageJson;
let main = 'index';
for (const name of mainFields) {
// $FlowFixMe[invalid-computed-prop]
if (typeof pkg[name] === 'string' && pkg[name].length) {
main = pkg[name];
break;
}
}
// NOTE: Additional variants are used when checking for subpath replacements
// against the main entry point. This inconsistent with those matched by
// `redirectModulePath`, but we are preserving this long-standing behaviour.
const variants = [
main,
main.slice(0, 2) === './' ? main.slice(2) : './' + main,
].flatMap(variant => [
variant,
variant + '.js',
variant + '.json',
variant.replace(/(\.js|\.json)$/, ''),
]);
const replacement = matchSubpathFromMainFields(variants, pkg, mainFields);
if (typeof replacement === 'string') {
return replacement;
}
return main;
}
/**
* Get the resolved file path for the given import specifier based on any
* `package.json` rules. Returns `false` if the module should be
* [ignored](https://github.com/defunctzombie/package-browser-field-spec#ignore-a-module),
* and returns the original path if no `package.json` mapping is matched. Does
* not test file existence.
*
* Implements legacy (non-exports) package resolution behaviour based on the
* ["browser" field spec](https://github.com/defunctzombie/package-browser-field-spec).
*
* This is the default implementation of `context.redirectModulePath`.
*/
export function redirectModulePath(
context: $ReadOnly<{
getPackageForModule: ResolutionContext['getPackageForModule'],
mainFields: ResolutionContext['mainFields'],
originModulePath: ResolutionContext['originModulePath'],
...
}>,
/**
* The module path being imported. This may be:
*
* - A relative specifier (beginning with '.'), which may be redirected by a
* `package.json` file local to `context.originModulePath`.
* - Note: A path begining with '/' is treated as an absolute specifier
* (non-standard).
* - A bare specifier (e.g. 'some-pkg', 'some-pkg/foo'), which may be
* redirected by `package.json` rules in the containing package.
* - An absolute specifier, which may be redirected by `package.json` rules
* in the containing package (non-standard, "browser" spec only).
*
* See https://nodejs.org/docs/latest-v19.x/api/esm.html#import-specifiers
*/
modulePath: string,
): string | false {
const {getPackageForModule, mainFields, originModulePath} = context;
const isModulePathAbsolute = path.isAbsolute(modulePath);
const containingPackage = getPackageForModule(
isModulePathAbsolute ? modulePath : originModulePath,
);
if (containingPackage == null) {
// No package.json rules apply
return modulePath;
}
let redirectedPath;
if (modulePath.startsWith('.') || isModulePathAbsolute) {
const packageRelativeModulePath = isModulePathAbsolute
? // If the module path is absolute, containingPackage is relative to it
// (see above).
containingPackage.packageRelativePath
: // Otherwise containingPackage is relative to the origin module.
// Origin's package-relative directory joined with the target module's
// origin-relative path gives us the module's package-relative path.
path.join(
path.dirname(containingPackage.packageRelativePath),
modulePath,
);
redirectedPath = matchSubpathFromMainFields(
// Use prefixed POSIX path for lookup in package.json
'./' + toPosixPath(packageRelativeModulePath),
containingPackage.packageJson,
mainFields,
);
if (typeof redirectedPath === 'string') {
// BRITTLE ASSUMPTION: This is always treated as a package-relative path
// and is converted back, even if the redirected path is a specifier
// referring to another package.
redirectedPath = path.resolve(containingPackage.rootPath, redirectedPath);
}
} else {
// Otherwise, `modulePath` may be an unprefixed relative path or a bare
// specifier (can also be an absolute specifier prefixed with a URL scheme).
// This is used only by the "browser" spec.
redirectedPath = matchSubpathFromMainFields(
modulePath,
containingPackage.packageJson,
mainFields,
);
}
if (redirectedPath != null) {
return redirectedPath;
}
return modulePath;
}
/**
* Get the mapped replacement for the given subpath defined by matching
* `mainFields` entries in the passed `package.json`
* (https://github.com/defunctzombie/package-browser-field-spec#replace-specific-files---advanced).
*
* Returns either:
* - A `string` with the matched replacement subpath.
* - `false`, indicating the module should be ignored.
* - `null` when there is no entry for the subpath.
*/
function matchSubpathFromMainFields(
/**
* The subpath, or set of subpath variants, to match. Can be either a
* package-relative subpath (beginning with '.') or a bare import specifier
* which may replace a module in another package.
*/
subpath: string | $ReadOnlyArray<string>,
pkg: PackageJson,
mainFields: $ReadOnlyArray<string>,
): string | false | null {
const fieldValues = mainFields
// $FlowFixMe[invalid-computed-prop]
.map(name => pkg[name])
.filter(value => value != null && typeof value !== 'string');
if (!fieldValues.length) {
return null;
}
const replacements = Object.assign({}, ...fieldValues.reverse());
const variants = Array.isArray(subpath)
? subpath
: expandSubpathVariants(subpath);
for (const variant of variants) {
const replacement = replacements[variant];
if (replacement != null) {
return replacement;
}
}
return null;
}
/**
* Get the expanded variants for a given subpath to try against mappings in
* `package.json`. This is unique to "main" and the "browser" spec.
*/
function expandSubpathVariants(subpath: string): Array<string> {
return [subpath, subpath + '.js', subpath + '.json'];
}

View File

@@ -0,0 +1,12 @@
"use strict";
var _PackageResolve = require("./PackageResolve");
function createDefaultContext(context, dependency) {
return {
redirectModulePath: (modulePath) =>
(0, _PackageResolve.redirectModulePath)(context, modulePath),
dependency,
...context,
};
}
module.exports = createDefaultContext;

View File

@@ -0,0 +1,39 @@
/**
* 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 {ResolutionContext} from './types';
import type {TransformResultDependency} from 'metro/src/DeltaBundler/types.flow';
import {redirectModulePath} from './PackageResolve';
type PartialContext = $ReadOnly<{
...ResolutionContext,
redirectModulePath?: ResolutionContext['redirectModulePath'],
}>;
/**
* Helper used by the `metro` package to create the `ResolutionContext` object.
* As context values can be overridden by callers, this occurs externally to
* `resolve.js`.
*/
function createDefaultContext(
context: PartialContext,
dependency: TransformResultDependency,
): ResolutionContext {
return {
redirectModulePath: (modulePath: string) =>
redirectModulePath(context, modulePath),
dependency,
...context,
};
}
module.exports = createDefaultContext;

View File

@@ -0,0 +1,16 @@
"use strict";
class FailedToResolveNameError extends Error {
constructor(dirPaths, extraPaths) {
const displayDirPaths = dirPaths.concat(extraPaths);
const hint = displayDirPaths.length ? " or in these directories:" : "";
super(
`Module does not exist in the Haste module map${hint}\n` +
displayDirPaths.map((dirPath) => ` ${dirPath}`).join("\n") +
"\n"
);
this.dirPaths = dirPaths;
this.extraPaths = extraPaths;
}
}
module.exports = FailedToResolveNameError;

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
* @oncall react_native
*/
'use strict';
class FailedToResolveNameError extends Error {
dirPaths: $ReadOnlyArray<string>;
extraPaths: $ReadOnlyArray<string>;
constructor(
dirPaths: $ReadOnlyArray<string>,
extraPaths: $ReadOnlyArray<string>,
) {
const displayDirPaths = dirPaths.concat(extraPaths);
const hint = displayDirPaths.length ? ' or in these directories:' : '';
super(
`Module does not exist in the Haste module map${hint}\n` +
displayDirPaths.map(dirPath => ` ${dirPath}`).join('\n') +
'\n',
);
this.dirPaths = dirPaths;
this.extraPaths = extraPaths;
}
}
module.exports = FailedToResolveNameError;

View File

@@ -0,0 +1,16 @@
"use strict";
const formatFileCandidates = require("./formatFileCandidates");
class FailedToResolvePathError extends Error {
constructor(candidates) {
super(
"The module could not be resolved because none of these files exist:\n\n" +
[candidates.file, candidates.dir]
.filter(Boolean)
.map((candidates) => ` * ${formatFileCandidates(candidates)}`)
.join("\n")
);
this.candidates = candidates;
}
}
module.exports = FailedToResolvePathError;

View File

@@ -0,0 +1,33 @@
/**
* 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
*/
'use strict';
import type {FileAndDirCandidates} from '../types';
const formatFileCandidates = require('./formatFileCandidates');
class FailedToResolvePathError extends Error {
candidates: FileAndDirCandidates;
constructor(candidates: FileAndDirCandidates) {
super(
'The module could not be resolved because none of these files exist:\n\n' +
[candidates.file, candidates.dir]
.filter(Boolean)
.map(candidates => ` * ${formatFileCandidates(candidates)}`)
.join('\n'),
);
this.candidates = candidates;
}
}
module.exports = FailedToResolvePathError;

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class InvalidPackageConfigurationError extends Error {
constructor(opts) {
super(
`The package ${opts.packagePath} contains an invalid package.json ` +
"configuration. Consider raising this issue with the package " +
"maintainer(s).\nReason: " +
opts.reason
);
Object.assign(this, opts);
}
}
exports.default = InvalidPackageConfigurationError;

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.
*
* @flow strict
* @format
* @oncall react_native
*/
/**
* Raised when a package contains an invalid `package.json` configuration.
*/
export default class InvalidPackageConfigurationError extends Error {
/**
* The description of the error cause.
*/
reason: string;
/**
* Absolute path of the package being resolved.
*/
packagePath: string;
constructor(
opts: $ReadOnly<{
reason: string,
packagePath: string,
}>,
) {
super(
`The package ${opts.packagePath} contains an invalid package.json ` +
'configuration. Consider raising this issue with the package ' +
'maintainer(s).\nReason: ' +
opts.reason,
);
Object.assign(this, opts);
}
}

View File

@@ -0,0 +1,16 @@
"use strict";
const formatFileCandidates = require("./formatFileCandidates");
class InvalidPackageError extends Error {
constructor(opts) {
super(
`The package \`${opts.packageJsonPath}\` is invalid because it ` +
"specifies a `main` module field that could not be resolved (" +
`\`${opts.mainModulePath}\`. None of these files exist:\n\n` +
` * ${formatFileCandidates(opts.fileCandidates)}\n` +
` * ${formatFileCandidates(opts.indexCandidates)}`
);
Object.assign(this, opts);
}
}
module.exports = InvalidPackageError;

View File

@@ -0,0 +1,58 @@
/**
* 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
*/
'use strict';
import type {FileCandidates} from '../types';
const formatFileCandidates = require('./formatFileCandidates');
class InvalidPackageError extends Error {
/**
* The file candidates we tried to find to resolve the `main` field of the
* package. Ex. `/js/foo/beep(.js|.json)?` if `main` is specifying `./beep`
* as the entry point.
*/
fileCandidates: FileCandidates;
/**
* The 'index' file candidates we tried to find to resolve the `main` field of
* the package. Ex. `/js/foo/beep/index(.js|.json)?` if `main` is specifying
* `./beep` as the entry point.
*/
indexCandidates: FileCandidates;
/**
* The full path to the main module that was attempted.
*/
mainModulePath: string;
/**
* Full path the package we were trying to resolve.
* Ex. `/js/foo/package.json`.
*/
packageJsonPath: string;
constructor(opts: {
+fileCandidates: FileCandidates,
+indexCandidates: FileCandidates,
+mainModulePath: string,
+packageJsonPath: string,
}) {
super(
`The package \`${opts.packageJsonPath}\` is invalid because it ` +
'specifies a `main` module field that could not be resolved (' +
`\`${opts.mainModulePath}\`. None of these files exist:\n\n` +
` * ${formatFileCandidates(opts.fileCandidates)}\n` +
` * ${formatFileCandidates(opts.indexCandidates)}`,
);
Object.assign(this, opts);
}
}
module.exports = InvalidPackageError;

View File

@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class PackagePathNotExportedError extends Error {}
exports.default = PackagePathNotExportedError;

View File

@@ -0,0 +1,16 @@
/**
* 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
*/
/**
* Raised when package exports do not define or permit a target subpath in the
* package for the given module.
*/
export default class PackagePathNotExportedError extends Error {}

View File

@@ -0,0 +1,13 @@
"use strict";
function formatFileCandidates(candidates) {
if (candidates.type === "asset") {
return candidates.name;
}
let formatted = candidates.filePathPrefix;
if (candidates.candidateExts.length) {
formatted += "(" + candidates.candidateExts.filter(Boolean).join("|") + ")";
}
return formatted;
}
module.exports = formatFileCandidates;

View File

@@ -0,0 +1,27 @@
/**
* 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
*/
'use strict';
import type {FileCandidates} from '../types';
function formatFileCandidates(candidates: FileCandidates): string {
if (candidates.type === 'asset') {
return candidates.name;
}
let formatted = candidates.filePathPrefix;
if (candidates.candidateExts.length) {
formatted += '(' + candidates.candidateExts.filter(Boolean).join('|') + ')';
}
return formatted;
}
module.exports = formatFileCandidates;

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.
*
* @format
* @oncall react_native
*/
export * from './types';
import {Resolution, ResolutionContext} from './types';
export function resolve(
context: ResolutionContext,
moduleName: string,
platform: string | null,
): Resolution;

View File

@@ -0,0 +1,10 @@
"use strict";
const Resolver = {
FailedToResolveNameError: require("./errors/FailedToResolveNameError"),
FailedToResolvePathError: require("./errors/FailedToResolvePathError"),
formatFileCandidates: require("./errors/formatFileCandidates"),
InvalidPackageError: require("./errors/InvalidPackageError"),
resolve: require("./resolve"),
};
module.exports = Resolver;

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
export type {
AssetFileResolution,
CustomResolutionContext,
CustomResolver,
CustomResolverOptions,
DoesFileExist,
FileAndDirCandidates,
FileCandidates,
FileResolution,
FileSystemLookup,
ResolutionContext,
Resolution,
ResolveAsset,
Result,
} from './types';
const Resolver = {
FailedToResolveNameError: require('./errors/FailedToResolveNameError'),
FailedToResolvePathError: require('./errors/FailedToResolvePathError'),
formatFileCandidates: require('./errors/formatFileCandidates'),
InvalidPackageError: require('./errors/InvalidPackageError'),
resolve: require('./resolve'),
};
module.exports = Resolver;

View File

@@ -0,0 +1,457 @@
"use strict";
var _FailedToResolveNameError = _interopRequireDefault(
require("./errors/FailedToResolveNameError")
);
var _FailedToResolvePathError = _interopRequireDefault(
require("./errors/FailedToResolvePathError")
);
var _formatFileCandidates = _interopRequireDefault(
require("./errors/formatFileCandidates")
);
var _InvalidPackageConfigurationError = _interopRequireDefault(
require("./errors/InvalidPackageConfigurationError")
);
var _InvalidPackageError = _interopRequireDefault(
require("./errors/InvalidPackageError")
);
var _PackagePathNotExportedError = _interopRequireDefault(
require("./errors/PackagePathNotExportedError")
);
var _PackageExportsResolve = require("./PackageExportsResolve");
var _PackageResolve = require("./PackageResolve");
var _resolveAsset = _interopRequireDefault(require("./resolveAsset"));
var _isAssetFile = _interopRequireDefault(require("./utils/isAssetFile"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function resolve(context, moduleName, platform) {
const resolveRequest = context.resolveRequest;
if (resolveRequest && resolveRequest !== resolve) {
return resolveRequest(
Object.freeze({
...context,
resolveRequest: resolve,
}),
moduleName,
platform
);
}
if (isRelativeImport(moduleName) || _path.default.isAbsolute(moduleName)) {
const result = resolveModulePath(context, moduleName, platform);
if (result.type === "failed") {
throw new _FailedToResolvePathError.default(result.candidates);
}
return result.resolution;
}
const realModuleName = context.redirectModulePath(moduleName);
if (realModuleName === false) {
return {
type: "empty",
};
}
const { originModulePath } = context;
const isDirectImport =
isRelativeImport(realModuleName) ||
_path.default.isAbsolute(realModuleName);
if (isDirectImport) {
const fromModuleParentIdx =
originModulePath.lastIndexOf("node_modules" + _path.default.sep) + 13;
const originModuleDir = originModulePath.slice(
0,
originModulePath.indexOf(_path.default.sep, fromModuleParentIdx)
);
const absPath = _path.default.join(originModuleDir, realModuleName);
const result = resolveModulePath(context, absPath, platform);
if (result.type === "failed") {
throw new _FailedToResolvePathError.default(result.candidates);
}
return result.resolution;
}
if (context.allowHaste) {
const normalizedName = normalizePath(realModuleName);
const result = resolveHasteName(context, normalizedName, platform);
if (result.type === "resolved") {
return result.resolution;
}
}
const { disableHierarchicalLookup } = context;
const nodeModulesPaths = [];
let next = _path.default.dirname(originModulePath);
if (!disableHierarchicalLookup) {
let candidate;
do {
candidate = next;
const nodeModulesPath = candidate.endsWith(_path.default.sep)
? candidate + "node_modules"
: candidate + _path.default.sep + "node_modules";
nodeModulesPaths.push(nodeModulesPath);
next = _path.default.dirname(candidate);
} while (candidate !== next);
}
nodeModulesPaths.push(...context.nodeModulesPaths);
const extraPaths = [];
const parsedSpecifier = parsePackageSpecifier(realModuleName);
const { extraNodeModules } = context;
if (extraNodeModules && extraNodeModules[parsedSpecifier.packageName]) {
const newPackageName = extraNodeModules[parsedSpecifier.packageName];
extraPaths.push(
_path.default.join(newPackageName, parsedSpecifier.posixSubpath)
);
}
const allDirPaths = nodeModulesPaths
.map((nodeModulePath) => {
let lookupResult = null;
if (context.unstable_fileSystemLookup) {
const mustBeDirectory =
parsedSpecifier.posixSubpath !== "." ||
parsedSpecifier.packageName.length > parsedSpecifier.firstPart.length
? nodeModulePath + _path.default.sep + parsedSpecifier.firstPart
: nodeModulePath;
lookupResult = context.unstable_fileSystemLookup(mustBeDirectory);
if (!lookupResult.exists || lookupResult.type !== "d") {
return null;
}
}
return _path.default.join(nodeModulePath, realModuleName);
})
.filter(Boolean)
.concat(extraPaths);
for (let i = 0; i < allDirPaths.length; ++i) {
const candidate = context.redirectModulePath(allDirPaths[i]);
if (candidate === false) {
return {
type: "empty",
};
}
const result = resolvePackage(context, candidate, platform);
if (result.type === "resolved") {
return result.resolution;
}
}
throw new _FailedToResolveNameError.default(nodeModulesPaths, extraPaths);
}
function parsePackageSpecifier(specifier) {
const normalized =
_path.default.sep === "/" ? specifier : specifier.replaceAll("\\", "/");
const firstSepIdx = normalized.indexOf("/");
if (normalized.startsWith("@") && firstSepIdx !== -1) {
const secondSepIdx = normalized.indexOf("/", firstSepIdx + 1);
if (secondSepIdx === -1) {
return {
firstPart: normalized.slice(0, firstSepIdx),
packageName: normalized,
posixSubpath: ".",
};
}
return {
firstPart: normalized.slice(0, firstSepIdx),
packageName: normalized.slice(0, secondSepIdx),
posixSubpath: "." + normalized.slice(secondSepIdx),
};
}
if (firstSepIdx === -1) {
return {
firstPart: normalized,
packageName: normalized,
posixSubpath: ".",
};
}
const packageName = normalized.slice(0, firstSepIdx);
return {
firstPart: packageName,
packageName,
posixSubpath: "." + normalized.slice(firstSepIdx),
};
}
function resolveModulePath(context, toModuleName, platform) {
const modulePath = _path.default.isAbsolute(toModuleName)
? resolveWindowsPath(toModuleName)
: _path.default.join(
_path.default.dirname(context.originModulePath),
toModuleName
);
const redirectedPath = context.redirectModulePath(modulePath);
if (redirectedPath === false) {
return resolvedAs({
type: "empty",
});
}
const dirPath = _path.default.dirname(redirectedPath);
const fileName = _path.default.basename(redirectedPath);
const fileResult = redirectedPath.endsWith(_path.default.sep)
? null
: resolveFile(context, dirPath, fileName, platform);
if (fileResult != null && fileResult.type === "resolved") {
return fileResult;
}
const dirResult = resolvePackageEntryPoint(context, redirectedPath, platform);
if (dirResult.type === "resolved") {
return dirResult;
}
return failedFor({
file: fileResult?.candidates ?? null,
dir: dirResult.candidates,
});
}
function resolveHasteName(context, moduleName, platform) {
const modulePath = context.resolveHasteModule(moduleName);
if (modulePath != null) {
return resolvedAs({
type: "sourceFile",
filePath: modulePath,
});
}
let packageName = moduleName;
let packageJsonPath = context.resolveHastePackage(packageName);
while (packageJsonPath == null && packageName && packageName !== ".") {
packageName = _path.default.dirname(packageName);
packageJsonPath = context.resolveHastePackage(packageName);
}
if (packageJsonPath == null) {
return failedFor();
}
const packageDirPath = _path.default.dirname(packageJsonPath);
const pathInModule = moduleName.substring(packageName.length + 1);
const potentialModulePath = _path.default.join(packageDirPath, pathInModule);
const result = resolvePackage(context, potentialModulePath, platform);
if (result.type === "resolved") {
return result;
}
const { candidates } = result;
const opts = {
moduleName,
packageName,
pathInModule,
candidates,
};
throw new MissingFileInHastePackageError(opts);
}
class MissingFileInHastePackageError extends Error {
constructor(opts) {
super(
`While resolving module \`${opts.moduleName}\`, ` +
`the Haste package \`${opts.packageName}\` was found. However the ` +
`module \`${opts.pathInModule}\` could not be found within ` +
"the package. Indeed, none of these files exist:\n\n" +
[opts.candidates.file, opts.candidates.dir]
.filter(Boolean)
.map(
(candidates) =>
` * \`${(0, _formatFileCandidates.default)(candidates)}\``
)
.join("\n")
);
Object.assign(this, opts);
}
}
function resolvePackage(context, absoluteCandidatePath, platform) {
if (context.unstable_enablePackageExports) {
const pkg = context.getPackageForModule(absoluteCandidatePath);
const exportsField = pkg?.packageJson.exports;
if (pkg != null && exportsField != null) {
let conditionNamesOverride = context.unstable_conditionNames;
if (pkg.packageJson.name === "@babel/runtime") {
conditionNamesOverride = context.unstable_conditionNames.filter(
(condition) => condition !== "import"
);
}
try {
const packageExportsResult = (0,
_PackageExportsResolve.resolvePackageTargetFromExports)(
{
...context,
unstable_conditionNames: conditionNamesOverride,
},
pkg.rootPath,
absoluteCandidatePath,
pkg.packageRelativePath,
exportsField,
platform
);
if (packageExportsResult != null) {
return resolvedAs(packageExportsResult);
}
} catch (e) {
if (e instanceof _PackagePathNotExportedError.default) {
context.unstable_logWarning(
e.message +
" Falling back to file-based resolution. Consider updating the " +
"call site or asking the package maintainer(s) to expose this API."
);
} else if (e instanceof _InvalidPackageConfigurationError.default) {
context.unstable_logWarning(
e.message + " Falling back to file-based resolution."
);
} else {
throw e;
}
}
}
}
return resolveModulePath(context, absoluteCandidatePath, platform);
}
function resolvePackageEntryPoint(context, packagePath, platform) {
const dirLookup = context.unstable_fileSystemLookup?.(packagePath);
if (
dirLookup != null &&
(dirLookup.exists === false || dirLookup.type !== "d")
) {
return failedFor({
type: "sourceFile",
filePathPrefix: packagePath,
candidateExts: [],
});
}
const packageJsonPath = _path.default.join(packagePath, "package.json");
if (!context.doesFileExist(packageJsonPath)) {
return resolveFile(context, packagePath, "index", platform);
}
const packageInfo = {
rootPath: _path.default.dirname(packageJsonPath),
packageJson: context.getPackage(packageJsonPath) ?? {},
};
const mainModulePath = _path.default.join(
packageInfo.rootPath,
(0, _PackageResolve.getPackageEntryPoint)(context, packageInfo, platform)
);
const fileResult = resolveFile(
context,
_path.default.dirname(mainModulePath),
_path.default.basename(mainModulePath),
platform
);
if (fileResult.type === "resolved") {
return fileResult;
}
const indexResult = resolveFile(context, mainModulePath, "index", platform);
if (indexResult.type !== "resolved") {
throw new _InvalidPackageError.default({
packageJsonPath,
mainModulePath,
fileCandidates: fileResult.candidates,
indexCandidates: indexResult.candidates,
});
}
return indexResult;
}
function resolveFile(context, dirPath, fileName, platform) {
if ((0, _isAssetFile.default)(fileName, context.assetExts)) {
const assetResolutions = (0, _resolveAsset.default)(
context,
_path.default.join(dirPath, fileName)
);
if (assetResolutions == null) {
return failedFor({
type: "asset",
name: fileName,
});
}
return resolvedAs(assetResolutions);
}
const candidateExts = [];
const filePathPrefix = _path.default.join(dirPath, fileName);
const sfContext = {
...context,
candidateExts,
filePathPrefix,
};
const sourceFileResolution = resolveSourceFile(sfContext, platform);
if (sourceFileResolution != null) {
if (typeof sourceFileResolution === "string") {
return resolvedAs({
type: "sourceFile",
filePath: sourceFileResolution,
});
}
return resolvedAs(sourceFileResolution);
}
return failedFor({
type: "sourceFile",
filePathPrefix,
candidateExts,
});
}
function resolveSourceFile(context, platform) {
let filePath = resolveSourceFileForAllExts(context, "");
if (filePath) {
return filePath;
}
const { sourceExts } = context;
for (let i = 0; i < sourceExts.length; i++) {
const ext = `.${sourceExts[i]}`;
filePath = resolveSourceFileForAllExts(context, ext, platform);
if (filePath != null) {
return filePath;
}
}
return null;
}
function resolveSourceFileForAllExts(context, sourceExt, platform) {
if (platform != null) {
const ext = `.${platform}${sourceExt}`;
const filePath = resolveSourceFileForExt(context, ext);
if (filePath) {
return filePath;
}
}
if (context.preferNativePlatform && sourceExt !== "") {
const filePath = resolveSourceFileForExt(context, `.native${sourceExt}`);
if (filePath) {
return filePath;
}
}
const filePath = resolveSourceFileForExt(context, sourceExt);
return filePath;
}
function resolveSourceFileForExt(context, extension) {
const filePath = `${context.filePathPrefix}${extension}`;
const redirectedPath =
extension !== "" ? context.redirectModulePath(filePath) : filePath;
if (redirectedPath === false) {
return {
type: "empty",
};
}
if (context.unstable_fileSystemLookup) {
const lookupResult = context.unstable_fileSystemLookup(redirectedPath);
if (lookupResult.exists && lookupResult.type === "f") {
return lookupResult.realPath;
}
} else if (context.doesFileExist(redirectedPath)) {
return redirectedPath;
}
context.candidateExts.push(extension);
return null;
}
function resolveWindowsPath(modulePath) {
if (_path.default.sep !== "\\") {
return modulePath;
}
return _path.default.resolve(modulePath);
}
function isRelativeImport(filePath) {
return /^[.][.]?(?:[/]|$)/.test(filePath);
}
function normalizePath(modulePath) {
if (_path.default.sep === "/") {
modulePath = _path.default.normalize(modulePath);
} else if (_path.default.posix) {
modulePath = _path.default.posix.normalize(modulePath);
}
return modulePath.replace(/\/$/, "");
}
function resolvedAs(resolution) {
return {
type: "resolved",
resolution,
};
}
function failedFor(candidates) {
return {
type: "failed",
candidates,
};
}
module.exports = resolve;

View File

@@ -0,0 +1,623 @@
/**
* 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
* @format
* @oncall react_native
*/
'use strict';
import type {
FileAndDirCandidates,
FileCandidates,
Resolution,
ResolutionContext,
Result,
} from './types';
import FailedToResolveNameError from './errors/FailedToResolveNameError';
import FailedToResolvePathError from './errors/FailedToResolvePathError';
import formatFileCandidates from './errors/formatFileCandidates';
import InvalidPackageConfigurationError from './errors/InvalidPackageConfigurationError';
import InvalidPackageError from './errors/InvalidPackageError';
import PackagePathNotExportedError from './errors/PackagePathNotExportedError';
import {resolvePackageTargetFromExports} from './PackageExportsResolve';
import {getPackageEntryPoint} from './PackageResolve';
import resolveAsset from './resolveAsset';
import isAssetFile from './utils/isAssetFile';
import path from 'path';
function resolve(
context: ResolutionContext,
moduleName: string,
platform: string | null,
): Resolution {
const resolveRequest = context.resolveRequest;
if (
resolveRequest &&
// Prevent infinite recursion in the trivial case
resolveRequest !== resolve
) {
return resolveRequest(
Object.freeze({...context, resolveRequest: resolve}),
moduleName,
platform,
);
}
if (isRelativeImport(moduleName) || path.isAbsolute(moduleName)) {
const result = resolveModulePath(context, moduleName, platform);
if (result.type === 'failed') {
throw new FailedToResolvePathError(result.candidates);
}
return result.resolution;
}
const realModuleName = context.redirectModulePath(moduleName);
// exclude
if (realModuleName === false) {
return {type: 'empty'};
}
const {originModulePath} = context;
const isDirectImport =
isRelativeImport(realModuleName) || path.isAbsolute(realModuleName);
if (isDirectImport) {
// derive absolute path /.../node_modules/originModuleDir/realModuleName
const fromModuleParentIdx =
originModulePath.lastIndexOf('node_modules' + path.sep) + 13;
const originModuleDir = originModulePath.slice(
0,
originModulePath.indexOf(path.sep, fromModuleParentIdx),
);
const absPath = path.join(originModuleDir, realModuleName);
const result = resolveModulePath(context, absPath, platform);
if (result.type === 'failed') {
throw new FailedToResolvePathError(result.candidates);
}
return result.resolution;
}
/**
* At this point, realModuleName is not a "direct" (absolute or relative)
* import, so it's either Haste name or a package specifier.
*/
if (context.allowHaste) {
const normalizedName = normalizePath(realModuleName);
const result = resolveHasteName(context, normalizedName, platform);
if (result.type === 'resolved') {
return result.resolution;
}
}
/**
* realModuleName is now a package specifier.
*/
const {disableHierarchicalLookup} = context;
const nodeModulesPaths = [];
let next = path.dirname(originModulePath);
if (!disableHierarchicalLookup) {
let candidate;
do {
candidate = next;
const nodeModulesPath = candidate.endsWith(path.sep)
? candidate + 'node_modules'
: candidate + path.sep + 'node_modules';
nodeModulesPaths.push(nodeModulesPath);
next = path.dirname(candidate);
} while (candidate !== next);
}
// Fall back to `nodeModulesPaths` after hierarchical lookup, similar to $NODE_PATH
nodeModulesPaths.push(...context.nodeModulesPaths);
const extraPaths = [];
const parsedSpecifier = parsePackageSpecifier(realModuleName);
const {extraNodeModules} = context;
if (extraNodeModules && extraNodeModules[parsedSpecifier.packageName]) {
const newPackageName = extraNodeModules[parsedSpecifier.packageName];
extraPaths.push(path.join(newPackageName, parsedSpecifier.posixSubpath));
}
const allDirPaths = nodeModulesPaths
.map(nodeModulePath => {
let lookupResult = null;
if (context.unstable_fileSystemLookup) {
// Insight: The module can only exist if there is a `node_modules` at
// this path. Redirections cannot succeed, because we will never look
// beyond a node_modules path segment for finding the closest
// package.json. Moreover, if the specifier contains a '/' separator,
// the first part *must* be a real directory, because it is the
// shallowest path that can possibly contain a redirecting package.json.
const mustBeDirectory =
parsedSpecifier.posixSubpath !== '.' ||
parsedSpecifier.packageName.length > parsedSpecifier.firstPart.length
? nodeModulePath + path.sep + parsedSpecifier.firstPart
: nodeModulePath;
lookupResult = context.unstable_fileSystemLookup(mustBeDirectory);
if (!lookupResult.exists || lookupResult.type !== 'd') {
return null;
}
}
return path.join(nodeModulePath, realModuleName);
})
.filter(Boolean)
.concat(extraPaths);
for (let i = 0; i < allDirPaths.length; ++i) {
const candidate = context.redirectModulePath(allDirPaths[i]);
if (candidate === false) {
return {type: 'empty'};
}
// candidate should be absolute here - we assume that redirectModulePath
// always returns an absolute path when given an absolute path.
const result = resolvePackage(context, candidate, platform);
if (result.type === 'resolved') {
return result.resolution;
}
}
throw new FailedToResolveNameError(nodeModulesPaths, extraPaths);
}
function parsePackageSpecifier(specifier: string) {
const normalized =
path.sep === '/' ? specifier : specifier.replaceAll('\\', '/');
const firstSepIdx = normalized.indexOf('/');
if (normalized.startsWith('@') && firstSepIdx !== -1) {
const secondSepIdx = normalized.indexOf('/', firstSepIdx + 1);
if (secondSepIdx === -1) {
return {
firstPart: normalized.slice(0, firstSepIdx),
packageName: normalized,
posixSubpath: '.',
};
}
return {
firstPart: normalized.slice(0, firstSepIdx),
packageName: normalized.slice(0, secondSepIdx),
posixSubpath: '.' + normalized.slice(secondSepIdx),
};
}
if (firstSepIdx === -1) {
return {
firstPart: normalized,
packageName: normalized,
posixSubpath: '.',
};
}
const packageName = normalized.slice(0, firstSepIdx);
return {
firstPart: packageName,
packageName,
posixSubpath: '.' + normalized.slice(firstSepIdx),
};
}
/**
* Resolve any kind of module path, whether it's a file or a directory.
* For example we may want to resolve './foobar'. The closest
* `package.json` may define a redirection for this path, for example
* `/smth/lib/foobar`, that may be further resolved to
* `/smth/lib/foobar/index.ios.js`.
*/
function resolveModulePath(
context: ResolutionContext,
toModuleName: string,
platform: string | null,
): Result<Resolution, FileAndDirCandidates> {
const modulePath = path.isAbsolute(toModuleName)
? resolveWindowsPath(toModuleName)
: path.join(path.dirname(context.originModulePath), toModuleName);
const redirectedPath = context.redirectModulePath(modulePath);
if (redirectedPath === false) {
return resolvedAs({type: 'empty'});
}
const dirPath = path.dirname(redirectedPath);
const fileName = path.basename(redirectedPath);
const fileResult: ?Result<Resolution, FileCandidates> =
// require('./foo/') should never resolve to ./foo.js - a trailing slash
// implies we should resolve as a directory only.
redirectedPath.endsWith(path.sep)
? null
: resolveFile(context, dirPath, fileName, platform);
if (fileResult != null && fileResult.type === 'resolved') {
return fileResult;
}
const dirResult = resolvePackageEntryPoint(context, redirectedPath, platform);
if (dirResult.type === 'resolved') {
return dirResult;
}
return failedFor({
file: fileResult?.candidates ?? null,
dir: dirResult.candidates,
});
}
/**
* Resolve a module as a Haste module or package. For example we might try to
* resolve `Foo`, that is provided by file `/smth/Foo.js`. Or, in the case of
* a Haste package, it could be `/smth/Foo/index.js`.
*/
function resolveHasteName(
context: ResolutionContext,
moduleName: string,
platform: string | null,
): Result<Resolution, void> {
const modulePath = context.resolveHasteModule(moduleName);
if (modulePath != null) {
return resolvedAs({type: 'sourceFile', filePath: modulePath});
}
let packageName = moduleName;
let packageJsonPath = context.resolveHastePackage(packageName);
while (packageJsonPath == null && packageName && packageName !== '.') {
packageName = path.dirname(packageName);
packageJsonPath = context.resolveHastePackage(packageName);
}
if (packageJsonPath == null) {
return failedFor();
}
const packageDirPath = path.dirname(packageJsonPath);
const pathInModule = moduleName.substring(packageName.length + 1);
const potentialModulePath = path.join(packageDirPath, pathInModule);
const result = resolvePackage(context, potentialModulePath, platform);
if (result.type === 'resolved') {
return result;
}
const {candidates} = result;
const opts = {moduleName, packageName, pathInModule, candidates};
throw new MissingFileInHastePackageError(opts);
}
class MissingFileInHastePackageError extends Error {
candidates: FileAndDirCandidates;
moduleName: string;
packageName: string;
pathInModule: string;
constructor(opts: {
+candidates: FileAndDirCandidates,
+moduleName: string,
+packageName: string,
+pathInModule: string,
}) {
super(
`While resolving module \`${opts.moduleName}\`, ` +
`the Haste package \`${opts.packageName}\` was found. However the ` +
`module \`${opts.pathInModule}\` could not be found within ` +
'the package. Indeed, none of these files exist:\n\n' +
[opts.candidates.file, opts.candidates.dir]
.filter(Boolean)
.map(candidates => ` * \`${formatFileCandidates(candidates)}\``)
.join('\n'),
);
Object.assign(this, opts);
}
}
/**
* Resolve a package entry point or subpath target.
*
* This should be used when resolving a bare import specifier prefixed with the
* package name. Use `resolveModulePath` instead to scope to legacy "browser"
* spec behaviour, which is also applicable to relative and absolute imports.
*/
function resolvePackage(
context: ResolutionContext,
/**
* The absolute path to a file or directory that may be contained within an
* npm package, e.g. from being joined with `context.extraNodeModules`.
*/
absoluteCandidatePath: string,
platform: string | null,
): Result<Resolution, FileAndDirCandidates> {
if (context.unstable_enablePackageExports) {
const pkg = context.getPackageForModule(absoluteCandidatePath);
const exportsField = pkg?.packageJson.exports;
if (pkg != null && exportsField != null) {
let conditionNamesOverride = context.unstable_conditionNames;
// HACK!: Do not assert the "import" condition for `@babel/runtime`. This
// is a workaround for ESM <-> CJS interop, as we need the CJS versions of
// `@babel/runtime` helpers.
// TODO(T154157178): Remove with better "require"/"import" solution
if (pkg.packageJson.name === '@babel/runtime') {
conditionNamesOverride = context.unstable_conditionNames.filter(
condition => condition !== 'import',
);
}
try {
const packageExportsResult = resolvePackageTargetFromExports(
{...context, unstable_conditionNames: conditionNamesOverride},
pkg.rootPath,
absoluteCandidatePath,
pkg.packageRelativePath,
exportsField,
platform,
);
if (packageExportsResult != null) {
return resolvedAs(packageExportsResult);
}
} catch (e) {
if (e instanceof PackagePathNotExportedError) {
context.unstable_logWarning(
e.message +
' Falling back to file-based resolution. Consider updating the ' +
'call site or asking the package maintainer(s) to expose this API.',
);
} else if (e instanceof InvalidPackageConfigurationError) {
context.unstable_logWarning(
e.message + ' Falling back to file-based resolution.',
);
} else {
throw e;
}
}
}
}
return resolveModulePath(context, absoluteCandidatePath, platform);
}
/**
* Attempt to resolve a module path as an npm package entry point, or resolve as
* a file if no `package.json` file is present.
*
* Implements legacy (non-exports) package resolution behaviour based on the
* ["browser" field spec](https://github.com/defunctzombie/package-browser-field-spec):
* - Looks for a "main" entry point based on `context.mainFields`.
* - Considers any "main" subpaths after expending source and platform-specific
* extensions, e.g. `./lib/index` -> `./lib/index.ios.js`.
* - Falls back to a child `index.js` file, e.g. `./lib` -> `./lib/index.js`.
*/
function resolvePackageEntryPoint(
context: ResolutionContext,
packagePath: string,
platform: string | null,
): Result<Resolution, FileCandidates> {
const dirLookup = context.unstable_fileSystemLookup?.(packagePath);
if (
dirLookup != null &&
(dirLookup.exists === false || dirLookup.type !== 'd')
) {
return failedFor({
type: 'sourceFile',
filePathPrefix: packagePath,
candidateExts: [],
});
}
const packageJsonPath = path.join(packagePath, 'package.json');
if (!context.doesFileExist(packageJsonPath)) {
return resolveFile(context, packagePath, 'index', platform);
}
const packageInfo = {
rootPath: path.dirname(packageJsonPath),
packageJson: context.getPackage(packageJsonPath) ?? {},
};
const mainModulePath = path.join(
packageInfo.rootPath,
getPackageEntryPoint(context, packageInfo, platform),
);
const fileResult = resolveFile(
context,
path.dirname(mainModulePath),
path.basename(mainModulePath),
platform,
);
if (fileResult.type === 'resolved') {
return fileResult;
}
// Fallback: Attempt to resolve any file at <subpath>/index.js
const indexResult = resolveFile(context, mainModulePath, 'index', platform);
if (indexResult.type !== 'resolved') {
throw new InvalidPackageError({
packageJsonPath,
mainModulePath,
fileCandidates: fileResult.candidates,
indexCandidates: indexResult.candidates,
});
}
return indexResult;
}
/**
* Given a file name for a particular directory, return a resolution result
* depending on whether or not we found the corresponding module as a file. For
* example, we might ask for `foo.png`, that resolves to
* `['/js/beep/foo.ios.png']`. Or we may ask for `boop`, that resolves to
* `/js/boop.android.ts`. On the other hand this function does not resolve
* directory-based module names: for example `boop` will not resolve to
* `/js/boop/index.js` (see `_loadAsDir` for that).
*/
function resolveFile(
context: ResolutionContext,
dirPath: string,
fileName: string,
platform: string | null,
): Result<Resolution, FileCandidates> {
if (isAssetFile(fileName, context.assetExts)) {
const assetResolutions = resolveAsset(
context,
path.join(dirPath, fileName),
);
if (assetResolutions == null) {
return failedFor({type: 'asset', name: fileName});
}
return resolvedAs(assetResolutions);
}
const candidateExts: Array<string> = [];
const filePathPrefix = path.join(dirPath, fileName);
const sfContext = {...context, candidateExts, filePathPrefix};
const sourceFileResolution = resolveSourceFile(sfContext, platform);
if (sourceFileResolution != null) {
if (typeof sourceFileResolution === 'string') {
return resolvedAs({type: 'sourceFile', filePath: sourceFileResolution});
}
return resolvedAs(sourceFileResolution);
}
return failedFor({type: 'sourceFile', filePathPrefix, candidateExts});
}
type SourceFileContext = $ReadOnly<{
...ResolutionContext,
candidateExts: Array<string>,
filePathPrefix: string,
}>;
// Either a full path, or a restricted subset of Resolution.
type SourceFileResolution = ?string | $ReadOnly<{type: 'empty'}>;
/**
* A particular 'base path' can resolve to a number of possibilities depending
* on the context. For example `foo/bar` could resolve to `foo/bar.ios.js`, or
* to `foo/bar.js`. If can also resolve to the bare path `foo/bar` itself, as
* supported by Node.js resolution. On the other hand it doesn't support
* `foo/bar.ios`, for historical reasons.
*
* Return the full path of the resolved module, `null` if no resolution could
* be found, or `{type: 'empty'}` if redirected to an empty module.
*/
function resolveSourceFile(
context: SourceFileContext,
platform: ?string,
): SourceFileResolution {
let filePath = resolveSourceFileForAllExts(context, '');
if (filePath) {
return filePath;
}
const {sourceExts} = context;
for (let i = 0; i < sourceExts.length; i++) {
const ext = `.${sourceExts[i]}`;
filePath = resolveSourceFileForAllExts(context, ext, platform);
if (filePath != null) {
return filePath;
}
}
return null;
}
/**
* For a particular extension, ex. `js`, we want to try a few possibilities,
* such as `foo.ios.js`, `foo.native.js`, and of course `foo.js`. Return the
* full path of the resolved module, `null` if no resolution could be found, or
* `{type: 'empty'}` if redirected to an empty module.
*/
function resolveSourceFileForAllExts(
context: SourceFileContext,
sourceExt: string,
platform: ?string,
): SourceFileResolution {
if (platform != null) {
const ext = `.${platform}${sourceExt}`;
const filePath = resolveSourceFileForExt(context, ext);
if (filePath) {
return filePath;
}
}
if (context.preferNativePlatform && sourceExt !== '') {
const filePath = resolveSourceFileForExt(context, `.native${sourceExt}`);
if (filePath) {
return filePath;
}
}
const filePath = resolveSourceFileForExt(context, sourceExt);
return filePath;
}
/**
* We try to resolve a single possible extension. If it doesn't exist, then
* we make sure to add the extension to a list of candidates for reporting.
*/
function resolveSourceFileForExt(
context: SourceFileContext,
extension: string,
): SourceFileResolution {
const filePath = `${context.filePathPrefix}${extension}`;
const redirectedPath =
// Any redirections for the bare path have already happened
extension !== '' ? context.redirectModulePath(filePath) : filePath;
if (redirectedPath === false) {
return {type: 'empty'};
}
if (context.unstable_fileSystemLookup) {
const lookupResult = context.unstable_fileSystemLookup(redirectedPath);
if (lookupResult.exists && lookupResult.type === 'f') {
return lookupResult.realPath;
}
} else if (context.doesFileExist(redirectedPath)) {
return redirectedPath;
}
context.candidateExts.push(extension);
return null;
}
// HasteFS stores paths with backslashes on Windows, this ensures the path is in
// the proper format. Will also add drive letter if not present so `/root` will
// resolve to `C:\root`. Noop on other platforms.
function resolveWindowsPath(modulePath: string) {
if (path.sep !== '\\') {
return modulePath;
}
return path.resolve(modulePath);
}
function isRelativeImport(filePath: string) {
return /^[.][.]?(?:[/]|$)/.test(filePath);
}
function normalizePath(modulePath: any | string) {
if (path.sep === '/') {
modulePath = path.normalize(modulePath);
} else if (path.posix) {
modulePath = path.posix.normalize(modulePath);
}
return modulePath.replace(/\/$/, '');
}
function resolvedAs<TResolution, TCandidates>(
resolution: TResolution,
): Result<TResolution, TCandidates> {
return {type: 'resolved', resolution};
}
function failedFor<TResolution, TCandidates>(
candidates: TCandidates,
): Result<TResolution, TCandidates> {
return {type: 'failed', candidates};
}
module.exports = resolve;

View File

@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = resolveAsset;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function resolveAsset(context, filePath) {
const dirPath = _path.default.dirname(filePath);
const extension = _path.default.extname(filePath);
const basename = _path.default.basename(filePath, extension);
try {
if (!/@\d+(?:\.\d+)?x$/.test(basename)) {
const assets = context.resolveAsset(dirPath, basename, extension);
if (assets != null) {
return {
type: "assetFiles",
filePaths: assets,
};
}
}
} catch (e) {}
return null;
}

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
* @oncall react_native
*/
import type {AssetResolution, ResolutionContext} from './types';
import path from 'path';
/**
* Resolve a file path as an asset. Returns the set of files found after
* expanding asset resolutions (e.g. `icon@2x.png`). Users may override this
* behaviour via `context.resolveAsset`.
*/
export default function resolveAsset(
context: ResolutionContext,
filePath: string,
): AssetResolution | null {
const dirPath = path.dirname(filePath);
const extension = path.extname(filePath);
const basename = path.basename(filePath, extension);
try {
if (!/@\d+(?:\.\d+)?x$/.test(basename)) {
const assets = context.resolveAsset(dirPath, basename, extension);
if (assets != null) {
return {
type: 'assetFiles',
filePaths: assets,
};
}
}
} catch (e) {}
return null;
}

View File

@@ -0,0 +1,184 @@
/**
* 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 {TransformResultDependency} from 'metro';
export type Result<TResolution, TCandidates> =
| {readonly type: 'resolved'; readonly resolution: TResolution}
| {readonly type: 'failed'; readonly candidates: TCandidates};
export type Resolution = FileResolution | Readonly<{type: 'empty'}>;
export type SourceFileResolution = Readonly<{
type: 'sourceFile';
filePath: string;
}>;
export type AssetFileResolution = ReadonlyArray<string>;
export type AssetResolution = Readonly<{
type: 'assetFiles';
filePaths: AssetFileResolution;
}>;
export type FileResolution = AssetResolution | SourceFileResolution;
export interface FileAndDirCandidates {
readonly dir: FileCandidates;
readonly file: FileCandidates;
}
/**
* This is a way to describe what files we tried to look for when resolving
* a module name as file. This is mainly used for error reporting, so that
* we can explain why we cannot resolve a module.
*/
export type FileCandidates =
// We only tried to resolve a specific asset.
| {readonly type: 'asset'; readonly name: string}
// We attempted to resolve a name as being a source file (ex. JavaScript,
// JSON...), in which case there can be several extensions we tried, for
// example `/js/foo.ios.js`, `/js/foo.js`, etc. for a single prefix '/js/foo'.
| {
readonly type: 'sourceFile';
filePathPrefix: string;
readonly candidateExts: ReadonlyArray<string>;
};
export type ExportMap = Readonly<{
[subpathOrCondition: string]: ExportMap | string | null;
}>;
export interface PackageJson {
readonly name?: string;
readonly main?: string;
readonly exports?: string | ExportMap;
}
export interface PackageInfo {
readonly packageJson: PackageJson;
readonly rootPath: string;
}
export interface PackageForModule extends PackageInfo {
/* A system-separated subpath (with no './' prefix) that reflects the subpath
of the given candidate relative to the returned rootPath. */
readonly packageRelativePath: string;
}
/**
* Check existence of a single file.
*/
export type DoesFileExist = (filePath: string) => boolean;
export type IsAssetFile = (fileName: string) => boolean;
/**
* Performs a lookup against an absolute or project-relative path to determine
* whether it exists as a file or directory. Follows any symlinks, and returns
* a real absolute path on existence.
*/
export type FileSystemLookup = (
absoluteOrProjectRelativePath: string,
) => {exists: false} | {exists: true; type: 'f' | 'd'; realPath: string};
/**
* Given a directory path and the base asset name, return a list of all the
* asset file names that match the given base name in that directory. Return
* null if there's no such named asset. `platform` is used to identify
* platform-specific assets, ex. `foo.ios.js` instead of a generic `foo.js`.
*/
export type ResolveAsset = (
dirPath: string,
assetName: string,
extension: string,
) => ReadonlyArray<string> | undefined;
export interface ResolutionContext {
readonly assetExts: ReadonlyArray<string>;
readonly allowHaste: boolean;
readonly customResolverOptions: CustomResolverOptions;
readonly disableHierarchicalLookup: boolean;
readonly doesFileExist: DoesFileExist;
readonly extraNodeModules?: {[key: string]: string};
/**
* Get the parsed contents of the specified `package.json` file.
*/
readonly getPackage: (packageJsonPath: string) => PackageJson | null;
/**
* Get the closest package scope, parsed `package.json` and relative subpath
* for a given absolute candidate path (which need not exist), or null if
* there is no package.json closer than the nearest node_modules directory.
*
* @deprecated See https://github.com/facebook/metro/commit/29c77bff31e2475a086bc3f04073f485da8f9ff0
*/
readonly getPackageForModule: (
absoluteModulePath: string,
) => PackageForModule | null;
/**
* The dependency descriptor, within the origin module, corresponding to the
* current resolution request. This is provided for diagnostic purposes ONLY
* and may not be used for resolution purposes.
*/
readonly dependency?: TransformResultDependency;
/**
* The ordered list of fields to read in `package.json` to resolve a main
* entry point based on the "browser" field spec.
*/
readonly mainFields: ReadonlyArray<string>;
/**
* Full path of the module that is requiring or importing the module to be
* resolved. This may not be the only place this dependency was found,
* as resolutions can be cached.
*/
readonly originModulePath: string;
readonly nodeModulesPaths: ReadonlyArray<string>;
readonly preferNativePlatform: boolean;
readonly resolveAsset: ResolveAsset;
readonly redirectModulePath: (modulePath: string) => string | false;
/**
* Given a name, this should return the full path to the file that provides
* a Haste module of that name. Ex. for `Foo` it may return `/smth/Foo.js`.
*/
readonly resolveHasteModule: (name: string) => string | undefined;
/**
* Given a name, this should return the full path to the package manifest that
* provides a Haste package of that name. Ex. for `Foo` it may return
* `/smth/Foo/package.json`.
*/
readonly resolveHastePackage: (name: string) => string | undefined;
readonly resolveRequest?: CustomResolver;
readonly sourceExts: ReadonlyArray<string>;
unstable_conditionNames: ReadonlyArray<string>;
unstable_conditionsByPlatform: Readonly<{
[platform: string]: ReadonlyArray<string>;
}>;
unstable_enablePackageExports: boolean;
unstable_fileSystemLookup?: FileSystemLookup | null;
unstable_logWarning: (message: string) => void;
}
export interface CustomResolutionContext extends ResolutionContext {
readonly resolveRequest: CustomResolver;
}
export type CustomResolver = (
context: CustomResolutionContext,
moduleName: string,
platform: string | null,
) => Resolution;
export type CustomResolverOptions = Readonly<{
[option: string]: unknown;
}>;

View File

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

View File

@@ -0,0 +1,211 @@
/**
* 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
*/
'use strict';
import type {TransformResultDependency} from 'metro/src/DeltaBundler/types.flow';
export type Result<+TResolution, +TCandidates> =
| {+type: 'resolved', +resolution: TResolution}
| {+type: 'failed', +candidates: TCandidates};
export type Resolution = FileResolution | {+type: 'empty'};
export type SourceFileResolution = $ReadOnly<{
type: 'sourceFile',
filePath: string,
}>;
export type AssetFileResolution = $ReadOnlyArray<string>;
export type AssetResolution = $ReadOnly<{
type: 'assetFiles',
filePaths: AssetFileResolution,
}>;
export type FileResolution = AssetResolution | SourceFileResolution;
export type FileAndDirCandidates = {
+dir: ?FileCandidates,
+file: ?FileCandidates,
};
/**
* This is a way to describe what files we tried to look for when resolving
* a module name as file. This is mainly used for error reporting, so that
* we can explain why we cannot resolve a module.
*/
export type FileCandidates =
// We only tried to resolve a specific asset.
| {+type: 'asset', +name: string}
// We attempted to resolve a name as being a source file (ex. JavaScript,
// JSON...), in which case there can be several extensions we tried, for
// example `/js/foo.ios.js`, `/js/foo.js`, etc. for a single prefix '/js/foo'.
| {
+type: 'sourceFile',
filePathPrefix: string,
+candidateExts: $ReadOnlyArray<string>,
};
export type ExportMap = $ReadOnly<{
[subpathOrCondition: string]: string | ExportMap | null,
}>;
/** "exports" mapping where values may be legacy Node.js <13.7 array format. */
export type ExportMapWithFallbacks = $ReadOnly<{
[subpath: string]: $Values<ExportMap> | ExportValueWithFallback,
}>;
/** "exports" subpath value when in legacy Node.js <13.7 array format. */
export type ExportValueWithFallback =
| $ReadOnlyArray<ExportMap | string>
// JSON can also contain exotic nested array structure, which will not be parsed
| $ReadOnlyArray<$ReadOnlyArray<mixed>>;
export type ExportsField =
| string
| $ReadOnlyArray<string>
| ExportValueWithFallback
| ExportMap
| ExportMapWithFallbacks;
export type PackageJson = $ReadOnly<{
name?: string,
main?: string,
exports?: ExportsField,
...
}>;
export type PackageInfo = $ReadOnly<{
packageJson: PackageJson,
rootPath: string,
}>;
export type PackageForModule = $ReadOnly<{
...PackageInfo,
/* A system-separated subpath (with no './' prefix) that reflects the subpath
of the given candidate relative to the returned rootPath. */
packageRelativePath: string,
}>;
/**
* Check existence of a single file.
*/
export type DoesFileExist = (filePath: string) => boolean;
/**
* Performs a lookup against an absolute or project-relative path to determine
* whether it exists as a file or directory. Follows any symlinks, and returns
* a real absolute path on existence.
*/
export type FileSystemLookup = (
absoluteOrProjectRelativePath: string,
) => {exists: false} | {exists: true, type: 'f' | 'd', realPath: string};
/**
* Given a directory path and the base asset name, return a list of all the
* asset file names that match the given base name in that directory. Return
* null if there's no such named asset. `platform` is used to identify
* platform-specific assets, ex. `foo.ios.js` instead of a generic `foo.js`.
*/
export type ResolveAsset = (
dirPath: string,
assetName: string,
extension: string,
) => ?$ReadOnlyArray<string>;
export type ResolutionContext = $ReadOnly<{
allowHaste: boolean,
assetExts: $ReadOnlySet<string>,
customResolverOptions: CustomResolverOptions,
disableHierarchicalLookup: boolean,
doesFileExist: DoesFileExist,
extraNodeModules: ?{[string]: string, ...},
/** Is resolving for a development bundle. */
dev: boolean,
/**
* Get the parsed contents of the specified `package.json` file.
*/
getPackage: (packageJsonPath: string) => ?PackageJson,
/**
* Get the closest package scope, parsed `package.json` and relative subpath
* for a given absolute candidate path (which need not exist), or null if
* there is no package.json closer than the nearest node_modules directory.
*
* @deprecated See https://github.com/facebook/metro/commit/29c77bff31e2475a086bc3f04073f485da8f9ff0
*/
getPackageForModule: (absoluteModulePath: string) => ?PackageForModule,
/**
* The dependency descriptor, within the origin module, corresponding to the
* current resolution request. This is provided for diagnostic purposes ONLY
* and may not be used for resolution purposes.
*/
dependency?: TransformResultDependency,
/**
* The ordered list of fields to read in `package.json` to resolve a main
* entry point based on the "browser" field spec.
*/
mainFields: $ReadOnlyArray<string>,
/**
* Full path of the module that is requiring or importing the module to be
* resolved. This may not be the only place this dependency was found,
* as resolutions can be cached.
*/
originModulePath: string,
nodeModulesPaths: $ReadOnlyArray<string>,
preferNativePlatform: boolean,
resolveAsset: ResolveAsset,
redirectModulePath: (modulePath: string) => string | false,
/**
* Given a name, this should return the full path to the file that provides
* a Haste module of that name. Ex. for `Foo` it may return `/smth/Foo.js`.
*/
resolveHasteModule: (name: string) => ?string,
/**
* Given a name, this should return the full path to the package manifest that
* provides a Haste package of that name. Ex. for `Foo` it may return
* `/smth/Foo/package.json`.
*/
resolveHastePackage: (name: string) => ?string,
resolveRequest?: ?CustomResolver,
sourceExts: $ReadOnlyArray<string>,
unstable_conditionNames: $ReadOnlyArray<string>,
unstable_conditionsByPlatform: $ReadOnly<{
[platform: string]: $ReadOnlyArray<string>,
}>,
unstable_enablePackageExports: boolean,
unstable_fileSystemLookup?: ?FileSystemLookup,
unstable_logWarning: (message: string) => void,
}>;
export type CustomResolutionContext = $ReadOnly<{
...ResolutionContext,
resolveRequest: CustomResolver,
}>;
export type CustomResolver = (
context: CustomResolutionContext,
moduleName: string,
platform: string | null,
) => Resolution;
export type CustomResolverOptions = {
__proto__: null,
+[string]: mixed,
...
};

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = isAssetFile;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function isAssetFile(filePath, assetExts) {
const baseName = _path.default.basename(filePath);
for (let i = baseName.length - 1; i >= 0; i--) {
if (baseName[i] === ".") {
const ext = baseName.slice(i + 1);
if (assetExts.has(ext)) {
return true;
}
}
}
return false;
}

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
* @oncall react_native
*/
import path from 'path';
/**
* Determine if a file path should be considered an asset file based on the
* given `assetExts`.
*/
export default function isAssetFile(
filePath: string,
assetExts: $ReadOnlySet<string>,
): boolean {
const baseName = path.basename(filePath);
for (let i = baseName.length - 1; i >= 0; i--) {
if (baseName[i] === '.') {
const ext = baseName.slice(i + 1);
if (assetExts.has(ext)) {
return true;
}
}
}
return false;
}

View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = toPosixPath;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
const MATCH_NON_POSIX_PATH_SEPS = new RegExp(
"\\" + _path.default.win32.sep,
"g"
);
function toPosixPath(relativePathOrSpecifier) {
if (_path.default.sep === _path.default.posix.sep) {
return relativePathOrSpecifier;
}
return relativePathOrSpecifier.replace(
MATCH_NON_POSIX_PATH_SEPS,
_path.default.posix.sep
);
}

View File

@@ -0,0 +1,29 @@
/**
* 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
*/
import path from 'path';
const MATCH_NON_POSIX_PATH_SEPS = new RegExp('\\' + path.win32.sep, 'g');
/**
* Replace path separators in the passed string to coerce to a POSIX path. This
* is a no-op on POSIX systems.
*/
export default function toPosixPath(relativePathOrSpecifier: string): string {
if (path.sep === path.posix.sep) {
return relativePathOrSpecifier;
}
return relativePathOrSpecifier.replace(
MATCH_NON_POSIX_PATH_SEPS,
path.posix.sep,
);
}