1import spawnAsync from '@expo/spawn-async';
2import fs from 'fs/promises';
3
4import * as Log from '../../../log';
5import { Prerequisite, PrerequisiteCommandError } from '../Prerequisite';
6
7const ERROR_CODE = 'XCODE_DEVELOPER_DISK_IMAGE';
8async function getXcodePathAsync(): Promise<string> {
9  try {
10    const { stdout } = await spawnAsync('xcode-select', ['-p']);
11    if (stdout) {
12      return stdout.trim();
13    }
14  } catch (error: any) {
15    Log.debug(`Could not find Xcode path: %O`, error);
16  }
17  throw new PrerequisiteCommandError(ERROR_CODE, 'Unable to locate Xcode.');
18}
19
20export class XcodeDeveloperDiskImagePrerequisite extends Prerequisite<string, { version: string }> {
21  static instance = new XcodeDeveloperDiskImagePrerequisite();
22
23  async assertImplementation({ version }: { version: string }): Promise<string> {
24    const xcodePath = await getXcodePathAsync();
25    // Like "11.2 (15C107)"
26    const versions = await fs.readdir(`${xcodePath}/Platforms/iPhoneOS.platform/DeviceSupport/`);
27    const prefix = version.match(/\d+\.\d+/);
28    if (prefix === null) {
29      throw new PrerequisiteCommandError(ERROR_CODE, `Invalid iOS version: ${version}`);
30    }
31    for (const directory of versions) {
32      if (directory.includes(prefix[0])) {
33        return `${xcodePath}/Platforms/iPhoneOS.platform/DeviceSupport/${directory}/DeveloperDiskImage.dmg`;
34      }
35    }
36    throw new PrerequisiteCommandError(
37      ERROR_CODE,
38      `Unable to find Developer Disk Image path for SDK ${version}.`
39    );
40  }
41}
42