1import { Command } from '@expo/commander'; 2import spawnAsync from '@expo/spawn-async'; 3import chalk from 'chalk'; 4import fs from 'fs-extra'; 5import path from 'path'; 6 7import { EXPO_DIR, ANDROID_DIR } from '../Constants'; 8import { getReactNativeSubmoduleDir } from '../Directories'; 9import logger from '../Logger'; 10import { getNextSDKVersionAsync } from '../ProjectVersions'; 11import { transformFileAsync } from '../Transforms'; 12 13type ActionOptions = { 14 checkout?: string; 15 sdkVersion?: string; 16}; 17 18const REACT_NATIVE_SUBMODULE_PATH = getReactNativeSubmoduleDir(); 19const REACT_ANDROID_PATH = path.join(ANDROID_DIR, 'ReactAndroid'); 20const REACT_COMMON_PATH = path.join(ANDROID_DIR, 'ReactCommon'); 21const REACT_ANDROID_GRADLE_PATH = path.join(REACT_ANDROID_PATH, 'build.gradle'); 22 23async function checkoutReactNativeSubmoduleAsync(checkoutRef: string): Promise<void> { 24 await spawnAsync('git', ['fetch'], { 25 cwd: REACT_NATIVE_SUBMODULE_PATH, 26 }); 27 await spawnAsync('git', ['checkout', checkoutRef], { 28 cwd: REACT_NATIVE_SUBMODULE_PATH, 29 }); 30} 31 32async function updateReactAndroidAsync(sdkVersion: string): Promise<void> { 33 console.log(`Cleaning ${chalk.magenta(path.relative(EXPO_DIR, REACT_ANDROID_PATH))}...`); 34 await fs.remove(REACT_ANDROID_PATH); 35 36 console.log(`Cleaning ${chalk.magenta(path.relative(EXPO_DIR, REACT_COMMON_PATH))}...`); 37 await fs.remove(REACT_COMMON_PATH); 38 39 console.log( 40 `Running ${chalk.blue('ReactAndroidCodeTransformer')} with ${chalk.yellow( 41 `./gradlew :tools:execute --args ${sdkVersion}` 42 )} command...` 43 ); 44 await spawnAsync('./gradlew', [':tools:execute', '--args', sdkVersion], { 45 cwd: ANDROID_DIR, 46 stdio: 'inherit', 47 }); 48 49 logger.info( 50 ' Transforming', 51 chalk.magenta('ReactAndroid/build.gradle'), 52 'to make use of', 53 chalk.yellow('NDK_ABI_FILTERS') 54 ); 55 await transformFileAsync(REACT_ANDROID_GRADLE_PATH, [ 56 { 57 find: /^(def reactNativeArchitectures\(\) {)/m, 58 replaceWith: `$1\n if (System.getenv('NDK_ABI_FILTERS')) { return System.getenv('NDK_ABI_FILTERS'); }`, 59 }, 60 ]); 61 await transformFileAsync(REACT_ANDROID_GRADLE_PATH, [ 62 { 63 find: /^(\s*jsRootDir\s*=\s*)file\(.+\)$/m, 64 replaceWith: 65 '$1file("$projectDir/../../react-native-lab/react-native/Libraries")' + 66 '\n codegenDir = file("$projectDir/../../react-native-lab/react-native/packages/react-native-codegen")', 67 }, 68 { 69 find: /^(\s*reactRoot\s*=\s*)file\(.+\)$/m, 70 replaceWith: '$1file("$projectDir/../../react-native-lab/react-native")', 71 }, 72 { 73 find: /^(\s*reactNativeRootDir\s*=\s*)file\(.+\)$/m, 74 replaceWith: '$1file("$projectDir/../../react-native-lab/react-native")', 75 }, 76 { 77 find: /api\("androidx.appcompat:appcompat:\d+\.\d+\.\d+"\)/, 78 replaceWith: 'api("androidx.appcompat:appcompat:1.2.0")', 79 }, 80 { 81 find: /compileSdkVersion\s+\d+/, 82 replaceWith: 'compileSdkVersion 30', 83 }, 84 ]); 85} 86 87async function action(options: ActionOptions) { 88 if (options.checkout) { 89 console.log( 90 `Checking out ${chalk.magenta( 91 path.relative(EXPO_DIR, REACT_NATIVE_SUBMODULE_PATH) 92 )} submodule at ${chalk.blue(options.checkout)} ref...` 93 ); 94 await checkoutReactNativeSubmoduleAsync(options.checkout); 95 } 96 97 // When we're updating React Native, we mostly want it to be for the next SDK that isn't versioned yet. 98 const androidSdkVersion = options.sdkVersion || (await getNextSDKVersionAsync('android')); 99 100 if (!androidSdkVersion) { 101 throw new Error( 102 'Cannot obtain next SDK version. Try to run with --sdkVersion <sdkVersion> flag.' 103 ); 104 } 105 106 console.log( 107 `Updating ${chalk.green('ReactAndroid')} for SDK ${chalk.cyan(androidSdkVersion)} ...` 108 ); 109 await updateReactAndroidAsync(androidSdkVersion); 110} 111 112export default (program: Command) => { 113 program 114 .command('update-react-native') 115 .alias('update-rn', 'urn') 116 .description( 117 'Updates React Native submodule and applies Expo-specific code transformations on ReactAndroid and ReactCommon folders.' 118 ) 119 .option( 120 '-c, --checkout [string]', 121 "Git's ref to the commit, tag or branch on which the React Native submodule should be checkouted." 122 ) 123 .option( 124 '-s, --sdkVersion [string]', 125 'SDK version for which the forked React Native will be used. Defaults to the newest SDK version increased by a major update.' 126 ) 127 .asyncAction(action); 128}; 129