From c99dcf5efd7d20ab37ebb196b58b751ac007ca54 Mon Sep 17 00:00:00 2001 From: Lucas Bento Date: Wed, 18 Sep 2019 16:28:09 +0200 Subject: [PATCH] doctor: improve `ios-deploy` installation (#726) * Separate CocoaPods installation functions to be reused * Improve the UI of CocoaPods installation on `doctor` * Calculate the size of the cocoapods prompt question to remove it correctly * Refactor install method declaration when installing cocoapods * Use callbacks to clean `brewInstall` function * Clean up question size calculation on cocoapods * Move message removal functions to `common` in `doctor` * Add prompt message to show options for method of installation for `ios-deploy` * Fix unknown export * Minor refactor on `ora` type import * Remove unneeded `.toLowerCase()` * Remove unused `try...catch` block * Minor refactor * Log error message when installing `cocoapods` & `ios-deploy` * Revert `ios-deploy` installation command --- .../src/commands/doctor/checkInstallation.ts | 10 +- .../commands/doctor/healthchecks/cocoaPods.ts | 23 +--- .../commands/doctor/healthchecks/common.ts | 21 +++- .../commands/doctor/healthchecks/iosDeploy.ts | 113 ++++++++++++++---- packages/cli/src/tools/installPods.ts | 3 +- 5 files changed, 115 insertions(+), 55 deletions(-) diff --git a/packages/cli/src/commands/doctor/checkInstallation.ts b/packages/cli/src/commands/doctor/checkInstallation.ts index cbcd83866..bb65d0d56 100644 --- a/packages/cli/src/commands/doctor/checkInstallation.ts +++ b/packages/cli/src/commands/doctor/checkInstallation.ts @@ -1,10 +1,10 @@ import semver from 'semver'; import commandExists from 'command-exists'; -const PACKAGE_MANAGERS = { - YARN: 'YARN', - NPM: 'NPM', -}; +export enum PACKAGE_MANAGERS { + YARN = 'YARN', + NPM = 'NPM', +} const checkSoftwareInstalled = async (command: string) => { try { @@ -32,4 +32,4 @@ const doesSoftwareNeedToBeFixed = ({ ); }; -export {PACKAGE_MANAGERS, checkSoftwareInstalled, doesSoftwareNeedToBeFixed}; +export {checkSoftwareInstalled, doesSoftwareNeedToBeFixed}; diff --git a/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts b/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts index 406d6da2a..9d50c1f31 100644 --- a/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts +++ b/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts @@ -1,32 +1,15 @@ import execa from 'execa'; import chalk from 'chalk'; -import readline from 'readline'; -import wcwidth from 'wcwidth'; import {logger} from '@react-native-community/cli-tools'; import {checkSoftwareInstalled} from '../checkInstallation'; import { promptCocoaPodsInstallationQuestion, runSudo, } from '../../../tools/installPods'; +import {removeMessage} from './common'; import {brewInstall} from '../../../tools/brewInstall'; import {HealthCheckInterface} from '../types'; -function calculateQuestionSize(promptQuestion: string) { - return Math.max( - 1, - Math.ceil(wcwidth(promptQuestion) / (process.stdout.columns || 80)), - ); -} - -function clearQuestion(promptQuestion: string) { - readline.moveCursor( - process.stdout, - 0, - -calculateQuestionSize(promptQuestion), - ); - readline.clearScreenDown(process.stdout); -} - export default { label: 'CocoaPods', getDiagnostics: async () => ({ @@ -49,7 +32,7 @@ export default { const loaderSucceedMessage = `CocoaPods (installed with ${installMethodCapitalized})`; // Remove the prompt after the question of how to install CocoaPods is answered - clearQuestion(promptQuestion); + removeMessage(promptQuestion); if (installMethod === 'gem') { loader.start(loaderInstallationMessage); @@ -69,7 +52,7 @@ export default { return loader.succeed(loaderSucceedMessage); } catch (error) { loader.fail(); - logger.log(chalk.dim(`\n${error}`)); + logger.log(chalk.dim(`\n${error.message}`)); return logger.log( `An error occured while trying to install CocoaPods. Please try again manually: ${chalk.bold( diff --git a/packages/cli/src/commands/doctor/healthchecks/common.ts b/packages/cli/src/commands/doctor/healthchecks/common.ts index e90662857..69a23b44c 100644 --- a/packages/cli/src/commands/doctor/healthchecks/common.ts +++ b/packages/cli/src/commands/doctor/healthchecks/common.ts @@ -1,5 +1,8 @@ -import {logger} from '@react-native-community/cli-tools'; import chalk from 'chalk'; +import readline from 'readline'; +import wcwidth from 'wcwidth'; +import stripAnsi from 'strip-ansi'; +import {logger} from '@react-native-community/cli-tools'; // Space is necessary to keep correct ordering on screen const logMessage = (message: string) => logger.log(` ${message}`); @@ -34,4 +37,18 @@ const logManualInstallation = ({ } }; -export {logManualInstallation}; +// Calculate the size of a message on terminal based on rows +function calculateMessageSize(message: string) { + return Math.max( + 1, + Math.ceil(wcwidth(stripAnsi(message)) / (process.stdout.columns || 80)), + ); +} + +// Clear the message from the terminal +function removeMessage(message: string) { + readline.moveCursor(process.stdout, 0, -calculateMessageSize(message)); + readline.clearScreenDown(process.stdout); +} + +export {logManualInstallation, removeMessage}; diff --git a/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts b/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts index cfc2ddabf..877fda56a 100644 --- a/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts +++ b/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts @@ -1,58 +1,119 @@ import execa from 'execa'; +import chalk from 'chalk'; +// @ts-ignore untyped +import inquirer from 'inquirer'; +import {logger} from '@react-native-community/cli-tools'; import {checkSoftwareInstalled, PACKAGE_MANAGERS} from '../checkInstallation'; import {packageManager} from './packageManagers'; -import {logManualInstallation} from './common'; +import {logManualInstallation, removeMessage} from './common'; import {HealthCheckInterface} from '../types'; +import {Ora} from 'ora'; -const getInstallationCommand = () => { +const label = 'ios-deploy'; + +const installationWithYarn = 'yarn global add ios-deploy'; +const installationWithNpm = 'npm install ios-deploy --global'; + +const identifyInstallationCommand = () => { if (packageManager === PACKAGE_MANAGERS.YARN) { - return 'yarn global add ios-deploy'; + return installationWithYarn; } if (packageManager === PACKAGE_MANAGERS.NPM) { - return 'npm install ios-deploy --global'; + return installationWithNpm; } return undefined; }; +const installLibrary = async ({ + installationCommand, + packageManagerToUse, + loader, +}: { + installationCommand: string; + packageManagerToUse: 'yarn' | 'npm'; + loader: Ora; +}) => { + try { + loader.start(`${label} (installing with ${packageManagerToUse})`); + + const installationCommandArgs = installationCommand.split(' '); + + await execa(installationCommandArgs[0], installationCommandArgs.splice(1)); + + loader.succeed(`${label} (installed with ${packageManagerToUse})`); + } catch (error) { + loader.fail(); + logger.log(chalk.dim(`\n${error.message}`)); + + logManualInstallation({ + healthcheck: 'ios-deploy', + command: installationCommand, + }); + } +}; + export default { - label: 'ios-deploy', + label, isRequired: false, getDiagnostics: async () => ({ needsToBeFixed: await checkSoftwareInstalled('ios-deploy'), }), runAutomaticFix: async ({loader}) => { - const installationCommand = getInstallationCommand(); + loader.stop(); + + const installationCommand = identifyInstallationCommand(); // This means that we couldn't "guess" the package manager if (installationCommand === undefined) { - loader.fail(); + const promptQuestion = `ios-deploy needs to be installed either by ${chalk.bold( + 'yarn', + )} ${chalk.reset('or')} ${chalk.bold( + 'npm', + )} ${chalk.reset()}, which one do you want to use?`; + const installWithYarn = 'yarn'; + const installWithNpm = 'npm'; + const skipInstallation = 'Skip installation'; - // Then we just print out the URL that the user can head to download the library - logManualInstallation({ - healthcheck: 'ios-deploy', - url: 'https://github.com/ios-control/ios-deploy#readme', - }); - return; - } + const {chosenPackageManager} = await inquirer.prompt([ + { + type: 'list', + name: 'chosenPackageManager', + message: promptQuestion, + choices: [installWithYarn, installWithNpm, skipInstallation], + }, + ]); - try { - const installationCommandArgs = installationCommand.split(' '); + removeMessage(`? ${promptQuestion} ${chosenPackageManager}`); - await execa( - installationCommandArgs[0], - installationCommandArgs.splice(1), - ); + if (chosenPackageManager === skipInstallation) { + loader.fail(); - loader.succeed(); - } catch (_error) { - loader.fail(); + // Then we just print out the URL that the user can head to download the library + logManualInstallation({ + healthcheck: 'ios-deploy', + url: 'https://github.com/ios-control/ios-deploy#readme', + }); - logManualInstallation({ - healthcheck: 'ios-deploy', - command: installationCommand, + return; + } + + const shouldInstallWithYarn = chosenPackageManager === installWithYarn; + + return installLibrary({ + installationCommand: shouldInstallWithYarn + ? installationWithYarn + : installationWithNpm, + loader, + packageManagerToUse: chosenPackageManager, }); } + + return installLibrary({ + installationCommand, + packageManagerToUse: packageManager!.toLowerCase() as 'yarn' | 'npm', + loader, + }); }, } as HealthCheckInterface; diff --git a/packages/cli/src/tools/installPods.ts b/packages/cli/src/tools/installPods.ts index e2012d123..cae6b30cd 100644 --- a/packages/cli/src/tools/installPods.ts +++ b/packages/cli/src/tools/installPods.ts @@ -4,7 +4,6 @@ import chalk from 'chalk'; import ora from 'ora'; // @ts-ignore untyped import inquirer from 'inquirer'; -import stripAnsi from 'strip-ansi'; import {logger} from '@react-native-community/cli-tools'; import {NoopLoader} from './loader'; // @ts-ignore untyped @@ -74,7 +73,7 @@ async function promptCocoaPodsInstallationQuestion(): Promise< return { installMethod: shouldInstallWithGem ? 'gem' : 'homebrew', // This is used for removing the message in `doctor` after it's answered - promptQuestion: `? ${stripAnsi(promptQuestion)} ${ + promptQuestion: `? ${promptQuestion} ${ shouldInstallWithGem ? installWithGem : installWithHomebrew }`, };