xref: /expo/packages/@expo/cli/src/start/startAsync.ts (revision dad749c2)
1import { ExpoConfig, getConfig } from '@expo/config';
2import chalk from 'chalk';
3
4import * as Log from '../log';
5import getDevClientProperties from '../utils/analytics/getDevClientProperties';
6import { logEvent } from '../utils/analytics/rudderstackClient';
7import { env } from '../utils/env';
8import { installExitHooks } from '../utils/exit';
9import { profile } from '../utils/profile';
10import { validateDependenciesVersionsAsync } from './doctor/dependencies/validateDependenciesVersions';
11import { TypeScriptProjectPrerequisite } from './doctor/typescript/TypeScriptProjectPrerequisite';
12import { WebSupportProjectPrerequisite } from './doctor/web/WebSupportProjectPrerequisite';
13import { startInterfaceAsync } from './interface/startInterface';
14import { Options, resolvePortsAsync } from './resolveOptions';
15import { BundlerStartOptions } from './server/BundlerDevServer';
16import { DevServerManager, MultiBundlerStartOptions } from './server/DevServerManager';
17import { openPlatformsAsync } from './server/openPlatforms';
18
19async function getMultiBundlerStartOptions(
20  projectRoot: string,
21  { forceManifestType, ...options }: Options,
22  settings: { webOnly?: boolean }
23): Promise<[BundlerStartOptions, MultiBundlerStartOptions]> {
24  const commonOptions: BundlerStartOptions = {
25    mode: options.dev ? 'development' : 'production',
26    devClient: options.devClient,
27    forceManifestType,
28    privateKeyPath: options.privateKeyPath ?? undefined,
29    https: options.https,
30    maxWorkers: options.maxWorkers,
31    resetDevServer: options.clear,
32    minify: options.minify,
33    location: {
34      hostType: options.host,
35      scheme: options.scheme,
36    },
37  };
38  const multiBundlerSettings = await resolvePortsAsync(projectRoot, options, settings);
39
40  const multiBundlerStartOptions: MultiBundlerStartOptions = [];
41
42  if (options.web || settings.webOnly) {
43    multiBundlerStartOptions.push({
44      type: 'webpack',
45      options: {
46        ...commonOptions,
47        port: multiBundlerSettings.webpackPort,
48      },
49    });
50  }
51
52  if (!settings.webOnly) {
53    multiBundlerStartOptions.push({
54      type: 'metro',
55      options: {
56        ...commonOptions,
57        port: multiBundlerSettings.metroPort,
58      },
59    });
60  }
61
62  return [commonOptions, multiBundlerStartOptions];
63}
64
65export async function startAsync(
66  projectRoot: string,
67  options: Options,
68  settings: { webOnly?: boolean }
69) {
70  Log.log(chalk.gray(`Starting project at ${projectRoot}`));
71
72  const { exp, pkg } = profile(getConfig)(projectRoot);
73
74  if (!options.forceManifestType) {
75    const easUpdatesUrlRegex = /^https:\/\/(staging-)?u\.expo\.dev/;
76    const isEasUpdatesUrl = exp.updates?.url ? easUpdatesUrlRegex.test(exp.updates.url) : false;
77    options.forceManifestType = isEasUpdatesUrl ? 'expo-updates' : 'classic';
78  }
79
80  const [defaultOptions, startOptions] = await getMultiBundlerStartOptions(
81    projectRoot,
82    options,
83    settings
84  );
85
86  const devServerManager = new DevServerManager(projectRoot, defaultOptions);
87
88  // Validations
89
90  if (options.web || settings.webOnly) {
91    await devServerManager.ensureProjectPrerequisiteAsync(WebSupportProjectPrerequisite);
92  }
93
94  await devServerManager.ensureProjectPrerequisiteAsync(TypeScriptProjectPrerequisite);
95
96  if (!settings.webOnly && !options.devClient) {
97    await profile(validateDependenciesVersionsAsync)(projectRoot, exp, pkg);
98  }
99
100  // Some tracking thing
101
102  if (options.devClient) {
103    track(projectRoot, exp);
104  }
105
106  await profile(devServerManager.startAsync.bind(devServerManager))(startOptions);
107
108  // Open project on devices.
109  await profile(openPlatformsAsync)(devServerManager, options);
110
111  // Present the Terminal UI.
112  if (!env.CI) {
113    await profile(startInterfaceAsync)(devServerManager, {
114      platforms: exp.platforms ?? ['ios', 'android', 'web'],
115    });
116  } else {
117    // Display the server location in CI...
118    const url = devServerManager.getDefaultDevServer()?.getDevServerUrl();
119    if (url) {
120      Log.log(chalk`Waiting on {underline ${url}}`);
121    }
122  }
123
124  // Final note about closing the server.
125  const logLocation = settings.webOnly ? 'in the browser console' : 'below';
126  Log.log(
127    chalk`Logs for your project will appear ${logLocation}.${
128      env.CI ? '' : chalk.dim(` Press Ctrl+C to exit.`)
129    }`
130  );
131}
132
133function track(projectRoot: string, exp: ExpoConfig) {
134  logEvent('dev client start command', {
135    status: 'started',
136    ...getDevClientProperties(projectRoot, exp),
137  });
138  installExitHooks(() => {
139    logEvent('dev client start command', {
140      status: 'finished',
141      ...getDevClientProperties(projectRoot, exp),
142    });
143    // UnifiedAnalytics.flush();
144  });
145}
146