1c94ad8a2SEvan Baconimport { getConfig } from '@expo/config';
2c94ad8a2SEvan Baconimport * as PackageManager from '@expo/package-manager';
3c94ad8a2SEvan Baconimport chalk from 'chalk';
4c94ad8a2SEvan Bacon
5*8a424bebSJames Ideimport { fixPackagesAsync } from './installAsync';
6*8a424bebSJames Ideimport { Options } from './resolveOptions';
7c94ad8a2SEvan Baconimport * as Log from '../log';
8c94ad8a2SEvan Baconimport {
9c94ad8a2SEvan Bacon  getVersionedDependenciesAsync,
10c94ad8a2SEvan Bacon  logIncorrectDependencies,
11c94ad8a2SEvan Bacon} from '../start/doctor/dependencies/validateDependenciesVersions';
1229128565SEvan Baconimport { isInteractive } from '../utils/interactive';
136fb32dddSKeith Kurakimport { learnMore } from '../utils/link';
14c94ad8a2SEvan Baconimport { confirmAsync } from '../utils/prompts';
156fb32dddSKeith Kurakimport { joinWithCommasAnd } from '../utils/strings';
16c94ad8a2SEvan Bacon
17474a7a4bSEvan Baconconst debug = require('debug')('expo:install:check') as typeof console.log;
18474a7a4bSEvan Bacon
19c94ad8a2SEvan Bacon// Exposed for testing.
20c94ad8a2SEvan Baconexport async function checkPackagesAsync(
21c94ad8a2SEvan Bacon  projectRoot: string,
22c94ad8a2SEvan Bacon  {
23c94ad8a2SEvan Bacon    packages,
24c94ad8a2SEvan Bacon    packageManager,
25c94ad8a2SEvan Bacon    options: { fix },
26c94ad8a2SEvan Bacon    packageManagerArguments,
27c94ad8a2SEvan Bacon  }: {
28c94ad8a2SEvan Bacon    /**
29c94ad8a2SEvan Bacon     * List of packages to version
30c94ad8a2SEvan Bacon     * @example ['uuid', 'react-native-reanimated@latest']
31c94ad8a2SEvan Bacon     */
32c94ad8a2SEvan Bacon    packages: string[];
33c94ad8a2SEvan Bacon    /** Package manager to use when installing the versioned packages. */
348d3f3824SCedric van Putten    packageManager: PackageManager.NodePackageManager;
35c94ad8a2SEvan Bacon
36c94ad8a2SEvan Bacon    /** How the check should resolve */
37c94ad8a2SEvan Bacon    options: Pick<Options, 'fix'>;
38c94ad8a2SEvan Bacon    /**
39c94ad8a2SEvan Bacon     * Extra parameters to pass to the `packageManager` when installing versioned packages.
40c94ad8a2SEvan Bacon     * @example ['--no-save']
41c94ad8a2SEvan Bacon     */
42c94ad8a2SEvan Bacon    packageManagerArguments: string[];
43c94ad8a2SEvan Bacon  }
44c94ad8a2SEvan Bacon) {
45c94ad8a2SEvan Bacon  // Read the project Expo config without plugins.
46c94ad8a2SEvan Bacon  const { exp, pkg } = getConfig(projectRoot, {
47c94ad8a2SEvan Bacon    // Sometimes users will add a plugin to the config before installing the library,
48c94ad8a2SEvan Bacon    // this wouldn't work unless we dangerously disable plugin serialization.
49c94ad8a2SEvan Bacon    skipPlugins: true,
50c94ad8a2SEvan Bacon  });
51c94ad8a2SEvan Bacon
526fb32dddSKeith Kurak  if (pkg.expo?.install?.exclude?.length) {
536fb32dddSKeith Kurak    Log.log(
546fb32dddSKeith Kurak      chalk`Skipped ${fix ? 'fixing' : 'checking'} dependencies: ${joinWithCommasAnd(
556fb32dddSKeith Kurak        pkg.expo.install.exclude
566fb32dddSKeith Kurak      )}. These dependencies are listed in {bold expo.install.exclude} in package.json. ${learnMore(
576fb32dddSKeith Kurak        'https://expo.dev/more/expo-cli/#configuring-dependency-validation'
586fb32dddSKeith Kurak      )}`
596fb32dddSKeith Kurak    );
606fb32dddSKeith Kurak  }
616fb32dddSKeith Kurak
62c94ad8a2SEvan Bacon  const dependencies = await getVersionedDependenciesAsync(projectRoot, exp, pkg, packages);
63c94ad8a2SEvan Bacon
64c94ad8a2SEvan Bacon  if (!dependencies.length) {
65c94ad8a2SEvan Bacon    Log.exit(chalk.greenBright('Dependencies are up to date'), 0);
66c94ad8a2SEvan Bacon  }
67c94ad8a2SEvan Bacon
68c94ad8a2SEvan Bacon  logIncorrectDependencies(dependencies);
69c94ad8a2SEvan Bacon
70c94ad8a2SEvan Bacon  const value =
71c94ad8a2SEvan Bacon    // If `--fix` then always fix.
72c94ad8a2SEvan Bacon    fix ||
73c94ad8a2SEvan Bacon    // Otherwise prompt to fix when not running in CI.
7429128565SEvan Bacon    (isInteractive() && (await confirmAsync({ message: 'Fix dependencies?' }).catch(() => false)));
75c94ad8a2SEvan Bacon
76c94ad8a2SEvan Bacon  if (value) {
778d3f3824SCedric van Putten    debug('Installing fixed dependencies:', dependencies);
78c94ad8a2SEvan Bacon    // Install the corrected dependencies.
798d3f3824SCedric van Putten    return fixPackagesAsync(projectRoot, {
80c94ad8a2SEvan Bacon      packageManager,
818d3f3824SCedric van Putten      packages: dependencies,
82c94ad8a2SEvan Bacon      packageManagerArguments,
83c94ad8a2SEvan Bacon      sdkVersion: exp.sdkVersion!,
84c94ad8a2SEvan Bacon    });
85c94ad8a2SEvan Bacon  }
86c94ad8a2SEvan Bacon  // Exit with non-zero exit code if any of the dependencies are out of date.
87c94ad8a2SEvan Bacon  Log.exit(chalk.red('Found outdated dependencies'), 1);
88c94ad8a2SEvan Bacon}
89