1import spawnAsync, { SpawnOptions } from '@expo/spawn-async'; 2import chalk from 'chalk'; 3 4import * as Log from '../../../log'; 5import { CommandError } from '../../../utils/errors'; 6 7export async function xcrunAsync(args: (string | undefined)[], options?: SpawnOptions) { 8 Log.debug('Running: xcrun ' + args.join(' ')); 9 try { 10 return await spawnAsync('xcrun', args.filter(Boolean) as string[], options); 11 } catch (e) { 12 throwXcrunError(e); 13 } 14} 15 16function throwXcrunError(e: any): never { 17 if (isLicenseOutOfDate(e.stdout) || isLicenseOutOfDate(e.stderr)) { 18 throw new CommandError( 19 'XCODE_LICENSE_NOT_ACCEPTED', 20 'Xcode license is not accepted. Please run `sudo xcodebuild -license`.' 21 ); 22 } else if (e.stderr?.includes('not a developer tool or in PATH')) { 23 throw new CommandError( 24 'SIMCTL_NOT_AVAILABLE', 25 `You may need to run ${chalk.bold( 26 'sudo xcode-select -s /Applications/Xcode.app' 27 )} and try again.` 28 ); 29 } 30 // Attempt to craft a better error message... 31 if (Array.isArray(e.output)) { 32 e.message += '\n' + e.output.join('\n').trim(); 33 } else if (e.stderr) { 34 e.message += '\n' + e.stderr; 35 } 36 throw e; 37} 38 39function isLicenseOutOfDate(text: string) { 40 if (!text) { 41 return false; 42 } 43 44 const lower = text.toLowerCase(); 45 return lower.includes('xcode') && lower.includes('license'); 46} 47