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