1import { Command } from '@expo/commander'; 2import JsonFile from '@expo/json-file'; 3import fs from 'fs-extra'; 4import path from 'path'; 5 6import { EXPO_DIR } from '../Constants'; 7import logger from '../Logger'; 8import { getPackageViewAsync } from '../Npm'; 9import { transformFileAsync } from '../Transforms'; 10import { installAsync as workspaceInstallAsync } from '../Workspace'; 11 12export default (program: Command) => { 13 program 14 .command('setup-react-native-nightly') 15 .description('Setup expo/expo monorepo to install react-native nightly build for testing') 16 .asyncAction(main); 17}; 18 19async function main() { 20 const nightlyVersion = (await getPackageViewAsync('react-native'))?.['dist-tags'].nightly; 21 if (!nightlyVersion) { 22 throw new Error('Unable to get react-native nightly version.'); 23 } 24 25 logger.info('Adding pinned packages:'); 26 const pinnedPackages = { 27 'react-native': nightlyVersion, 28 }; 29 await addPinnedPackagesAsync(pinnedPackages); 30 31 logger.info('Yarning...'); 32 await workspaceInstallAsync(); 33 34 await updateReactNativePackageAsync(); 35 36 await patchReanimatedAsync(); 37 38 logger.info('Setting up project files for bare-expo.'); 39 await updateBareExpoAsync(); 40} 41 42async function addPinnedPackagesAsync(packages: Record<string, string>) { 43 const workspacePackageJsonPath = path.join(EXPO_DIR, 'package.json'); 44 const json = await JsonFile.readAsync(workspacePackageJsonPath); 45 json.resolutions ||= {}; 46 for (const [name, version] of Object.entries(packages)) { 47 logger.log(' ', `${name}@${version}`); 48 json.resolutions[name] = version; 49 } 50 await JsonFile.writeAsync(workspacePackageJsonPath, json); 51} 52 53async function updateReactNativePackageAsync() { 54 const root = path.join(EXPO_DIR, 'node_modules', 'react-native'); 55 56 // Third party libraries used to use react-native minor version, update the version 9999.9999.9999 as the latest version 57 await transformFileAsync(path.join(root, 'package.json'), [ 58 { 59 find: '"version": "0.0.0-', 60 replaceWith: '"version": "9999.9999.9999-', 61 }, 62 ]); 63 await transformFileAsync(path.join(root, 'ReactAndroid', 'gradle.properties'), [ 64 { 65 find: 'VERSION_NAME=0.0.0-', 66 replaceWith: 'VERSION_NAME=9999.9999.9999-', 67 }, 68 ]); 69 70 // Build hermes source from the main branch 71 await fs.writeFile(path.join(root, 'sdks', '.hermesversion'), 'main'); 72 await transformFileAsync(path.join(root, 'sdks', 'hermes-engine', 'hermes-engine.podspec'), [ 73 { 74 // Because we changed the version in package.json, the `isNightly` check in hermes-engine.podspec is broken 75 find: "isNightly = version.start_with?('0.0.0-')", 76 replaceWith: 'isNightly = true', 77 }, 78 ]); 79 80 // Remove unused hermes build artifacts to reduce build time 81 await transformFileAsync(path.join(root, 'sdks', 'hermes-engine', 'hermes-engine.podspec'), [ 82 { 83 find: './utils/build-mac-framework.sh', 84 replaceWith: '', 85 }, 86 ]); 87 await transformFileAsync( 88 path.join(root, 'sdks', 'hermes-engine', 'utils', 'build-ios-framework.sh'), 89 [ 90 { 91 find: 'build_apple_framework "iphoneos" "arm64" "$ios_deployment_target"', 92 replaceWith: '', 93 }, 94 { 95 find: 'build_apple_framework "catalyst" "x86_64;arm64" "$ios_deployment_target"', 96 replaceWith: '', 97 }, 98 { 99 find: 'create_universal_framework "iphoneos" "iphonesimulator" "catalyst"', 100 replaceWith: 'create_universal_framework "iphonesimulator"', 101 }, 102 ] 103 ); 104} 105 106async function patchReanimatedAsync() { 107 // Workaround for reanimated doesn't support the hermes where building from source 108 const root = path.join(EXPO_DIR, 'node_modules', 'react-native-reanimated'); 109 await transformFileAsync(path.join(root, 'android', 'build.gradle'), [ 110 { 111 find: /\bdef hermesAAR = file\(.+\)/g, 112 replaceWith: 113 'def hermesAAR = file("$reactNative/ReactAndroid/hermes-engine/build/outputs/aar/hermes-engine-debug.aar")', 114 }, 115 ]); 116 117 // Remove this after reanimated support react-native 0.71 118 await transformFileAsync(path.join(root, 'android', 'CMakeLists.txt'), [ 119 { 120 find: /(\s*"\$\{NODE_MODULES_DIR\}\/react-native\/ReactAndroid\/src\/main\/jni")/g, 121 replaceWith: 122 '$1\n "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule"', 123 }, 124 ]); 125} 126 127async function updateBareExpoAsync() { 128 const gradlePropsFile = path.join(EXPO_DIR, 'apps', 'bare-expo', 'android', 'gradle.properties'); 129 let content = await fs.readFile(gradlePropsFile, 'utf8'); 130 if (!content.match('reactNativeNightly=true')) { 131 content += `\nreactNativeNightly=true\n`; 132 await fs.writeFile(gradlePropsFile, content); 133 } 134} 135