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 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 const packagePath = path.dirname(require.resolve('expo-dev-menu/package.json')); 102 const relativePath = path.relative(config.modRequest.platformProjectRoot, packagePath); 103 podfile = addLines(podfile, 'use_react_native', 0, [ 104 ` pod 'expo-dev-menu', path: '${relativePath}', :configurations => :debug`, 105 ]); 106 } 107 return podfile; 108 }); 109 return config; 110 }, 111 ]); 112}; 113 114const withDevMenu = (config: ExpoConfig) => { 115 config = withDevMenuActivity(config); 116 config = withDevMenuPodfile(config); 117 config = withDevMenuAppDelegate(config); 118 return config; 119}; 120 121export default createRunOncePlugin(withDevMenu, pkg.name, pkg.version); 122