xref: /expo/packages/@expo/cli/src/start/startAsync.ts (revision 9ba03fb0)
18d307f52SEvan Baconimport { ExpoConfig, getConfig } from '@expo/config';
28d307f52SEvan Baconimport chalk from 'chalk';
38d307f52SEvan Bacon
48d307f52SEvan Baconimport { validateDependenciesVersionsAsync } from './doctor/dependencies/validateDependenciesVersions';
58d307f52SEvan Baconimport { WebSupportProjectPrerequisite } from './doctor/web/WebSupportProjectPrerequisite';
68d307f52SEvan Baconimport { startInterfaceAsync } from './interface/startInterface';
78d307f52SEvan Baconimport { Options, resolvePortsAsync } from './resolveOptions';
88d307f52SEvan Baconimport { BundlerStartOptions } from './server/BundlerDevServer';
98d307f52SEvan Baconimport { DevServerManager, MultiBundlerStartOptions } from './server/DevServerManager';
108d307f52SEvan Baconimport { openPlatformsAsync } from './server/openPlatforms';
116d6b81f9SEvan Baconimport { getPlatformBundlers, PlatformBundlers } from './server/platformBundlers';
128a424bebSJames Ideimport * as Log from '../log';
138a424bebSJames Ideimport getDevClientProperties from '../utils/analytics/getDevClientProperties';
148a424bebSJames Ideimport { logEventAsync } from '../utils/analytics/rudderstackClient';
158a424bebSJames Ideimport { installExitHooks } from '../utils/exit';
168a424bebSJames Ideimport { isInteractive } from '../utils/interactive';
178a424bebSJames Ideimport { setNodeEnv } from '../utils/nodeEnv';
188a424bebSJames Ideimport { profile } from '../utils/profile';
198d307f52SEvan Bacon
208d307f52SEvan Baconasync function getMultiBundlerStartOptions(
218d307f52SEvan Bacon  projectRoot: string,
22*9ba03fb0SWill Schurman  options: Options,
236d6b81f9SEvan Bacon  settings: { webOnly?: boolean },
246d6b81f9SEvan Bacon  platformBundlers: PlatformBundlers
258d307f52SEvan Bacon): Promise<[BundlerStartOptions, MultiBundlerStartOptions]> {
268d307f52SEvan Bacon  const commonOptions: BundlerStartOptions = {
278d307f52SEvan Bacon    mode: options.dev ? 'development' : 'production',
288d307f52SEvan Bacon    devClient: options.devClient,
29e377ff85SWill Schurman    privateKeyPath: options.privateKeyPath ?? undefined,
308d307f52SEvan Bacon    https: options.https,
318d307f52SEvan Bacon    maxWorkers: options.maxWorkers,
328d307f52SEvan Bacon    resetDevServer: options.clear,
338d307f52SEvan Bacon    minify: options.minify,
348d307f52SEvan Bacon    location: {
358d307f52SEvan Bacon      hostType: options.host,
368d307f52SEvan Bacon      scheme: options.scheme,
378d307f52SEvan Bacon    },
388d307f52SEvan Bacon  };
398d307f52SEvan Bacon  const multiBundlerSettings = await resolvePortsAsync(projectRoot, options, settings);
408d307f52SEvan Bacon
416d6b81f9SEvan Bacon  const optionalBundlers: Partial<PlatformBundlers> = { ...platformBundlers };
426d6b81f9SEvan Bacon  // In the default case, we don't want to start multiple bundlers since this is
436d6b81f9SEvan Bacon  // a bit slower. Our priority (for legacy) is native platforms.
446d6b81f9SEvan Bacon  if (!options.web) {
456d6b81f9SEvan Bacon    delete optionalBundlers['web'];
468d307f52SEvan Bacon  }
478d307f52SEvan Bacon
486d6b81f9SEvan Bacon  const bundlers = [...new Set(Object.values(optionalBundlers))];
496d6b81f9SEvan Bacon  const multiBundlerStartOptions = bundlers.map((bundler) => {
506d6b81f9SEvan Bacon    const port =
516d6b81f9SEvan Bacon      bundler === 'webpack' ? multiBundlerSettings.webpackPort : multiBundlerSettings.metroPort;
526d6b81f9SEvan Bacon    return {
536d6b81f9SEvan Bacon      type: bundler,
548d307f52SEvan Bacon      options: {
558d307f52SEvan Bacon        ...commonOptions,
566d6b81f9SEvan Bacon        port,
578d307f52SEvan Bacon      },
586d6b81f9SEvan Bacon    };
598d307f52SEvan Bacon  });
608d307f52SEvan Bacon
618d307f52SEvan Bacon  return [commonOptions, multiBundlerStartOptions];
628d307f52SEvan Bacon}
638d307f52SEvan Bacon
648d307f52SEvan Baconexport async function startAsync(
658d307f52SEvan Bacon  projectRoot: string,
668d307f52SEvan Bacon  options: Options,
678d307f52SEvan Bacon  settings: { webOnly?: boolean }
688d307f52SEvan Bacon) {
698d307f52SEvan Bacon  Log.log(chalk.gray(`Starting project at ${projectRoot}`));
708d307f52SEvan Bacon
712dd43328SEvan Bacon  setNodeEnv(options.dev ? 'development' : 'production');
726a750d06SEvan Bacon  require('@expo/env').load(projectRoot);
738d307f52SEvan Bacon  const { exp, pkg } = profile(getConfig)(projectRoot);
748d307f52SEvan Bacon
756d6b81f9SEvan Bacon  const platformBundlers = getPlatformBundlers(exp);
766d6b81f9SEvan Bacon
778d307f52SEvan Bacon  const [defaultOptions, startOptions] = await getMultiBundlerStartOptions(
788d307f52SEvan Bacon    projectRoot,
798d307f52SEvan Bacon    options,
806d6b81f9SEvan Bacon    settings,
816d6b81f9SEvan Bacon    platformBundlers
828d307f52SEvan Bacon  );
838d307f52SEvan Bacon
848d307f52SEvan Bacon  const devServerManager = new DevServerManager(projectRoot, defaultOptions);
858d307f52SEvan Bacon
868d307f52SEvan Bacon  // Validations
878d307f52SEvan Bacon
888d307f52SEvan Bacon  if (options.web || settings.webOnly) {
898d307f52SEvan Bacon    await devServerManager.ensureProjectPrerequisiteAsync(WebSupportProjectPrerequisite);
908d307f52SEvan Bacon  }
918d307f52SEvan Bacon
9233643b60SEvan Bacon  // Start the server as soon as possible.
9333643b60SEvan Bacon  await profile(devServerManager.startAsync.bind(devServerManager))(startOptions);
9433643b60SEvan Bacon
9533643b60SEvan Bacon  if (!settings.webOnly) {
966a750d06SEvan Bacon    await devServerManager.watchEnvironmentVariables();
976a750d06SEvan Bacon
9833643b60SEvan Bacon    // After the server starts, we can start attempting to bootstrap TypeScript.
9933643b60SEvan Bacon    await devServerManager.bootstrapTypeScriptAsync();
10033643b60SEvan Bacon  }
1018d307f52SEvan Bacon
1028d307f52SEvan Bacon  if (!settings.webOnly && !options.devClient) {
1038d307f52SEvan Bacon    await profile(validateDependenciesVersionsAsync)(projectRoot, exp, pkg);
1048d307f52SEvan Bacon  }
1058d307f52SEvan Bacon
1068d307f52SEvan Bacon  // Some tracking thing
1078d307f52SEvan Bacon
1088d307f52SEvan Bacon  if (options.devClient) {
10991c8580dSWill Schurman    await trackAsync(projectRoot, exp);
1108d307f52SEvan Bacon  }
1118d307f52SEvan Bacon
1128d307f52SEvan Bacon  // Open project on devices.
1138d307f52SEvan Bacon  await profile(openPlatformsAsync)(devServerManager, options);
1148d307f52SEvan Bacon
1158d307f52SEvan Bacon  // Present the Terminal UI.
11629128565SEvan Bacon  if (isInteractive()) {
1178d307f52SEvan Bacon    await profile(startInterfaceAsync)(devServerManager, {
1188d307f52SEvan Bacon      platforms: exp.platforms ?? ['ios', 'android', 'web'],
1198d307f52SEvan Bacon    });
1208d307f52SEvan Bacon  } else {
1218d307f52SEvan Bacon    // Display the server location in CI...
1228d307f52SEvan Bacon    const url = devServerManager.getDefaultDevServer()?.getDevServerUrl();
1238d307f52SEvan Bacon    if (url) {
1248d307f52SEvan Bacon      Log.log(chalk`Waiting on {underline ${url}}`);
1258d307f52SEvan Bacon    }
1268d307f52SEvan Bacon  }
1278d307f52SEvan Bacon
1288d307f52SEvan Bacon  // Final note about closing the server.
1298d307f52SEvan Bacon  const logLocation = settings.webOnly ? 'in the browser console' : 'below';
1308d307f52SEvan Bacon  Log.log(
1318d307f52SEvan Bacon    chalk`Logs for your project will appear ${logLocation}.${
13229128565SEvan Bacon      isInteractive() ? chalk.dim(` Press Ctrl+C to exit.`) : ''
1338d307f52SEvan Bacon    }`
1348d307f52SEvan Bacon  );
1358d307f52SEvan Bacon}
1368d307f52SEvan Bacon
13791c8580dSWill Schurmanasync function trackAsync(projectRoot: string, exp: ExpoConfig): Promise<void> {
13891c8580dSWill Schurman  await logEventAsync('dev client start command', {
1398d307f52SEvan Bacon    status: 'started',
1408d307f52SEvan Bacon    ...getDevClientProperties(projectRoot, exp),
1418d307f52SEvan Bacon  });
14291c8580dSWill Schurman  installExitHooks(async () => {
14391c8580dSWill Schurman    await logEventAsync('dev client start command', {
1448d307f52SEvan Bacon      status: 'finished',
1458d307f52SEvan Bacon      ...getDevClientProperties(projectRoot, exp),
1468d307f52SEvan Bacon    });
1478d307f52SEvan Bacon    // UnifiedAnalytics.flush();
1488d307f52SEvan Bacon  });
1498d307f52SEvan Bacon}
150