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 { fixPackagesAsync } 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: PackageManager.NodePackageManager;
33
34    /** How the check should resolve */
35    options: Pick<Options, 'fix'>;
36    /**
37     * Extra parameters to pass to the `packageManager` when installing versioned packages.
38     * @example ['--no-save']
39     */
40    packageManagerArguments: string[];
41  }
42) {
43  // Read the project Expo config without plugins.
44  const { exp, pkg } = getConfig(projectRoot, {
45    // Sometimes users will add a plugin to the config before installing the library,
46    // this wouldn't work unless we dangerously disable plugin serialization.
47    skipPlugins: true,
48  });
49
50  const dependencies = await getVersionedDependenciesAsync(projectRoot, exp, pkg, packages);
51
52  if (!dependencies.length) {
53    Log.exit(chalk.greenBright('Dependencies are up to date'), 0);
54  }
55
56  logIncorrectDependencies(dependencies);
57
58  const value =
59    // If `--fix` then always fix.
60    fix ||
61    // Otherwise prompt to fix when not running in CI.
62    (isInteractive() && (await confirmAsync({ message: 'Fix dependencies?' }).catch(() => false)));
63
64  if (value) {
65    debug('Installing fixed dependencies:', dependencies);
66    // Install the corrected dependencies.
67    return fixPackagesAsync(projectRoot, {
68      packageManager,
69      packages: dependencies,
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