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