1import { execSync } from 'child_process'; 2 3import * as SimControl from './simctl'; 4import { CommandError } from '../../../utils/errors'; 5 6const debug = require('debug')('expo:start:platforms:ios:getBestSimulator') as typeof console.log; 7 8type DeviceContext = Partial<Pick<SimControl.Device, 'osType'>>; 9 10/** 11 * Returns the default device stored in the Simulator.app settings. 12 * This helps us to get the device that the user opened most recently regardless of which tool they used. 13 */ 14function getDefaultSimulatorDeviceUDID() { 15 try { 16 const defaultDeviceUDID = execSync( 17 `defaults read com.apple.iphonesimulator CurrentDeviceUDID`, 18 { stdio: 'pipe' } 19 ).toString(); 20 return defaultDeviceUDID.trim(); 21 } catch { 22 return null; 23 } 24} 25 26export async function getBestBootedSimulatorAsync({ 27 osType, 28}: DeviceContext = {}): Promise<SimControl.Device | null> { 29 const [simulatorOpenedByApp] = await SimControl.getBootedSimulatorsAsync(); 30 // This should prevent opening a second simulator in the chance that default 31 // simulator doesn't match what the Simulator app would open by default. 32 if ( 33 simulatorOpenedByApp?.udid && 34 (!osType || (osType && simulatorOpenedByApp.osType === osType)) 35 ) { 36 debug(`First booted simulator: ${simulatorOpenedByApp?.windowName}`); 37 return simulatorOpenedByApp; 38 } 39 40 debug(`No booted simulator matching requirements (osType: ${osType}).`); 41 return null; 42} 43 44/** 45 * Returns the most preferred simulator UDID without booting anything. 46 * 47 * 1. If the simulator app defines a default simulator and the osType is not defined. 48 * 2. If the osType is defined, then check if the default udid matches the osType. 49 * 3. If all else fails, return the first found simulator. 50 */ 51export async function getBestUnbootedSimulatorAsync({ osType }: DeviceContext = {}): Promise< 52 string | null 53> { 54 const defaultId = getDefaultSimulatorDeviceUDID(); 55 debug(`Default simulator ID: ${defaultId}`); 56 57 if (defaultId && !osType) { 58 return defaultId; 59 } 60 61 const simulators = await getSelectableSimulatorsAsync({ osType }); 62 63 if (!simulators.length) { 64 // TODO: Prompt to install the simulators 65 throw new CommandError( 66 'UNSUPPORTED_OS_TYPE', 67 `No ${osType || 'iOS'} devices available in Simulator.app` 68 ); 69 } 70 71 // If the default udid is defined, then check to ensure its osType matches the required os. 72 if (defaultId) { 73 const defaultSimulator = simulators.find((device) => device.udid === defaultId); 74 if (defaultSimulator?.osType === osType) { 75 return defaultId; 76 } 77 } 78 79 // Return first selectable device. 80 return simulators[0]?.udid ?? null; 81} 82 83/** 84 * Get all simulators supported by Expo Go (iOS only). 85 */ 86export async function getSelectableSimulatorsAsync({ osType = 'iOS' }: DeviceContext = {}): Promise< 87 SimControl.Device[] 88> { 89 const simulators = await SimControl.getDevicesAsync(); 90 return simulators.filter((device) => device.isAvailable && device.osType === osType); 91} 92 93/** 94 * Get 'best' simulator for the user based on: 95 * 1. Currently booted simulator. 96 * 2. Last simulator that was opened. 97 * 3. First simulator that was opened. 98 */ 99export async function getBestSimulatorAsync({ osType }: DeviceContext): Promise<string | null> { 100 const simulatorOpenedByApp = await getBestBootedSimulatorAsync({ osType }); 101 102 if (simulatorOpenedByApp) { 103 return simulatorOpenedByApp.udid; 104 } 105 106 return await getBestUnbootedSimulatorAsync({ osType }); 107} 108