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' | 'yarn'>;
7
8function resolveOptions(options: Options): Options {
9  if (options.npm && options.yarn) {
10    throw new CommandError('BAD_ARGS', 'Specify at most one of: --npm, --yarn');
11  }
12  return {
13    ...options,
14  };
15}
16
17/** Given a list of CLI args, return a sorted set of args based on categories used in a complex command. */
18export function parseVariadicArguments(argv: string[]): {
19  variadic: string[];
20  extras: string[];
21  flags: Record<string, boolean>;
22} {
23  const variadic: string[] = [];
24  const flags: Record<string, boolean> = {};
25
26  for (const arg of argv) {
27    if (!arg.startsWith('-')) {
28      variadic.push(arg);
29    } else if (arg === '--') {
30      break;
31    } else {
32      flags[arg] = true;
33    }
34  }
35
36  // Everything after `--` that is not an option is passed to the underlying install command.
37  const extras: string[] = [];
38
39  const extraOperator = argv.indexOf('--');
40  if (extraOperator > -1 && argv.length > extraOperator + 1) {
41    const extraArgs = argv.slice(extraOperator + 1);
42    if (extraArgs.includes('--')) {
43      throw new CommandError('BAD_ARGS', 'Unexpected multiple --');
44    }
45    extras.push(...extraArgs);
46    Log.debug('Extra arguments: ' + extras.join(', '));
47  }
48
49  Log.debug(`Parsed arguments (variadic: %O, flags: %O, extra: %O)`, variadic, flags, extras);
50
51  return {
52    variadic,
53    flags,
54    extras,
55  };
56}
57
58export async function resolveArgsAsync(
59  argv: string[]
60): Promise<{ variadic: string[]; options: Options; extras: string[] }> {
61  const { variadic, extras, flags } = parseVariadicArguments(argv);
62
63  assertUnexpectedObjectKeys(['--npm', '--yarn'], flags);
64
65  return {
66    // Variadic arguments like `npx expo install react react-dom` -> ['react', 'react-dom']
67    variadic,
68    options: resolveOptions({
69      yarn: !!flags['--yarn'],
70      npm: !!flags['--npm'],
71    }),
72    extras,
73  };
74}
75
76function assertUnexpectedObjectKeys(keys: string[], obj: Record<string, any>): void {
77  const unexpectedKeys = Object.keys(obj).filter((key) => !keys.includes(key));
78  if (unexpectedKeys.length > 0) {
79    throw new CommandError('BAD_ARGS', `Unexpected: ${unexpectedKeys.join(', ')}`);
80  }
81}
82