Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
"semver": "^6.3.0",
"serve-static": "^1.13.1",
"shell-quote": "1.6.1",
"strip-ansi": "^5.2.0",
"sudo-prompt": "^9.0.0",
"wcwidth": "^1.0.1",
"ws": "^1.1.0"
},
"peerDependencies": {
Expand All @@ -71,6 +73,7 @@
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.5.2",
"@types/semver": "^6.0.2",
"@types/wcwidth": "^1.0.0",
"slash": "^3.0.0",
"snapshot-diff": "^0.5.0"
}
Expand Down
85 changes: 83 additions & 2 deletions packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,92 @@
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 {installCocoaPods} from '../../../tools/installPods';
import {
promptCocoaPodsInstallationQuestion,
runSudo,
} from '../../../tools/installPods';
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 () => ({
needsToBeFixed: await checkSoftwareInstalled('pod'),
}),
runAutomaticFix: async ({loader}) => await installCocoaPods(loader),
runAutomaticFix: async ({loader}) => {
loader.stop();

const {
installMethod,
promptQuestion,
} = await promptCocoaPodsInstallationQuestion();

// Capitalise `Homebrew` when printing on the screen
const installMethodCapitalized =
installMethod === 'homebrew'
? installMethod.substr(0, 1).toUpperCase() + installMethod.substr(1)
: installMethod;
const loaderInstallationMessage = `CocoaPods (installing with ${installMethodCapitalized})`;
const loaderSucceedMessage = `CocoaPods (installed with ${installMethodCapitalized})`;

// Remove the prompt after the question of how to install CocoaPods is answered
clearQuestion(promptQuestion);

if (installMethod === 'gem') {
loader.start(loaderInstallationMessage);

const options = ['install', 'cocoapods', '--no-document'];

try {
// First attempt to install `cocoapods`
await execa('gem', options);

return loader.succeed(loaderSucceedMessage);
} catch (_error) {
// If that doesn't work then try with sudo
try {
await runSudo(`gem ${options.join(' ')}`);

return loader.succeed(loaderSucceedMessage);
} catch (error) {
loader.fail();
logger.log(chalk.dim(`\n${error}`));

return logger.log(
`An error occured while trying to install CocoaPods. Please try again manually: ${chalk.bold(
'sudo gem install cocoapods',
)}`,
);
}
}
}

if (installMethod === 'homebrew') {
return await brewInstall({
pkg: 'cocoapods',
label: loaderInstallationMessage,
loader,
onSuccess: () => loader.succeed(loaderSucceedMessage),
});
}
},
} as HealthCheckInterface;
21 changes: 19 additions & 2 deletions packages/cli/src/tools/brewInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,32 @@ type InstallArgs = {
pkg: string;
label?: string;
loader: ora.Ora;
onSuccess?: () => void;
onFail?: () => void;
};

