109bb6093SEvan Baconimport { getConfig } from '@expo/config'; 209bb6093SEvan Baconimport * as PackageManager from '@expo/package-manager'; 36caf5755SEvan Baconimport chalk from 'chalk'; 409bb6093SEvan Bacon 58a424bebSJames Ideimport { checkPackagesAsync } from './checkPackages'; 68a424bebSJames Ideimport { Options } from './resolveOptions'; 709bb6093SEvan Baconimport * as Log from '../log'; 88d3f3824SCedric van Puttenimport { 98d3f3824SCedric van Putten getOperationLog, 108d3f3824SCedric van Putten getVersionedPackagesAsync, 118d3f3824SCedric van Putten} from '../start/doctor/dependencies/getVersionedPackages'; 128d3f3824SCedric van Puttenimport { getVersionedDependenciesAsync } from '../start/doctor/dependencies/validateDependenciesVersions'; 138d3f3824SCedric van Puttenimport { groupBy } from '../utils/array'; 1409bb6093SEvan Baconimport { findUpProjectRootOrAssert } from '../utils/findUp'; 156fb32dddSKeith Kurakimport { learnMore } from '../utils/link'; 162dd43328SEvan Baconimport { setNodeEnv } from '../utils/nodeEnv'; 176fb32dddSKeith Kurakimport { joinWithCommasAnd } from '../utils/strings'; 1809bb6093SEvan Bacon 1909bb6093SEvan Baconexport async function installAsync( 2009bb6093SEvan Bacon packages: string[], 217b57a441SEvan Bacon options: Options & { projectRoot?: string }, 2209bb6093SEvan Bacon packageManagerArguments: string[] = [] 2309bb6093SEvan Bacon) { 242dd43328SEvan Bacon setNodeEnv('development'); 2509bb6093SEvan Bacon // Locate the project root based on the process current working directory. 2609bb6093SEvan Bacon // This enables users to run `npx expo install` from a subdirectory of the project. 277b57a441SEvan Bacon const projectRoot = options.projectRoot ?? findUpProjectRootOrAssert(process.cwd()); 286a750d06SEvan Bacon require('@expo/env').load(projectRoot); 2909bb6093SEvan Bacon 3009bb6093SEvan Bacon // Resolve the package manager used by the project, or based on the provided arguments. 3109bb6093SEvan Bacon const packageManager = PackageManager.createForProject(projectRoot, { 3209bb6093SEvan Bacon npm: options.npm, 3309bb6093SEvan Bacon yarn: options.yarn, 349b1b5ec6SEvan Bacon bun: options.bun, 356caf5755SEvan Bacon pnpm: options.pnpm, 366caf5755SEvan Bacon silent: options.silent, 378d3f3824SCedric van Putten log: Log.log, 3809bb6093SEvan Bacon }); 3909bb6093SEvan Bacon 40c94ad8a2SEvan Bacon if (options.check || options.fix) { 41c94ad8a2SEvan Bacon return await checkPackagesAsync(projectRoot, { 42c94ad8a2SEvan Bacon packages, 43c94ad8a2SEvan Bacon options, 44c94ad8a2SEvan Bacon packageManager, 45c94ad8a2SEvan Bacon packageManagerArguments, 46c94ad8a2SEvan Bacon }); 47c94ad8a2SEvan Bacon } 48c94ad8a2SEvan Bacon 4909bb6093SEvan Bacon // Read the project Expo config without plugins. 5009bb6093SEvan Bacon const { exp } = getConfig(projectRoot, { 5109bb6093SEvan Bacon // Sometimes users will add a plugin to the config before installing the library, 5209bb6093SEvan Bacon // this wouldn't work unless we dangerously disable plugin serialization. 5309bb6093SEvan Bacon skipPlugins: true, 5409bb6093SEvan Bacon }); 5509bb6093SEvan Bacon 5609bb6093SEvan Bacon // Resolve the versioned packages, then install them. 5709bb6093SEvan Bacon return installPackagesAsync(projectRoot, { 5809bb6093SEvan Bacon packageManager, 5909bb6093SEvan Bacon packages, 6009bb6093SEvan Bacon packageManagerArguments, 6109bb6093SEvan Bacon sdkVersion: exp.sdkVersion!, 6209bb6093SEvan Bacon }); 6309bb6093SEvan Bacon} 6409bb6093SEvan Bacon 6509bb6093SEvan Bacon/** Version packages and install in a project. */ 6609bb6093SEvan Baconexport async function installPackagesAsync( 6709bb6093SEvan Bacon projectRoot: string, 6809bb6093SEvan Bacon { 6909bb6093SEvan Bacon packages, 7009bb6093SEvan Bacon packageManager, 7109bb6093SEvan Bacon sdkVersion, 7209bb6093SEvan Bacon packageManagerArguments, 7309bb6093SEvan Bacon }: { 7409bb6093SEvan Bacon /** 758d3f3824SCedric van Putten * List of packages to version, grouped by the type of dependency. 7609bb6093SEvan Bacon * @example ['uuid', 'react-native-reanimated@latest'] 7709bb6093SEvan Bacon */ 7809bb6093SEvan Bacon packages: string[]; 7909bb6093SEvan Bacon /** Package manager to use when installing the versioned packages. */ 808d3f3824SCedric van Putten packageManager: PackageManager.NodePackageManager; 8109bb6093SEvan Bacon /** 8209bb6093SEvan Bacon * SDK to version `packages` for. 8309bb6093SEvan Bacon * @example '44.0.0' 8409bb6093SEvan Bacon */ 8509bb6093SEvan Bacon sdkVersion: string; 8609bb6093SEvan Bacon /** 8709bb6093SEvan Bacon * Extra parameters to pass to the `packageManager` when installing versioned packages. 8809bb6093SEvan Bacon * @example ['--no-save'] 8909bb6093SEvan Bacon */ 9009bb6093SEvan Bacon packageManagerArguments: string[]; 9109bb6093SEvan Bacon } 9209bb6093SEvan Bacon): Promise<void> { 936fb32dddSKeith Kurak // Read the project Expo config without plugins. 946fb32dddSKeith Kurak const { pkg } = getConfig(projectRoot, { 956fb32dddSKeith Kurak // Sometimes users will add a plugin to the config before installing the library, 966fb32dddSKeith Kurak // this wouldn't work unless we dangerously disable plugin serialization. 976fb32dddSKeith Kurak skipPlugins: true, 986fb32dddSKeith Kurak }); 996fb32dddSKeith Kurak 1006fb32dddSKeith Kurak //assertNotInstallingExcludedPackages(projectRoot, packages, pkg); 1016fb32dddSKeith Kurak 10209bb6093SEvan Bacon const versioning = await getVersionedPackagesAsync(projectRoot, { 10309bb6093SEvan Bacon packages, 10409bb6093SEvan Bacon // sdkVersion is always defined because we don't skipSDKVersionRequirement in getConfig. 10509bb6093SEvan Bacon sdkVersion, 1066fb32dddSKeith Kurak pkg, 10709bb6093SEvan Bacon }); 10809bb6093SEvan Bacon 1096caf5755SEvan Bacon Log.log( 1106caf5755SEvan Bacon chalk`\u203A Installing ${ 1116caf5755SEvan Bacon versioning.messages.length ? versioning.messages.join(' and ') + ' ' : '' 1126caf5755SEvan Bacon }using {bold ${packageManager.name}}` 1136caf5755SEvan Bacon ); 11409bb6093SEvan Bacon 1156fb32dddSKeith Kurak if (versioning.excludedNativeModules.length) { 1166fb32dddSKeith Kurak Log.log( 1176fb32dddSKeith Kurak chalk`\u203A Using latest version instead of ${joinWithCommasAnd( 1186fb32dddSKeith Kurak versioning.excludedNativeModules.map( 1196fb32dddSKeith Kurak ({ bundledNativeVersion, name }) => `${bundledNativeVersion} for ${name}` 1206fb32dddSKeith Kurak ) 1216fb32dddSKeith Kurak )} because ${ 1226fb32dddSKeith Kurak versioning.excludedNativeModules.length > 1 ? 'they are' : 'it is' 1236fb32dddSKeith Kurak } listed in {bold expo.install.exclude} in package.json. ${learnMore( 1246fb32dddSKeith Kurak 'https://expo.dev/more/expo-cli/#configuring-dependency-validation' 1256fb32dddSKeith Kurak )}` 1266fb32dddSKeith Kurak ); 1276fb32dddSKeith Kurak } 1286fb32dddSKeith Kurak 1298d3f3824SCedric van Putten await packageManager.addAsync([...packageManagerArguments, ...versioning.packages]); 13009bb6093SEvan Bacon 13109bb6093SEvan Bacon await applyPluginsAsync(projectRoot, versioning.packages); 13209bb6093SEvan Bacon} 13309bb6093SEvan Bacon 1348d3f3824SCedric van Puttenexport async function fixPackagesAsync( 1358d3f3824SCedric van Putten projectRoot: string, 1368d3f3824SCedric van Putten { 1378d3f3824SCedric van Putten packages, 1388d3f3824SCedric van Putten packageManager, 1398d3f3824SCedric van Putten sdkVersion, 1408d3f3824SCedric van Putten packageManagerArguments, 1418d3f3824SCedric van Putten }: { 1428d3f3824SCedric van Putten packages: Awaited<ReturnType<typeof getVersionedDependenciesAsync>>; 1438d3f3824SCedric van Putten /** Package manager to use when installing the versioned packages. */ 1448d3f3824SCedric van Putten packageManager: PackageManager.NodePackageManager; 1458d3f3824SCedric van Putten /** 1468d3f3824SCedric van Putten * SDK to version `packages` for. 1478d3f3824SCedric van Putten * @example '44.0.0' 1488d3f3824SCedric van Putten */ 1498d3f3824SCedric van Putten sdkVersion: string; 1508d3f3824SCedric van Putten /** 1518d3f3824SCedric van Putten * Extra parameters to pass to the `packageManager` when installing versioned packages. 1528d3f3824SCedric van Putten * @example ['--no-save'] 1538d3f3824SCedric van Putten */ 1548d3f3824SCedric van Putten packageManagerArguments: string[]; 1558d3f3824SCedric van Putten } 1568d3f3824SCedric van Putten): Promise<void> { 1578d3f3824SCedric van Putten if (!packages.length) { 1588d3f3824SCedric van Putten return; 1598d3f3824SCedric van Putten } 1608d3f3824SCedric van Putten 1618d3f3824SCedric van Putten const { dependencies = [], devDependencies = [] } = groupBy(packages, (dep) => dep.packageType); 1628d3f3824SCedric van Putten const versioningMessages = getOperationLog({ 1638d3f3824SCedric van Putten othersCount: 0, // All fixable packages are versioned 1648d3f3824SCedric van Putten nativeModulesCount: packages.length, 1658d3f3824SCedric van Putten sdkVersion, 1668d3f3824SCedric van Putten }); 1678d3f3824SCedric van Putten 1688d3f3824SCedric van Putten Log.log( 1698d3f3824SCedric van Putten chalk`\u203A Installing ${ 1708d3f3824SCedric van Putten versioningMessages.length ? versioningMessages.join(' and ') + ' ' : '' 1718d3f3824SCedric van Putten }using {bold ${packageManager.name}}` 1728d3f3824SCedric van Putten ); 1738d3f3824SCedric van Putten 1748d3f3824SCedric van Putten if (dependencies.length) { 1758d3f3824SCedric van Putten const versionedPackages = dependencies.map( 1768d3f3824SCedric van Putten (dep) => `${dep.packageName}@${dep.expectedVersionOrRange}` 1778d3f3824SCedric van Putten ); 1788d3f3824SCedric van Putten 1798d3f3824SCedric van Putten await packageManager.addAsync([...packageManagerArguments, ...versionedPackages]); 1808d3f3824SCedric van Putten 1818d3f3824SCedric van Putten await applyPluginsAsync(projectRoot, versionedPackages); 1828d3f3824SCedric van Putten } 1838d3f3824SCedric van Putten 1848d3f3824SCedric van Putten if (devDependencies.length) { 1858d3f3824SCedric van Putten await packageManager.addDevAsync([ 1868d3f3824SCedric van Putten ...packageManagerArguments, 1878d3f3824SCedric van Putten ...devDependencies.map((dep) => `${dep.packageName}@${dep.expectedVersionOrRange}`), 1888d3f3824SCedric van Putten ]); 1898d3f3824SCedric van Putten } 1908d3f3824SCedric van Putten} 1918d3f3824SCedric van Putten 19209bb6093SEvan Bacon/** 19309bb6093SEvan Bacon * A convenience feature for automatically applying Expo Config Plugins to the `app.json` after installing them. 19409bb6093SEvan Bacon * This should be dropped in favor of autolinking in the future. 19509bb6093SEvan Bacon */ 19609bb6093SEvan Baconasync function applyPluginsAsync(projectRoot: string, packages: string[]) { 197*1a3a1db5SEvan Bacon const { autoAddConfigPluginsAsync } = await import('./utils/autoAddConfigPlugins.js'); 19809bb6093SEvan Bacon 19909bb6093SEvan Bacon try { 2000f1a896eSKim Brandwijk const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true }); 20109bb6093SEvan Bacon 20209bb6093SEvan Bacon // Only auto add plugins if the plugins array is defined or if the project is using SDK +42. 20309bb6093SEvan Bacon await autoAddConfigPluginsAsync( 20409bb6093SEvan Bacon projectRoot, 20509bb6093SEvan Bacon exp, 20609bb6093SEvan Bacon // Split any possible NPM tags. i.e. `expo@latest` -> `expo` 20709bb6093SEvan Bacon packages.map((pkg) => pkg.split('@')[0]).filter(Boolean) 20809bb6093SEvan Bacon ); 20909bb6093SEvan Bacon } catch (error: any) { 21009bb6093SEvan Bacon // If we fail to apply plugins, the log a warning and continue. 21109bb6093SEvan Bacon if (error.isPluginError) { 21209bb6093SEvan Bacon Log.warn(`Skipping config plugin check: ` + error.message); 21309bb6093SEvan Bacon return; 21409bb6093SEvan Bacon } 21509bb6093SEvan Bacon // Any other error, rethrow. 21609bb6093SEvan Bacon throw error; 21709bb6093SEvan Bacon } 21809bb6093SEvan Bacon} 219