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