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