first commit

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

View File

@@ -0,0 +1,21 @@
import type { RNConfigDependencyAndroid, RNConfigReactNativePlatformsConfigAndroid } from './reactNativeConfig.types';
import type { ExpoModuleConfig } from '../ExpoModuleConfig';
export declare function resolveDependencyConfigImplAndroidAsync(packageRoot: string, reactNativeConfig: RNConfigReactNativePlatformsConfigAndroid | null | undefined, expoModuleConfig?: ExpoModuleConfig | null): Promise<RNConfigDependencyAndroid | null>;
/**
* Parse the `RNConfigDependencyAndroid.packageName`
*/
export declare function parsePackageNameAsync(manifestPath: string | null, gradlePath: string | null): Promise<string | null>;
/**
* Parse the Java or Kotlin class name to for `ReactPackage` or `(Base|Turbo)ReactPackage`.
*/
export declare function parseNativePackageClassNameAsync(packageRoot: string, androidDir: string): Promise<string | null>;
export declare function matchNativePackageClassName(_filePath: string, contents: Buffer): string | null;
export declare function parseLibraryNameAsync(androidDir: string, packageJson: any): Promise<string | null>;
export declare function parseComponentDescriptorsAsync(packageRoot: string, packageJson: any): Promise<string[]>;
export declare function findGradleAndManifestAsync({ androidDir, isLibrary, }: {
androidDir: string;
isLibrary: boolean;
}): Promise<{
gradle: string | null;
manifest: string | null;
}>;

View File

