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 { getNextSDKVersionAsync } from '../ProjectVersions'; 10import { transformFileAsync } from '../Transforms'; 11 12type ActionOptions = { 13 checkout?: string; 14 sdkVersion?: string; 15}; 16 17const REACT_NATIVE_SUBMODULE_PATH = getReactNativeSubmoduleDir(); 18const REACT_ANDROID_PATH = path.join(ANDROID_DIR, 'ReactAndroid'); 19const REACT_COMMON_PATH = path.join(ANDROID_DIR, 'ReactCommon'); 20const REACT_ANDROID_GRADLE_PATH = path.join(REACT_ANDROID_PATH, 'build.gradle'); 21const REACT_SDKS_PATHS = path.join(ANDROID_DIR, 'sdks'); 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(`Syncing ${chalk.magenta(path.relative(EXPO_DIR, REACT_SDKS_PATHS))}...`); 40 await fs.remove(REACT_SDKS_PATHS); 41 await fs.copy(path.join(REACT_NATIVE_SUBMODULE_PATH, 'sdks'), REACT_SDKS_PATHS); 42 43 console.log( 44 `Running ${chalk.blue('ReactAndroidCodeTransformer')} with ${chalk.yellow( 45 `./gradlew :tools:execute --args ${sdkVersion}` 46 )} command...` 47 ); 48 await spawnAsync('./gradlew', [':tools:execute', '--args', sdkVersion], { 49 cwd: ANDROID_DIR, 50 stdio: 'inherit', 51 }); 52 53 await transformFileAsync(REACT_ANDROID_GRADLE_PATH, [ 54 { 55 find: /^(\s*jsRootDir\s*=\s*)file\(.+\)$/m, 56 replaceWith: '$1file("$projectDir/../../react-native-lab/react-native/Libraries")', 57 }, 58 { 59 find: /^(\s*reactNativeDir\s*=\s*)file\(.+\)$/m, 60 replaceWith: '$1file("$projectDir/../../react-native-lab/react-native")', 61 }, 62 { 63 find: /^(\s*\/\/ We search for the codegen.*\n\s*\/\/ root packages folder.*\n\s*codegenDir = .*)$/m, 64 replaceWith: 65 ' codegenDir = file("$projectDir/../../react-native-lab/react-native/packages/react-native-codegen")', 66 }, 67 { 68 find: /api\("androidx.appcompat:appcompat:\d+\.\d+\.\d+"\)/, 69 replaceWith: 'api("androidx.appcompat:appcompat:1.2.0")', 70 }, 71 { 72 find: /compileSdkVersion\s+\d+/, 73 replaceWith: 'compileSdkVersion 30', 74 }, 75 ]); 76} 77 78async function action(options: ActionOptions) { 79 if (options.checkout) { 80 console.log( 81 `Checking out ${chalk.magenta( 82 path.relative(EXPO_DIR, REACT_NATIVE_SUBMODULE_PATH) 83 )} submodule at ${chalk.blue(options.checkout)} ref...` 84 ); 85 await checkoutReactNativeSubmoduleAsync(options.checkout); 86 } 87 88 // When we're updating React Native, we mostly want it to be for the next SDK that isn't versioned yet. 89 const androidSdkVersion = options.sdkVersion || (await getNextSDKVersionAsync('android')); 90 91 if (!androidSdkVersion) { 92 throw new Error( 93 'Cannot obtain next SDK version. Try to run with --sdkVersion <sdkVersion> flag.' 94 ); 95 } 96 97 console.log( 98 `Updating ${chalk.green('ReactAndroid')} for SDK ${chalk.cyan(androidSdkVersion)} ...` 99 ); 100 await updateReactAndroidAsync(androidSdkVersion); 101} 102 103export default (program: Command) => { 104 program 105 .command('update-react-native') 106 .alias('update-rn', 'urn') 107 .description( 108 'Updates React Native submodule and applies Expo-specific code transformations on ReactAndroid and ReactCommon folders.' 109 ) 110 .option( 111 '-c, --checkout [string]', 112 "Git's ref to the commit, tag or branch on which the React Native submodule should be checkouted." 113 ) 114 .option( 115 '-s, --sdkVersion [string]', 116 'SDK version for which the forked React Native will be used. Defaults to the newest SDK version increased by a major update.' 117 ) 118 .asyncAction(action); 119}; 120