import chalk from 'chalk'; import fs from 'fs-extra'; import path from 'path'; import { IOS_DIR } from '../../Constants'; import logger from '../../Logger'; import { copyFileWithTransformsAsync } from '../../Transforms'; import { FileTransforms } from '../../Transforms.types'; import { searchFilesAsync } from '../../Utils'; import vendoredModulesTransforms from './transforms/vendoredModulesTransforms'; const IOS_VENDORED_DIR = path.join(IOS_DIR, 'vendored'); /** * Versions iOS vendored modules. */ export async function versionVendoredModulesAsync( sdkNumber: number, filterModules: string[] | null ): Promise { const prefix = `ABI${sdkNumber}_0_0`; const config = vendoredModulesTransforms(prefix); const baseTransforms = baseTransformsFactory(prefix); const unversionedDir = path.join(IOS_VENDORED_DIR, 'unversioned'); const versionedDir = path.join(IOS_VENDORED_DIR, `sdk${sdkNumber}`); const sourceDirents = (await fs.readdir(unversionedDir, { withFileTypes: true })).filter( (dirent) => { return dirent.isDirectory() && (!filterModules || filterModules.includes(dirent.name)); } ); for (const { name } of sourceDirents) { logger.info('🔃 Versioning vendored module %s', chalk.green(name)); const moduleConfig = config[name]; const sourceDirectory = path.join(unversionedDir, name); const targetDirectory = path.join(versionedDir, name); const files = await searchFilesAsync(sourceDirectory, '**'); await fs.remove(targetDirectory); for (const sourceFile of files) { await copyFileWithTransformsAsync({ sourceFile, sourceDirectory, targetDirectory, transforms: { path: [...baseTransforms.path, ...(moduleConfig?.path ?? [])], content: [...baseTransforms.content, ...(moduleConfig?.content ?? [])], }, }); } } } /** * Generates base transforms to apply for all vendored modules. */ function baseTransformsFactory(prefix: string): Required { return { path: [ { find: /([^/]+\.podspec\.json)$\b/, replaceWith: `${prefix}$1`, }, { find: /\b(RCT|RNC|RNG|RNR|REA|RNS)([^/]*\.)(h|m|mm)/, replaceWith: `${prefix}$1$2$3`, }, ], content: [ { paths: '*.podspec.json', find: /"name": "([\w-]+)"/, replaceWith: `"name": "${prefix}$1"`, }, { find: /\b(React)/g, replaceWith: `${prefix}$1`, }, { find: /\b(RCT|RNC|RNG|RNR|REA|RNS)(\w+)\b/g, replaceWith: `${prefix}$1$2`, }, { find: /facebook::/g, replaceWith: `${prefix}facebook::`, }, { find: /react::/g, replaceWith: `${prefix}React::`, }, { find: /using namespace (facebook|react)/g, replaceWith: (_, p1) => { return `using namespace ${prefix}${p1 === 'react' ? 'React' : p1}`; }, }, { // Objective-C only, see the comment in the rule below. paths: '*.{h,m,mm}', find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi, replaceWith: `${prefix}R$1`, }, { // Swift translates uppercased letters at the beginning of the method name to lowercased letters, we must comply with it. paths: '*.swift', find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi, replaceWith: (_, p1) => `${prefix.toLowerCase()}R${p1}`, }, { find: //, replaceWith: `<${prefix}jsi/${prefix}$1.h>`, }, { find: /(JSCExecutorFactory|HermesExecutorFactory)\.h/g, replaceWith: `${prefix}$1.h`, }, { find: /viewForReactTag/g, replaceWith: `viewFor${prefix}ReactTag`, }, { find: /isReactRootView/g, replaceWith: `is${prefix}ReactRootView`, }, { // Prefix only unindented `@objc` (notice `^` and `m` flag in the pattern). Method names shouldn't get prefixed. paths: '*.swift', find: /^@objc\(([^)]+)\)/gm, replaceWith: `@objc(${prefix}$1)`, }, { paths: '*.podspec.json', find: new RegExp(`${prefix}React-${prefix}RCT`, 'g'), replaceWith: `${prefix}React-RCT`, }, { paths: '*.podspec.json', find: new RegExp(`${prefix}React-Core\\/${prefix}RCT`, 'g'), replaceWith: `${prefix}React-Core/RCT`, }, ], }; }