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