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