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