18d3f3824SCedric van Puttenimport spawnAsync, { SpawnOptions, SpawnResult } from '@expo/spawn-async'; 28d3f3824SCedric van Puttenimport chalk from 'chalk'; 38d3f3824SCedric van Puttenimport { existsSync } from 'fs'; 48d3f3824SCedric van Puttenimport { Ora } from 'ora'; 58d3f3824SCedric van Puttenimport os from 'os'; 68d3f3824SCedric van Puttenimport path from 'path'; 78d3f3824SCedric van Putten 88d3f3824SCedric van Puttenimport { spawnSudoAsync } from '../utils/spawn'; 98d3f3824SCedric van Putten 108d3f3824SCedric van Puttenexport type CocoaPodsErrorCode = 'NON_INTERACTIVE' | 'NO_CLI' | 'COMMAND_FAILED'; 118d3f3824SCedric van Putten 128d3f3824SCedric van Puttenexport class CocoaPodsError extends Error { 138d3f3824SCedric van Putten readonly name = 'CocoaPodsError'; 148d3f3824SCedric van Putten readonly isPackageManagerError = true; 158d3f3824SCedric van Putten 16*8a424bebSJames Ide constructor( 17*8a424bebSJames Ide message: string, 18*8a424bebSJames Ide public code: CocoaPodsErrorCode, 19*8a424bebSJames Ide public cause?: Error 20*8a424bebSJames Ide ) { 218d3f3824SCedric van Putten super(cause ? `${message}\n└─ Cause: ${cause.message}` : message); 228d3f3824SCedric van Putten } 238d3f3824SCedric van Putten} 248d3f3824SCedric van Putten 258d3f3824SCedric van Puttenexport function extractMissingDependencyError(errorOutput: string): [string, string] | null { 268d3f3824SCedric van Putten // [!] Unable to find a specification for `expo-dev-menu-interface` depended upon by `expo-dev-launcher` 278d3f3824SCedric van Putten const results = errorOutput.match( 288d3f3824SCedric van Putten /Unable to find a specification for ['"`]([\w-_\d\s]+)['"`] depended upon by ['"`]([\w-_\d\s]+)['"`]/ 298d3f3824SCedric van Putten ); 308d3f3824SCedric van Putten if (results) { 318d3f3824SCedric van Putten return [results[1], results[2]]; 328d3f3824SCedric van Putten } 338d3f3824SCedric van Putten return null; 348d3f3824SCedric van Putten} 358d3f3824SCedric van Putten 368d3f3824SCedric van Puttenexport class CocoaPodsPackageManager { 378d3f3824SCedric van Putten options: SpawnOptions; 388d3f3824SCedric van Putten 398d3f3824SCedric van Putten private silent: boolean; 408d3f3824SCedric van Putten 418d3f3824SCedric van Putten static getPodProjectRoot(projectRoot: string): string | null { 428d3f3824SCedric van Putten if (CocoaPodsPackageManager.isUsingPods(projectRoot)) return projectRoot; 438d3f3824SCedric van Putten const iosProject = path.join(projectRoot, 'ios'); 448d3f3824SCedric van Putten if (CocoaPodsPackageManager.isUsingPods(iosProject)) return iosProject; 458d3f3824SCedric van Putten const macOsProject = path.join(projectRoot, 'macos'); 468d3f3824SCedric van Putten if (CocoaPodsPackageManager.isUsingPods(macOsProject)) return macOsProject; 478d3f3824SCedric van Putten return null; 488d3f3824SCedric van Putten } 498d3f3824SCedric van Putten 508d3f3824SCedric van Putten static isUsingPods(projectRoot: string): boolean { 518d3f3824SCedric van Putten return existsSync(path.join(projectRoot, 'Podfile')); 528d3f3824SCedric van Putten } 538d3f3824SCedric van Putten 548d3f3824SCedric van Putten static async gemInstallCLIAsync( 558d3f3824SCedric van Putten nonInteractive: boolean = false, 568d3f3824SCedric van Putten spawnOptions: SpawnOptions = { stdio: 'inherit' } 578d3f3824SCedric van Putten ): Promise<void> { 588d3f3824SCedric van Putten const options = ['install', 'cocoapods', '--no-document']; 598d3f3824SCedric van Putten 608d3f3824SCedric van Putten try { 618d3f3824SCedric van Putten // In case the user has run sudo before running the command we can properly install CocoaPods without prompting for an interaction. 628d3f3824SCedric van Putten await spawnAsync('gem', options, spawnOptions); 638d3f3824SCedric van Putten } catch (error: any) { 648d3f3824SCedric van Putten if (nonInteractive) { 658d3f3824SCedric van Putten throw new CocoaPodsError( 668d3f3824SCedric van Putten 'Failed to install CocoaPods CLI with gem (recommended)', 678d3f3824SCedric van Putten 'COMMAND_FAILED', 688d3f3824SCedric van Putten error 698d3f3824SCedric van Putten ); 708d3f3824SCedric van Putten } 718d3f3824SCedric van Putten // If the user doesn't have permission then we can prompt them to use sudo. 728d3f3824SCedric van Putten await spawnSudoAsync(['gem', ...options], spawnOptions); 738d3f3824SCedric van Putten } 748d3f3824SCedric van Putten } 758d3f3824SCedric van Putten 768d3f3824SCedric van Putten static async brewLinkCLIAsync(spawnOptions: SpawnOptions = { stdio: 'inherit' }): Promise<void> { 778d3f3824SCedric van Putten await spawnAsync('brew', ['link', 'cocoapods'], spawnOptions); 788d3f3824SCedric van Putten } 798d3f3824SCedric van Putten 808d3f3824SCedric van Putten static async brewInstallCLIAsync( 818d3f3824SCedric van Putten spawnOptions: SpawnOptions = { stdio: 'inherit' } 828d3f3824SCedric van Putten ): Promise<void> { 838d3f3824SCedric van Putten await spawnAsync('brew', ['install', 'cocoapods'], spawnOptions); 848d3f3824SCedric van Putten } 858d3f3824SCedric van Putten 868d3f3824SCedric van Putten static async installCLIAsync({ 878d3f3824SCedric van Putten nonInteractive = false, 888d3f3824SCedric van Putten spawnOptions = { stdio: 'inherit' }, 898d3f3824SCedric van Putten }: { 908d3f3824SCedric van Putten nonInteractive?: boolean; 918d3f3824SCedric van Putten spawnOptions?: SpawnOptions; 928d3f3824SCedric van Putten }): Promise<boolean> { 938d3f3824SCedric van Putten if (!spawnOptions) { 948d3f3824SCedric van Putten spawnOptions = { stdio: 'inherit' }; 958d3f3824SCedric van Putten } 968d3f3824SCedric van Putten const silent = !!spawnOptions.ignoreStdio; 978d3f3824SCedric van Putten 988d3f3824SCedric van Putten try { 998d3f3824SCedric van Putten !silent && console.log(`\u203A Attempting to install CocoaPods CLI with Gem`); 1008d3f3824SCedric van Putten await CocoaPodsPackageManager.gemInstallCLIAsync(nonInteractive, spawnOptions); 1018d3f3824SCedric van Putten !silent && console.log(`\u203A Successfully installed CocoaPods CLI with Gem`); 1028d3f3824SCedric van Putten return true; 1038d3f3824SCedric van Putten } catch (error: any) { 1048d3f3824SCedric van Putten if (!silent) { 1058d3f3824SCedric van Putten console.log(chalk.yellow(`\u203A Failed to install CocoaPods CLI with Gem`)); 1068d3f3824SCedric van Putten console.log(chalk.red(error.stderr ?? error.message)); 1078d3f3824SCedric van Putten console.log(`\u203A Attempting to install CocoaPods CLI with Homebrew`); 1088d3f3824SCedric van Putten } 1098d3f3824SCedric van Putten try { 1108d3f3824SCedric van Putten await CocoaPodsPackageManager.brewInstallCLIAsync(spawnOptions); 1118d3f3824SCedric van Putten if (!(await CocoaPodsPackageManager.isCLIInstalledAsync(spawnOptions))) { 1128d3f3824SCedric van Putten try { 1138d3f3824SCedric van Putten await CocoaPodsPackageManager.brewLinkCLIAsync(spawnOptions); 1148d3f3824SCedric van Putten // Still not available after linking? Bail out 1158d3f3824SCedric van Putten if (!(await CocoaPodsPackageManager.isCLIInstalledAsync(spawnOptions))) { 1168d3f3824SCedric van Putten throw new CocoaPodsError( 1178d3f3824SCedric van Putten 'CLI could not be installed automatically with gem or Homebrew, please install CocoaPods manually and try again', 1188d3f3824SCedric van Putten 'NO_CLI', 1198d3f3824SCedric van Putten error 1208d3f3824SCedric van Putten ); 1218d3f3824SCedric van Putten } 1228d3f3824SCedric van Putten } catch (error: any) { 1238d3f3824SCedric van Putten throw new CocoaPodsError( 1248d3f3824SCedric van Putten 'Homebrew installation appeared to succeed but CocoaPods CLI not found in PATH and unable to link.', 1258d3f3824SCedric van Putten 'NO_CLI', 1268d3f3824SCedric van Putten error 1278d3f3824SCedric van Putten ); 1288d3f3824SCedric van Putten } 1298d3f3824SCedric van Putten } 1308d3f3824SCedric van Putten 1318d3f3824SCedric van Putten !silent && console.log(`\u203A Successfully installed CocoaPods CLI with Homebrew`); 1328d3f3824SCedric van Putten return true; 1338d3f3824SCedric van Putten } catch (error: any) { 1348d3f3824SCedric van Putten !silent && 1358d3f3824SCedric van Putten console.warn( 1368d3f3824SCedric van Putten chalk.yellow( 1378d3f3824SCedric van Putten `\u203A Failed to install CocoaPods with Homebrew. Please install CocoaPods CLI manually and try again.` 1388d3f3824SCedric van Putten ) 1398d3f3824SCedric van Putten ); 1408d3f3824SCedric van Putten throw new CocoaPodsError( 1418d3f3824SCedric van Putten `Failed to install CocoaPods with Homebrew. Please install CocoaPods CLI manually and try again.`, 1428d3f3824SCedric van Putten 'NO_CLI', 1438d3f3824SCedric van Putten error 1448d3f3824SCedric van Putten ); 1458d3f3824SCedric van Putten } 1468d3f3824SCedric van Putten } 1478d3f3824SCedric van Putten } 1488d3f3824SCedric van Putten 1498d3f3824SCedric van Putten static isAvailable(projectRoot: string, silent: boolean): boolean { 1508d3f3824SCedric van Putten if (process.platform !== 'darwin') { 1518d3f3824SCedric van Putten !silent && console.log(chalk.red('CocoaPods is only supported on macOS machines')); 1528d3f3824SCedric van Putten return false; 1538d3f3824SCedric van Putten } 1548d3f3824SCedric van Putten if (!CocoaPodsPackageManager.isUsingPods(projectRoot)) { 1558d3f3824SCedric van Putten !silent && console.log(chalk.yellow('CocoaPods is not supported in this project')); 1568d3f3824SCedric van Putten return false; 1578d3f3824SCedric van Putten } 1588d3f3824SCedric van Putten return true; 1598d3f3824SCedric van Putten } 1608d3f3824SCedric van Putten 1618d3f3824SCedric van Putten static async isCLIInstalledAsync( 1628d3f3824SCedric van Putten spawnOptions: SpawnOptions = { stdio: 'inherit' } 1638d3f3824SCedric van Putten ): Promise<boolean> { 1648d3f3824SCedric van Putten try { 1658d3f3824SCedric van Putten await spawnAsync('pod', ['--version'], spawnOptions); 1668d3f3824SCedric van Putten return true; 1678d3f3824SCedric van Putten } catch { 1688d3f3824SCedric van Putten return false; 1698d3f3824SCedric van Putten } 1708d3f3824SCedric van Putten } 1718d3f3824SCedric van Putten 1728d3f3824SCedric van Putten constructor({ cwd, silent }: { cwd: string; silent?: boolean }) { 1738d3f3824SCedric van Putten this.silent = !!silent; 1748d3f3824SCedric van Putten this.options = { 1758d3f3824SCedric van Putten cwd, 1768d3f3824SCedric van Putten // We use pipe by default instead of inherit so that we can capture stderr/stdout and process it for errors. 1778d3f3824SCedric van Putten // Later we'll also pipe the stdout/stderr to the terminal when silent is false. 1788d3f3824SCedric van Putten stdio: 'pipe', 1798d3f3824SCedric van Putten }; 1808d3f3824SCedric van Putten } 1818d3f3824SCedric van Putten 1828d3f3824SCedric van Putten get name() { 1838d3f3824SCedric van Putten return 'CocoaPods'; 1848d3f3824SCedric van Putten } 1858d3f3824SCedric van Putten 1868d3f3824SCedric van Putten /** Runs `pod install` and attempts to automatically run known troubleshooting steps automatically. */ 1878d3f3824SCedric van Putten async installAsync({ spinner }: { spinner?: Ora } = {}) { 1888d3f3824SCedric van Putten await this._installAsync({ spinner }); 1898d3f3824SCedric van Putten } 1908d3f3824SCedric van Putten 1918d3f3824SCedric van Putten public isCLIInstalledAsync() { 1928d3f3824SCedric van Putten return CocoaPodsPackageManager.isCLIInstalledAsync(this.options); 1938d3f3824SCedric van Putten } 1948d3f3824SCedric van Putten 1958d3f3824SCedric van Putten public installCLIAsync() { 1968d3f3824SCedric van Putten return CocoaPodsPackageManager.installCLIAsync({ 1978d3f3824SCedric van Putten nonInteractive: true, 1988d3f3824SCedric van Putten spawnOptions: this.options, 1998d3f3824SCedric van Putten }); 2008d3f3824SCedric van Putten } 2018d3f3824SCedric van Putten 2028d3f3824SCedric van Putten async handleInstallErrorAsync({ 2038d3f3824SCedric van Putten error, 2048d3f3824SCedric van Putten shouldUpdate = true, 2058d3f3824SCedric van Putten updatedPackages = [], 2068d3f3824SCedric van Putten spinner, 2078d3f3824SCedric van Putten }: { 2088d3f3824SCedric van Putten error: any; 2098d3f3824SCedric van Putten spinner?: Ora; 2108d3f3824SCedric van Putten shouldUpdate?: boolean; 2118d3f3824SCedric van Putten updatedPackages?: string[]; 2128d3f3824SCedric van Putten }) { 2138d3f3824SCedric van Putten // Unknown errors are rethrown. 2148d3f3824SCedric van Putten if (!error.output) { 2158d3f3824SCedric van Putten throw error; 2168d3f3824SCedric van Putten } 2178d3f3824SCedric van Putten 2188d3f3824SCedric van Putten // To emulate a `pod install --repo-update` error, enter your `ios/Podfile.lock` and change one of `PODS` version numbers to some lower value. 2198d3f3824SCedric van Putten // const isPodRepoUpdateError = shouldPodRepoUpdate(output); 2208d3f3824SCedric van Putten if (!shouldUpdate) { 2218d3f3824SCedric van Putten // If we can't automatically fix the error, we'll just rethrow it with some known troubleshooting info. 2228d3f3824SCedric van Putten throw getImprovedPodInstallError(error, { 2238d3f3824SCedric van Putten cwd: this.options.cwd, 2248d3f3824SCedric van Putten }); 2258d3f3824SCedric van Putten } 2268d3f3824SCedric van Putten 2278d3f3824SCedric van Putten // Collect all of the spawn info. 2288d3f3824SCedric van Putten const errorOutput = error.output.join(os.EOL).trim(); 2298d3f3824SCedric van Putten 2308d3f3824SCedric van Putten // Extract useful information from the error message and push it to the spinner. 2318d3f3824SCedric van Putten const { updatePackage, shouldUpdateRepo } = getPodUpdateMessage(errorOutput); 2328d3f3824SCedric van Putten 2338d3f3824SCedric van Putten if (!updatePackage || updatedPackages.includes(updatePackage)) { 2348d3f3824SCedric van Putten // `pod install --repo-update`... 2358d3f3824SCedric van Putten // Attempt to install again but this time with install --repo-update enabled. 2368d3f3824SCedric van Putten return await this._installAsync({ 2378d3f3824SCedric van Putten spinner, 2388d3f3824SCedric van Putten shouldRepoUpdate: true, 2398d3f3824SCedric van Putten // Include a boolean to ensure pod install --repo-update isn't invoked in the unlikely case where the pods fail to update. 2408d3f3824SCedric van Putten shouldUpdate: false, 2418d3f3824SCedric van Putten updatedPackages, 2428d3f3824SCedric van Putten }); 2438d3f3824SCedric van Putten } 2448d3f3824SCedric van Putten // Store the package we should update to prevent a loop. 2458d3f3824SCedric van Putten updatedPackages.push(updatePackage); 2468d3f3824SCedric van Putten 2478d3f3824SCedric van Putten // If a single package is broken, we'll try to update it. 2488d3f3824SCedric van Putten // You can manually test this by changing a version number in your `Podfile.lock`. 2498d3f3824SCedric van Putten 2508d3f3824SCedric van Putten // Attempt `pod update <package> <--no-repo-update>` and then try again. 2518d3f3824SCedric van Putten return await this.runInstallTypeCommandAsync( 2528d3f3824SCedric van Putten ['update', updatePackage, shouldUpdateRepo ? '' : '--no-repo-update'].filter(Boolean), 2538d3f3824SCedric van Putten { 2548d3f3824SCedric van Putten formatWarning() { 2558d3f3824SCedric van Putten const updateMessage = `Failed to update ${chalk.bold( 2568d3f3824SCedric van Putten updatePackage 2578d3f3824SCedric van Putten )}. Attempting to update the repo instead.`; 2588d3f3824SCedric van Putten return updateMessage; 2598d3f3824SCedric van Putten }, 2608d3f3824SCedric van Putten spinner, 2618d3f3824SCedric van Putten updatedPackages, 2628d3f3824SCedric van Putten } 2638d3f3824SCedric van Putten ); 2648d3f3824SCedric van Putten // // If update succeeds, we'll try to install again (skipping `pod install --repo-update`). 2658d3f3824SCedric van Putten // return await this._installAsync({ 2668d3f3824SCedric van Putten // spinner, 2678d3f3824SCedric van Putten // shouldUpdate: false, 2688d3f3824SCedric van Putten // updatedPackages, 2698d3f3824SCedric van Putten // }); 2708d3f3824SCedric van Putten } 2718d3f3824SCedric van Putten 2728d3f3824SCedric van Putten private async _installAsync({ 2738d3f3824SCedric van Putten shouldRepoUpdate, 2748d3f3824SCedric van Putten ...props 2758d3f3824SCedric van Putten }: { 2768d3f3824SCedric van Putten spinner?: Ora; 2778d3f3824SCedric van Putten shouldUpdate?: boolean; 2788d3f3824SCedric van Putten updatedPackages?: string[]; 2798d3f3824SCedric van Putten shouldRepoUpdate?: boolean; 2808d3f3824SCedric van Putten } = {}): Promise<SpawnResult> { 2818d3f3824SCedric van Putten return await this.runInstallTypeCommandAsync( 2828d3f3824SCedric van Putten ['install', shouldRepoUpdate ? '--repo-update' : ''].filter(Boolean), 2838d3f3824SCedric van Putten { 2848d3f3824SCedric van Putten formatWarning(error: any) { 2858d3f3824SCedric van Putten // Extract useful information from the error message and push it to the spinner. 2868d3f3824SCedric van Putten return getPodRepoUpdateMessage(error.output.join(os.EOL).trim()).message; 2878d3f3824SCedric van Putten }, 2888d3f3824SCedric van Putten ...props, 2898d3f3824SCedric van Putten } 2908d3f3824SCedric van Putten ); 2918d3f3824SCedric van Putten } 2928d3f3824SCedric van Putten 2938d3f3824SCedric van Putten private async runInstallTypeCommandAsync( 2948d3f3824SCedric van Putten command: string[], 2958d3f3824SCedric van Putten { 2968d3f3824SCedric van Putten formatWarning, 2978d3f3824SCedric van Putten ...props 2988d3f3824SCedric van Putten }: { 2998d3f3824SCedric van Putten formatWarning?: (error: Error) => string; 3008d3f3824SCedric van Putten spinner?: Ora; 3018d3f3824SCedric van Putten shouldUpdate?: boolean; 3028d3f3824SCedric van Putten updatedPackages?: string[]; 3038d3f3824SCedric van Putten } = {} 3048d3f3824SCedric van Putten ): Promise<SpawnResult> { 3058d3f3824SCedric van Putten try { 3068d3f3824SCedric van Putten return await this._runAsync(command); 3078d3f3824SCedric van Putten } catch (error: any) { 3088d3f3824SCedric van Putten if (formatWarning) { 3098d3f3824SCedric van Putten const warning = formatWarning(error); 3108d3f3824SCedric van Putten if (props.spinner) { 3118d3f3824SCedric van Putten props.spinner.text = chalk.bold(warning); 3128d3f3824SCedric van Putten } 3138d3f3824SCedric van Putten if (!this.silent) { 3148d3f3824SCedric van Putten console.warn(chalk.yellow(warning)); 3158d3f3824SCedric van Putten } 3168d3f3824SCedric van Putten } 3178d3f3824SCedric van Putten 3188d3f3824SCedric van Putten return await this.handleInstallErrorAsync({ error, ...props }); 3198d3f3824SCedric van Putten } 3208d3f3824SCedric van Putten } 3218d3f3824SCedric van Putten 3228d3f3824SCedric van Putten async addWithParametersAsync(names: string[], parameters: string[]) { 3238d3f3824SCedric van Putten throw new Error('Unimplemented'); 3248d3f3824SCedric van Putten } 3258d3f3824SCedric van Putten 3268d3f3824SCedric van Putten addAsync(names: string[] = []) { 3278d3f3824SCedric van Putten throw new Error('Unimplemented'); 3288d3f3824SCedric van Putten } 3298d3f3824SCedric van Putten 3308d3f3824SCedric van Putten addDevAsync(names: string[] = []) { 3318d3f3824SCedric van Putten throw new Error('Unimplemented'); 3328d3f3824SCedric van Putten } 3338d3f3824SCedric van Putten 3348d3f3824SCedric van Putten addGlobalAsync(names: string[] = []) { 3358d3f3824SCedric van Putten throw new Error('Unimplemented'); 3368d3f3824SCedric van Putten } 3378d3f3824SCedric van Putten 3388d3f3824SCedric van Putten removeAsync(names: string[] = []) { 3398d3f3824SCedric van Putten throw new Error('Unimplemented'); 3408d3f3824SCedric van Putten } 3418d3f3824SCedric van Putten 3428d3f3824SCedric van Putten removeDevAsync(names: string[] = []) { 3438d3f3824SCedric van Putten throw new Error('Unimplemented'); 3448d3f3824SCedric van Putten } 3458d3f3824SCedric van Putten 3468d3f3824SCedric van Putten removeGlobalAsync(names: string[] = []) { 3478d3f3824SCedric van Putten throw new Error('Unimplemented'); 3488d3f3824SCedric van Putten } 3498d3f3824SCedric van Putten 3508d3f3824SCedric van Putten async versionAsync() { 3518d3f3824SCedric van Putten const { stdout } = await spawnAsync('pod', ['--version'], this.options); 3528d3f3824SCedric van Putten return stdout.trim(); 3538d3f3824SCedric van Putten } 3548d3f3824SCedric van Putten 3558d3f3824SCedric van Putten async configAsync(key: string): Promise<string> { 3568d3f3824SCedric van Putten throw new Error('Unimplemented'); 3578d3f3824SCedric van Putten } 3588d3f3824SCedric van Putten 3598d3f3824SCedric van Putten async removeLockfileAsync() { 3608d3f3824SCedric van Putten throw new Error('Unimplemented'); 3618d3f3824SCedric van Putten } 3628d3f3824SCedric van Putten 3638d3f3824SCedric van Putten async uninstallAsync() { 3648d3f3824SCedric van Putten throw new Error('Unimplemented'); 3658d3f3824SCedric van Putten } 3668d3f3824SCedric van Putten 3678d3f3824SCedric van Putten // Private 3688d3f3824SCedric van Putten private async podRepoUpdateAsync(): Promise<void> { 3698d3f3824SCedric van Putten try { 3708d3f3824SCedric van Putten await this._runAsync(['repo', 'update']); 3718d3f3824SCedric van Putten } catch (error: any) { 3728d3f3824SCedric van Putten error.message = error.message || (error.stderr ?? error.stdout); 3738d3f3824SCedric van Putten 3748d3f3824SCedric van Putten throw new CocoaPodsError( 3758d3f3824SCedric van Putten 'The command `pod install --repo-update` failed', 3768d3f3824SCedric van Putten 'COMMAND_FAILED', 3778d3f3824SCedric van Putten error 3788d3f3824SCedric van Putten ); 3798d3f3824SCedric van Putten } 3808d3f3824SCedric van Putten } 3818d3f3824SCedric van Putten 3828d3f3824SCedric van Putten // Exposed for testing 3838d3f3824SCedric van Putten async _runAsync(args: string[]): Promise<SpawnResult> { 3848d3f3824SCedric van Putten if (!this.silent) { 3858d3f3824SCedric van Putten console.log(`> pod ${args.join(' ')}`); 3868d3f3824SCedric van Putten } 3878d3f3824SCedric van Putten const promise = spawnAsync( 3888d3f3824SCedric van Putten 'pod', 3898d3f3824SCedric van Putten [ 3908d3f3824SCedric van Putten ...args, 3918d3f3824SCedric van Putten // Enables colors while collecting output. 3928d3f3824SCedric van Putten '--ansi', 3938d3f3824SCedric van Putten ], 3948d3f3824SCedric van Putten { 3958d3f3824SCedric van Putten // Add the cwd and other options to the spawn options. 3968d3f3824SCedric van Putten ...this.options, 3978d3f3824SCedric van Putten // We use pipe by default instead of inherit so that we can capture stderr/stdout and process it for errors. 3988d3f3824SCedric van Putten // This is particularly required for the `pod install --repo-update` error. 3998d3f3824SCedric van Putten 4008d3f3824SCedric van Putten // Later we'll also pipe the stdout/stderr to the terminal when silent is false, 4018d3f3824SCedric van Putten // currently this means we lose out on the ansi colors unless passing the `--ansi` flag to every command. 4028d3f3824SCedric van Putten stdio: 'pipe', 4038d3f3824SCedric van Putten } 4048d3f3824SCedric van Putten ); 4058d3f3824SCedric van Putten 4068d3f3824SCedric van Putten if (!this.silent) { 4078d3f3824SCedric van Putten // If not silent, pipe the stdout/stderr to the terminal. 4088d3f3824SCedric van Putten // We only do this when the `stdio` is set to `pipe` (collect the results for parsing), `inherit` won't contain `promise.child`. 4098d3f3824SCedric van Putten if (promise.child.stdout) { 4108d3f3824SCedric van Putten promise.child.stdout.pipe(process.stdout); 4118d3f3824SCedric van Putten } 4128d3f3824SCedric van Putten } 4138d3f3824SCedric van Putten 4148d3f3824SCedric van Putten return await promise; 4158d3f3824SCedric van Putten } 4168d3f3824SCedric van Putten} 4178d3f3824SCedric van Putten 4188d3f3824SCedric van Putten/** When pods are outdated, they'll throw an error informing you to run "pod install --repo-update" */ 4198d3f3824SCedric van Puttenfunction shouldPodRepoUpdate(errorOutput: string) { 4208d3f3824SCedric van Putten const output = errorOutput; 4218d3f3824SCedric van Putten const isPodRepoUpdateError = 4228d3f3824SCedric van Putten output.includes('pod repo update') || output.includes('--no-repo-update'); 4238d3f3824SCedric van Putten return isPodRepoUpdateError; 4248d3f3824SCedric van Putten} 4258d3f3824SCedric van Putten 4268d3f3824SCedric van Puttenexport function getPodUpdateMessage(output: string) { 4278d3f3824SCedric van Putten const props = output.match( 4288d3f3824SCedric van Putten /run ['"`]pod update ([\w-_\d/]+)( --no-repo-update)?['"`] to apply changes/ 4298d3f3824SCedric van Putten ); 4308d3f3824SCedric van Putten 4318d3f3824SCedric van Putten return { 4328d3f3824SCedric van Putten updatePackage: props?.[1] ?? null, 4338d3f3824SCedric van Putten shouldUpdateRepo: !props?.[2], 4348d3f3824SCedric van Putten }; 4358d3f3824SCedric van Putten} 4368d3f3824SCedric van Putten 4378d3f3824SCedric van Puttenexport function getPodRepoUpdateMessage(errorOutput: string) { 4388d3f3824SCedric van Putten const warningInfo = extractMissingDependencyError(errorOutput); 4398d3f3824SCedric van Putten const brokenPackage = getPodUpdateMessage(errorOutput); 4408d3f3824SCedric van Putten 4418d3f3824SCedric van Putten let message: string; 4428d3f3824SCedric van Putten if (warningInfo) { 4438d3f3824SCedric van Putten message = `Couldn't install: ${warningInfo[1]} » ${chalk.underline(warningInfo[0])}.`; 4448d3f3824SCedric van Putten } else if (brokenPackage?.updatePackage) { 4458d3f3824SCedric van Putten message = `Couldn't install: ${brokenPackage?.updatePackage}.`; 4468d3f3824SCedric van Putten } else { 4478d3f3824SCedric van Putten message = `Couldn't install Pods.`; 4488d3f3824SCedric van Putten } 4498d3f3824SCedric van Putten message += ` Updating the Pods project and trying again...`; 4508d3f3824SCedric van Putten return { message, ...brokenPackage }; 4518d3f3824SCedric van Putten} 4528d3f3824SCedric van Putten 4538d3f3824SCedric van Putten/** 4548d3f3824SCedric van Putten * Format the CocoaPods CLI install error. 4558d3f3824SCedric van Putten * 4568d3f3824SCedric van Putten * @param error Error from CocoaPods CLI `pod install` command. 4578d3f3824SCedric van Putten * @returns 4588d3f3824SCedric van Putten */ 4598d3f3824SCedric van Puttenexport function getImprovedPodInstallError( 4608d3f3824SCedric van Putten error: SpawnResult & Error, 4618d3f3824SCedric van Putten { cwd = process.cwd() }: Pick<SpawnOptions, 'cwd'> 4628d3f3824SCedric van Putten): Error { 4638d3f3824SCedric van Putten // Collect all of the spawn info. 4648d3f3824SCedric van Putten const errorOutput = error.output.join(os.EOL).trim(); 4658d3f3824SCedric van Putten 4668d3f3824SCedric van Putten if (error.stdout.match(/No [`'"]Podfile[`'"] found in the project directory/)) { 4678d3f3824SCedric van Putten // Ran pod install but no Podfile was found. 4688d3f3824SCedric van Putten error.message = `No Podfile found in directory: ${cwd}. Ensure CocoaPods is setup any try again.`; 4698d3f3824SCedric van Putten } else if (shouldPodRepoUpdate(errorOutput)) { 4708d3f3824SCedric van Putten // Ran pod install but the install --repo-update step failed. 4718d3f3824SCedric van Putten const warningInfo = extractMissingDependencyError(errorOutput); 4728d3f3824SCedric van Putten let reason: string; 4738d3f3824SCedric van Putten if (warningInfo) { 4748d3f3824SCedric van Putten reason = `Couldn't install: ${warningInfo[1]} » ${chalk.underline(warningInfo[0])}`; 4758d3f3824SCedric van Putten } else { 4768d3f3824SCedric van Putten reason = `This is often due to native package versions mismatching`; 4778d3f3824SCedric van Putten } 4788d3f3824SCedric van Putten 4798d3f3824SCedric van Putten // Attempt to provide a helpful message about the missing NPM dependency (containing a CocoaPod) since React Native 4808d3f3824SCedric van Putten // developers will almost always be using autolinking and not interacting with CocoaPods directly. 4818d3f3824SCedric van Putten let solution: string; 4828d3f3824SCedric van Putten if (warningInfo?.[0]) { 4838d3f3824SCedric van Putten // If the missing package is named `expo-dev-menu`, `react-native`, etc. then it might not be installed in the project. 4848d3f3824SCedric van Putten if (warningInfo[0].match(/^(?:@?expo|@?react)(-|\/)/)) { 4858d3f3824SCedric van Putten solution = `Ensure the node module "${warningInfo[0]}" is installed in your project, then run 'npx pod-install' to try again.`; 4868d3f3824SCedric van Putten } else { 4878d3f3824SCedric van Putten solution = `Ensure the CocoaPod "${warningInfo[0]}" is installed in your project, then run 'npx pod-install' to try again.`; 4888d3f3824SCedric van Putten } 4898d3f3824SCedric van Putten } else { 4908d3f3824SCedric van Putten // Brute force 4918d3f3824SCedric van Putten solution = `Try deleting the 'ios/Pods' folder or the 'ios/Podfile.lock' file and running 'npx pod-install' to resolve.`; 4928d3f3824SCedric van Putten } 4938d3f3824SCedric van Putten error.message = `${reason}. ${solution}`; 4948d3f3824SCedric van Putten 4958d3f3824SCedric van Putten // Attempt to provide the troubleshooting info from CocoaPods CLI at the bottom of the error message. 4968d3f3824SCedric van Putten if (error.stdout) { 4978d3f3824SCedric van Putten const cocoapodsDebugInfo = error.stdout.split(os.EOL); 4988d3f3824SCedric van Putten // The troubleshooting info starts with `[!]`, capture everything after that. 4998d3f3824SCedric van Putten const firstWarning = cocoapodsDebugInfo.findIndex((v) => v.startsWith('[!]')); 5008d3f3824SCedric van Putten if (firstWarning !== -1) { 5018d3f3824SCedric van Putten const warning = cocoapodsDebugInfo.slice(firstWarning).join(os.EOL); 5028d3f3824SCedric van Putten error.message += `\n\n${chalk.gray(warning)}`; 5038d3f3824SCedric van Putten } 5048d3f3824SCedric van Putten } 5058d3f3824SCedric van Putten return new CocoaPodsError( 5068d3f3824SCedric van Putten 'Command `pod install --repo-update` failed.', 5078d3f3824SCedric van Putten 'COMMAND_FAILED', 5088d3f3824SCedric van Putten error 5098d3f3824SCedric van Putten ); 5108d3f3824SCedric van Putten } else { 5118d3f3824SCedric van Putten let stderr: string | null = error.stderr.trim(); 5128d3f3824SCedric van Putten 5138d3f3824SCedric van Putten // CocoaPods CLI prints the useful error to stdout... 5148d3f3824SCedric van Putten const usefulError = error.stdout.match(/\[!\]\s((?:.|\n)*)/)?.[1]; 5158d3f3824SCedric van Putten 5168d3f3824SCedric van Putten // If there is a useful error message then prune the less useful info. 5178d3f3824SCedric van Putten if (usefulError) { 5188d3f3824SCedric van Putten // Delete unhelpful CocoaPods CLI error message. 5198d3f3824SCedric van Putten if (error.message?.match(/pod exited with non-zero code: 1/)) { 5208d3f3824SCedric van Putten error.message = ''; 5218d3f3824SCedric van Putten } 5228d3f3824SCedric van Putten stderr = null; 5238d3f3824SCedric van Putten } 5248d3f3824SCedric van Putten 5258d3f3824SCedric van Putten error.message = [usefulError, error.message, stderr].filter(Boolean).join('\n'); 5268d3f3824SCedric van Putten } 5278d3f3824SCedric van Putten 5288d3f3824SCedric van Putten return new CocoaPodsError('Command `pod install` failed.', 'COMMAND_FAILED', error); 5298d3f3824SCedric van Putten} 530