18d307f52SEvan Baconimport assert from 'assert'; 28d307f52SEvan Bacon 3*8df9096fSEvan Baconimport { hasDirectDevClientDependency } from '../utils/analytics/getDevClientProperties'; 48d307f52SEvan Baconimport { AbortCommandError, CommandError } from '../utils/errors'; 58d307f52SEvan Baconimport { resolvePortAsync } from '../utils/port'; 68d307f52SEvan Bacon 78d307f52SEvan Baconexport type Options = { 8e377ff85SWill Schurman privateKeyPath: string | null; 98d307f52SEvan Bacon android: boolean; 108d307f52SEvan Bacon web: boolean; 118d307f52SEvan Bacon ios: boolean; 128d307f52SEvan Bacon offline: boolean; 138d307f52SEvan Bacon clear: boolean; 148d307f52SEvan Bacon dev: boolean; 158d307f52SEvan Bacon https: boolean; 168d307f52SEvan Bacon maxWorkers: number; 178d307f52SEvan Bacon port: number; 188d307f52SEvan Bacon /** Should instruct the bundler to create minified bundles. */ 198d307f52SEvan Bacon minify: boolean; 208d307f52SEvan Bacon devClient: boolean; 2129975bfdSEvan Bacon scheme: string | null; 228d307f52SEvan Bacon host: 'localhost' | 'lan' | 'tunnel'; 238d307f52SEvan Bacon}; 248d307f52SEvan Bacon 258d307f52SEvan Baconexport async function resolveOptionsAsync(projectRoot: string, args: any): Promise<Options> { 2641c91838SEvan Bacon if (args['--dev-client'] && args['--go']) { 2741c91838SEvan Bacon throw new CommandError('BAD_ARGS', 'Cannot use both --dev-client and --go together.'); 2841c91838SEvan Bacon } 298d307f52SEvan Bacon const host = resolveHostType({ 308d307f52SEvan Bacon host: args['--host'], 318d307f52SEvan Bacon offline: args['--offline'], 328d307f52SEvan Bacon lan: args['--lan'], 338d307f52SEvan Bacon localhost: args['--localhost'], 348d307f52SEvan Bacon tunnel: args['--tunnel'], 358d307f52SEvan Bacon }); 368d307f52SEvan Bacon 37*8df9096fSEvan Bacon // User can force the default target by passing either `--dev-client` or `--go`. They can also 38*8df9096fSEvan Bacon // swap between them during development by pressing `s`. 39*8df9096fSEvan Bacon const isUserDefinedDevClient = 40*8df9096fSEvan Bacon !!args['--dev-client'] || (args['--go'] == null ? false : !args['--go']); 41*8df9096fSEvan Bacon 42*8df9096fSEvan Bacon // If the user didn't specify `--dev-client` or `--go` we check if they have the dev client package 43*8df9096fSEvan Bacon // in their package.json. 44*8df9096fSEvan Bacon const isAutoDevClient = 45*8df9096fSEvan Bacon args['--dev-client'] == null && 46*8df9096fSEvan Bacon args['--go'] == null && 47*8df9096fSEvan Bacon hasDirectDevClientDependency(projectRoot); 48*8df9096fSEvan Bacon 49*8df9096fSEvan Bacon const isDevClient = isAutoDevClient || isUserDefinedDevClient; 5041c91838SEvan Bacon 518d307f52SEvan Bacon const scheme = await resolveSchemeAsync(projectRoot, { 528d307f52SEvan Bacon scheme: args['--scheme'], 5341c91838SEvan Bacon devClient: isDevClient, 548d307f52SEvan Bacon }); 558d307f52SEvan Bacon 568d307f52SEvan Bacon return { 57c14835f6SWill Schurman privateKeyPath: args['--private-key-path'] ?? null, 588d307f52SEvan Bacon 598d307f52SEvan Bacon android: !!args['--android'], 608d307f52SEvan Bacon web: !!args['--web'], 618d307f52SEvan Bacon ios: !!args['--ios'], 628d307f52SEvan Bacon offline: !!args['--offline'], 638d307f52SEvan Bacon 648d307f52SEvan Bacon clear: !!args['--clear'], 658d307f52SEvan Bacon dev: !args['--no-dev'], 668d307f52SEvan Bacon https: !!args['--https'], 678d307f52SEvan Bacon maxWorkers: args['--max-workers'], 688d307f52SEvan Bacon port: args['--port'], 698d307f52SEvan Bacon minify: !!args['--minify'], 708d307f52SEvan Bacon 7141c91838SEvan Bacon devClient: isDevClient, 728d307f52SEvan Bacon 738d307f52SEvan Bacon scheme, 748d307f52SEvan Bacon host, 758d307f52SEvan Bacon }; 768d307f52SEvan Bacon} 778d307f52SEvan Bacon 788d307f52SEvan Baconexport async function resolveSchemeAsync( 798d307f52SEvan Bacon projectRoot: string, 808d307f52SEvan Bacon options: { scheme?: string; devClient?: boolean } 818d307f52SEvan Bacon): Promise<string | null> { 8241c91838SEvan Bacon const resolveFrom = require('resolve-from') as typeof import('resolve-from'); 838d307f52SEvan Bacon 848d307f52SEvan Bacon const isDevClientPackageInstalled = (() => { 858d307f52SEvan Bacon try { 868d307f52SEvan Bacon // we check if `expo-dev-launcher` is installed instead of `expo-dev-client` 878d307f52SEvan Bacon // because someone could install only launcher. 888d307f52SEvan Bacon resolveFrom(projectRoot, 'expo-dev-launcher'); 898d307f52SEvan Bacon return true; 908d307f52SEvan Bacon } catch { 918d307f52SEvan Bacon return false; 928d307f52SEvan Bacon } 938d307f52SEvan Bacon })(); 948d307f52SEvan Bacon 958d307f52SEvan Bacon if (typeof options.scheme === 'string') { 968d307f52SEvan Bacon // Use the custom scheme 978d307f52SEvan Bacon return options.scheme ?? null; 988d307f52SEvan Bacon } else if (options.devClient || isDevClientPackageInstalled) { 9941c91838SEvan Bacon const { getOptionalDevClientSchemeAsync } = 10041c91838SEvan Bacon require('../utils/scheme') as typeof import('../utils/scheme'); 1018d307f52SEvan Bacon // Attempt to find the scheme or warn the user how to setup a custom scheme 1028d307f52SEvan Bacon return await getOptionalDevClientSchemeAsync(projectRoot); 1038d307f52SEvan Bacon } else { 1048d307f52SEvan Bacon // Ensure this is reset when users don't use `--scheme`, `--dev-client` and don't have the `expo-dev-client` package installed. 1058d307f52SEvan Bacon return null; 1068d307f52SEvan Bacon } 1078d307f52SEvan Bacon} 1088d307f52SEvan Bacon 1098d307f52SEvan Bacon/** Resolve and assert host type options. */ 1108d307f52SEvan Baconexport function resolveHostType(options: { 1118d307f52SEvan Bacon host?: string; 1128d307f52SEvan Bacon offline?: boolean; 1138d307f52SEvan Bacon lan?: boolean; 1148d307f52SEvan Bacon localhost?: boolean; 1158d307f52SEvan Bacon tunnel?: boolean; 1168d307f52SEvan Bacon}): 'lan' | 'tunnel' | 'localhost' { 1178d307f52SEvan Bacon if ( 1188d307f52SEvan Bacon [options.offline, options.host, options.lan, options.localhost, options.tunnel].filter((i) => i) 1198d307f52SEvan Bacon .length > 1 1208d307f52SEvan Bacon ) { 1218d307f52SEvan Bacon throw new CommandError( 1228d307f52SEvan Bacon 'BAD_ARGS', 1238d307f52SEvan Bacon 'Specify at most one of: --offline, --host, --tunnel, --lan, --localhost' 1248d307f52SEvan Bacon ); 1258d307f52SEvan Bacon } 1268d307f52SEvan Bacon 1278d307f52SEvan Bacon if (options.offline) { 1288d307f52SEvan Bacon // Force `lan` in offline mode. 1298d307f52SEvan Bacon return 'lan'; 1308d307f52SEvan Bacon } else if (options.host) { 1318d307f52SEvan Bacon assert.match(options.host, /^(lan|tunnel|localhost)$/); 1328d307f52SEvan Bacon return options.host as 'lan' | 'tunnel' | 'localhost'; 1338d307f52SEvan Bacon } else if (options.tunnel) { 1348d307f52SEvan Bacon return 'tunnel'; 1358d307f52SEvan Bacon } else if (options.lan) { 1368d307f52SEvan Bacon return 'lan'; 1378d307f52SEvan Bacon } else if (options.localhost) { 1388d307f52SEvan Bacon return 'localhost'; 1398d307f52SEvan Bacon } 1408d307f52SEvan Bacon return 'lan'; 1418d307f52SEvan Bacon} 1428d307f52SEvan Bacon 1438d307f52SEvan Bacon/** Resolve the port options for all supported bundlers. */ 1448d307f52SEvan Baconexport async function resolvePortsAsync( 1458d307f52SEvan Bacon projectRoot: string, 1468d307f52SEvan Bacon options: Partial<Pick<Options, 'port' | 'devClient'>>, 1478d307f52SEvan Bacon settings: { webOnly?: boolean } 1488d307f52SEvan Bacon) { 1498d307f52SEvan Bacon const multiBundlerSettings: { webpackPort?: number; metroPort?: number } = {}; 1508d307f52SEvan Bacon 1518d307f52SEvan Bacon if (settings.webOnly) { 1528d307f52SEvan Bacon const webpackPort = await resolvePortAsync(projectRoot, { 1538d307f52SEvan Bacon defaultPort: options.port, 1548d307f52SEvan Bacon // Default web port 1558d307f52SEvan Bacon fallbackPort: 19006, 1568d307f52SEvan Bacon }); 1578d307f52SEvan Bacon if (!webpackPort) { 1588d307f52SEvan Bacon throw new AbortCommandError(); 1598d307f52SEvan Bacon } 1608d307f52SEvan Bacon multiBundlerSettings.webpackPort = webpackPort; 1618d307f52SEvan Bacon } else { 16247d62600SKudo Chien const fallbackPort = process.env.RCT_METRO_PORT 16329975bfdSEvan Bacon ? parseInt(process.env.RCT_METRO_PORT, 10) 16429975bfdSEvan Bacon : 8081; 1658d307f52SEvan Bacon const metroPort = await resolvePortAsync(projectRoot, { 1668d307f52SEvan Bacon defaultPort: options.port, 16747d62600SKudo Chien fallbackPort, 1688d307f52SEvan Bacon }); 1698d307f52SEvan Bacon if (!metroPort) { 1708d307f52SEvan Bacon throw new AbortCommandError(); 1718d307f52SEvan Bacon } 1728d307f52SEvan Bacon multiBundlerSettings.metroPort = metroPort; 1738d307f52SEvan Bacon } 1748d307f52SEvan Bacon 1758d307f52SEvan Bacon return multiBundlerSettings; 1768d307f52SEvan Bacon} 177