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