1import assert from 'assert'; 2 3import { hasDirectDevClientDependency } from '../utils/analytics/getDevClientProperties'; 4import { AbortCommandError, CommandError } from '../utils/errors'; 5import { resolvePortAsync } from '../utils/port'; 6 7export type Options = { 8 privateKeyPath: string | null; 9 android: boolean; 10 web: boolean; 11 ios: boolean; 12 offline: boolean; 13 clear: boolean; 14 dev: boolean; 15 https: boolean; 16 maxWorkers: number; 17 port: number; 18 /** Should instruct the bundler to create minified bundles. */ 19 minify: boolean; 20 devClient: boolean; 21 scheme: string | null; 22 host: 'localhost' | 'lan' | 'tunnel'; 23}; 24 25export async function resolveOptionsAsync(projectRoot: string, args: any): Promise<Options> { 26 if (args['--dev-client'] && args['--go']) { 27 throw new CommandError('BAD_ARGS', 'Cannot use both --dev-client and --go together.'); 28 } 29 const host = resolveHostType({ 30 host: args['--host'], 31 offline: args['--offline'], 32 lan: args['--lan'], 33 localhost: args['--localhost'], 34 tunnel: args['--tunnel'], 35 }); 36 37 // User can force the default target by passing either `--dev-client` or `--go`. They can also 38 // swap between them during development by pressing `s`. 39 const isUserDefinedDevClient = 40 !!args['--dev-client'] || (args['--go'] == null ? false : !args['--go']); 41 42 // If the user didn't specify `--dev-client` or `--go` we check if they have the dev client package 43 // in their package.json. 44 const isAutoDevClient = 45 args['--dev-client'] == null && 46 args['--go'] == null && 47 hasDirectDevClientDependency(projectRoot); 48 49 const isDevClient = isAutoDevClient || isUserDefinedDevClient; 50 51 const scheme = await resolveSchemeAsync(projectRoot, { 52 scheme: args['--scheme'], 53 devClient: isDevClient, 54 }); 55 56 return { 57 privateKeyPath: args['--private-key-path'] ?? null, 58 59 android: !!args['--android'], 60 web: !!args['--web'], 61 ios: !!args['--ios'], 62 offline: !!args['--offline'], 63 64 clear: !!args['--clear'], 65 dev: !args['--no-dev'], 66 https: !!args['--https'], 67 maxWorkers: args['--max-workers'], 68 port: args['--port'], 69 minify: !!args['--minify'], 70 71 devClient: isDevClient, 72 73 scheme, 74 host, 75 }; 76} 77 78export async function resolveSchemeAsync( 79 projectRoot: string, 80 options: { scheme?: string; devClient?: boolean } 81): Promise<string | null> { 82 const resolveFrom = require('resolve-from') as typeof import('resolve-from'); 83 84 const isDevClientPackageInstalled = (() => { 85 try { 86 // we check if `expo-dev-launcher` is installed instead of `expo-dev-client` 87 // because someone could install only launcher. 88 resolveFrom(projectRoot, 'expo-dev-launcher'); 89 return true; 90 } catch { 91 return false; 92 } 93 })(); 94 95 if (typeof options.scheme === 'string') { 96 // Use the custom scheme 97 return options.scheme ?? null; 98 } else if (options.devClient || isDevClientPackageInstalled) { 99 const { getOptionalDevClientSchemeAsync } = 100 require('../utils/scheme') as typeof import('../utils/scheme'); 101 // Attempt to find the scheme or warn the user how to setup a custom scheme 102 return await getOptionalDevClientSchemeAsync(projectRoot); 103 } else { 104 // Ensure this is reset when users don't use `--scheme`, `--dev-client` and don't have the `expo-dev-client` package installed. 105 return null; 106 } 107} 108 109/** Resolve and assert host type options. */ 110export function resolveHostType(options: { 111 host?: string; 112 offline?: boolean; 113 lan?: boolean; 114 localhost?: boolean; 115 tunnel?: boolean; 116}): 'lan' | 'tunnel' | 'localhost' { 117 if ( 118 [options.offline, options.host, options.lan, options.localhost, options.tunnel].filter((i) => i) 119 .length > 1 120 ) { 121 throw new CommandError( 122 'BAD_ARGS', 123 'Specify at most one of: --offline, --host, --tunnel, --lan, --localhost' 124 ); 125 } 126 127 if (options.offline) { 128 // Force `lan` in offline mode. 129 return 'lan'; 130 } else if (options.host) { 131 assert.match(options.host, /^(lan|tunnel|localhost)$/); 132 return options.host as 'lan' | 'tunnel' | 'localhost'; 133 } else if (options.tunnel) { 134 return 'tunnel'; 135 } else if (options.lan) { 136 return 'lan'; 137 } else if (options.localhost) { 138 return 'localhost'; 139 } 140 return 'lan'; 141} 142 143/** Resolve the port options for all supported bundlers. */ 144export async function resolvePortsAsync( 145 projectRoot: string, 146 options: Partial<Pick<Options, 'port' | 'devClient'>>, 147 settings: { webOnly?: boolean } 148) { 149 const multiBundlerSettings: { webpackPort?: number; metroPort?: number } = {}; 150 151 if (settings.webOnly) { 152 const webpackPort = await resolvePortAsync(projectRoot, { 153 defaultPort: options.port, 154 // Default web port 155 fallbackPort: 19006, 156 }); 157 if (!webpackPort) { 158 throw new AbortCommandError(); 159 } 160 multiBundlerSettings.webpackPort = webpackPort; 161 } else { 162 const fallbackPort = process.env.RCT_METRO_PORT 163 ? parseInt(process.env.RCT_METRO_PORT, 10) 164 : 8081; 165 const metroPort = await resolvePortAsync(projectRoot, { 166 defaultPort: options.port, 167 fallbackPort, 168 }); 169 if (!metroPort) { 170 throw new AbortCommandError(); 171 } 172 multiBundlerSettings.metroPort = metroPort; 173 } 174 175 return multiBundlerSettings; 176} 177