18d307f52SEvan Baconimport { getConfig } from '@expo/config'; 28d307f52SEvan Baconimport { AndroidConfig, IOSConfig } from '@expo/config-plugins'; 3def098a4SEvan Baconimport { getInfoPlistPathFromPbxproj } from '@expo/config-plugins/build/ios/utils/getInfoPlistPath'; 48d307f52SEvan Baconimport plist from '@expo/plist'; 58d307f52SEvan Baconimport fs from 'fs'; 6def098a4SEvan Baconimport path from 'path'; 78d307f52SEvan Baconimport resolveFrom from 'resolve-from'; 88d307f52SEvan Bacon 9*8a424bebSJames Ideimport { intersecting } from './array'; 108d307f52SEvan Baconimport * as Log from '../log'; 118d307f52SEvan Baconimport { 128d307f52SEvan Bacon hasRequiredAndroidFilesAsync, 138d307f52SEvan Bacon hasRequiredIOSFilesAsync, 148d307f52SEvan Bacon} from '../prebuild/clearNativeFolder'; 158d307f52SEvan Bacon 16def098a4SEvan Baconconst debug = require('debug')('expo:utils:scheme') as typeof console.log; 17def098a4SEvan Bacon 188d307f52SEvan Bacon// sort longest to ensure uniqueness. 198d307f52SEvan Bacon// this might be undesirable as it causes the QR code to be longer. 208d307f52SEvan Baconfunction sortLongest(obj: string[]): string[] { 218d307f52SEvan Bacon return obj.sort((a, b) => b.length - a.length); 228d307f52SEvan Bacon} 238d307f52SEvan Bacon 240b50ef93SCedric van Putten/** 250b50ef93SCedric van Putten * Resolve the scheme for the dev client using two methods: 260b50ef93SCedric van Putten * - filter on known Expo schemes, starting with `exp+`, avoiding 3rd party schemes. 270b50ef93SCedric van Putten * - filter on longest to ensure uniqueness. 280b50ef93SCedric van Putten */ 290b50ef93SCedric van Puttenfunction resolveExpoOrLongestScheme(schemes: string[]): string[] { 300b50ef93SCedric van Putten const expoOnlySchemes = schemes.filter((scheme) => scheme.startsWith('exp+')); 310b50ef93SCedric van Putten return expoOnlySchemes.length > 0 ? sortLongest(expoOnlySchemes) : sortLongest(schemes); 320b50ef93SCedric van Putten} 330b50ef93SCedric van Putten 348d307f52SEvan Bacon// TODO: Revisit and test after run code is merged. 35def098a4SEvan Baconexport async function getSchemesForIosAsync(projectRoot: string): Promise<string[]> { 368d307f52SEvan Bacon try { 37def098a4SEvan Bacon const infoPlistBuildProperty = getInfoPlistPathFromPbxproj(projectRoot); 38def098a4SEvan Bacon debug(`ios application Info.plist path:`, infoPlistBuildProperty); 39def098a4SEvan Bacon if (infoPlistBuildProperty) { 40def098a4SEvan Bacon const configPath = path.join(projectRoot, 'ios', infoPlistBuildProperty); 418d307f52SEvan Bacon const rawPlist = fs.readFileSync(configPath, 'utf8'); 428d307f52SEvan Bacon const plistObject = plist.parse(rawPlist); 43def098a4SEvan Bacon const schemes = IOSConfig.Scheme.getSchemesFromPlist(plistObject); 44def098a4SEvan Bacon debug(`ios application schemes:`, schemes); 450b50ef93SCedric van Putten return resolveExpoOrLongestScheme(schemes); 46def098a4SEvan Bacon } 47def098a4SEvan Bacon } catch (error) { 48def098a4SEvan Bacon debug(`expected error collecting ios application schemes for the main target:`, error); 49def098a4SEvan Bacon } 508d307f52SEvan Bacon // No ios folder or some other error 518d307f52SEvan Bacon return []; 528d307f52SEvan Bacon} 538d307f52SEvan Bacon 548d307f52SEvan Bacon// TODO: Revisit and test after run code is merged. 55def098a4SEvan Baconexport async function getSchemesForAndroidAsync(projectRoot: string): Promise<string[]> { 568d307f52SEvan Bacon try { 578d307f52SEvan Bacon const configPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectRoot); 588d307f52SEvan Bacon const manifest = await AndroidConfig.Manifest.readAndroidManifestAsync(configPath); 59def098a4SEvan Bacon const schemes = await AndroidConfig.Scheme.getSchemesFromManifest(manifest); 60def098a4SEvan Bacon debug(`android application schemes:`, schemes); 610b50ef93SCedric van Putten return resolveExpoOrLongestScheme(schemes); 62def098a4SEvan Bacon } catch (error) { 63def098a4SEvan Bacon debug(`expected error collecting android application schemes for the main activity:`, error); 648d307f52SEvan Bacon // No android folder or some other error 658d307f52SEvan Bacon return []; 668d307f52SEvan Bacon } 678d307f52SEvan Bacon} 688d307f52SEvan Bacon 698d307f52SEvan Bacon// TODO: Revisit and test after run code is merged. 708d307f52SEvan Baconasync function getManagedDevClientSchemeAsync(projectRoot: string): Promise<string | null> { 718d307f52SEvan Bacon const { exp } = getConfig(projectRoot); 728d307f52SEvan Bacon try { 738d307f52SEvan Bacon const getDefaultScheme = require(resolveFrom(projectRoot, 'expo-dev-client/getDefaultScheme')); 748d307f52SEvan Bacon const scheme = getDefaultScheme(exp); 758d307f52SEvan Bacon return scheme; 7698ecfc87SJames Ide } catch { 778d307f52SEvan Bacon Log.warn( 788d307f52SEvan Bacon '\nDevelopment build: Unable to get the default URI scheme for the project. Please make sure the expo-dev-client package is installed.' 798d307f52SEvan Bacon ); 808d307f52SEvan Bacon return null; 818d307f52SEvan Bacon } 828d307f52SEvan Bacon} 838d307f52SEvan Bacon 848d307f52SEvan Bacon// TODO: Revisit and test after run code is merged. 858d307f52SEvan Baconexport async function getOptionalDevClientSchemeAsync(projectRoot: string): Promise<string | null> { 868d307f52SEvan Bacon const [hasIos, hasAndroid] = await Promise.all([ 878d307f52SEvan Bacon hasRequiredIOSFilesAsync(projectRoot), 888d307f52SEvan Bacon hasRequiredAndroidFilesAsync(projectRoot), 898d307f52SEvan Bacon ]); 908d307f52SEvan Bacon 918d307f52SEvan Bacon const [ios, android] = await Promise.all([ 928d307f52SEvan Bacon getSchemesForIosAsync(projectRoot), 938d307f52SEvan Bacon getSchemesForAndroidAsync(projectRoot), 948d307f52SEvan Bacon ]); 958d307f52SEvan Bacon 968d307f52SEvan Bacon // Allow managed projects 978d307f52SEvan Bacon if (!hasIos && !hasAndroid) { 988d307f52SEvan Bacon return getManagedDevClientSchemeAsync(projectRoot); 998d307f52SEvan Bacon } 1008d307f52SEvan Bacon 1018d307f52SEvan Bacon let matching: string; 1028d307f52SEvan Bacon // Allow for only one native project to exist. 1038d307f52SEvan Bacon if (!hasIos) { 1048d307f52SEvan Bacon matching = android[0]; 1058d307f52SEvan Bacon } else if (!hasAndroid) { 1068d307f52SEvan Bacon matching = ios[0]; 1078d307f52SEvan Bacon } else { 1088d307f52SEvan Bacon [matching] = intersecting(ios, android); 1098d307f52SEvan Bacon } 1108d307f52SEvan Bacon return matching ?? null; 1118d307f52SEvan Bacon} 112