1import { getConfig } from '@expo/config'; 2import * as PackageManager from '@expo/package-manager'; 3import chalk from 'chalk'; 4 5import * as Log from '../log'; 6import { getVersionedPackagesAsync } from '../start/doctor/dependencies/getVersionedPackages'; 7import { findUpProjectRootOrAssert } from '../utils/findUp'; 8import { checkPackagesAsync } from './checkPackages'; 9import { Options } from './resolveOptions'; 10 11export async function installAsync( 12 packages: string[], 13 options: Options & { projectRoot?: string }, 14 packageManagerArguments: string[] = [] 15) { 16 // Locate the project root based on the process current working directory. 17 // This enables users to run `npx expo install` from a subdirectory of the project. 18 const projectRoot = options.projectRoot ?? findUpProjectRootOrAssert(process.cwd()); 19 20 // Resolve the package manager used by the project, or based on the provided arguments. 21 const packageManager = PackageManager.createForProject(projectRoot, { 22 npm: options.npm, 23 yarn: options.yarn, 24 pnpm: options.pnpm, 25 log: Log.log, 26 silent: options.silent, 27 }); 28 29 if (options.check || options.fix) { 30 return await checkPackagesAsync(projectRoot, { 31 packages, 32 options, 33 packageManager, 34 packageManagerArguments, 35 }); 36 } 37 38 // Read the project Expo config without plugins. 39 const { exp } = getConfig(projectRoot, { 40 // Sometimes users will add a plugin to the config before installing the library, 41 // this wouldn't work unless we dangerously disable plugin serialization. 42 skipPlugins: true, 43 }); 44 45 // Resolve the versioned packages, then install them. 46 return installPackagesAsync(projectRoot, { 47 packageManager, 48 packages, 49 packageManagerArguments, 50 sdkVersion: exp.sdkVersion!, 51 }); 52} 53 54/** Version packages and install in a project. */ 55export async function installPackagesAsync( 56 projectRoot: string, 57 { 58 packages, 59 packageManager, 60 sdkVersion, 61 packageManagerArguments, 62 }: { 63 /** 64 * List of packages to version 65 * @example ['uuid', 'react-native-reanimated@latest'] 66 */ 67 packages: string[]; 68 /** Package manager to use when installing the versioned packages. */ 69 packageManager: 70 | PackageManager.NpmPackageManager 71 | PackageManager.YarnPackageManager 72 | PackageManager.PnpmPackageManager; 73 /** 74 * SDK to version `packages` for. 75 * @example '44.0.0' 76 */ 77 sdkVersion: string; 78 /** 79 * Extra parameters to pass to the `packageManager` when installing versioned packages. 80 * @example ['--no-save'] 81 */ 82 packageManagerArguments: string[]; 83 } 84): Promise<void> { 85 const versioning = await getVersionedPackagesAsync(projectRoot, { 86 packages, 87 // sdkVersion is always defined because we don't skipSDKVersionRequirement in getConfig. 88 sdkVersion, 89 }); 90 91 Log.log( 92 chalk`\u203A Installing ${ 93 versioning.messages.length ? versioning.messages.join(' and ') + ' ' : '' 94 }using {bold ${packageManager.name}}` 95 ); 96 97 await packageManager.addWithParametersAsync(versioning.packages, packageManagerArguments); 98 99 await applyPluginsAsync(projectRoot, versioning.packages); 100} 101 102/** 103 * A convenience feature for automatically applying Expo Config Plugins to the `app.json` after installing them. 104 * This should be dropped in favor of autolinking in the future. 105 */ 106async function applyPluginsAsync(projectRoot: string, packages: string[]) { 107 const { autoAddConfigPluginsAsync } = await import('./utils/autoAddConfigPlugins'); 108 109 try { 110 const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); 111 112 // Only auto add plugins if the plugins array is defined or if the project is using SDK +42. 113 await autoAddConfigPluginsAsync( 114 projectRoot, 115 exp, 116 // Split any possible NPM tags. i.e. `expo@latest` -> `expo` 117 packages.map((pkg) => pkg.split('@')[0]).filter(Boolean) 118 ); 119 } catch (error: any) { 120 // If we fail to apply plugins, the log a warning and continue. 121 if (error.isPluginError) { 122 Log.warn(`Skipping config plugin check: ` + error.message); 123 return; 124 } 125 // Any other error, rethrow. 126 throw error; 127 } 128} 129