@@ -0,0 +1,279 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveDependencyConfigImplAndroidAsync = resolveDependencyConfigImplAndroidAsync;
exports.parsePackageNameAsync = parsePackageNameAsync;
exports.parseNativePackageClassNameAsync = parseNativePackageClassNameAsync;
exports.matchNativePackageClassName = matchNativePackageClassName;
exports.parseLibraryNameAsync = parseLibraryNameAsync;
exports.parseComponentDescriptorsAsync = parseComponentDescriptorsAsync;
exports.findGradleAndManifestAsync = findGradleAndManifestAsync;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const concurrency_1 = require("../concurrency");
const utils_1 = require("../utils");
async function resolveDependencyConfigImplAndroidAsync(packageRoot, reactNativeConfig, expoModuleConfig) {
if (reactNativeConfig === null) {
// Skip autolinking for this package.
return null;
}
// NOTE(@kitten): We allow `reactNativeConfig === undefined` here. That indicates a missing config file
// However, React Native modules with left out config files are explicitly supported and valid
const sourceDir = reactNativeConfig?.sourceDir || 'android';
const androidDir = path_1.default.join(packageRoot, sourceDir);
const { gradle, manifest } = await findGradleAndManifestAsync({ androidDir, isLibrary: true });
const isPureCxxDependency = reactNativeConfig?.cxxModuleCMakeListsModuleName != null &&
reactNativeConfig?.cxxModuleCMakeListsPath != null &&
reactNativeConfig?.cxxModuleHeaderName != null &&
!manifest &&
!gradle;
if (!manifest && !gradle && !isPureCxxDependency) {
return null;
}
if (reactNativeConfig === undefined && expoModuleConfig?.supportsPlatform('android')) {
if (!!gradle && !expoModuleConfig?.rawConfig.android?.gradlePath) {
// If the React Native module has a gradle file and the Expo module doesn't redirect it,
// they will conflict and we can't link both at the same time
return null;
}
}
let packageInstance = null;
let packageImportPath = null;
if (!isPureCxxDependency) {
const packageName = reactNativeConfig?.packageName || (await parsePackageNameAsync(manifest, gradle));
if (!packageName) {
return null;
}
const nativePackageClassName = await parseNativePackageClassNameAsync(packageRoot, androidDir);
if (!nativePackageClassName) {
return null;
}
packageImportPath =
reactNativeConfig?.packageImportPath || `import ${packageName}.${nativePackageClassName};`;
packageInstance = reactNativeConfig?.packageInstance || `new ${nativePackageClassName}()`;
}
const packageJson = await (0, utils_1.loadPackageJson)((0, utils_1.fastJoin)(packageRoot, 'package.json'));
const buildTypes = reactNativeConfig?.buildTypes || [];
const dependencyConfiguration = reactNativeConfig?.dependencyConfiguration;
const libraryName = reactNativeConfig?.libraryName || (await parseLibraryNameAsync(androidDir, packageJson));
const componentDescriptors = reactNativeConfig?.componentDescriptors ||
(await parseComponentDescriptorsAsync(packageRoot, packageJson));
let cmakeListsPath = reactNativeConfig?.cmakeListsPath
? path_1.default.join(androidDir, reactNativeConfig?.cmakeListsPath)
: path_1.default.join(androidDir, 'build/generated/source/codegen/jni/CMakeLists.txt');
const cxxModuleCMakeListsModuleName = reactNativeConfig?.cxxModuleCMakeListsModuleName || null;
const cxxModuleHeaderName = reactNativeConfig?.cxxModuleHeaderName || null;
let cxxModuleCMakeListsPath = reactNativeConfig?.cxxModuleCMakeListsPath
? path_1.default.join(androidDir, reactNativeConfig?.cxxModuleCMakeListsPath)
: null;
if (process.platform === 'win32') {
cmakeListsPath = cmakeListsPath.replace(/\\/g, '/');
if (cxxModuleCMakeListsPath) {
cxxModuleCMakeListsPath = cxxModuleCMakeListsPath.replace(/\\/g, '/');
}
}
const result = {
sourceDir: androidDir,
packageImportPath,
packageInstance,
dependencyConfiguration,
buildTypes,
libraryName,
componentDescriptors,
cmakeListsPath,
cxxModuleCMakeListsModuleName,
cxxModuleCMakeListsPath,
cxxModuleHeaderName,
isPureCxxDependency,
};
if (!result.libraryName) {
delete result.libraryName;
}
if (!result.dependencyConfiguration) {
delete result.dependencyConfiguration;
}
return result;
}
/**
* Parse the `RNConfigDependencyAndroid.packageName`
*/
async function parsePackageNameAsync(manifestPath, gradlePath) {
if (gradlePath) {
const gradleContents = await promises_1.default.readFile(gradlePath, 'utf8');
const match = gradleContents.match(/namespace\s*[=]*\s*["'](.+?)["']/);
if (match) {
return match[1];
}
}
if (manifestPath) {
const manifestContents = await promises_1.default.readFile(manifestPath, 'utf8');
const match = manifestContents.match(/package="(.+?)"/);
if (match) {
return match[1];
}
}
return null;
}
/**
* Parse the Java or Kotlin class name to for `ReactPackage` or `(Base|Turbo)ReactPackage`.
*/
async function parseNativePackageClassNameAsync(packageRoot, androidDir) {
// Search for **/*Package.{java,kt} files first
for await (const entry of (0, utils_1.scanFilesRecursively)(androidDir, undefined, true)) {
if (entry.name.endsWith('Package.java') || entry.name.endsWith('Package.kt')) {
try {
const contents = await promises_1.default.readFile(entry.path);
const matched = matchNativePackageClassName(entry.path, contents);
if (matched) {
return matched;
}
}
catch {
continue;
}
}
}
// Early return if the module is an Expo module
if (await (0, utils_1.fileExistsAsync)(path_1.default.join(packageRoot, 'expo-module.config.json'))) {
return null;
}
// Search all **/*.{java,kt} files
for await (const entry of (0, utils_1.scanFilesRecursively)(androidDir, undefined, true)) {
if (entry.name.endsWith('.java') || entry.name.endsWith('.kt')) {
const contents = await promises_1.default.readFile(entry.path);
const matched = matchNativePackageClassName(entry.path, contents);
if (matched) {
return matched;
}
}
}
return null;
}
let lazyReactPackageRegex = null;
let lazyTurboReactPackageRegex = null;
function matchNativePackageClassName(_filePath, contents) {
const fileContents = contents.toString();
// [0] Match ReactPackage
if (!lazyReactPackageRegex) {
lazyReactPackageRegex =
/class\s+(\w+[^(\s]*)[\s\w():]*(\s+implements\s+|:)[\s\w():,]*[^{]*ReactPackage/;
}
const matchReactPackage = fileContents.match(lazyReactPackageRegex);
if (matchReactPackage) {
return matchReactPackage[1];
}
// [1] Match (Base|Turbo)ReactPackage
if (!lazyTurboReactPackageRegex) {
lazyTurboReactPackageRegex =
/class\s+(\w+[^(\s]*)[\s\w():]*(\s+extends\s+|:)[\s\w():,]*[^{]*(Base|Turbo)ReactPackage/;
}
const matchTurboReactPackage = fileContents.match(lazyTurboReactPackageRegex);
if (matchTurboReactPackage) {
return matchTurboReactPackage[1];
}
return null;
}
async function parseLibraryNameAsync(androidDir, packageJson) {
// [0] `codegenConfig.name` from package.json
if (packageJson.codegenConfig?.name) {
return packageJson.codegenConfig.name;
}
const libraryNameRegExp = /libraryName = ["'](.+)["']/;
const gradlePath = path_1.default.join(androidDir, 'build.gradle');
// [1] `libraryName` from build.gradle
if (await (0, utils_1.fileExistsAsync)(gradlePath)) {
const buildGradleContents = await promises_1.default.readFile(gradlePath, 'utf8');
const match = buildGradleContents.match(libraryNameRegExp);
if (match) {
return match[1];
}
}
// [2] `libraryName` from build.gradle.kts
const gradleKtsPath = path_1.default.join(androidDir, 'build.gradle.kts');
if (await (0, utils_1.fileExistsAsync)(gradleKtsPath)) {
const buildGradleContents = await promises_1.default.readFile(gradleKtsPath, 'utf8');
const match = buildGradleContents.match(libraryNameRegExp);
if (match) {
return match[1];
}
}
return null;
}
async function parseComponentDescriptorsAsync(packageRoot, packageJson) {
const jsRoot = packageJson?.codegenConfig?.jsSrcsDir
? path_1.default.join(packageRoot, packageJson.codegenConfig.jsSrcsDir)
: packageRoot;
const extRe = /\.[tj]sx?$/;
const results = new Set();
for await (const entry of (0, utils_1.scanFilesRecursively)(jsRoot)) {
if (extRe.test(entry.name)) {
const contents = await promises_1.default.readFile(entry.path, 'utf8');
const matched = matchComponentDescriptors(entry.path, contents);
if (matched) {
results.add(matched);
}
}
}
return [...results].sort((a, b) => a.localeCompare(b));
}
let lazyCodegenComponentRegex = null;
function matchComponentDescriptors(_filePath, contents) {
if (!lazyCodegenComponentRegex) {
lazyCodegenComponentRegex =
/codegenNativeComponent(<.*>)?\s*\(\s*["'`](\w+)["'`](,?[\s\S]+interfaceOnly:\s*(\w+))?/m;
}
const match = contents.match(lazyCodegenComponentRegex);
if (!(match?.[4] === 'true') && match?.[2]) {
return `${match[2]}ComponentDescriptor`;
}
return null;
}
const findAndroidManifestsAsync = async (targetPath) => {
const files = (0, utils_1.scanFilesRecursively)(targetPath, (parentPath, name) => {
switch (name) {
case 'build':
case 'debug':
case 'Pods':
return false;
case 'Examples':
case 'examples':
// Only ignore top-level examples directories in `targetPath` but not nested ones
return parentPath !== targetPath;
case 'android':
return !/[\\/]sdks[\\/]hermes$/.test(parentPath);
case 'androidTest':
case 'test':
return !/[\\/]src$/.test(parentPath);
default:
return true;
}
});
const manifestPaths = [];
for await (const entry of files) {
if (entry.name === 'AndroidManifest.xml') {
manifestPaths.push(entry.path);
}
}
return manifestPaths.sort((a, b) => a.localeCompare(b));
};
const getFileCandidatesAsync = async (targetPath, fileNames) => {
const gradlePaths = await (0, concurrency_1.taskAll)(fileNames, (fileName) => (0, utils_1.fileExistsAsync)(path_1.default.join(targetPath, fileName)));
return gradlePaths.filter((file) => file != null).sort((a, b) => a.localeCompare(b));
};
async function findGradleAndManifestAsync({ androidDir, isLibrary, }) {
const [manifests, gradles] = await Promise.all([
findAndroidManifestsAsync(androidDir),
getFileCandidatesAsync(isLibrary ? androidDir : path_1.default.join(androidDir, 'app'), [
'build.gradle',
'build.gradle.kts',
]),
]);
// TODO(@kitten): We can't optimise this because of the prior `includes()` pattern. Is this meant to be startsWith?
const manifest = manifests.find((manifest) => manifest.includes('src/main/')) ??
manifests.sort((a, b) => a.localeCompare(b))[0];
const gradle = gradles.sort((a, b) => a.localeCompare(b))[0];
return { gradle: gradle || null, manifest: manifest || null };
}
//# sourceMappingURL=androidResolver.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
import type { RNConfigReactNativeConfig } from './reactNativeConfig.types';
type LoadConfigAsync = <T extends RNConfigReactNativeConfig>(packageRoot: string) => Promise<T | null>;
/**
* Load the `react-native.config.js` or `react-native.config.ts` from the package.
*/
export declare const loadConfigAsync: LoadConfigAsync;
export {};

View File

@@ -0,0 +1,33 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadConfigAsync = void 0;
const require_utils_1 = require("@expo/require-utils");
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const memoize_1 = require("../memoize");
const utils_1 = require("../utils");
const mockedNativeModules = path_1.default.join(__dirname, '..', '..', 'node_modules_mock');
/**
* Load the `react-native.config.js` or `react-native.config.ts` from the package.
*/
exports.loadConfigAsync = (0, memoize_1.memoize)(async function loadConfigAsync(packageRoot) {
const configPath = (await Promise.all(['react-native.config.js', 'react-native.config.ts'].map(async (fileName) => {
const file = path_1.default.join(packageRoot, fileName);
return (await (0, utils_1.fileExistsAsync)(file)) ? file : null;
}))).find((path) => path != null);
if (configPath) {
const mod = (0, require_utils_1.evalModule)(await promises_1.default.readFile(configPath, 'utf8'), configPath,
// NOTE: We need to mock the Community CLI temporarily, because
// some packages are checking the version of the CLI in the `react-native.config.js` file.
// We can remove this once we remove this check from packages.
{ paths: [mockedNativeModules] });
return mod.default ?? mod ?? null;
}
else {
return null;
}
});
//# sourceMappingURL=config.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/reactNativeConfig/config.ts"],"names":[],"mappings":";;;;;;AAAA,uDAAiD;AACjD,2DAA6B;AAC7B,gDAAwB;AAExB,wCAAqC;AACrC,oCAA2C;AAM3C,MAAM,mBAAmB,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,mBAAmB,CAAC,CAAC;AAMlF;;GAEG;AACU,QAAA,eAAe,GAAoB,IAAA,iBAAO,EAAC,KAAK,UAAU,eAAe,CAEpF,WAAmB;IACnB,MAAM,UAAU,GAAG,CACjB,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,wBAAwB,EAAE,wBAAwB,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC1E,MAAM,IAAI,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QAC9C,OAAO,CAAC,MAAM,IAAA,uBAAe,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC,CAAC,CACH,CACF,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAC/B,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,IAAA,0BAAU,EACpB,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,EACrC,UAAU;QACV,+DAA+D;QAC/D,0FAA0F;QAC1F,8DAA8D;QAC9D,EAAE,KAAK,EAAE,CAAC,mBAAmB,CAAC,EAAE,CACjC,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,IAAI,IAAI,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["import { evalModule } from '@expo/require-utils';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nimport { memoize } from '../memoize';\nimport { fileExistsAsync } from '../utils';\nimport type {\n RNConfigReactNativeConfig,\n RNConfigReactNativeProjectConfig,\n} from './reactNativeConfig.types';\n\nconst mockedNativeModules = path.join(__dirname, '..', '..', 'node_modules_mock');\n\ntype LoadConfigAsync = <T extends RNConfigReactNativeConfig>(\n packageRoot: string\n) => Promise<T | null>;\n\n/**\n * Load the `react-native.config.js` or `react-native.config.ts` from the package.\n */\nexport const loadConfigAsync: LoadConfigAsync = memoize(async function loadConfigAsync<\n T extends RNConfigReactNativeConfig,\n>(packageRoot: string): Promise<T | null> {\n const configPath = (\n await Promise.all(\n ['react-native.config.js', 'react-native.config.ts'].map(async (fileName) => {\n const file = path.join(packageRoot, fileName);\n return (await fileExistsAsync(file)) ? file : null;\n })\n )\n ).find((path) => path != null);\n if (configPath) {\n const mod = evalModule(\n await fs.readFile(configPath, 'utf8'),\n configPath,\n // NOTE: We need to mock the Community CLI temporarily, because\n // some packages are checking the version of the CLI in the `react-native.config.js` file.\n // We can remove this once we remove this check from packages.\n { paths: [mockedNativeModules] }\n );\n return mod.default ?? mod ?? null;\n } else {\n return null;\n }\n});\n"]}

View File

@@ -0,0 +1,2 @@
export * from './reactNativeConfig';
export * from './reactNativeConfig.types';

View File

@@ -0,0 +1,19 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./reactNativeConfig"), exports);
__exportStar(require("./reactNativeConfig.types"), exports);
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reactNativeConfig/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,4DAA0C","sourcesContent":["export * from './reactNativeConfig';\nexport * from './reactNativeConfig.types';\n"]}

View File

@@ -0,0 +1,6 @@
import type { RNConfigDependencyIos, RNConfigReactNativePlatformsConfigIos } from './reactNativeConfig.types';
import type { ExpoModuleConfig } from '../ExpoModuleConfig';
export declare function resolveDependencyConfigImplIosAsync(resolution: {
path: string;
version: string;
}, reactNativeConfig: RNConfigReactNativePlatformsConfigIos | null | undefined, expoModuleConfig?: ExpoModuleConfig | null): Promise<RNConfigDependencyIos | null>;

View File

@@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveDependencyConfigImplIosAsync = resolveDependencyConfigImplIosAsync;
const path_1 = __importDefault(require("path"));
const utils_1 = require("../utils");
/** Find first *.podspec file in target directory */
const findPodspecFile = async (targetPath) => {
const podspecFiles = await (0, utils_1.listFilesSorted)(targetPath, (basename) => {
return basename.endsWith('.podspec');
});
// NOTE(@kitten): Compare case-insensitively against basename of derived name
const mainBasename = path_1.default.basename(targetPath).toLowerCase();
const mainPodspecFile = podspecFiles.find((podspecFile) => path_1.default.basename(podspecFile, '.podspec').toLowerCase() === mainBasename);
return mainPodspecFile ?? (podspecFiles.length > 0 ? podspecFiles[0] : null);
};
async function resolveDependencyConfigImplIosAsync(resolution, reactNativeConfig, expoModuleConfig) {
if (reactNativeConfig === null) {
// Skip autolinking for this package.
return null;
}
const podspecPath = await findPodspecFile(resolution.path);
if (!podspecPath) {
return null;
}
if (reactNativeConfig === undefined && expoModuleConfig?.supportsPlatform('apple')) {
// Check if Expo podspec files contain the React Native podspec file
const overlappingPodspecPath = expoModuleConfig.applePodspecPaths().find((targetFile) => {
const expoPodspecPath = path_1.default.normalize(path_1.default.join(resolution.path, targetFile));
return expoPodspecPath === path_1.default.normalize(podspecPath);
});
// NOTE(@kitten): If we don't have a react-native.config.{js,ts} file and the
// package is also an Expo module, we only link it as a React Native module
// if both don't point at the same podspec file
if (overlappingPodspecPath != null) {
return null;
}
}
return {
podspecPath,
version: resolution.version,
configurations: reactNativeConfig?.configurations || [],
scriptPhases: reactNativeConfig?.scriptPhases || [],
};
}
//# sourceMappingURL=iosResolver.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"iosResolver.js","sourceRoot":"","sources":["../../src/reactNativeConfig/iosResolver.ts"],"names":[],"mappings":";;;;;AAuBA,kFAmCC;AAzDD,gDAAwB;AAOxB,oCAA2C;AAE3C,oDAAoD;AACpD,MAAM,eAAe,GAAG,KAAK,EAAE,UAAkB,EAA0B,EAAE;IAC3E,MAAM,YAAY,GAAG,MAAM,IAAA,uBAAe,EAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,EAAE;QAClE,OAAO,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IACH,6EAA6E;IAC7E,MAAM,YAAY,GAAG,cAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7D,MAAM,eAAe,GAAG,YAAY,CAAC,IAAI,CACvC,CAAC,WAAW,EAAE,EAAE,CAAC,cAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,WAAW,EAAE,KAAK,YAAY,CACvF,CAAC;IACF,OAAO,eAAe,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/E,CAAC,CAAC;AAEK,KAAK,UAAU,mCAAmC,CACvD,UAA6C,EAC7C,iBAA2E,EAC3E,gBAA0C;IAE1C,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;QAC/B,qCAAqC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC3D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,iBAAiB,KAAK,SAAS,IAAI,gBAAgB,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QACnF,oEAAoE;QACpE,MAAM,sBAAsB,GAAG,gBAAgB,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE;YACtF,MAAM,eAAe,GAAG,cAAI,CAAC,SAAS,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;YAC/E,OAAO,eAAe,KAAK,cAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QACH,6EAA6E;QAC7E,2EAA2E;QAC3E,+CAA+C;QAC/C,IAAI,sBAAsB,IAAI,IAAI,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO;QACL,WAAW;QACX,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,cAAc,EAAE,iBAAiB,EAAE,cAAc,IAAI,EAAE;QACvD,YAAY,EAAE,iBAAiB,EAAE,YAAY,IAAI,EAAE;KACpD,CAAC;AACJ,CAAC","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport type {\n RNConfigDependencyIos,\n RNConfigReactNativePlatformsConfigIos,\n} from './reactNativeConfig.types';\nimport type { ExpoModuleConfig } from '../ExpoModuleConfig';\nimport { listFilesSorted } from '../utils';\n\n/** Find first *.podspec file in target directory */\nconst findPodspecFile = async (targetPath: string): Promise<string | null> => {\n const podspecFiles = await listFilesSorted(targetPath, (basename) => {\n return basename.endsWith('.podspec');\n });\n // NOTE(@kitten): Compare case-insensitively against basename of derived name\n const mainBasename = path.basename(targetPath).toLowerCase();\n const mainPodspecFile = podspecFiles.find(\n (podspecFile) => path.basename(podspecFile, '.podspec').toLowerCase() === mainBasename\n );\n return mainPodspecFile ?? (podspecFiles.length > 0 ? podspecFiles[0] : null);\n};\n\nexport async function resolveDependencyConfigImplIosAsync(\n resolution: { path: string; version: string },\n reactNativeConfig: RNConfigReactNativePlatformsConfigIos | null | undefined,\n expoModuleConfig?: ExpoModuleConfig | null\n): Promise<RNConfigDependencyIos | null> {\n if (reactNativeConfig === null) {\n // Skip autolinking for this package.\n return null;\n }\n\n const podspecPath = await findPodspecFile(resolution.path);\n if (!podspecPath) {\n return null;\n }\n\n if (reactNativeConfig === undefined && expoModuleConfig?.supportsPlatform('apple')) {\n // Check if Expo podspec files contain the React Native podspec file\n const overlappingPodspecPath = expoModuleConfig.applePodspecPaths().find((targetFile) => {\n const expoPodspecPath = path.normalize(path.join(resolution.path, targetFile));\n return expoPodspecPath === path.normalize(podspecPath);\n });\n // NOTE(@kitten): If we don't have a react-native.config.{js,ts} file and the\n // package is also an Expo module, we only link it as a React Native module\n // if both don't point at the same podspec file\n if (overlappingPodspecPath != null) {\n return null;\n }\n }\n\n return {\n podspecPath,\n version: resolution.version,\n configurations: reactNativeConfig?.configurations || [],\n scriptPhases: reactNativeConfig?.scriptPhases || [],\n };\n}\n"]}

View File

@@ -0,0 +1,18 @@
import type { SupportedPlatform } from '../types';
import type { RNConfigDependency, RNConfigReactNativeAppProjectConfig, RNConfigReactNativeProjectConfig, RNConfigResult } from './reactNativeConfig.types';
import { AutolinkingOptions } from '../commands/autolinkingOptions';
import { DependencyResolution } from '../dependencies';
export declare function resolveReactNativeModule(resolution: DependencyResolution, projectConfig: RNConfigReactNativeProjectConfig | null, platform: SupportedPlatform, excludeNames: Set<string>): Promise<RNConfigDependency | null>;
interface CreateRNConfigParams {
appRoot: string;
sourceDir: string | undefined;
autolinkingOptions: AutolinkingOptions & {
platform: SupportedPlatform;
};
}
/**
* Create config for react-native core autolinking.
*/
export declare function createReactNativeConfigAsync({ appRoot, sourceDir, autolinkingOptions, }: CreateRNConfigParams): Promise<RNConfigResult>;
export declare function resolveAppProjectConfigAsync(projectRoot: string, platform: SupportedPlatform, sourceDir?: string): Promise<RNConfigReactNativeAppProjectConfig>;
export {};

View File

@@ -0,0 +1,151 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveReactNativeModule = resolveReactNativeModule;
exports.createReactNativeConfigAsync = createReactNativeConfigAsync;
exports.resolveAppProjectConfigAsync = resolveAppProjectConfigAsync;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const androidResolver_1 = require("./androidResolver");
const config_1 = require("./config");
const iosResolver_1 = require("./iosResolver");
const ExpoModuleConfig_1 = require("../ExpoModuleConfig");
const dependencies_1 = require("../dependencies");
const webResolver_1 = require("./webResolver");
const isMissingFBReactNativeSpecCodegenOutput = async (reactNativePath) => {
const generatedDir = path_1.default.resolve(reactNativePath, 'React/FBReactNativeSpec');
try {
const stat = await fs_1.default.promises.lstat(generatedDir);
return !stat.isDirectory();
}
catch {
return true;
}
};
async function resolveReactNativeModule(resolution, projectConfig, platform, excludeNames) {
if (excludeNames.has(resolution.name)) {
return null;
}
else if (resolution.name === 'react-native' || resolution.name === 'react-native-macos') {
// Starting from version 0.76, the `react-native` package only defines platforms
// when @react-native-community/cli-platform-android/ios is installed.
// Therefore, we need to manually filter it out.
// NOTE(@kitten): `loadConfigAsync` is skipped too, because react-native's config is too slow
return null;
}
const libraryConfig = (await (0, config_1.loadConfigAsync)(resolution.path));
const reactNativeConfig = {
...libraryConfig?.dependency,
...projectConfig?.dependencies?.[resolution.name],
};
if (Object.keys(libraryConfig?.platforms ?? {}).length > 0) {
// Package defines platforms would be a platform host package.
// The rnc-cli will skip this package.
return null;
}
let maybeExpoModuleConfig;
if (!libraryConfig) {
// NOTE(@kitten): If we don't have an explicit react-native.config.{js,ts} file,
// we should pass the Expo Module config (if it exists) to the resolvers below,
// which can then decide if the React Native inferred config and Expo Module
// configs conflict
try {
maybeExpoModuleConfig = await (0, ExpoModuleConfig_1.discoverExpoModuleConfigAsync)(resolution.path);
}
catch {
// We ignore invalid Expo Modules for the purpose of auto-linking and
// pretend the config doesn't exist, if it isn't valid JSON
}
}
let platformData = null;
if (platform === 'android') {
platformData = await (0, androidResolver_1.resolveDependencyConfigImplAndroidAsync)(resolution.path, reactNativeConfig.platforms?.android, maybeExpoModuleConfig);
}
else if (platform === 'ios') {
platformData = await (0, iosResolver_1.resolveDependencyConfigImplIosAsync)(resolution, reactNativeConfig.platforms?.ios, maybeExpoModuleConfig);
}
else if (platform === 'web') {
platformData = await (0, webResolver_1.checkDependencyWebAsync)(resolution, reactNativeConfig, maybeExpoModuleConfig);
}
return (platformData && {
root: resolution.path,
name: resolution.name,
platforms: {
[platform]: platformData,
},
});
}
/**
* Create config for react-native core autolinking.
*/
async function createReactNativeConfigAsync({ appRoot, sourceDir, autolinkingOptions, }) {
const excludeNames = new Set(autolinkingOptions.exclude);
const projectConfig = (await (0, config_1.loadConfigAsync)(appRoot));
// custom native modules should be resolved first so that they can override other modules
const searchPaths = autolinkingOptions.nativeModulesDir
? [autolinkingOptions.nativeModulesDir, ...autolinkingOptions.searchPaths]
: autolinkingOptions.searchPaths;
const limitDepth = autolinkingOptions.legacy_shallowReactNativeLinking ? 1 : undefined;
const resolutions = (0, dependencies_1.mergeResolutionResults)(await Promise.all([
(0, dependencies_1.scanDependenciesFromRNProjectConfig)(appRoot, projectConfig),
...searchPaths.map((searchPath) => (0, dependencies_1.scanDependenciesInSearchPath)(searchPath)),
(0, dependencies_1.scanDependenciesRecursively)(appRoot, { limitDepth }),
]));
const dependencies = await (0, dependencies_1.filterMapResolutionResult)(resolutions, (resolution) => resolveReactNativeModule(resolution, projectConfig, autolinkingOptions.platform, excludeNames));
// See: https://github.com/facebook/react-native/pull/53690
// When we're building react-native from source without these generated files, we need to force them to be generated
// Every published react-native version (or out-of-tree version) should have these files, but building from the raw repo won't (e.g. Expo Go)
const reactNativeResolution = resolutions['react-native'];
if (reactNativeResolution &&
autolinkingOptions.platform === 'ios' &&
(await isMissingFBReactNativeSpecCodegenOutput(reactNativeResolution.path))) {
dependencies['react-native'] = {
root: reactNativeResolution.path,
name: 'react-native',
platforms: {
ios: {
// This will trigger a warning in list_native_modules but will trigger the artifacts
// codegen codepath as expected
podspecPath: '',
version: reactNativeResolution.version,
configurations: [],
scriptPhases: [],
},
},
};
}
return {
root: appRoot,
reactNativePath: resolutions['react-native']?.path,
dependencies,
project: await resolveAppProjectConfigAsync(appRoot, autolinkingOptions.platform, sourceDir),
};
}
async function resolveAppProjectConfigAsync(projectRoot, platform, sourceDir) {
// TODO(@kitten): use the commandRoot here to find these files in non <projectRoot>/<platform> folders
if (platform === 'android') {
const androidDir = sourceDir ?? path_1.default.join(projectRoot, 'android');
const { gradle, manifest } = await (0, androidResolver_1.findGradleAndManifestAsync)({ androidDir, isLibrary: false });
if (gradle == null || manifest == null) {
return {};
}
const packageName = await (0, androidResolver_1.parsePackageNameAsync)(manifest, gradle);
return {
android: {
packageName: packageName ?? '',
sourceDir: sourceDir ?? path_1.default.join(projectRoot, 'android'),
},
};
}
if (platform === 'ios') {
return {
ios: {
sourceDir: sourceDir ?? path_1.default.join(projectRoot, 'ios'),
},
};
}
return {};
}
//# sourceMappingURL=reactNativeConfig.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,84 @@
/** Dependency configuration for Android platform. */
export interface RNConfigDependencyAndroid {
sourceDir: string;
packageImportPath: string | null;
packageInstance: string | null;
dependencyConfiguration?: string;
buildTypes: string[];
libraryName?: string | null;
componentDescriptors?: string[] | null;
cmakeListsPath?: string | null;
cxxModuleCMakeListsModuleName?: string | null;
cxxModuleCMakeListsPath?: string | null;
cxxModuleHeaderName?: string | null;
isPureCxxDependency?: boolean;
}
/** Dependency configuration for iOS platform. */
export interface RNConfigDependencyIos {
podspecPath: string;
version: string;
configurations: string[];
scriptPhases: any[];
}
/** Dependency configuration for Web platform. */
export interface RNConfigDependencyWeb {
version: string;
}
/** Dependency configuration. */
export interface RNConfigDependency {
root: string;
name: string;
platforms: {
android?: RNConfigDependencyAndroid;
ios?: RNConfigDependencyIos;
web?: RNConfigDependencyWeb;
};
}
/** Result of 'react-native-config' command. */
export interface RNConfigResult {
root: string;
reactNativePath: string;
dependencies: Record<string, RNConfigDependency>;
project: {
ios?: {
sourceDir: string;
};
};
}
export type RNConfigReactNativePlatformsConfigAndroid = any;
export type RNConfigReactNativePlatformsConfigIos = any;
export type RNConfigReactNativePlatformsConfigWeb = any;
export interface RNConfigReactNativePlatformsConfig {
root?: string;
platforms?: {
android?: RNConfigReactNativePlatformsConfigAndroid;
ios?: RNConfigReactNativePlatformsConfigIos;
web?: RNConfigReactNativePlatformsConfigWeb;
};
}
/**
* The `react-native.config.js` config from projectRoot.
*/
export interface RNConfigReactNativeProjectConfig {
dependencies?: Record<string, RNConfigReactNativePlatformsConfig>;
}
/**
* The `react-native.config.js` config from library packageRoot.
*/
export interface RNConfigReactNativeLibraryConfig {
dependency?: RNConfigReactNativePlatformsConfig;
platforms?: any;
}
export type RNConfigReactNativeConfig = RNConfigReactNativeProjectConfig | RNConfigReactNativeLibraryConfig;
/**
* The `project` config represents the app project configuration.
*/
export interface RNConfigReactNativeAppProjectConfig {
android?: {
sourceDir: string;
packageName: string;
};
ios?: {
sourceDir: string;
};
}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=reactNativeConfig.types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"reactNativeConfig.types.js","sourceRoot":"","sources":["../../src/reactNativeConfig/reactNativeConfig.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SupportedPlatform } from '../types';\n\n/** Dependency configuration for Android platform. */\nexport interface RNConfigDependencyAndroid {\n sourceDir: string;\n packageImportPath: string | null;\n packageInstance: string | null;\n dependencyConfiguration?: string;\n buildTypes: string[];\n libraryName?: string | null;\n componentDescriptors?: string[] | null;\n cmakeListsPath?: string | null;\n cxxModuleCMakeListsModuleName?: string | null;\n cxxModuleCMakeListsPath?: string | null;\n cxxModuleHeaderName?: string | null;\n isPureCxxDependency?: boolean;\n}\n\n/** Dependency configuration for iOS platform. */\nexport interface RNConfigDependencyIos {\n podspecPath: string;\n version: string;\n configurations: string[];\n scriptPhases: any[];\n}\n\n/** Dependency configuration for Web platform. */\nexport interface RNConfigDependencyWeb {\n version: string;\n}\n\n/** Dependency configuration. */\nexport interface RNConfigDependency {\n root: string;\n name: string;\n platforms: {\n android?: RNConfigDependencyAndroid;\n ios?: RNConfigDependencyIos;\n web?: RNConfigDependencyWeb;\n };\n}\n\n/** Result of 'react-native-config' command. */\nexport interface RNConfigResult {\n root: string;\n reactNativePath: string;\n dependencies: Record<string, RNConfigDependency>;\n project: {\n ios?: {\n sourceDir: string;\n };\n };\n}\n\nexport type RNConfigReactNativePlatformsConfigAndroid = any;\nexport type RNConfigReactNativePlatformsConfigIos = any;\nexport type RNConfigReactNativePlatformsConfigWeb = any;\n\nexport interface RNConfigReactNativePlatformsConfig {\n root?: string;\n platforms?: {\n android?: RNConfigReactNativePlatformsConfigAndroid;\n ios?: RNConfigReactNativePlatformsConfigIos;\n web?: RNConfigReactNativePlatformsConfigWeb;\n };\n}\n\n/**\n * The `react-native.config.js` config from projectRoot.\n */\nexport interface RNConfigReactNativeProjectConfig {\n dependencies?: Record<string, RNConfigReactNativePlatformsConfig>;\n}\n\n/**\n * The `react-native.config.js` config from library packageRoot.\n */\nexport interface RNConfigReactNativeLibraryConfig {\n dependency?: RNConfigReactNativePlatformsConfig;\n platforms?: any;\n}\n\nexport type RNConfigReactNativeConfig =\n | RNConfigReactNativeProjectConfig\n | RNConfigReactNativeLibraryConfig;\n\n/**\n * The `project` config represents the app project configuration.\n */\nexport interface RNConfigReactNativeAppProjectConfig {\n android?: {\n sourceDir: string;\n packageName: string;\n };\n ios?: {\n sourceDir: string;\n };\n}\n"]}

View File

@@ -0,0 +1,6 @@
import type { ExpoModuleConfig } from '../ExpoModuleConfig';
import { RNConfigReactNativePlatformsConfig, RNConfigDependencyWeb } from './reactNativeConfig.types';
export declare function checkDependencyWebAsync(resolution: {
path: string;
version: string;
}, reactNativeConfig: RNConfigReactNativePlatformsConfig | null | undefined, expoModuleConfig?: ExpoModuleConfig | null): Promise<RNConfigDependencyWeb | null>;

View File

@@ -0,0 +1,37 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkDependencyWebAsync = checkDependencyWebAsync;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
async function checkDependencyWebAsync(resolution, reactNativeConfig, expoModuleConfig) {
if (!reactNativeConfig || expoModuleConfig) {
// Skip autolinking for this package.
// Skip autolinking web when we have an expo module config
return null;
}
const hasReactNativeConfig = !!reactNativeConfig && Object.keys(reactNativeConfig).length > 0;
if (!hasReactNativeConfig) {
const packageJson = JSON.parse(await promises_1.default.readFile(path_1.default.join(resolution.path, 'package.json'), 'utf8'));
const peerDependencies = packageJson.peerDependencies && typeof packageJson.peerDependencies === 'object'
? packageJson.peerDependencies
: {};
const codegenConfig = packageJson.codegenConfig && typeof packageJson.codegenConfig === 'object'
? packageJson.codegenConfig
: null;
const hasReactNativePeer = !!peerDependencies['react-native'];
const hasCodegenConfig = !!codegenConfig && Object.keys(codegenConfig).length > 0;
// NOTE(@kitten): This is a heuristic for React Native modules that don't have a config file
// They'll still be considered a native module when they have a peer dependency on react-native
// and contain a `codegenConfig` entry
if (!hasReactNativePeer || !hasCodegenConfig) {
return null;
}
}
return {
version: resolution.version,
};
}
//# sourceMappingURL=webResolver.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"webResolver.js","sourceRoot":"","sources":["../../src/reactNativeConfig/webResolver.ts"],"names":[],"mappings":";;;;;AASA,0DAqCC;AA9CD,2DAA6B;AAC7B,gDAAwB;AAQjB,KAAK,UAAU,uBAAuB,CAC3C,UAA6C,EAC7C,iBAAwE,EACxE,gBAA0C;IAE1C,IAAI,CAAC,iBAAiB,IAAI,gBAAgB,EAAE,CAAC;QAC3C,qCAAqC;QACrC,0DAA0D;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,oBAAoB,GAAG,CAAC,CAAC,iBAAiB,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9F,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,MAAM,kBAAE,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACtE,CAAC;QACF,MAAM,gBAAgB,GACpB,WAAW,CAAC,gBAAgB,IAAI,OAAO,WAAW,CAAC,gBAAgB,KAAK,QAAQ;YAC9E,CAAC,CAAC,WAAW,CAAC,gBAAgB;YAC9B,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,aAAa,GACjB,WAAW,CAAC,aAAa,IAAI,OAAO,WAAW,CAAC,aAAa,KAAK,QAAQ;YACxE,CAAC,CAAC,WAAW,CAAC,aAAa;YAC3B,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,kBAAkB,GAAG,CAAC,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QAC9D,MAAM,gBAAgB,GAAG,CAAC,CAAC,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAClF,4FAA4F;QAC5F,+FAA+F;QAC/F,sCAAsC;QACtC,IAAI,CAAC,kBAAkB,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,UAAU,CAAC,OAAO;KAC5B,CAAC;AACJ,CAAC","sourcesContent":["import fs from 'fs/promises';\nimport path from 'path';\n\nimport type { ExpoModuleConfig } from '../ExpoModuleConfig';\nimport {\n RNConfigReactNativePlatformsConfig,\n RNConfigDependencyWeb,\n} from './reactNativeConfig.types';\n\nexport async function checkDependencyWebAsync(\n resolution: { path: string; version: string },\n reactNativeConfig: RNConfigReactNativePlatformsConfig | null | undefined,\n expoModuleConfig?: ExpoModuleConfig | null\n): Promise<RNConfigDependencyWeb | null> {\n if (!reactNativeConfig || expoModuleConfig) {\n // Skip autolinking for this package.\n // Skip autolinking web when we have an expo module config\n return null;\n }\n\n const hasReactNativeConfig = !!reactNativeConfig && Object.keys(reactNativeConfig).length > 0;\n if (!hasReactNativeConfig) {\n const packageJson = JSON.parse(\n await fs.readFile(path.join(resolution.path, 'package.json'), 'utf8')\n );\n const peerDependencies: Record<string, unknown> =\n packageJson.peerDependencies && typeof packageJson.peerDependencies === 'object'\n ? packageJson.peerDependencies\n : {};\n const codegenConfig: Record<string, unknown> | null =\n packageJson.codegenConfig && typeof packageJson.codegenConfig === 'object'\n ? packageJson.codegenConfig\n : null;\n const hasReactNativePeer = !!peerDependencies['react-native'];\n const hasCodegenConfig = !!codegenConfig && Object.keys(codegenConfig).length > 0;\n // NOTE(@kitten): This is a heuristic for React Native modules that don't have a config file\n // They'll still be considered a native module when they have a peer dependency on react-native\n // and contain a `codegenConfig` entry\n if (!hasReactNativePeer || !hasCodegenConfig) {\n return null;\n }\n }\n\n return {\n version: resolution.version,\n };\n}\n"]}