1import { execAsync } from '@expo/osascript';
2import spawnAsync from '@expo/spawn-async';
3
4import * as Log from '../../../log';
5import { Prerequisite, PrerequisiteCommandError } from '../Prerequisite';
6
7const debug = require('debug')('expo:doctor:apple:simulatorApp') as typeof console.log;
8
9async function getSimulatorAppIdAsync(): Promise<string | null> {
10  try {
11    return (await execAsync('id of app "Simulator"')).trim();
12  } catch {
13    // This error may occur in CI where the users intends to install just the simulators but no Xcode.
14  }
15  return null;
16}
17
18export class SimulatorAppPrerequisite extends Prerequisite {
19  static instance = new SimulatorAppPrerequisite();
20
21  async assertImplementation(): Promise<void> {
22    const result = await getSimulatorAppIdAsync();
23    if (!result) {
24      // This error may occur in CI where the users intends to install just the simulators but no Xcode.
25      throw new PrerequisiteCommandError(
26        'SIMULATOR_APP',
27        "Can't determine id of Simulator app; the Simulator is most likely not installed on this machine. Run `sudo xcode-select -s /Applications/Xcode.app`"
28      );
29    }
30    if (
31      result !== 'com.apple.iphonesimulator' &&
32      result !== 'com.apple.CoreSimulator.SimulatorTrampoline'
33    ) {
34      throw new PrerequisiteCommandError(
35        'SIMULATOR_APP',
36        "Simulator is installed but is identified as '" + result + "'; don't know what that is."
37      );
38    }
39    debug(`Simulator app id: ${result}`);
40
41    try {
42      // make sure we can run simctl
43      await spawnAsync('xcrun', ['simctl', 'help']);
44    } catch (error: any) {
45      Log.warn(`Unable to run simctl:\n${error.toString()}`);
46      throw new PrerequisiteCommandError(
47        'SIMCTL',
48        'xcrun is not configured correctly. Ensure `sudo xcode-select --reset` works before running this command again.'
49      );
50    }
51  }
52}
53