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