18d307f52SEvan Baconimport spawnAsync, { SpawnOptions } from '@expo/spawn-async'; 28d307f52SEvan Baconimport chalk from 'chalk'; 38d307f52SEvan Bacon 48d307f52SEvan Baconimport { CommandError } from '../../../utils/errors'; 58d307f52SEvan Bacon 6*474a7a4bSEvan Baconconst debug = require('debug')('expo:start:platforms:ios:xcrun') as typeof console.log; 7*474a7a4bSEvan Bacon 88d307f52SEvan Baconexport async function xcrunAsync(args: (string | undefined)[], options?: SpawnOptions) { 9*474a7a4bSEvan Bacon debug('Running: xcrun ' + args.join(' ')); 108d307f52SEvan Bacon try { 1129975bfdSEvan Bacon return await spawnAsync('xcrun', args.filter(Boolean) as string[], options); 128d307f52SEvan Bacon } catch (e) { 138d307f52SEvan Bacon throwXcrunError(e); 148d307f52SEvan Bacon } 158d307f52SEvan Bacon} 168d307f52SEvan Bacon 178d307f52SEvan Baconfunction throwXcrunError(e: any): never { 188d307f52SEvan Bacon if (isLicenseOutOfDate(e.stdout) || isLicenseOutOfDate(e.stderr)) { 198d307f52SEvan Bacon throw new CommandError( 208d307f52SEvan Bacon 'XCODE_LICENSE_NOT_ACCEPTED', 218d307f52SEvan Bacon 'Xcode license is not accepted. Please run `sudo xcodebuild -license`.' 228d307f52SEvan Bacon ); 238d307f52SEvan Bacon } else if (e.stderr?.includes('not a developer tool or in PATH')) { 248d307f52SEvan Bacon throw new CommandError( 258d307f52SEvan Bacon 'SIMCTL_NOT_AVAILABLE', 268d307f52SEvan Bacon `You may need to run ${chalk.bold( 278d307f52SEvan Bacon 'sudo xcode-select -s /Applications/Xcode.app' 288d307f52SEvan Bacon )} and try again.` 298d307f52SEvan Bacon ); 308d307f52SEvan Bacon } 318d307f52SEvan Bacon // Attempt to craft a better error message... 328d307f52SEvan Bacon if (Array.isArray(e.output)) { 338d307f52SEvan Bacon e.message += '\n' + e.output.join('\n').trim(); 348d307f52SEvan Bacon } else if (e.stderr) { 358d307f52SEvan Bacon e.message += '\n' + e.stderr; 368d307f52SEvan Bacon } 378d307f52SEvan Bacon throw e; 388d307f52SEvan Bacon} 398d307f52SEvan Bacon 408d307f52SEvan Baconfunction isLicenseOutOfDate(text: string) { 418d307f52SEvan Bacon if (!text) { 428d307f52SEvan Bacon return false; 438d307f52SEvan Bacon } 448d307f52SEvan Bacon 458d307f52SEvan Bacon const lower = text.toLowerCase(); 468d307f52SEvan Bacon return lower.includes('xcode') && lower.includes('license'); 478d307f52SEvan Bacon} 48