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