1import { promptDeviceAsync } from './promptDevice';
2import * as Log from '../../../log';
3import {
4  AppleDeviceManager,
5  ensureSimulatorOpenAsync,
6} from '../../../start/platforms/ios/AppleDeviceManager';
7import { assertSystemRequirementsAsync } from '../../../start/platforms/ios/assertSystemRequirements';
8import { sortDefaultDeviceToBeginningAsync } from '../../../start/platforms/ios/promptAppleDevice';
9import { OSType } from '../../../start/platforms/ios/simctl';
10import * as SimControl from '../../../start/platforms/ios/simctl';
11import { CommandError } from '../../../utils/errors';
12import { profile } from '../../../utils/profile';
13import { logDeviceArgument } from '../../hints';
14import * as AppleDevice from '../appleDevice/AppleDevice';
15
16type AnyDevice = SimControl.Device | AppleDevice.ConnectedDevice;
17
18/** Get a list of devices (called destinations) that are connected to the host machine. Filter by `osType` if defined. */
19async function getDevicesAsync({ osType }: { osType?: OSType } = {}): Promise<AnyDevice[]> {
20  const connectedDevices = await AppleDevice.getConnectedDevicesAsync();
21
22  const simulators = await sortDefaultDeviceToBeginningAsync(
23    await profile(SimControl.getDevicesAsync)(),
24    osType
25  );
26
27  const devices = [...connectedDevices, ...simulators];
28
29  // If osType is defined, then filter out ineligible simulators.
30  // Only do this inside of the device selection so users who pass the entire device udid can attempt to select any simulator (even if it's invalid).
31  return osType ? filterDevicesForOsType(devices, osType) : devices;
32}
33
34/** @returns a list of devices, filtered by the provided `osType`. */
35function filterDevicesForOsType(devices: AnyDevice[], osType: OSType): AnyDevice[] {
36  return devices.filter((device) => !('osType' in device) || device.osType === osType);
37}
38
39/** Given a `device` argument from the CLI, parse and prompt our way to a usable device for building. */
40export async function resolveDeviceAsync(
41  device?: string | boolean,
42  { osType }: { osType?: OSType } = {}
43): Promise<AnyDevice> {
44  await assertSystemRequirementsAsync();
45
46  if (!device) {
47    /** Finds the first possible device and returns in a booted state. */
48    const manager = await AppleDeviceManager.resolveAsync({
49      device: {
50        osType,
51      },
52    });
53    Log.debug(
54      `Resolved default device (name: ${manager.device.name}, udid: ${manager.device.udid}, osType: ${osType})`
55    );
56    return manager.device;
57  }
58
59  const devices: AnyDevice[] = await getDevicesAsync({
60    osType,
61  });
62
63  const resolved =
64    device === true
65      ? // `--device` (no props after)
66        await promptDeviceAsync(devices)
67      : // `--device <name|udid>`
68        findDeviceFromSearchValue(devices, device.toLowerCase());
69
70  return ensureBootedAsync(resolved);
71}
72
73/** @returns `true` if the given device is a simulator. */
74export function isSimulatorDevice(device: AnyDevice): boolean {
75  return (
76    !('deviceType' in device) ||
77    device.deviceType.startsWith('com.apple.CoreSimulator.SimDeviceType.')
78  );
79}
80
81/** @returns device matching the `searchValue` against name or UDID. */
82function findDeviceFromSearchValue(devices: AnyDevice[], searchValue: string): AnyDevice {
83  const device = devices.find(
84    (device) =>
85      device.udid.toLowerCase() === searchValue || device.name.toLowerCase() === searchValue
86  );
87  if (!device) {
88    throw new CommandError('BAD_ARGS', `No device UDID or name matching "${searchValue}"`);
89  }
90  return device;
91}
92
93/** Ensures the device is booted if it's a simulator. */
94async function ensureBootedAsync(device: AnyDevice): Promise<AnyDevice> {
95  // --device with no props after
96  logDeviceArgument(device.udid);
97  if (isSimulatorDevice(device)) {
98    return ensureSimulatorOpenAsync({ udid: device.udid });
99  }
100  return device;
101}
102