async function brewInstall({pkg, label, loader}: InstallArgs) {
async function brewInstall({
pkg,
label,
loader,
onSuccess,
onFail,
}: InstallArgs) {
loader.start(label);

try {
await execa('brew', ['install', pkg]);

loader.succeed();
if (typeof onSuccess === 'function') {
return onSuccess();
}

return loader.succeed();
} catch (error) {
if (typeof onFail === 'function') {
return onFail();
}

loader.fail();
logger.log(chalk.dim(`\n${error.stderr}`));
logger.log(
Expand Down
116 changes: 72 additions & 44 deletions packages/cli/src/tools/installPods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ 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
import sudo from 'sudo-prompt';
import {brewInstall} from './brewInstall';

type PromptCocoaPodsInstallation = {
installMethod: 'gem' | 'homebrew';
promptQuestion: string;
};

async function updatePods(loader: ora.Ora) {
try {
loader.start(
Expand Down Expand Up @@ -43,56 +49,79 @@ function runSudo(command: string): Promise<void> {
});
}

async function installCocoaPods(loader: ora.Ora) {
loader.stop();

const withGem = 'Yes, with gem (may require sudo)';
const withHomebrew = 'Yes, with Homebrew';
async function promptCocoaPodsInstallationQuestion(): Promise<
PromptCocoaPodsInstallation
> {
const promptQuestion = `CocoaPods ${chalk.dim.underline(
'(https://cocoapods.org/)',
)} ${chalk.reset.bold(
'is not installed. CocoaPods is necessary for the iOS project to run correctly. Do you want to install it?',
)}`;
const installWithGem = 'Yes, with gem (may require sudo)';
const installWithHomebrew = 'Yes, with Homebrew';

const {shouldInstallCocoaPods} = await inquirer.prompt([
{
type: 'list',
name: 'shouldInstallCocoaPods',
message: `CocoaPods ${chalk.dim.underline(
'(https://cocoapods.org/)',
)} ${chalk.reset.bold(
'is not installed. CocoaPods is necessary for the iOS project to run correctly. Do you want to install it?',
)}`,
choices: [withGem, withHomebrew],
message: promptQuestion,
choices: [installWithGem, installWithHomebrew],
},
]);

switch (shouldInstallCocoaPods) {
case withGem:
const options = ['install', 'cocoapods', '--no-document'];
loader.start('Installing CocoaPods');
try {
// First attempt to install `cocoapods`
await execa('gem', options);
} catch (_error) {
// If that doesn't work then try with sudo
try {
await runSudo(`gem ${options.join(' ')}`);
} catch (error) {
loader.fail();
logger.error(error.stderr);

throw new Error(
`An error occured while trying to install CocoaPods, which is required by this template.\nPlease try again manually: sudo gem install cocoapods.\nCocoaPods documentation: ${chalk.dim.underline(
'https://cocoapods.org/',
)}`,
);
}
}
loader.succeed();
break;
case withHomebrew:
await brewInstall({
pkg: 'cocoapods',
label: 'Installing CocoaPods',
loader,
});
break;
const shouldInstallWithGem = shouldInstallCocoaPods === installWithGem;

return {
installMethod: shouldInstallWithGem ? 'gem' : 'homebrew',
// This is used for removing the message in `doctor` after it's answered
promptQuestion: `? ${stripAnsi(promptQuestion)} ${
shouldInstallWithGem ? installWithGem : installWithHomebrew
}`,
};
}

async function installCocoaPodsWithGem() {
const options = ['install', 'cocoapods', '--no-document'];

try {
// First attempt to install `cocoapods`
await execa('gem', options);
} catch (_error) {
// If that doesn't work then try with sudo
await runSudo(`gem ${options.join(' ')}`);
}
}

async function installCocoaPods(loader: ora.Ora) {
loader.stop();

const {installMethod} = await promptCocoaPodsInstallationQuestion();

if (installMethod === 'gem') {
loader.start('Installing CocoaPods');

try {
await installCocoaPodsWithGem();

return loader.succeed();
} catch (error) {
loader.fail();
logger.error(error.stderr);

throw new Error(
`An error occured while trying to install CocoaPods, which is required by this template.\nPlease try again manually: sudo gem install cocoapods.\nCocoaPods documentation: ${chalk.dim.underline(
'https://cocoapods.org/',
)}`,
);
}
}

if (installMethod === 'homebrew') {
return await brewInstall({
pkg: 'cocoapods',
label: 'Installing CocoaPods',
loader,
});
}
}

Expand Down Expand Up @@ -134,7 +163,6 @@ async function installPods({
}

try {
loader.succeed();
loader.start(
`Installing CocoaPods dependencies ${chalk.dim(
'(this may take a few minutes)',
Expand All @@ -158,6 +186,6 @@ async function installPods({
}
}

export {installCocoaPods};
export {promptCocoaPodsInstallationQuestion, runSudo, installCocoaPods};

export default installPods;
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==

"@types/wcwidth@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/wcwidth/-/wcwidth-1.0.0.tgz#a58f4673050f98c46ae8f852340889343b21a1f5"
integrity sha512-X/WFfwGCIisEnd9EOSsX/jt7BHPDkcvQVYwVzc1nsE2K5bC56mWKnmNs0wyjcGcQsP7Wxq2zWSmhDDbF5Z7dDg==

"@types/xmldoc@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@types/xmldoc/-/xmldoc-1.1.4.tgz#5867d4e29739719c633bf16413c5a4a4c1c3c802"
Expand Down