1import { getConfig } from '@expo/config';
2import * as PackageManager from '@expo/package-manager';
3import chalk from 'chalk';
4
5import * as Log from '../log';
6import {
7  getVersionedDependenciesAsync,
8  logIncorrectDependencies,
9} from '../start/doctor/dependencies/validateDependenciesVersions';
10import { isInteractive } from '../utils/interactive';
11import { confirmAsync } from '../utils/prompts';
12import { installPackagesAsync } from './installAsync';
13import { Options } from './resolveOptions';
14
15const debug = require('debug')('expo:install:check') as typeof console.log;
16
17// Exposed for testing.
18export async function checkPackagesAsync(
19  projectRoot: string,
20  {
21    packages,
22    packageManager,
23    options: { fix },
24    packageManagerArguments,
25  }: {
26    /**
27     * List of packages to version
28     * @example ['uuid', 'react-native-reanimated@latest']
29     */
30    packages: string[];
31    /** Package manager to use when installing the versioned packages. */
32    packageManager:
33      | PackageManager.NpmPackageManager
34      | PackageManager.YarnPackageManager
35      | PackageManager.PnpmPackageManager;
36
37    /** How the check should resolve */
38    options: Pick<Options, 'fix'>;
39    /**
40     * Extra parameters to pass to the `packageManager` when installing versioned packages.
41     * @example ['--no-save']
42     */
43    packageManagerArguments: string[];
44  }
45) {
46  // Read the project Expo config without plugins.
47  const { exp, pkg } = getConfig(projectRoot, {
48    // Sometimes users will add a plugin to the config before installing the library,
49    // this wouldn't work unless we dangerously disable plugin serialization.
50    skipPlugins: true,
51  });
52
53  const dependencies = await getVersionedDependenciesAsync(projectRoot, exp, pkg, packages);
54
55  if (!dependencies.length) {
56    Log.exit(chalk.greenBright('Dependencies are up to date'), 0);
57  }
58
59  logIncorrectDependencies(dependencies);
60
61  const value =
62    // If `--fix` then always fix.
63    fix ||
64    // Otherwise prompt to fix when not running in CI.
65    (isInteractive() && (await confirmAsync({ message: 'Fix dependencies?' }).catch(() => false)));
66
67  if (value) {
68    // Just pass in the names, the install function will resolve the versions again.
69    const fixedDependencies = dependencies.map((dependency) => dependency.packageName);
70    debug('Installing fixed dependencies:', fixedDependencies);
71    // Install the corrected dependencies.
72    return installPackagesAsync(projectRoot, {
73      packageManager,
74      packages: fixedDependencies,
75      packageManagerArguments,
76      sdkVersion: exp.sdkVersion!,
77    });
78  }
79  // Exit with non-zero exit code if any of the dependencies are out of date.
80  Log.exit(chalk.red('Found outdated dependencies'), 1);
81}
82