1import { IOSConfig } from '@expo/config-plugins'; 2import chalk from 'chalk'; 3import path from 'path'; 4 5import * as Log from '../../../log'; 6import { CommandError } from '../../../utils/errors'; 7import { profile } from '../../../utils/profile'; 8import { selectAsync } from '../../../utils/prompts'; 9import { Options, ProjectInfo, XcodeConfiguration } from '../XcodeBuild.types'; 10 11const debug = require('debug')('expo:run:ios:options:resolveNativeScheme') as typeof console.log; 12 13type NativeSchemeProps = { 14 name: string; 15 osType?: string; 16}; 17 18export async function resolveNativeSchemePropsAsync( 19 projectRoot: string, 20 options: Pick<Options, 'scheme' | 'configuration'>, 21 xcodeProject: ProjectInfo 22): Promise<NativeSchemeProps> { 23 return ( 24 (await promptOrQueryNativeSchemeAsync(projectRoot, options)) ?? 25 getDefaultNativeScheme(projectRoot, options, xcodeProject) 26 ); 27} 28 29/** Resolve the native iOS build `scheme` for a given `configuration`. If the `scheme` isn't provided then the user will be prompted to select one. */ 30export async function promptOrQueryNativeSchemeAsync( 31 projectRoot: string, 32 { scheme, configuration }: { scheme?: string | boolean; configuration?: XcodeConfiguration } 33): Promise<NativeSchemeProps | null> { 34 const schemes = IOSConfig.BuildScheme.getRunnableSchemesFromXcodeproj(projectRoot, { 35 configuration, 36 }); 37 if (!schemes.length) { 38 throw new CommandError('IOS_MALFORMED', 'No native iOS build schemes found'); 39 } 40 41 if (scheme === true) { 42 if (schemes.length === 1) { 43 Log.log(`Auto selecting only available scheme: ${schemes[0].name}`); 44 return schemes[0]; 45 } 46 const resolvedSchemeName = await selectAsync( 47 'Select a scheme', 48 schemes.map((value) => { 49 const isApp = 50 value.type === IOSConfig.Target.TargetType.APPLICATION && value.osType === 'iOS'; 51 return { 52 value: value.name, 53 title: isApp ? chalk.bold(value.name) + chalk.gray(' (app)') : value.name, 54 }; 55 }), 56 { 57 nonInteractiveHelp: `--scheme: argument must be provided with a string in non-interactive mode. Valid choices are: ${schemes.join( 58 ', ' 59 )}`, 60 } 61 ); 62 return schemes.find(({ name }) => resolvedSchemeName === name) ?? null; 63 } 64 // Attempt to match the schemes up so we can open the correct simulator 65 return scheme ? schemes.find(({ name }) => name === scheme) || { name: scheme } : null; 66} 67 68export function getDefaultNativeScheme( 69 projectRoot: string, 70 options: Pick<Options, 'configuration'>, 71 xcodeProject: Pick<ProjectInfo, 'name'> 72): NativeSchemeProps { 73 // If the resolution failed then we should just use the first runnable scheme that 74 // matches the provided configuration. 75 const resolvedSchemes = profile(IOSConfig.BuildScheme.getRunnableSchemesFromXcodeproj)( 76 projectRoot, 77 { 78 configuration: options.configuration, 79 } 80 ); 81 82 // If there are multiple schemes, then the default should be the application. 83 if (resolvedSchemes.length > 1) { 84 const scheme = 85 resolvedSchemes.find(({ type }) => type === IOSConfig.Target.TargetType.APPLICATION) ?? 86 resolvedSchemes[0]; 87 debug(`Using default scheme: ${scheme.name}`); 88 return scheme; 89 } 90 91 // If we couldn't find the scheme, then we'll guess at it, 92 // this is needed for cases where the native code hasn't been generated yet. 93 if (resolvedSchemes[0]) { 94 return resolvedSchemes[0]; 95 } 96 return { 97 name: path.basename(xcodeProject.name, path.extname(xcodeProject.name)), 98 }; 99} 100