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