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