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