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