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