1import { 2 ConfigPlugin, 3 ExportedConfigWithProps, 4 WarningAggregator, 5 withDangerousMod, 6 withMainActivity, 7} from '@expo/config-plugins'; 8import { ExpoConfig } from '@expo/config-types'; 9import fs from 'fs'; 10import path from 'path'; 11 12import { withDevMenuAppDelegate } from './withDevMenuAppDelegate'; 13 14const DEV_MENU_ANDROID_IMPORT = 'expo.modules.devmenu.react.DevMenuAwareReactActivity'; 15const DEV_MENU_ACTIVITY_CLASS = 'public class MainActivity extends DevMenuAwareReactActivity {'; 16 17const DEV_MENU_POD_IMPORT = 18 "pod 'expo-dev-menu', path: '../node_modules/expo-dev-menu', :configurations => :debug"; 19 20async function readFileAsync(path: string): Promise<string> { 21 return fs.promises.readFile(path, 'utf8'); 22} 23 24async function saveFileAsync(path: string, content: string): Promise<void> { 25 return fs.promises.writeFile(path, content, 'utf8'); 26} 27 28function addJavaImports(javaSource: string, javaImports: string[]): string { 29 const lines = javaSource.split('\n'); 30 const lineIndexWithPackageDeclaration = lines.findIndex(line => line.match(/^package .*;$/)); 31 for (const javaImport of javaImports) { 32 if (!javaSource.includes(javaImport)) { 33 const importStatement = `import ${javaImport};`; 34 lines.splice(lineIndexWithPackageDeclaration + 1, 0, importStatement); 35 } 36 } 37 return lines.join('\n'); 38} 39 40function addLines(content: string, find: string | RegExp, offset: number, toAdd: string[]) { 41 const lines = content.split('\n'); 42 43 let lineIndex = lines.findIndex(line => line.match(find)); 44 45 for (const newLine of toAdd) { 46 if (!content.includes(newLine)) { 47 lines.splice(lineIndex + offset, 0, newLine); 48 lineIndex++; 49 } 50 } 51 52 return lines.join('\n'); 53} 54 55async function editPodfile(config: ExportedConfigWithProps, action: (podfile: string) => string) { 56 const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile'); 57 try { 58 const podfile = action(await readFileAsync(podfilePath)); 59 60 return await saveFileAsync(podfilePath, podfile); 61 } catch (e) { 62 WarningAggregator.addWarningIOS('expo-dev-menu', `Couldn't modified AppDelegate.m - ${e}.`); 63 } 64} 65 66const withDevMenuActivity: ConfigPlugin = config => { 67 return withMainActivity(config, config => { 68 if (config.modResults.language === 'java') { 69 let content = config.modResults.contents; 70 content = addJavaImports(content, [DEV_MENU_ANDROID_IMPORT]); 71 content = content.replace( 72 'public class MainActivity extends ReactActivity {', 73 DEV_MENU_ACTIVITY_CLASS 74 ); 75 config.modResults.contents = content; 76 } else { 77 WarningAggregator.addWarningAndroid( 78 'expo-dev-menu', 79 `Cannot automatically configure MainActivity if it's not java` 80 ); 81 } 82 83 return config; 84 }); 85}; 86 87const withDevMenuPodfile: ConfigPlugin = config => { 88 return withDangerousMod(config, [ 89 'ios', 90 async config => { 91 await editPodfile(config, podfile => { 92 podfile = podfile.replace("platform :ios, '10.0'", "platform :ios, '11.0'"); 93 // Match both variations of Ruby config: 94 // unknown: pod 'expo-dev-menu', path: '../node_modules/expo-dev-menu', :configurations => :debug 95 // Rubocop: pod 'expo-dev-menu', path: '../node_modules/expo-dev-menu', configurations: :debug 96 if ( 97 !podfile.match( 98 /pod ['"]expo-dev-menu['"],\s?path: ['"]\.\.\/node_modules\/expo-dev-menu['"],\s?:?configurations:?\s(?:=>\s)?:debug/ 99 ) 100 ) { 101 podfile = addLines(podfile, 'use_react_native', 0, [` ${DEV_MENU_POD_IMPORT}`]); 102 } 103 return podfile; 104 }); 105 return config; 106 }, 107 ]); 108}; 109 110const withDevMenu = (config: ExpoConfig) => { 111 config = withDevMenuActivity(config); 112 config = withDevMenuPodfile(config); 113 config = withDevMenuAppDelegate(config); 114 return config; 115}; 116 117export default withDevMenu; 118