- 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>
271 lines
8.6 KiB
Plaintext
271 lines
8.6 KiB
Plaintext
/**
|
|
* 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 {
|
|
ExportMapWithFallbacks,
|
|
ExportsField,
|
|
ExportsLikeMap,
|
|
FileResolution,
|
|
NormalizedExportsLikeMap,
|
|
ResolutionContext,
|
|
} from './types';
|
|
|
|
import InvalidPackageConfigurationError from './errors/InvalidPackageConfigurationError';
|
|
import PackagePathNotExportedError from './errors/PackagePathNotExportedError';
|
|
import resolveAsset from './resolveAsset';
|
|
import isAssetFile from './utils/isAssetFile';
|
|
import {isSubpathDefinedInExportsLike} from './utils/isSubpathDefinedInExportsLike';
|
|
import {matchSubpathFromExportsLike} from './utils/matchSubpathFromExportsLike';
|
|
import toPosixPath from './utils/toPosixPath';
|
|
import path from 'path';
|
|
|
|
/**
|
|
* 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 (!isSubpathDefinedInExportsLike(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} = matchSubpathFromExportsLike(
|
|
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.replaceAll('*', patternMatch) : target,
|
|
);
|
|
|
|
if (isAssetFile(filePath, context.assetExts)) {
|
|
const assetResult = resolveAsset(context, filePath);
|
|
|
|
if (assetResult != null) {
|
|
return assetResult;
|
|
}
|
|
}
|
|
|
|
const lookupResult = context.fileSystemLookup(filePath);
|
|
if (lookupResult.exists && lookupResult.type === 'f') {
|
|
return {
|
|
type: 'sourceFile',
|
|
filePath: lookupResult.realPath,
|
|
};
|
|
}
|
|
|
|
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>,
|
|
NormalizedExportsLikeMap,
|
|
> = 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,
|
|
): NormalizedExportsLikeMap {
|
|
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')) {
|
|
// $FlowFixMe[incompatible-type] 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: NormalizedExportsLikeMap = new Map([['.', rootValue]]);
|
|
_normalizedExportsFields.set(exportsField, result);
|
|
return result;
|
|
}
|
|
|
|
const firstLevelKeys = Object.keys(rootValue);
|
|
const subpathKeys = firstLevelKeys.filter(key => key.startsWith('.'));
|
|
const importKeys = firstLevelKeys.filter(key => key.startsWith('#'));
|
|
|
|
if (importKeys.length + subpathKeys.length === firstLevelKeys.length) {
|
|
const result: NormalizedExportsLikeMap = 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: NormalizedExportsLikeMap = 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: ExportsLikeMap | ExportMapWithFallbacks,
|
|
createConfigError: (reason: string) => Error,
|
|
): ExportsLikeMap {
|
|
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 | ExportsLikeMap | null},
|
|
);
|
|
}
|
|
|
|
function findInvalidPathSegment(subpath: string): ?string {
|
|
for (const segment of subpath.split(/[\\/]/)) {
|
|
if (
|
|
segment === '' ||
|
|
segment === '.' ||
|
|
segment === '..' ||
|
|
segment === 'node_modules'
|
|
) {
|
|
return segment;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|