xref: /expo/packages/@expo/cli/src/utils/scheme.ts (revision 2fd75d6d)
1import { getConfig } from '@expo/config';
2import { AndroidConfig, IOSConfig } from '@expo/config-plugins';
3import plist from '@expo/plist';
4import fs from 'fs';
5import resolveFrom from 'resolve-from';
6
7import * as Log from '../log';
8import {
9  hasRequiredAndroidFilesAsync,
10  hasRequiredIOSFilesAsync,
11} from '../prebuild/clearNativeFolder';
12import { intersecting } from './array';
13
14// sort longest to ensure uniqueness.
15// this might be undesirable as it causes the QR code to be longer.
16function sortLongest(obj: string[]): string[] {
17  return obj.sort((a, b) => b.length - a.length);
18}
19
20// TODO: Revisit and test after run code is merged.
21export async function getSchemesForIosAsync(projectRoot: string) {
22  try {
23    const configPath = IOSConfig.Paths.getInfoPlistPath(projectRoot);
24    const rawPlist = fs.readFileSync(configPath, 'utf8');
25    const plistObject = plist.parse(rawPlist);
26    return sortLongest(IOSConfig.Scheme.getSchemesFromPlist(plistObject));
27  } catch {
28    // No ios folder or some other error
29    return [];
30  }
31}
32
33// TODO: Revisit and test after run code is merged.
34export async function getSchemesForAndroidAsync(projectRoot: string) {
35  try {
36    const configPath = await AndroidConfig.Paths.getAndroidManifestAsync(projectRoot);
37    const manifest = await AndroidConfig.Manifest.readAndroidManifestAsync(configPath);
38    return sortLongest(await AndroidConfig.Scheme.getSchemesFromManifest(manifest));
39  } catch {
40    // No android folder or some other error
41    return [];
42  }
43}
44
45// TODO: Revisit and test after run code is merged.
46async function getManagedDevClientSchemeAsync(projectRoot: string): Promise<string | null> {
47  const { exp } = getConfig(projectRoot);
48  try {
49    const getDefaultScheme = require(resolveFrom(projectRoot, 'expo-dev-client/getDefaultScheme'));
50    const scheme = getDefaultScheme(exp);
51    return scheme;
52  } catch {
53    Log.warn(
54      '\nDevelopment build: Unable to get the default URI scheme for the project. Please make sure the expo-dev-client package is installed.'
55    );
56    return null;
57  }
58}
59
60// TODO: Revisit and test after run code is merged.
61export async function getOptionalDevClientSchemeAsync(projectRoot: string): Promise<string | null> {
62  const [hasIos, hasAndroid] = await Promise.all([
63    hasRequiredIOSFilesAsync(projectRoot),
64    hasRequiredAndroidFilesAsync(projectRoot),
65  ]);
66
67  const [ios, android] = await Promise.all([
68    getSchemesForIosAsync(projectRoot),
69    getSchemesForAndroidAsync(projectRoot),
70  ]);
71
72  // Allow managed projects
73  if (!hasIos && !hasAndroid) {
74    return getManagedDevClientSchemeAsync(projectRoot);
75  }
76
77  let matching: string;
78  // Allow for only one native project to exist.
79  if (!hasIos) {
80    matching = android[0];
81  } else if (!hasAndroid) {
82    matching = ios[0];
83  } else {
84    [matching] = intersecting(ios, android);
85  }
86  return matching ?? null;
87}
88