/** * 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 */ 'use strict'; /** * This script crawls through a React Native application's dependencies and invokes the codegen * for any libraries that require it. * To enable codegen support, the library should include a config in the codegenConfig key * in a package.json file. */ const utils = require('./codegen-utils'); const generateSpecsCLIExecutor = require('./generate-specs-cli-executor'); const {execSync} = require('child_process'); const fs = require('fs'); const glob = require('glob'); const os = require('os'); const path = require('path'); const REACT_NATIVE_REPOSITORY_ROOT = path.join( __dirname, '..', '..', '..', '..', ); const REACT_NATIVE_PACKAGE_ROOT_FOLDER = path.join(__dirname, '..', '..'); const CODEGEN_REPO_PATH = `${REACT_NATIVE_REPOSITORY_ROOT}/packages/react-native-codegen`; const RNCORE_CONFIGS = { ios: path.join(REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'ReactCommon'), android: path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'ReactAndroid', 'build', 'generated', 'source', 'codegen', ), }; const CORE_LIBRARIES_WITH_OUTPUT_FOLDER = { rncore: RNCORE_CONFIGS, FBReactNativeSpec: { ios: path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'React', 'FBReactNativeSpec', ), android: path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'ReactAndroid', 'build', 'generated', 'source', 'codegen', ), }, }; const packageJsonPath = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'package.json', ); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); const REACT_NATIVE = packageJson.name; const MODULES_PROTOCOLS_H_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'RCTModulesConformingToProtocolsProviderH.template', ); const MODULES_PROTOCOLS_MM_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'RCTModulesConformingToProtocolsProviderMM.template', ); const THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'RCTThirdPartyComponentsProviderH.template', ); const THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'RCTThirdPartyComponentsProviderMM.template', ); const APP_DEPENDENCY_PROVIDER_H_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'RCTAppDependencyProviderH.template', ); const APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'RCTAppDependencyProviderMM.template', ); const APP_DEPENDENCY_PROVIDER_PODSPEC_TEMPLATE_PATH = path.join( REACT_NATIVE_PACKAGE_ROOT_FOLDER, 'scripts', 'codegen', 'templates', 'ReactAppDependencyProvider.podspec.template', ); const codegenLog = (text, info = false) => { // ANSI escape codes for colors and formatting const reset = '\x1b[0m'; const cyan = '\x1b[36m'; const yellow = '\x1b[33m'; const bold = '\x1b[1m'; const color = info ? yellow : ''; console.log(`${cyan}${bold}[Codegen]${reset} ${color}${text}${reset}`); }; // HELPERS function pkgJsonIncludesGeneratedCode(pkgJson) { return pkgJson.codegenConfig && pkgJson.codegenConfig.includesGeneratedCode; } function isReactNativeCoreLibrary(libraryName) { return libraryName in CORE_LIBRARIES_WITH_OUTPUT_FOLDER; } function readPkgJsonInDirectory(dir) { const pkgJsonPath = path.join(dir, 'package.json'); if (!fs.existsSync(pkgJsonPath)) { throw `[Codegen] Error: ${pkgJsonPath} does not exist.`; } return JSON.parse(fs.readFileSync(pkgJsonPath)); } function printDeprecationWarningIfNeeded(dependency) { if (dependency === REACT_NATIVE) { return; } codegenLog(`CodegenConfig Deprecated Setup for ${dependency}. The configuration file still contains the codegen in the libraries array. If possible, replace it with a single object. `); codegenLog(`BEFORE: { // ... "codegenConfig": { "libraries": [ { "name": "libName1", "type": "all|components|modules", "jsSrcsRoot": "libName1/js" }, { "name": "libName2", "type": "all|components|modules", "jsSrcsRoot": "libName2/src" } ] } } AFTER: { "codegenConfig": { "name": "libraries", "type": "all", "jsSrcsRoot": "." } } `); } // Reading Libraries function extractLibrariesFromConfigurationArray(configFile, dependencyPath) { return configFile.codegenConfig.libraries.map(config => { return { config, libraryPath: dependencyPath, }; }); } function extractLibrariesFromJSON(configFile, dependencyPath) { if (configFile.codegenConfig == null) { return []; } codegenLog(`Found ${configFile.name}`); if (configFile.codegenConfig.libraries == null) { const config = configFile.codegenConfig; return [ { config, libraryPath: dependencyPath, }, ]; } else { printDeprecationWarningIfNeeded(configFile.name); return extractLibrariesFromConfigurationArray(configFile, dependencyPath); } } const APPLE_PLATFORMS = ['ios', 'macos', 'tvos', 'visionos']; // Cocoapods specific platform keys function getCocoaPodsPlatformKey(platformName) { if (platformName === 'macos') { return 'osx'; } return platformName; } function extractSupportedApplePlatforms(dependency, dependencyPath) { codegenLog('Searching for podspec in the project dependencies.', true); const podspecs = glob.sync('*.podspec', {cwd: dependencyPath}); if (podspecs.length === 0) { return; } // Take the first podspec found const podspec = fs.readFileSync( path.join(dependencyPath, podspecs[0]), 'utf8', ); /** * Podspec can have platforms defined in two ways: * 1. `spec.platforms = { :ios => "11.0", :tvos => "11.0" }` * 2. `s.ios.deployment_target = "11.0"` * `s.tvos.deployment_target = "11.0"` */ const supportedPlatforms = podspec .split('\n') .filter( line => line.includes('platform') || line.includes('deployment_target'), ) .join(''); // Generate a map of supported platforms { [platform]: true/false } const supportedPlatformsMap = APPLE_PLATFORMS.reduce( (acc, platform) => ({ ...acc, [platform]: supportedPlatforms.includes( getCocoaPodsPlatformKey(platform), ), }), {}, ); const supportedPlatformsList = Object.keys(supportedPlatformsMap).filter( key => supportedPlatformsMap[key], ); if (supportedPlatformsList.length > 0) { codegenLog( `Supported Apple platforms: ${supportedPlatformsList.join( ', ', )} for ${dependency}`, ); } return supportedPlatformsMap; } function findExternalLibraries(pkgJson, projectRoot) { const dependencies = { ...pkgJson.dependencies, ...pkgJson.devDependencies, ...pkgJson.peerDependencies, }; // Determine which of these are codegen-enabled libraries codegenLog( 'Searching for codegen-enabled libraries in the project dependencies.', true, ); // Handle third-party libraries return Object.keys(dependencies).flatMap(dependency => { let configFilePath = ''; try { configFilePath = require.resolve(path.join(dependency, 'package.json'), { paths: [projectRoot], }); } catch (e) { // require.resolve fails if the dependency is a local node module. if ( dependencies[dependency].startsWith('.') || // handles relative paths dependencies[dependency].startsWith('/') // handles absolute paths ) { configFilePath = path.join( projectRoot, pkgJson.dependencies[dependency], 'package.json', ); } else { return []; } } const configFile = JSON.parse(fs.readFileSync(configFilePath)); const codegenConfigFileDir = path.dirname(configFilePath); return extractLibrariesFromJSON(configFile, codegenConfigFileDir); }); } function findLibrariesFromReactNativeConfig(projectRoot) { const rnConfigFileName = 'react-native.config.js'; codegenLog( `Searching for codegen-enabled libraries in ${rnConfigFileName}`, true, ); const rnConfigFilePath = path.resolve(projectRoot, rnConfigFileName); if (!fs.existsSync(rnConfigFilePath)) { return []; } const rnConfig = require(rnConfigFilePath); if (rnConfig.dependencies == null) { return []; } return Object.keys(rnConfig.dependencies).flatMap(name => { const dependencyConfig = rnConfig.dependencies[name]; if (!dependencyConfig.root) { return []; } const codegenConfigFileDir = path.resolve( projectRoot, dependencyConfig.root, ); let configFile; try { configFile = readPkgJsonInDirectory(codegenConfigFileDir); } catch { return []; } return extractLibrariesFromJSON(configFile, codegenConfigFileDir); }); } function findProjectRootLibraries(pkgJson, projectRoot) { codegenLog('Searching for codegen-enabled libraries in the app.', true); if (pkgJson.codegenConfig == null) { codegenLog( 'The "codegenConfig" field is not defined in package.json. Assuming there is nothing to generate at the app level.', true, ); return []; } if (typeof pkgJson.codegenConfig !== 'object') { throw 'The "codegenConfig" field must be an Object.'; } return extractLibrariesFromJSON(pkgJson, projectRoot); } // CodeGen function buildCodegenIfNeeded() { if (!fs.existsSync(CODEGEN_REPO_PATH)) { return; } // Assuming we are working in the react-native repo. We might need to build the codegen. // This will become unnecessary once we start using Babel Register for the codegen package. const libPath = path.join(CODEGEN_REPO_PATH, 'lib'); if (fs.existsSync(libPath) && fs.readdirSync(libPath).length > 0) { return; } codegenLog('Building react-native-codegen package.', true); execSync('yarn install', { cwd: CODEGEN_REPO_PATH, stdio: 'inherit', }); execSync('yarn build', { cwd: CODEGEN_REPO_PATH, stdio: 'inherit', }); } function readOutputDirFromPkgJson(pkgJson, platform) { const codegenConfig = pkgJson.codegenConfig; if (codegenConfig == null || typeof codegenConfig !== 'object') { return null; } const outputDir = codegenConfig.outputDir; if (outputDir == null) { return null; } if (typeof outputDir === 'string') { return outputDir; } if (typeof outputDir === 'object') { return outputDir[platform]; } return null; } function defaultOutputPathForAndroid(baseOutputPath) { return path.join( baseOutputPath, 'android', 'app', 'build', 'generated', 'source', 'codegen', ); } function defaultOutputPathForIOS(baseOutputPath) { return path.join(baseOutputPath, 'build', 'generated', 'ios'); } function computeOutputPath(projectRoot, baseOutputPath, pkgJson, platform) { if (baseOutputPath == null) { const outputDirFromPkgJson = readOutputDirFromPkgJson(pkgJson, platform); if (outputDirFromPkgJson != null) { baseOutputPath = path.join(projectRoot, outputDirFromPkgJson); } else { baseOutputPath = projectRoot; } } if (pkgJsonIncludesGeneratedCode(pkgJson)) { // Don't create nested directories for libraries to make importing generated headers easier. return baseOutputPath; } if (platform === 'android') { return defaultOutputPathForAndroid(baseOutputPath); } if (platform === 'ios') { return defaultOutputPathForIOS(baseOutputPath); } return baseOutputPath; } function reactNativeCoreLibraryOutputPath(libraryName, platform) { return CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName] ? CORE_LIBRARIES_WITH_OUTPUT_FOLDER[libraryName][platform] : null; } function generateSchemaInfo(library, platform) { const pathToJavaScriptSources = path.join( library.libraryPath, library.config.jsSrcsDir, ); codegenLog(`Processing ${library.config.name}`); const supportedApplePlatforms = extractSupportedApplePlatforms( library.config.name, library.libraryPath, ); // Generate one schema for the entire library... return { library: library, supportedApplePlatforms, schema: utils .getCombineJSToSchema() .combineSchemasInFileList( [pathToJavaScriptSources], platform, /NativeSampleTurboModule/, ), }; } function shouldSkipGenerationForRncore(schemaInfo, platform) { if (platform !== 'ios' || schemaInfo.library.config.name !== 'rncore') { return false; } const rncoreOutputPath = CORE_LIBRARIES_WITH_OUTPUT_FOLDER.rncore.ios; const rncoreAbsolutePath = path.resolve(rncoreOutputPath); return ( rncoreAbsolutePath.includes('node_modules') && fs.existsSync(rncoreAbsolutePath) && fs.readdirSync(rncoreAbsolutePath).length > 0 ); } function shouldSkipGenerationForFBReactNativeSpec(schemaInfo, platform) { if ( platform !== 'ios' || schemaInfo.library.config.name !== 'FBReactNativeSpec' ) { return false; } const fbReactNativeSpecOutputPath = CORE_LIBRARIES_WITH_OUTPUT_FOLDER.FBReactNativeSpec.ios; const fbReactNativeSpecAbsolutePath = path.resolve( fbReactNativeSpecOutputPath, ); return ( fbReactNativeSpecAbsolutePath.includes('node_modules') && fs.existsSync(fbReactNativeSpecAbsolutePath) && fs.readdirSync(fbReactNativeSpecAbsolutePath).length > 0 ); } function generateCode(outputPath, schemaInfo, includesGeneratedCode, platform) { if (shouldSkipGenerationForRncore(schemaInfo, platform)) { codegenLog( '[Codegen - rncore] Skipping iOS code generation for rncore as it has been generated already.', true, ); return; } if (shouldSkipGenerationForFBReactNativeSpec(schemaInfo, platform)) { codegenLog( '[Codegen - FBReactNativeSpec] Skipping iOS code generation for FBReactNativeSpec as it has been generated already.', true, ); return; } const libraryName = schemaInfo.library.config.name; const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), libraryName)); const tmpOutputDir = path.join(tmpDir, 'out'); fs.mkdirSync(tmpOutputDir, {recursive: true}); codegenLog(`Generating Native Code for ${libraryName} - ${platform}`); const useLocalIncludePaths = includesGeneratedCode; generateSpecsCLIExecutor.generateSpecFromInMemorySchema( platform, schemaInfo.schema, tmpOutputDir, libraryName, 'com.facebook.fbreact.specs', schemaInfo.library.config.type, useLocalIncludePaths, ); // Finally, copy artifacts to the final output directory. const outputDir = reactNativeCoreLibraryOutputPath(libraryName, platform) ?? outputPath; fs.mkdirSync(outputDir, {recursive: true}); fs.cpSync(tmpOutputDir, outputDir, {recursive: true}); codegenLog(`Generated artifacts: ${outputDir}`); } function generateSchemaInfos(libraries) { return libraries.map(generateSchemaInfo); } function generateNativeCode( outputPath, schemaInfos, includesGeneratedCode, platform, ) { return schemaInfos.map(schemaInfo => { generateCode(outputPath, schemaInfo, includesGeneratedCode, platform); }); } function mustGenerateNativeCode(includeLibraryPath, schemaInfo) { // If library's 'codegenConfig' sets 'includesGeneratedCode' to 'true', // then we assume that native code is shipped with the library, // and we don't need to generate it. return ( schemaInfo.library.libraryPath === includeLibraryPath || !schemaInfo.library.config.includesGeneratedCode ); } function findCodegenEnabledLibraries(pkgJson, projectRoot) { const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot); if (pkgJsonIncludesGeneratedCode(pkgJson)) { return projectLibraries; } else { return [ ...projectLibraries, ...findExternalLibraries(pkgJson, projectRoot), ...findLibrariesFromReactNativeConfig(projectRoot), ]; } } function generateCustomURLHandlers(libraries, outputDir) { const customImageURLLoaderClasses = libraries .flatMap( library => library?.config?.ios?.modulesConformingToProtocol?.RCTImageURLLoader, ) .filter(Boolean) .map(className => `@"${className}"`) .join(',\n\t\t'); const customImageDataDecoderClasses = libraries .flatMap( library => library?.config?.ios?.modulesConformingToProtocol?.RCTImageDataDecoder, ) .filter(Boolean) .map(className => `@"${className}"`) .join(',\n\t\t'); const customURLHandlerClasses = libraries .flatMap( library => library?.config?.ios?.modulesConformingToProtocol?.RCTURLRequestHandler, ) .filter(Boolean) .map(className => `@"${className}"`) .join(',\n\t\t'); const template = fs.readFileSync(MODULES_PROTOCOLS_MM_TEMPLATE_PATH, 'utf8'); const finalMMFile = template .replace(/{imageURLLoaderClassNames}/, customImageURLLoaderClasses) .replace(/{imageDataDecoderClassNames}/, customImageDataDecoderClasses) .replace(/{requestHandlersClassNames}/, customURLHandlerClasses); fs.mkdirSync(outputDir, {recursive: true}); fs.writeFileSync( path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.mm'), finalMMFile, ); const templateH = fs.readFileSync(MODULES_PROTOCOLS_H_TEMPLATE_PATH, 'utf8'); fs.writeFileSync( path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.h'), templateH, ); } function generateAppDependencyProvider(outputDir) { fs.mkdirSync(outputDir, {recursive: true}); codegenLog('Generating RCTAppDependencyProvider'); const templateH = fs.readFileSync( APP_DEPENDENCY_PROVIDER_H_TEMPLATE_PATH, 'utf8', ); const finalPathH = path.join(outputDir, 'RCTAppDependencyProvider.h'); fs.writeFileSync(finalPathH, templateH); codegenLog(`Generated artifact: ${finalPathH}`); const templateMM = fs.readFileSync( APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH, 'utf8', ); const finalPathMM = path.join(outputDir, 'RCTAppDependencyProvider.mm'); fs.writeFileSync(finalPathMM, templateMM); codegenLog(`Generated artifact: ${finalPathMM}`); // Generate the podspec file const templatePodspec = fs .readFileSync(APP_DEPENDENCY_PROVIDER_PODSPEC_TEMPLATE_PATH, 'utf8') .replace(/{react-native-version}/, packageJson.version) .replace(/{react-native-licence}/, packageJson.license); const finalPathPodspec = path.join( outputDir, 'ReactAppDependencyProvider.podspec', ); fs.writeFileSync(finalPathPodspec, templatePodspec); codegenLog(`Generated podspec: ${finalPathPodspec}`); } function generateRCTThirdPartyComponents(libraries, outputDir) { fs.mkdirSync(outputDir, {recursive: true}); // Generate Header File codegenLog('Generating RCTThirdPartyComponentsProvider.h'); const templateH = fs.readFileSync( THIRD_PARTY_COMPONENTS_H_TEMPLATE_PATH, 'utf8', ); const finalPathH = path.join(outputDir, 'RCTThirdPartyComponentsProvider.h'); fs.writeFileSync(finalPathH, templateH); codegenLog(`Generated artifact: ${finalPathH}`); codegenLog('Generating RCTThirdPartyComponentsProvider.mm'); let componentsInLibraries = {}; libraries.forEach(({config, libraryPath}) => { if (isReactNativeCoreLibrary(config.name) || config.type === 'modules') { return; } const libraryName = JSON.parse( fs.readFileSync(path.join(libraryPath, 'package.json')), ).name; if (config.ios?.componentProvider) { componentsInLibraries[libraryName] = Object.keys( config.ios?.componentProvider, ).map(componentName => { return { componentName, className: config.ios?.componentProvider[componentName], }; }); return; } codegenLog(`Crawling ${libraryName} library for components`); // crawl all files and subdirectories for file with the ".mm" extension const files = findFilesWithExtension(libraryPath, '.mm'); const componentsMapping = files .flatMap(file => findRCTComponentViewProtocolClass(file)) .filter(Boolean); if (componentsMapping.length !== 0) { codegenLog( `[DEPRECATED] ${libraryName} should add the 'ios.componentProvider' property in their codegenConfig`, true, ); } componentsInLibraries[libraryName] = componentsMapping; }); const thirdPartyComponentsMapping = Object.keys(componentsInLibraries) .flatMap(library => { const components = componentsInLibraries[library]; return components.map(({componentName, className}) => { return `\t\t@"${componentName}": NSClassFromString(@"${className}"), // ${library}`; }); }) .join('\n'); // Generate implementation file const templateMM = fs .readFileSync(THIRD_PARTY_COMPONENTS_MM_TEMPLATE_PATH, 'utf8') .replace(/{thirdPartyComponentsMapping}/, thirdPartyComponentsMapping); const finalPathMM = path.join( outputDir, 'RCTThirdPartyComponentsProvider.mm', ); fs.writeFileSync(finalPathMM, templateMM); codegenLog(`Generated artifact: ${finalPathMM}`); } // Given a path, return the paths of all the files with extension .mm in // the path dir and all its subdirectories. function findFilesWithExtension(filePath, extension) { const files = []; const dir = fs.readdirSync(filePath); dir.forEach(file => { const absolutePath = path.join(filePath, file); // Exclude files provided by react-native if (absolutePath.includes(`${path.sep}react-native${path.sep}`)) { return null; } // Skip hidden folders, that starts with `.` if (absolutePath.includes(`${path.sep}.`)) { return null; } if ( fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory() ) { files.push(...findFilesWithExtension(absolutePath, extension)); } else if (file.endsWith(extension)) { files.push(absolutePath); } }); return files; } // Given a filepath, read the file and look for a string that starts with 'Class ' // and ends with 'Cls(void)'. Return the string between the two. function findRCTComponentViewProtocolClass(filepath) { const fileContent = fs.readFileSync(filepath, 'utf8'); const regex = /Class (.*)Cls\(/; const match = fileContent.match(regex); if (match) { const componentName = match[1]; // split the file by \n // remove all the lines before the one that matches the regex above // find the first return statement after that that ends with .class // return what's between return and `.class` const lines = fileContent.split('\n'); const signatureIndex = lines.findIndex(line => regex.test(line)); const returnRegex = /return (.*)\.class/; const classNameMatch = String(lines.slice(signatureIndex)).match( returnRegex, ); if (classNameMatch) { const className = classNameMatch[1]; codegenLog(`Match found ${componentName} -> ${className}`); return { componentName, className, }; } console.warn( `Could not find class name for component ${componentName}. Register it manually`, ); return null; } return null; } // It removes all the empty files and empty folders // it finds, starting from `filepath`, recursively. // // This function is needed since, after aligning the codegen between // iOS and Android, we have to create empty folders in advance and // we don't know whether they will be populated up until the end of the process. // // @parameter filepath: the root path from which we want to remove the empty files and folders. function cleanupEmptyFilesAndFolders(filepath) { const stats = fs.statSync(filepath); if (stats.isFile() && stats.size === 0) { fs.rmSync(filepath); return; } else if (stats.isFile()) { return; } const dirContent = fs.readdirSync(filepath); dirContent.forEach(contentPath => cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)), ); // The original folder may be filled with empty folders // if that the case, we would also like to remove the parent. // Hence, we need to read the folder again. const newContent = fs.readdirSync(filepath); if (newContent.length === 0) { fs.rmdirSync(filepath); return; } } function generateRNCoreComponentsIOS(projectRoot /*: string */) /*: void*/ { const ios = 'ios'; buildCodegenIfNeeded(); const pkgJson = readPkgJsonInDirectory(projectRoot); const rncoreLib = findProjectRootLibraries(pkgJson, projectRoot).filter( library => library.config.name === 'rncore', )[0]; if (!rncoreLib) { throw new Error( "[Codegen] Can't find rncore library. Failed to generate rncore artifacts", ); } const rncoreSchemaInfo = generateSchemaInfo(rncoreLib, ios); generateCode('', rncoreSchemaInfo, false, ios); } function generateFBReactNativeSpecIOS(projectRoot /*: string */) /*: void*/ { const ios = 'ios'; buildCodegenIfNeeded(); const pkgJson = readPkgJsonInDirectory(projectRoot); const fbReactNativeSpecLib = findProjectRootLibraries( pkgJson, projectRoot, ).filter(library => library.config.name === 'FBReactNativeSpec')[0]; if (!fbReactNativeSpecLib) { throw new Error( "[Codegen] Can't find FBReactNativeSpec library. Failed to generate rncore artifacts", ); } const fbReactNativeSchemaInfo = generateSchemaInfo(fbReactNativeSpecLib, ios); generateCode('', fbReactNativeSchemaInfo, false, ios); } // Execute /** * This function is the entry point for the codegen. It: * - reads the package json * - extracts the libraries * - setups the CLI to generate the code * - generate the code * * @parameter projectRoot: the directory with the app source code, where the package.json lives. * @parameter baseOutputPath: the base output path for the CodeGen. * @parameter targetPlatform: the target platform. Supported values: 'android', 'ios', 'all'. * @throws If it can't find a config file for react-native. * @throws If it can't find a CodeGen configuration in the file. * @throws If it can't find a cli for the CodeGen. */ function execute(projectRoot, targetPlatform, baseOutputPath) { try { codegenLog(`Analyzing ${path.join(projectRoot, 'package.json')}`); const supportedPlatforms = ['android', 'ios']; if ( targetPlatform !== 'all' && !supportedPlatforms.includes(targetPlatform) ) { throw new Error( `Invalid target platform: ${targetPlatform}. Supported values are: ${supportedPlatforms.join( ', ', )}, all`, ); } const pkgJson = readPkgJsonInDirectory(projectRoot); buildCodegenIfNeeded(); const libraries = findCodegenEnabledLibraries(pkgJson, projectRoot); if (libraries.length === 0) { codegenLog('No codegen-enabled libraries found.', true); return; } let platforms = targetPlatform === 'all' ? supportedPlatforms : [targetPlatform]; for (const platform of platforms) { const outputPath = computeOutputPath( projectRoot, baseOutputPath, pkgJson, platform, ); const schemaInfos = generateSchemaInfos(libraries); generateNativeCode( outputPath, schemaInfos.filter(schemaInfo => mustGenerateNativeCode(projectRoot, schemaInfo), ), pkgJsonIncludesGeneratedCode(pkgJson), platform, ); generateRCTThirdPartyComponents(libraries, outputPath); generateCustomURLHandlers(libraries, outputPath); generateAppDependencyProvider(outputPath); cleanupEmptyFilesAndFolders(outputPath); } } catch (err) { codegenLog(err); process.exitCode = 1; } codegenLog('Done.', true); return; } module.exports = { execute, generateRNCoreComponentsIOS, generateFBReactNativeSpecIOS, // exported for testing purposes only: _extractLibrariesFromJSON: extractLibrariesFromJSON, _cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders, _extractSupportedApplePlatforms: extractSupportedApplePlatforms, };