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