1import * as PackageManager from '@expo/package-manager'; 2 3import * as Log from '../log'; 4import { CommandError } from '../utils/errors'; 5 6export type Options = Pick<PackageManager.CreateForProjectOptions, 'npm' | 'pnpm' | 'yarn'> & { 7 /** Check which packages need to be updated, does not install any provided packages. */ 8 check?: boolean; 9 /** Should the dependencies be fixed automatically. */ 10 fix?: boolean; 11 /** Should disable install output, used for commands like `prebuild` that run install internally. */ 12 silent?: boolean; 13}; 14 15function resolveOptions(options: Options): Options { 16 if (options.fix && options.check) { 17 throw new CommandError('BAD_ARGS', 'Specify at most one of: --check, --fix'); 18 } 19 if ([options.npm, options.pnpm, options.yarn].filter(Boolean).length > 1) { 20 throw new CommandError('BAD_ARGS', 'Specify at most one of: --npm, --pnpm, --yarn'); 21 } 22 return { 23 ...options, 24 }; 25} 26 27/** Given a list of CLI args, return a sorted set of args based on categories used in a complex command. */ 28export function parseVariadicArguments(argv: string[]): { 29 variadic: string[]; 30 extras: string[]; 31 flags: Record<string, boolean>; 32} { 33 const variadic: string[] = []; 34 const flags: Record<string, boolean> = {}; 35 36 for (const arg of argv) { 37 if (!arg.startsWith('-')) { 38 variadic.push(arg); 39 } else if (arg === '--') { 40 break; 41 } else { 42 flags[arg] = true; 43 } 44 } 45 46 // Everything after `--` that is not an option is passed to the underlying install command. 47 const extras: string[] = []; 48 49 const extraOperator = argv.indexOf('--'); 50 if (extraOperator > -1 && argv.length > extraOperator + 1) { 51 const extraArgs = argv.slice(extraOperator + 1); 52 if (extraArgs.includes('--')) { 53 throw new CommandError('BAD_ARGS', 'Unexpected multiple --'); 54 } 55 extras.push(...extraArgs); 56 Log.debug('Extra arguments: ' + extras.join(', ')); 57 } 58 59 Log.debug(`Parsed arguments (variadic: %O, flags: %O, extra: %O)`, variadic, flags, extras); 60 61 return { 62 variadic, 63 flags, 64 extras, 65 }; 66} 67 68export async function resolveArgsAsync( 69 argv: string[] 70): Promise<{ variadic: string[]; options: Options; extras: string[] }> { 71 const { variadic, extras, flags } = parseVariadicArguments(argv); 72 73 assertUnexpectedObjectKeys(['--check', '--fix', '--npm', '--pnpm', '--yarn'], flags); 74 75 return { 76 // Variadic arguments like `npx expo install react react-dom` -> ['react', 'react-dom'] 77 variadic, 78 options: resolveOptions({ 79 fix: !!flags['--fix'], 80 check: !!flags['--check'], 81 yarn: !!flags['--yarn'], 82 npm: !!flags['--npm'], 83 pnpm: !!flags['--pnpm'], 84 }), 85 extras, 86 }; 87} 88 89function assertUnexpectedObjectKeys(keys: string[], obj: Record<string, any>): void { 90 const unexpectedKeys = Object.keys(obj).filter((key) => !keys.includes(key)); 91 if (unexpectedKeys.length > 0) { 92 throw new CommandError('BAD_ARGS', `Unexpected: ${unexpectedKeys.join(', ')}`); 93 } 94} 95