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