1import npmPackageArg from 'npm-package-arg'; 2 3import { getVersionsAsync, SDKVersion } from '../../../api/getVersions'; 4import { getVersionedNativeModulesAsync } from './bundledNativeModules'; 5 6const debug = require('debug')( 7 'expo:doctor:dependencies:getVersionedPackages' 8) as typeof console.log; 9 10export type DependencyList = Record<string, string>; 11 12/** Adds `react-dom`, `react`, and `react-native` to the list of known package versions (`relatedPackages`) */ 13function normalizeSdkVersionObject(version?: SDKVersion): Record<string, string> { 14 if (!version) { 15 return {}; 16 } 17 const { relatedPackages, facebookReactVersion, facebookReactNativeVersion } = version; 18 19 const reactVersion = facebookReactVersion 20 ? { 21 react: facebookReactVersion, 22 'react-dom': facebookReactVersion, 23 } 24 : undefined; 25 26 return { 27 ...relatedPackages, 28 ...reactVersion, 29 'react-native': facebookReactNativeVersion, 30 }; 31} 32 33/** Get the known versions for a given SDK, combines all sources. */ 34export async function getCombinedKnownVersionsAsync({ 35 projectRoot, 36 sdkVersion, 37 skipCache, 38}: { 39 projectRoot: string; 40 sdkVersion?: string; 41 skipCache?: boolean; 42}) { 43 const bundledNativeModules = sdkVersion 44 ? await getVersionedNativeModulesAsync(projectRoot, sdkVersion) 45 : {}; 46 const versionsForSdk = await getRemoteVersionsForSdkAsync({ sdkVersion, skipCache }); 47 return { 48 ...versionsForSdk, 49 ...bundledNativeModules, 50 }; 51} 52 53/** @returns a key/value list of known dependencies and their version (including range). */ 54export async function getRemoteVersionsForSdkAsync({ 55 sdkVersion, 56 skipCache, 57}: { sdkVersion?: string; skipCache?: boolean } = {}): Promise<DependencyList> { 58 const { sdkVersions } = await getVersionsAsync({ skipCache }); 59 60 // We only want versioned dependencies so skip if they cannot be found. 61 if (!sdkVersion || !(sdkVersion in sdkVersions)) { 62 debug( 63 `Skipping versioned dependencies because the SDK version is not found. (sdkVersion: ${sdkVersion}, available: ${Object.keys( 64 sdkVersions 65 ).join(', ')})` 66 ); 67 return {}; 68 } 69 70 const version = sdkVersions[sdkVersion as keyof typeof sdkVersions] as unknown as SDKVersion; 71 72 return normalizeSdkVersionObject(version); 73} 74 75/** 76 * Versions a list of `packages` against a given `sdkVersion` based on local and remote versioning resources. 77 * 78 * @param projectRoot 79 * @param param1 80 * @returns 81 */ 82export async function getVersionedPackagesAsync( 83 projectRoot: string, 84 { 85 packages, 86 sdkVersion, 87 }: { 88 /** List of npm packages to process. */ 89 packages: string[]; 90 /** Target SDK Version number to version the `packages` for. */ 91 sdkVersion: string; 92 } 93): Promise<{ packages: string[]; messages: string[] }> { 94 const versionsForSdk = await getCombinedKnownVersionsAsync({ 95 projectRoot, 96 sdkVersion, 97 skipCache: true, 98 }); 99 100 let nativeModulesCount = 0; 101 let othersCount = 0; 102 103 const versionedPackages = packages.map((arg) => { 104 const { name, type, raw } = npmPackageArg(arg); 105 106 if (['tag', 'version', 'range'].includes(type) && name && versionsForSdk[name]) { 107 // Unimodule packages from npm registry are modified to use the bundled version. 108 // Some packages have the recommended version listed in https://exp.host/--/api/v2/versions. 109 nativeModulesCount++; 110 return `${name}@${versionsForSdk[name]}`; 111 } else { 112 // Other packages are passed through unmodified. 113 othersCount++; 114 return raw; 115 } 116 }); 117 118 const messages = getOperationLog({ 119 othersCount, 120 nativeModulesCount, 121 sdkVersion, 122 }); 123 124 return { 125 packages: versionedPackages, 126 messages, 127 }; 128} 129 130/** Craft a set of messages regarding the install operations. */ 131export function getOperationLog({ 132 nativeModulesCount, 133 sdkVersion, 134 othersCount, 135}: { 136 nativeModulesCount: number; 137 othersCount: number; 138 sdkVersion: string; 139}): string[] { 140 return [ 141 nativeModulesCount > 0 && 142 `${nativeModulesCount} SDK ${sdkVersion} compatible native ${ 143 nativeModulesCount === 1 ? 'module' : 'modules' 144 }`, 145 othersCount > 0 && `${othersCount} other ${othersCount === 1 ? 'package' : 'packages'}`, 146 ].filter(Boolean) as string[]; 147} 148