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