xref: /expo/tools/src/IOSSimulator.ts (revision eeffdb10)
1import { spawn } from 'child_process';
2import { Transform, TransformCallback, TransformOptions } from 'stream';
3import spawnAsync from '@expo/spawn-async';
4
5/**
6 * Starts an arbitrary iOS simulator so that simctl can reference a "booted" simulator.
7 */
8export async function startSimulatorAsync(): Promise<void> {
9  try {
10    await spawnAsync('xcrun', ['instruments', '-w', 'iPhone X (11.2) [']);
11  } catch (e) {
12    // Instruments exits with an expected error
13    if (!e.stderr.includes('Instruments Usage Error')) {
14      throw e;
15    }
16  }
17}
18
19export async function installSimulatorAppAsync(
20  simulatorId: string,
21  archivePath: string
22): Promise<void> {
23  try {
24    await spawnAsync('xcrun', ['simctl', 'install', simulatorId, archivePath]);
25  } catch (e) {
26    let error = new Error(e.stderr);
27    (error as any).status = e.status;
28    throw error;
29  }
30}
31
32export async function launchSimulatorAppAsync(
33  simulatorId: string,
34  bundleIdentifier: string
35): Promise<void> {
36  try {
37    await spawnAsync('xcrun', ['simctl', 'launch', simulatorId, bundleIdentifier]);
38  } catch (e) {
39    let error = new Error(e.stderr);
40    (error as any).status = e.status;
41    throw error;
42  }
43}
44
45export function getSimulatorLogProcess(simulatorId: string, predicate?: string) {
46  return spawn(
47    'xcrun',
48    [
49      'simctl',
50      'spawn',
51      simulatorId,
52      'log',
53      'stream',
54      '--style',
55      'json',
56      ...(predicate ? ['--predicate', predicate] : []),
57    ],
58    {
59      stdio: ['ignore', 'pipe', 'inherit'],
60    }
61  );
62}
63
64export class IOSLogStream extends Transform {
65  constructor(options?: TransformOptions) {
66    super({ ...options, objectMode: true });
67  }
68
69  _transform(data: Buffer, encoding: string, callback: TransformCallback): void {
70    // In practice, we receive each log entry as a separate chunk and can test if they are valid,
71    // JSON-formatted log entries
72    let entry;
73    try {
74      entry = JSON.parse(data.toString('utf8'));
75    } catch (e) {}
76
77    if (entry?.eventMessage) {
78      this.push(entry);
79    }
80    callback();
81  }
82}
83