1import * as osascript from '@expo/osascript'; 2import spawnAsync from '@expo/spawn-async'; 3 4import { Device } from './simctl'; 5import * as Log from '../../../log'; 6import { waitForActionAsync } from '../../../utils/delay'; 7import { CommandError } from '../../../utils/errors'; 8 9/** Open the Simulator.app and return when the system registers it as 'open'. */ 10export async function ensureSimulatorAppRunningAsync( 11 device: Partial<Pick<Device, 'udid'>>, 12 { 13 maxWaitTime, 14 }: { 15 maxWaitTime?: number; 16 } = {} 17): Promise<void> { 18 if (await isSimulatorAppRunningAsync()) { 19 return; 20 } 21 22 Log.log(`\u203A Opening the iOS simulator, this might take a moment.`); 23 24 // In theory this would ensure the correct simulator is booted as well. 25 // This isn't theory though, this is Xcode. 26 await openSimulatorAppAsync(device); 27 28 if (!(await waitForSimulatorAppToStart({ maxWaitTime }))) { 29 throw new CommandError( 30 'SIMULATOR_TIMEOUT', 31 `Simulator app did not open fast enough. Try opening Simulator first, then running your app.` 32 ); 33 } 34} 35 36async function waitForSimulatorAppToStart({ 37 maxWaitTime, 38}: { maxWaitTime?: number } = {}): Promise<boolean> { 39 return waitForActionAsync<boolean>({ 40 interval: 50, 41 maxWaitTime, 42 action: isSimulatorAppRunningAsync, 43 }); 44} 45 46// I think the app can be open while no simulators are booted. 47async function isSimulatorAppRunningAsync(): Promise<boolean> { 48 try { 49 const zeroMeansNo = ( 50 await osascript.execAsync( 51 'tell app "System Events" to count processes whose name is "Simulator"' 52 ) 53 ).trim(); 54 if (zeroMeansNo === '0') { 55 return false; 56 } 57 } catch (error: any) { 58 if (error.message.includes('Application isn’t running')) { 59 return false; 60 } 61 throw error; 62 } 63 64 return true; 65} 66 67async function openSimulatorAppAsync(device: { udid?: string }) { 68 const args = ['-a', 'Simulator']; 69 if (device.udid) { 70 // This has no effect if the app is already running. 71 args.push('--args', '-CurrentDeviceUDID', device.udid); 72 } 73 await spawnAsync('open', args); 74} 75