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