1import chalk from 'chalk'; 2import fs from 'fs-extra'; 3import path from 'path'; 4 5import { IOS_DIR } from '../../Constants'; 6import logger from '../../Logger'; 7import { copyFileWithTransformsAsync } from '../../Transforms'; 8import { FileTransforms } from '../../Transforms.types'; 9import { searchFilesAsync } from '../../Utils'; 10import vendoredModulesTransforms from './transforms/vendoredModulesTransforms'; 11 12const IOS_VENDORED_DIR = path.join(IOS_DIR, 'vendored'); 13 14/** 15 * Versions iOS vendored modules. 16 */ 17export async function versionVendoredModulesAsync( 18 sdkNumber: number, 19 filterModules: string[] | null 20): Promise<void> { 21 const prefix = `ABI${sdkNumber}_0_0`; 22 const config = vendoredModulesTransforms(prefix); 23 const baseTransforms = baseTransformsFactory(prefix); 24 const unversionedDir = path.join(IOS_VENDORED_DIR, 'unversioned'); 25 const versionedDir = vendoredDirectoryForSDK(sdkNumber); 26 const sourceDirents = (await fs.readdir(unversionedDir, { withFileTypes: true })).filter( 27 (dirent) => { 28 return dirent.isDirectory() && (!filterModules || filterModules.includes(dirent.name)); 29 } 30 ); 31 32 for (const { name } of sourceDirents) { 33 logger.info(' Versioning vendored module %s', chalk.green(name)); 34 35 const moduleConfig = config[name]; 36 const sourceDirectory = path.join(unversionedDir, name); 37 const targetDirectory = path.join(versionedDir, name); 38 const files = await searchFilesAsync(sourceDirectory, '**'); 39 40 await fs.remove(targetDirectory); 41 42 for (const sourceFile of files) { 43 await copyFileWithTransformsAsync({ 44 sourceFile, 45 sourceDirectory, 46 targetDirectory, 47 transforms: { 48 path: [...baseTransforms.path, ...(moduleConfig?.path ?? [])], 49 content: [...baseTransforms.content, ...(moduleConfig?.content ?? [])], 50 }, 51 }); 52 } 53 } 54} 55 56/** 57 * Removes the directory with vendored modules for given SDK number. 58 */ 59export async function removeVersionedVendoredModulesAsync(sdkNumber: number): Promise<void> { 60 const versionedDir = vendoredDirectoryForSDK(sdkNumber); 61 await fs.remove(versionedDir); 62} 63 64/** 65 * Generates base transforms to apply for all vendored modules. 66 */ 67function baseTransformsFactory(prefix: string): Required<FileTransforms> { 68 return { 69 path: [ 70 { 71 find: /([^/]+\.podspec\.json)$\b/, 72 replaceWith: `${prefix}$1`, 73 }, 74 { 75 find: /\b(RCT|RNC|RNG|RNR|REA|RNS)([^/]*\.)(h|m|mm)/, 76 replaceWith: `${prefix}$1$2$3`, 77 }, 78 ], 79 content: [ 80 { 81 paths: '*.podspec.json', 82 find: /"name": "([\w-]+)"/, 83 replaceWith: `"name": "${prefix}$1"`, 84 }, 85 { 86 // Prefixes `React` with word-boundary and also in `insertReactSubview`, `removeReactSubview`. 87 find: /(\b|insert|remove)(React)/g, 88 replaceWith: `$1${prefix}$2`, 89 }, 90 { 91 find: /\b(RCT|RNC|RNG|RNR|REA|RNS)(\w+)\b/g, 92 replaceWith: `${prefix}$1$2`, 93 }, 94 { 95 find: /facebook::/g, 96 replaceWith: `${prefix}facebook::`, 97 }, 98 { 99 find: /react::/g, 100 replaceWith: `${prefix}React::`, 101 }, 102 { 103 find: /using namespace (facebook|react)/g, 104 replaceWith: (_, p1) => { 105 return `using namespace ${prefix}${p1 === 'react' ? 'React' : p1}`; 106 }, 107 }, 108 { 109 // Objective-C only, see the comment in the rule below. 110 paths: '*.{h,m,mm}', 111 find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi, 112 replaceWith: `${prefix}R$1`, 113 }, 114 { 115 // Swift translates uppercased letters at the beginning of the method name to lowercased letters, we must comply with it. 116 paths: '*.swift', 117 find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi, 118 replaceWith: (_, p1) => `${prefix.toLowerCase()}R${p1}`, 119 }, 120 { 121 find: /<jsi\/(.*)\.h>/, 122 replaceWith: `<${prefix}jsi/${prefix}$1.h>`, 123 }, 124 { 125 find: /(JSCExecutorFactory|HermesExecutorFactory)\.h/g, 126 replaceWith: `${prefix}$1.h`, 127 }, 128 { 129 find: /viewForReactTag/g, 130 replaceWith: `viewFor${prefix}ReactTag`, 131 }, 132 { 133 find: /isReactRootView/g, 134 replaceWith: `is${prefix}ReactRootView`, 135 }, 136 { 137 // Prefix only unindented `@objc` (notice `^` and `m` flag in the pattern). Method names shouldn't get prefixed. 138 paths: '*.swift', 139 find: /^@objc\(([^)]+)\)/gm, 140 replaceWith: `@objc(${prefix}$1)`, 141 }, 142 { 143 paths: '*.podspec.json', 144 find: new RegExp(`${prefix}React-${prefix}RCT`, 'g'), 145 replaceWith: `${prefix}React-RCT`, 146 }, 147 { 148 paths: '*.podspec.json', 149 find: new RegExp(`${prefix}React-Core\\/${prefix}RCT`, 'g'), 150 replaceWith: `${prefix}React-Core/RCT`, 151 }, 152 ], 153 }; 154} 155 156/** 157 * Returns the vendored directory for given SDK number. 158 */ 159function vendoredDirectoryForSDK(sdkNumber: number): string { 160 return path.join(IOS_VENDORED_DIR, `sdk${sdkNumber}`); 161} 162