1import { getConfig } from '@expo/config'; 2import { AndroidConfig, IOSConfig } from '@expo/config-plugins'; 3import { getInfoPlistPathFromPbxproj } from '@expo/config-plugins/build/ios/utils/getInfoPlistPath'; 4import plist from '@expo/plist'; 5import fs from 'fs'; 6import path from 'path'; 7import resolveFrom from 'resolve-from'; 8 9import * as Log from '../log'; 10import { 11 hasRequiredAndroidFilesAsync, 12 hasRequiredIOSFilesAsync, 13} from '../prebuild/clearNativeFolder'; 14import { intersecting } from './array'; 15 16const debug = require('debug')('expo:utils:scheme') as typeof console.log; 17 18// sort longest to ensure uniqueness. 19// this might be undesirable as it causes the QR code to be longer. 20function sortLongest(obj: string[]): string[] { 21 return obj.sort((a, b) => b.length - a.length); 22} 23 24// TODO: Revisit and test after run code is merged. 25export async function getSchemesForIosAsync(projectRoot: string): Promise<string[]> { 26 try { 27 const infoPlistBuildProperty = getInfoPlistPathFromPbxproj(projectRoot); 28 debug(`ios application Info.plist path:`, infoPlistBuildProperty); 29 if (infoPlistBuildProperty) { 30 const configPath = path.join(projectRoot, 'ios', infoPlistBuildProperty); 31 const rawPlist = fs.readFileSync(configPath, 'utf8'); 32 const plistObject = plist.parse(rawPlist); 33 const schemes = IOSConfig.Scheme.getSchemesFromPlist(plistObject); 34 debug(`ios application schemes:`, schemes); 35 return sortLongest(schemes); 36 } 37 } catch (error) { 38 debug(`expected error collecting ios application schemes for the main target:`, error); 39 } 40 // No ios folder or some other error 41 return []; 42} 43 44// TODO: Revisit and test after run code is merged. 45export async function getSchemesForAndroidAsync(projectRoot: string): Promise<string[]> { 46 try { 47 const configPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectRoot); 48 const manifest = await AndroidConfig.Manifest.readAndroidManifestAsync(configPath); 49 const schemes = await AndroidConfig.Scheme.getSchemesFromManifest(manifest); 50 debug(`android application schemes:`, schemes); 51 return sortLongest(schemes); 52 } catch (error) { 53 debug(`expected error collecting android application schemes for the main activity:`, error); 54 // No android folder or some other error 55 return []; 56 } 57} 58 59// TODO: Revisit and test after run code is merged. 60async function getManagedDevClientSchemeAsync(projectRoot: string): Promise<string | null> { 61 const { exp } = getConfig(projectRoot); 62 try { 63 const getDefaultScheme = require(resolveFrom(projectRoot, 'expo-dev-client/getDefaultScheme')); 64 const scheme = getDefaultScheme(exp); 65 return scheme; 66 } catch { 67 Log.warn( 68 '\nDevelopment build: Unable to get the default URI scheme for the project. Please make sure the expo-dev-client package is installed.' 69 ); 70 return null; 71 } 72} 73 74// TODO: Revisit and test after run code is merged. 75export async function getOptionalDevClientSchemeAsync(projectRoot: string): Promise<string | null> { 76 const [hasIos, hasAndroid] = await Promise.all([ 77 hasRequiredIOSFilesAsync(projectRoot), 78 hasRequiredAndroidFilesAsync(projectRoot), 79 ]); 80 81 const [ios, android] = await Promise.all([ 82 getSchemesForIosAsync(projectRoot), 83 getSchemesForAndroidAsync(projectRoot), 84 ]); 85 86 // Allow managed projects 87 if (!hasIos && !hasAndroid) { 88 return getManagedDevClientSchemeAsync(projectRoot); 89 } 90 91 let matching: string; 92 // Allow for only one native project to exist. 93 if (!hasIos) { 94 matching = android[0]; 95 } else if (!hasAndroid) { 96 matching = ios[0]; 97 } else { 98 [matching] = intersecting(ios, android); 99 } 100 return matching ?? null; 101} 102