Files
self/app/scripts/run-ios-simulator.cjs
Justin Hernandez 1041babc7b Stabilize iOS simulator startup and crash mitigation flow (#1836)
* save sepc

* save wip

* save pr 2wip

* save wip rd 3

* finalize ios crash fix

* pr feedback and format
2026-03-10 20:09:03 -07:00

180 lines
4.3 KiB
JavaScript

// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
const { execFileSync } = require('child_process');
const path = require('path');
const APP_ROOT = path.resolve(__dirname, '..');
const DEVICE_PRIORITY = [
'iPhone 16 Pro',
'iPhone 16',
'iPhone 15 Pro',
'iPhone 15',
];
function normalizeRuntimeVersion(runtime) {
return runtime.replace(/\./g, '-');
}
function extractRuntimeVersion(runtime) {
const match = runtime.match(/iOS-(\d+(?:-\d+)*)/i);
if (!match) {
return [];
}
return match[1].split('-').map(part => Number.parseInt(part, 10));
}
function compareRuntimeVersions(left, right) {
const leftParts = extractRuntimeVersion(left);
const rightParts = extractRuntimeVersion(right);
const length = Math.max(leftParts.length, rightParts.length);
for (let index = 0; index < length; index += 1) {
const leftPart = leftParts[index] ?? 0;
const rightPart = rightParts[index] ?? 0;
if (leftPart !== rightPart) {
return rightPart - leftPart;
}
}
return 0;
}
function selectDevice(devicesJson) {
const runtimeFilter = process.env.IOS_SIMULATOR_RUNTIME?.trim();
const deviceFilter = process.env.IOS_SIMULATOR_DEVICE?.trim().toLowerCase();
let runtimeKeys = Object.keys(devicesJson).filter(runtime =>
runtime.includes('SimRuntime.iOS-'),
);
if (runtimeFilter) {
const normalizedRuntime = normalizeRuntimeVersion(runtimeFilter);
const expectedSuffix = `SimRuntime.iOS-${normalizedRuntime}`;
runtimeKeys = runtimeKeys.filter(runtime =>
runtime.endsWith(expectedSuffix),
);
if (runtimeKeys.length === 0) {
throw new Error(`No iOS runtime matching "${runtimeFilter}" found`);
}
}
runtimeKeys.sort(compareRuntimeVersions);
const availableIPhones = runtimeKeys.flatMap(runtime =>
devicesJson[runtime]
.filter(device => device.isAvailable && device.name.startsWith('iPhone'))
.map(device => ({
name: device.name,
udid: device.udid,
runtime,
})),
);
if (availableIPhones.length === 0) {
throw new Error('No available iPhone simulators found');
}
if (deviceFilter) {
const selectedDevice =
availableIPhones.find(
device => device.name.toLowerCase() === deviceFilter,
) ??
availableIPhones.find(device =>
device.name.toLowerCase().includes(deviceFilter),
);
if (!selectedDevice) {
const availableNames = [
...new Set(availableIPhones.map(device => device.name)),
];
throw new Error(
`No available iPhone matching "${process.env.IOS_SIMULATOR_DEVICE}". Available: ${availableNames.join(', ')}`,
);
}
return selectedDevice;
}
for (const deviceName of DEVICE_PRIORITY) {
const prioritizedDevice = availableIPhones.find(
device => device.name === deviceName,
);
if (prioritizedDevice) {
return prioritizedDevice;
}
}
return availableIPhones[0];
}
function runCommand(command, args, options = {}) {
return execFileSync(command, args, {
cwd: APP_ROOT,
stdio: 'inherit',
...options,
});
}
function sleep(milliseconds) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
}
function main() {
const output = execFileSync(
'xcrun',
['simctl', 'list', 'devices', 'available', '--json'],
{
encoding: 'utf8',
},
);
const { devices } = JSON.parse(output);
const simulator = selectDevice(devices);
console.log(`Simulator: ${simulator.name} (${simulator.runtime})`);
console.log(`UDID: ${simulator.udid}`);
try {
runCommand('xcrun', ['simctl', 'shutdown', 'all']);
} catch {
// Benign on fresh machines with no booted simulators.
}
runCommand('xcrun', ['simctl', 'boot', simulator.udid]);
runCommand('xcrun', ['simctl', 'bootstatus', simulator.udid, '-b']);
sleep(5000);
runCommand('yarn', [
'react-native',
'run-ios',
'--scheme',
'OpenPassport',
'--udid',
simulator.udid,
]);
}
if (require.main === module) {
try {
main();
} catch (error) {
console.error(`iOS simulator launch failed: ${error.message}`);
process.exit(1);
}
}
module.exports = {
compareRuntimeVersions,
extractRuntimeVersion,
main,
normalizeRuntimeVersion,
selectDevice,
};