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