1import { getPackageJson, PackageJSONConfig } from '@expo/config'; 2import JsonFile from '@expo/json-file'; 3import * as PackageManager from '@expo/package-manager'; 4import chalk from 'chalk'; 5import fs from 'fs'; 6import path from 'path'; 7 8import * as Log from '../log'; 9import { hashForDependencyMap } from '../prebuild/updatePackageJson'; 10import { ensureDirectoryAsync } from './dir'; 11import { EXPO_DEBUG } from './env'; 12import { logNewSection } from './ora'; 13 14const PROJECT_PREBUILD_SETTINGS = '.expo/prebuild'; 15const CACHED_PACKAGE_JSON = 'cached-packages.json'; 16 17function getTempPrebuildFolder(projectRoot: string) { 18 return path.join(projectRoot, PROJECT_PREBUILD_SETTINGS); 19} 20 21type PackageChecksums = { 22 dependencies: string; 23 devDependencies: string; 24}; 25 26function hasNewDependenciesSinceLastBuild(projectRoot: string, packageChecksums: PackageChecksums) { 27 // TODO: Maybe comparing lock files would be better... 28 const templateDirectory = getTempPrebuildFolder(projectRoot); 29 const tempPkgJsonPath = path.join(templateDirectory, CACHED_PACKAGE_JSON); 30 if (!fs.existsSync(tempPkgJsonPath)) { 31 return true; 32 } 33 const { dependencies, devDependencies } = JsonFile.read(tempPkgJsonPath); 34 // Only change the dependencies if the normalized hash changes, this helps to reduce meaningless changes. 35 const hasNewDependencies = packageChecksums.dependencies !== dependencies; 36 const hasNewDevDependencies = packageChecksums.devDependencies !== devDependencies; 37 38 return hasNewDependencies || hasNewDevDependencies; 39} 40 41function createPackageChecksums(pkg: PackageJSONConfig): PackageChecksums { 42 return { 43 dependencies: hashForDependencyMap(pkg.dependencies || {}), 44 devDependencies: hashForDependencyMap(pkg.devDependencies || {}), 45 }; 46} 47 48export async function hasPackageJsonDependencyListChangedAsync(projectRoot: string) { 49 const pkg = getPackageJson(projectRoot); 50 51 const packages = createPackageChecksums(pkg); 52 const hasNewDependencies = hasNewDependenciesSinceLastBuild(projectRoot, packages); 53 54 // Cache package.json 55 await ensureDirectoryAsync(getTempPrebuildFolder(projectRoot)); 56 const templateDirectory = path.join(getTempPrebuildFolder(projectRoot), CACHED_PACKAGE_JSON); 57 await JsonFile.writeAsync(templateDirectory, packages); 58 59 return hasNewDependencies; 60} 61 62export async function installCocoaPodsAsync(projectRoot: string) { 63 let step = logNewSection('Installing CocoaPods...'); 64 if (process.platform !== 'darwin') { 65 step.succeed('Skipped installing CocoaPods because operating system is not on macOS.'); 66 return false; 67 } 68 69 const packageManager = new PackageManager.CocoaPodsPackageManager({ 70 cwd: path.join(projectRoot, 'ios'), 71 silent: !EXPO_DEBUG, 72 }); 73 74 if (!(await packageManager.isCLIInstalledAsync())) { 75 try { 76 // prompt user -- do you want to install cocoapods right now? 77 step.text = 'CocoaPods CLI not found in your PATH, installing it now.'; 78 step.stopAndPersist(); 79 await PackageManager.CocoaPodsPackageManager.installCLIAsync({ 80 nonInteractive: true, 81 spawnOptions: { 82 ...packageManager.options, 83 // Don't silence this part 84 stdio: ['inherit', 'inherit', 'pipe'], 85 }, 86 }); 87 step.succeed('Installed CocoaPods CLI.'); 88 step = logNewSection('Running `pod install` in the `ios` directory.'); 89 } catch (error: any) { 90 step.stopAndPersist({ 91 symbol: '⚠️ ', 92 text: chalk.red('Unable to install the CocoaPods CLI.'), 93 }); 94 if (error instanceof PackageManager.CocoaPodsError) { 95 Log.log(error.message); 96 } else { 97 Log.log(`Unknown error: ${error.message}`); 98 } 99 return false; 100 } 101 } 102 103 try { 104 await packageManager.installAsync({ spinner: step }); 105 // Create cached list for later 106 await hasPackageJsonDependencyListChangedAsync(projectRoot).catch(() => null); 107 step.succeed('Installed pods and initialized Xcode workspace.'); 108 return true; 109 } catch (error: any) { 110 step.stopAndPersist({ 111 symbol: '⚠️ ', 112 text: chalk.red('Something went wrong running `pod install` in the `ios` directory.'), 113 }); 114 if (error instanceof PackageManager.CocoaPodsError) { 115 Log.log(error.message); 116 } else { 117 Log.log(`Unknown error: ${error.message}`); 118 } 119 return false; 120 } 121} 122