xref: /expo/tools/src/commands/ClientInstall.ts (revision a272999e)
1import { Command } from '@expo/commander';
2import spawnAsync from '@expo/spawn-async';
3import { Android, Config, Simulator, Versions } from '@expo/xdl';
4import chalk from 'chalk';
5
6import { STAGING_API_HOST } from '../Constants';
7import { Platform, getNewestSDKVersionAsync } from '../ProjectVersions';
8import askForPlatformAsync from '../utils/askForPlatformAsync';
9import askForSDKVersionAsync from '../utils/askForSDKVersionAsync';
10
11type ActionOptions = {
12  platform?: Platform;
13  sdkVersion?: string;
14};
15
16async function downloadAndInstallOnIOSAsync(clientUrl: string): Promise<void> {
17  if (!(await Simulator.isSimulatorInstalledAsync())) {
18    console.error(chalk.red('iOS simulator is not installed!'));
19    return;
20  }
21
22  console.log('Booting up iOS simulator...');
23
24  const simulator = await Simulator.ensureSimulatorOpenAsync();
25
26  console.log('Uninstalling previously installed Expo client...');
27
28  await Simulator.uninstallExpoAppFromSimulatorAsync(simulator);
29
30  console.log(`Installing Expo client from ${chalk.blue(clientUrl)} on iOS simulator...`);
31
32  const installResult = await Simulator.installExpoOnSimulatorAsync({ url: clientUrl, simulator });
33
34  if (installResult.status !== 0) {
35    throw new Error('Installing Expo client simulator failed!');
36  }
37
38  const appIdentifier = 'host.exp.Exponent';
39
40  console.log(`Launching Expo client with identifier ${chalk.blue(appIdentifier)}...`);
41
42  await spawnAsync('xcrun', ['simctl', 'launch', 'booted', appIdentifier]);
43}
44
45async function downloadAndInstallOnAndroidAsync(clientUrl: string): Promise<void> {
46  try {
47    console.log('Checking if the are any Android devices or emulators connected...');
48
49    const devices = await Android.getAttachedDevicesAsync();
50    if (devices.length === 0) {
51      throw new Error('No connected devices or emulators found.');
52    }
53
54    const device = devices[0];
55    if (devices.length > 1) {
56      console.log(
57        `More than one Android device found. Installing on the first one found, ${device.name}.`
58      );
59    }
60
61    if (!device.isAuthorized) {
62      throw new Error(
63        `This computer is not authorized for developing on ${device.name}. See https://expo.fyi/authorize-android-device.`
64      );
65    }
66
67    console.log('Uninstalling previously installed Expo client...');
68
69    await Android.uninstallExpoAsync(device);
70
71    console.log(
72      `Installing Expo client from ${chalk.blue(clientUrl)} on Android ${device.type}...`
73    );
74
75    await Android.installExpoAsync({ url: clientUrl, device });
76
77    console.log('Launching application...');
78
79    await Android.getAdbOutputAsync([
80      'shell',
81      'am',
82      'start',
83      '-n',
84      `host.exp.exponent/.LauncherActivity`,
85    ]);
86  } catch (error) {
87    console.error(chalk.red(`Unable to install Expo client: ${error.message}`));
88  }
89}
90
91async function action(options: ActionOptions) {
92  const platform = options.platform || (await askForPlatformAsync());
93  const sdkVersion =
94    options.sdkVersion ||
95    (await askForSDKVersionAsync(platform, await getNewestSDKVersionAsync(platform)));
96
97  if (!sdkVersion) {
98    throw new Error(`Unable to find SDK version. Try to use ${chalk.yellow('--sdkVersion')} flag.`);
99  }
100
101  // Set XDL config to use staging
102  Config.api.host = STAGING_API_HOST;
103
104  const versions = await Versions.versionsAsync();
105  const sdkConfiguration = versions?.sdkVersions?.[sdkVersion];
106
107  if (!sdkConfiguration) {
108    throw new Error(`Versions configuration for SDK ${chalk.cyan(sdkVersion)} not found!`);
109  }
110
111  const tarballKey = `${platform}ClientUrl`;
112  const clientUrl = sdkConfiguration[tarballKey];
113
114  if (!clientUrl) {
115    throw new Error(`Client url not found at ${chalk.yellow(tarballKey)} key of versions config!`);
116  }
117
118  switch (platform) {
119    case 'ios': {
120      await downloadAndInstallOnIOSAsync(clientUrl);
121      break;
122    }
123    case 'android': {
124      await downloadAndInstallOnAndroidAsync(clientUrl);
125      break;
126    }
127    default: {
128      throw new Error(`Platform "${platform}" not implemented!`);
129    }
130  }
131  console.log(chalk.green('Successfully installed and launched staging version of the client ��'));
132}
133
134export default (program: Command) => {
135  program
136    .command('client-install')
137    .alias('ci')
138    .description(
139      'Installs staging version of the client on iOS simulator, Android emulator or connected Android device.'
140    )
141    .option('-p, --platform [string]', 'Platform for which the client will be installed.')
142    .option('-s, --sdkVersion [string]', 'SDK version of the client to install.')
143    .asyncAction(action);
144};
145