18d307f52SEvan Baconimport * as osascript from '@expo/osascript'; 28d307f52SEvan Baconimport spawnAsync from '@expo/spawn-async'; 38d307f52SEvan Bacon 4*8a424bebSJames Ideimport { Device } from './simctl'; 58d307f52SEvan Baconimport * as Log from '../../../log'; 68d307f52SEvan Baconimport { waitForActionAsync } from '../../../utils/delay'; 78d307f52SEvan Baconimport { CommandError } from '../../../utils/errors'; 88d307f52SEvan Bacon 98d307f52SEvan Bacon/** Open the Simulator.app and return when the system registers it as 'open'. */ 108d307f52SEvan Baconexport async function ensureSimulatorAppRunningAsync( 118d307f52SEvan Bacon device: Partial<Pick<Device, 'udid'>>, 128d307f52SEvan Bacon { 138d307f52SEvan Bacon maxWaitTime, 148d307f52SEvan Bacon }: { 158d307f52SEvan Bacon maxWaitTime?: number; 168d307f52SEvan Bacon } = {} 178d307f52SEvan Bacon): Promise<void> { 188d307f52SEvan Bacon if (await isSimulatorAppRunningAsync()) { 198d307f52SEvan Bacon return; 208d307f52SEvan Bacon } 218d307f52SEvan Bacon 228d307f52SEvan Bacon Log.log(`\u203A Opening the iOS simulator, this might take a moment.`); 238d307f52SEvan Bacon 248d307f52SEvan Bacon // In theory this would ensure the correct simulator is booted as well. 258d307f52SEvan Bacon // This isn't theory though, this is Xcode. 268d307f52SEvan Bacon await openSimulatorAppAsync(device); 278d307f52SEvan Bacon 288d307f52SEvan Bacon if (!(await waitForSimulatorAppToStart({ maxWaitTime }))) { 298d307f52SEvan Bacon throw new CommandError( 308d307f52SEvan Bacon 'SIMULATOR_TIMEOUT', 318d307f52SEvan Bacon `Simulator app did not open fast enough. Try opening Simulator first, then running your app.` 328d307f52SEvan Bacon ); 338d307f52SEvan Bacon } 348d307f52SEvan Bacon} 358d307f52SEvan Bacon 368d307f52SEvan Baconasync function waitForSimulatorAppToStart({ 378d307f52SEvan Bacon maxWaitTime, 388d307f52SEvan Bacon}: { maxWaitTime?: number } = {}): Promise<boolean> { 398d307f52SEvan Bacon return waitForActionAsync<boolean>({ 408d307f52SEvan Bacon interval: 50, 418d307f52SEvan Bacon maxWaitTime, 428d307f52SEvan Bacon action: isSimulatorAppRunningAsync, 438d307f52SEvan Bacon }); 448d307f52SEvan Bacon} 458d307f52SEvan Bacon 468d307f52SEvan Bacon// I think the app can be open while no simulators are booted. 478d307f52SEvan Baconasync function isSimulatorAppRunningAsync(): Promise<boolean> { 488d307f52SEvan Bacon try { 498d307f52SEvan Bacon const zeroMeansNo = ( 508d307f52SEvan Bacon await osascript.execAsync( 518d307f52SEvan Bacon 'tell app "System Events" to count processes whose name is "Simulator"' 528d307f52SEvan Bacon ) 538d307f52SEvan Bacon ).trim(); 548d307f52SEvan Bacon if (zeroMeansNo === '0') { 558d307f52SEvan Bacon return false; 568d307f52SEvan Bacon } 5729975bfdSEvan Bacon } catch (error: any) { 588d307f52SEvan Bacon if (error.message.includes('Application isn’t running')) { 598d307f52SEvan Bacon return false; 608d307f52SEvan Bacon } 618d307f52SEvan Bacon throw error; 628d307f52SEvan Bacon } 638d307f52SEvan Bacon 648d307f52SEvan Bacon return true; 658d307f52SEvan Bacon} 668d307f52SEvan Bacon 678d307f52SEvan Baconasync function openSimulatorAppAsync(device: { udid?: string }) { 688d307f52SEvan Bacon const args = ['-a', 'Simulator']; 698d307f52SEvan Bacon if (device.udid) { 708d307f52SEvan Bacon // This has no effect if the app is already running. 718d307f52SEvan Bacon args.push('--args', '-CurrentDeviceUDID', device.udid); 728d307f52SEvan Bacon } 738d307f52SEvan Bacon await spawnAsync('open', args); 748d307f52SEvan Bacon} 75