From ddd4e2da51c76330a89d33b2878ad966905137ec Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Thu, 30 Jul 2020 15:54:53 -0700 Subject: [PATCH 1/8] Allow platforms and community packages to provide additional health checks --- packages/cli-types/src/index.ts | 100 ++++++++++++ packages/cli-types/src/ora.ts | 154 ++++++++++++++++++ packages/cli/src/commands/doctor/doctor.ts | 7 +- .../healthchecks/androidHomeEnvVariable.ts | 6 +- .../doctor/healthchecks/androidNDK.ts | 15 +- .../doctor/healthchecks/androidSDK.ts | 8 +- .../doctor/healthchecks/androidStudio.ts | 5 +- .../commands/doctor/healthchecks/cocoaPods.ts | 2 +- .../src/commands/doctor/healthchecks/index.ts | 81 +++++---- .../commands/doctor/healthchecks/iosDeploy.ts | 6 +- .../src/commands/doctor/healthchecks/jdk.ts | 9 +- .../commands/doctor/healthchecks/nodeJS.ts | 5 +- .../doctor/healthchecks/packageManagers.ts | 2 +- .../commands/doctor/healthchecks/python.ts | 8 +- .../commands/doctor/healthchecks/watchman.ts | 2 +- .../src/commands/doctor/healthchecks/xcode.ts | 5 +- .../src/commands/doctor/runAutomaticFix.ts | 5 +- packages/cli/src/commands/doctor/types.ts | 85 +--------- packages/cli/src/tools/config/index.ts | 2 + packages/cli/src/tools/config/schema.ts | 23 +++ packages/cli/src/tools/envinfo.ts | 2 +- 21 files changed, 368 insertions(+), 164 deletions(-) create mode 100644 packages/cli-types/src/ora.ts diff --git a/packages/cli-types/src/index.ts b/packages/cli-types/src/index.ts index 67af8e461..5863f70b7 100644 --- a/packages/cli-types/src/index.ts +++ b/packages/cli-types/src/index.ts @@ -11,6 +11,8 @@ import { AndroidDependencyConfig, AndroidDependencyParams, } from './android'; +import {Ora} from './ora'; +export {Ora} from './ora'; export type InquirerPrompt = any; @@ -120,6 +122,100 @@ export type ProjectConfig = { [key: string]: any; }; +export type NotFound = 'Not Found'; +type AvailableInformation = { + version: string; + path: string; +}; + +type Information = AvailableInformation | NotFound; + +export type EnvironmentInfo = { + System: { + OS: string; + CPU: string; + Memory: string; + Shell: AvailableInformation; + }; + Binaries: { + Node: AvailableInformation; + Yarn: AvailableInformation; + npm: AvailableInformation; + Watchman: AvailableInformation; + }; + SDKs: { + 'iOS SDK': { + Platforms: string[]; + }; + 'Android SDK': + | { + 'API Levels': string[] | NotFound; + 'Build Tools': string[] | NotFound; + 'System Images': string[] | NotFound; + 'Android NDK': string | NotFound; + } + | NotFound; + }; + IDEs: { + 'Android Studio': AvailableInformation | NotFound; + Emacs: AvailableInformation; + Nano: AvailableInformation; + VSCode: AvailableInformation; + Vim: AvailableInformation; + Xcode: AvailableInformation; + }; + Languages: { + Java: Information; + Python: Information; + }; +}; + +export type HealthCheckCategory = { + label: string; + healthchecks: HealthCheckInterface[]; +}; + +export type Healthchecks = { + common: HealthCheckCategory; + android: HealthCheckCategory; + ios?: HealthCheckCategory; +}; + +export type RunAutomaticFix = (args: { + loader: Ora; + logManualInstallation: ({ + healthcheck, + url, + command, + message, + }: { + healthcheck?: string; + url?: string; + command?: string; + message?: string; + }) => void; + environmentInfo: EnvironmentInfo; +}) => Promise | void; + +export type HealthCheckInterface = { + label: string; + visible?: boolean | void; + isRequired?: boolean; + description?: string; + getDiagnostics: ( + environmentInfo: EnvironmentInfo, + ) => Promise<{ + version?: string; + versions?: [string]; + versionRange?: string; + needsToBeFixed: boolean | string; + }>; + win32AutomaticFix?: RunAutomaticFix; + darwinAutomaticFix?: RunAutomaticFix; + linuxAutomaticFix?: RunAutomaticFix; + runAutomaticFix: RunAutomaticFix; +}; + /** * @property root - Root where the configuration has been resolved from * @property reactNativePath - Path to React Native source @@ -128,6 +224,7 @@ export type ProjectConfig = { * @property dependencies - Map of the dependencies that are present in the project * @property platforms - Map of available platforms (build-ins and dynamically loaded) * @property commands - An array of commands that are present in 3rd party packages + * @property healthChecks - An array of health check categories to add to doctor command */ export interface Config extends IOSNativeModulesConfig { root: string; @@ -151,6 +248,7 @@ export interface Config extends IOSNativeModulesConfig { [name: string]: PlatformConfig; }; commands: Command[]; + healthChecks: HealthCheckCategory[]; } /** @@ -175,6 +273,8 @@ export type UserDependencyConfig = { commands: Command[]; // An array of extra platforms to load platforms: Config['platforms']; + // Additional health checks + healthChecks: HealthCheckCategory[]; }; export { diff --git a/packages/cli-types/src/ora.ts b/packages/cli-types/src/ora.ts new file mode 100644 index 000000000..fe7950684 --- /dev/null +++ b/packages/cli-types/src/ora.ts @@ -0,0 +1,154 @@ +/** + * The types exported from here are all from the Ora package. + * Alternatively we could make this pacakge depend on the Ora + * pacakge just to export the actual types. + * + * If these types are ever out of sync with the actual types + * provided by Ora, the build of the CLI will break since we + * assign Ora objects to objects of these types + */ + +interface Spinner { + readonly interval?: number; + readonly frames: string[]; +} + +type Color = + | 'black' + | 'red' + | 'green' + | 'yellow' + | 'blue' + | 'magenta' + | 'cyan' + | 'white' + | 'gray'; + +// Not the full list, but should be compatible with the real Ora type +type SpinnerName = any; + +interface PersistOptions { + /** + Symbol to replace the spinner with. + + @default ' ' + */ + readonly symbol?: string; + + /** + Text to be persisted after the symbol. Default: Current `text`. + */ + readonly text?: string; + + /** + Text to be persisted before the symbol. Default: Current `prefixText`. + */ + readonly prefixText?: string; +} + +export interface Ora { + /** + A boolean of whether the instance is currently spinning. + */ + readonly isSpinning: boolean; + + /** + Change the text after the spinner. + */ + text: string; + + /** + Change the text before the spinner. + */ + prefixText: string; + + /** + Change the spinner color. + */ + color: Color; + + /** + Change the spinner. + */ + spinner: SpinnerName | Spinner; + + /** + Change the spinner indent. + */ + indent: number; + + /** + Start the spinner. + + @param text - Set the current text. + @returns The spinner instance. + */ + start(text?: string): Ora; + + /** + Stop and clear the spinner. + + @returns The spinner instance. + */ + stop(): Ora; + + /** + Stop the spinner, change it to a green `✔` and persist the current text, or `text` if provided. + + @param text - Will persist text if provided. + @returns The spinner instance. + */ + succeed(text?: string): Ora; + + /** + Stop the spinner, change it to a red `✖` and persist the current text, or `text` if provided. + + @param text - Will persist text if provided. + @returns The spinner instance. + */ + fail(text?: string): Ora; + + /** + Stop the spinner, change it to a yellow `⚠` and persist the current text, or `text` if provided. + + @param text - Will persist text if provided. + @returns The spinner instance. + */ + warn(text?: string): Ora; + + /** + Stop the spinner, change it to a blue `ℹ` and persist the current text, or `text` if provided. + + @param text - Will persist text if provided. + @returns The spinner instance. + */ + info(text?: string): Ora; + + /** + Stop the spinner and change the symbol or text. + + @returns The spinner instance. + */ + stopAndPersist(options?: PersistOptions): Ora; + + /** + Clear the spinner. + + @returns The spinner instance. + */ + clear(): Ora; + + /** + Manually render a new frame. + + @returns The spinner instance. + */ + render(): Ora; + + /** + Get a new frame. + + @returns The spinner instance. + */ + frame(): Ora; +} diff --git a/packages/cli/src/commands/doctor/doctor.ts b/packages/cli/src/commands/doctor/doctor.ts index a2b0a4a48..08acee2b6 100644 --- a/packages/cli/src/commands/doctor/doctor.ts +++ b/packages/cli/src/commands/doctor/doctor.ts @@ -4,13 +4,12 @@ import {getHealthchecks, HEALTHCHECK_TYPES} from './healthchecks'; import {getLoader} from '../../tools/loader'; import printFixOptions, {KEYS} from './printFixOptions'; import runAutomaticFix, {AUTOMATIC_FIX_LEVELS} from './runAutomaticFix'; -import {DetachedCommandFunction} from '@react-native-community/cli-types'; import { + DetachedCommandFunction, HealthCheckCategory, - HealthCheckCategoryResult, - HealthCheckResult, HealthCheckInterface, -} from './types'; +} from '@react-native-community/cli-types'; +import {HealthCheckCategoryResult, HealthCheckResult} from './types'; import getEnvironmentInfo from '../../tools/envinfo'; import {logMessage} from './healthchecks/common'; diff --git a/packages/cli/src/commands/doctor/healthchecks/androidHomeEnvVariable.ts b/packages/cli/src/commands/doctor/healthchecks/androidHomeEnvVariable.ts index 77b0a72a8..8ea5d5cb5 100644 --- a/packages/cli/src/commands/doctor/healthchecks/androidHomeEnvVariable.ts +++ b/packages/cli/src/commands/doctor/healthchecks/androidHomeEnvVariable.ts @@ -1,7 +1,5 @@ import chalk from 'chalk'; -import {Ora} from 'ora'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; // List of answers on how to set `ANDROID_HOME` for each platform const URLS = { @@ -25,7 +23,7 @@ export default { getDiagnostics: async () => ({ needsToBeFixed: !process.env.ANDROID_HOME, }), - runAutomaticFix: async ({loader}: {loader: Ora}) => { + runAutomaticFix: async ({loader, logManualInstallation}) => { // Variable could have been added if installing Android Studio so double checking if (process.env.ANDROID_HOME) { loader.succeed(); diff --git a/packages/cli/src/commands/doctor/healthchecks/androidNDK.ts b/packages/cli/src/commands/doctor/healthchecks/androidNDK.ts index c7a70c888..ce236f268 100644 --- a/packages/cli/src/commands/doctor/healthchecks/androidNDK.ts +++ b/packages/cli/src/commands/doctor/healthchecks/androidNDK.ts @@ -1,9 +1,10 @@ import chalk from 'chalk'; -import {Ora} from 'ora'; -import {logManualInstallation} from './common'; import versionRanges from '../versionRanges'; import {doesSoftwareNeedToBeFixed} from '../checkInstallation'; -import {EnvironmentInfo, HealthCheckInterface} from '../types'; +import { + EnvironmentInfo, + HealthCheckInterface, +} from '@react-native-community/cli-types'; export default { label: 'Android NDK', @@ -22,13 +23,7 @@ export default { versionRange: versionRanges.ANDROID_NDK, }; }, - runAutomaticFix: async ({ - loader, - environmentInfo, - }: { - loader: Ora; - environmentInfo: EnvironmentInfo; - }) => { + runAutomaticFix: async ({loader, logManualInstallation, environmentInfo}) => { const androidSdk = environmentInfo.SDKs['Android SDK']; const isNDKInstalled = androidSdk !== 'Not Found' && androidSdk['Android NDK'] !== 'Not Found'; diff --git a/packages/cli/src/commands/doctor/healthchecks/androidSDK.ts b/packages/cli/src/commands/doctor/healthchecks/androidSDK.ts index e4109a77f..c5d3035c1 100644 --- a/packages/cli/src/commands/doctor/healthchecks/androidSDK.ts +++ b/packages/cli/src/commands/doctor/healthchecks/androidSDK.ts @@ -3,8 +3,10 @@ import fs from 'fs'; import path from 'path'; import {logger} from '@react-native-community/cli-tools'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface, EnvironmentInfo} from '../types'; +import { + HealthCheckInterface, + EnvironmentInfo, +} from '@react-native-community/cli-types'; import findProjectRoot from '../../../tools/config/findProjectRoot'; import { getAndroidSdkRootInstallation, @@ -175,7 +177,7 @@ export default { 'Android SDK configured. You might need to restart your PC for all changes to take effect.', ); }, - runAutomaticFix: async ({loader, environmentInfo}) => { + runAutomaticFix: async ({loader, logManualInstallation, environmentInfo}) => { loader.fail(); if (isSDKInstalled(environmentInfo)) { diff --git a/packages/cli/src/commands/doctor/healthchecks/androidStudio.ts b/packages/cli/src/commands/doctor/healthchecks/androidStudio.ts index a68672d66..8c22ec59e 100644 --- a/packages/cli/src/commands/doctor/healthchecks/androidStudio.ts +++ b/packages/cli/src/commands/doctor/healthchecks/androidStudio.ts @@ -1,7 +1,6 @@ import {join} from 'path'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; import {downloadAndUnzip} from '../../../tools/downloadAndUnzip'; import {executeCommand} from '../../../tools/windows/executeWinCommand'; @@ -70,7 +69,7 @@ export default { `Android Studio installed successfully in "${installPath}".`, ); }, - runAutomaticFix: async ({loader}) => { + runAutomaticFix: async ({loader, logManualInstallation}) => { loader.fail(); return logManualInstallation({ diff --git a/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts b/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts index bc8ffd449..a34ee9c13 100644 --- a/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts +++ b/packages/cli/src/commands/doctor/healthchecks/cocoaPods.ts @@ -6,7 +6,7 @@ import { } from '../../../tools/installPods'; import {removeMessage, logError} from './common'; import {brewInstall} from '../../../tools/brewInstall'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; const label = 'CocoaPods'; diff --git a/packages/cli/src/commands/doctor/healthchecks/index.ts b/packages/cli/src/commands/doctor/healthchecks/index.ts index ebaad4b88..159fa7e6f 100644 --- a/packages/cli/src/commands/doctor/healthchecks/index.ts +++ b/packages/cli/src/commands/doctor/healthchecks/index.ts @@ -10,7 +10,11 @@ import androidNDK from './androidNDK'; import xcode from './xcode'; import cocoaPods from './cocoaPods'; import iosDeploy from './iosDeploy'; -import {Healthchecks} from '../types'; +import { + Healthchecks, + HealthCheckCategory, +} from '@react-native-community/cli-types'; +import loadConfig from '../../../tools/config'; export const HEALTHCHECK_TYPES = { ERROR: 'ERROR', @@ -22,33 +26,48 @@ type Options = { contributor: boolean | void; }; -export const getHealthchecks = ({contributor}: Options): Healthchecks => ({ - common: { - label: 'Common', - healthchecks: [ - nodeJS, - yarn, - npm, - ...(process.platform === 'darwin' ? [watchman] : []), - ...(process.platform === 'win32' ? [python] : []), - ], - }, - android: { - label: 'Android', - healthchecks: [ - jdk, - androidStudio, - androidSDK, - androidHomeEnvVariable, - ...(contributor ? [androidNDK] : []), - ], - }, - ...(process.platform === 'darwin' - ? { - ios: { - label: 'iOS', - healthchecks: [xcode, cocoaPods, iosDeploy], - }, - } - : {}), -}); +export const getHealthchecks = ({contributor}: Options): Healthchecks => { + let additionalChecks: HealthCheckCategory[] = []; + + // Todo: maybe detached commands should take the config as an arg, thats null if running in detacted. + // There is already another case of a health check running loadConfig inside a try, + // so we could save some time by passing it through + + // Doctor can run in a detached mode, where there isn't a config so this can fail + try { + let config = loadConfig(); + additionalChecks = config.healthChecks; + } catch {} + + return { + common: { + label: 'Common', + healthchecks: [ + nodeJS, + yarn, + npm, + ...(process.platform === 'darwin' ? [watchman] : []), + ...(process.platform === 'win32' ? [python] : []), + ], + }, + android: { + label: 'Android', + healthchecks: [ + jdk, + androidStudio, + androidSDK, + androidHomeEnvVariable, + ...(contributor ? [androidNDK] : []), + ], + }, + ...(process.platform === 'darwin' + ? { + ios: { + label: 'iOS', + healthchecks: [xcode, cocoaPods, iosDeploy], + }, + } + : {}), + ...additionalChecks, + }; +}; diff --git a/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts b/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts index 5e31502f4..19eb4fabd 100644 --- a/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts +++ b/packages/cli/src/commands/doctor/healthchecks/iosDeploy.ts @@ -4,8 +4,8 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import {isSoftwareNotInstalled, PACKAGE_MANAGERS} from '../checkInstallation'; import {packageManager} from './packageManagers'; -import {logManualInstallation, logError, removeMessage} from './common'; -import {HealthCheckInterface} from '../types'; +import {logError, removeMessage} from './common'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; import {Ora} from 'ora'; const label = 'ios-deploy'; @@ -60,7 +60,7 @@ export default { getDiagnostics: async () => ({ needsToBeFixed: await isSoftwareNotInstalled('ios-deploy'), }), - runAutomaticFix: async ({loader}) => { + runAutomaticFix: async ({loader, logManualInstallation}) => { loader.stop(); const installationCommand = identifyInstallationCommand(); diff --git a/packages/cli/src/commands/doctor/healthchecks/jdk.ts b/packages/cli/src/commands/doctor/healthchecks/jdk.ts index 27b5605d2..c025d43a0 100644 --- a/packages/cli/src/commands/doctor/healthchecks/jdk.ts +++ b/packages/cli/src/commands/doctor/healthchecks/jdk.ts @@ -1,8 +1,7 @@ import {join} from 'path'; import versionRanges from '../versionRanges'; import {doesSoftwareNeedToBeFixed} from '../checkInstallation'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; import {downloadAndUnzip} from '../../../tools/downloadAndUnzip'; import { @@ -10,8 +9,6 @@ import { updateEnvironment, } from '../../../tools/windows/environmentVariables'; -import {Ora} from 'ora'; - export default { label: 'JDK', getDiagnostics: async ({Languages}) => ({ @@ -29,7 +26,7 @@ export default { : Languages.Java.version, versionRange: versionRanges.JAVA, }), - win32AutomaticFix: async ({loader}: {loader: Ora}) => { + win32AutomaticFix: async ({loader}) => { try { // Installing JDK 11 because later versions seem to cause issues with gradle at the moment const installerUrl = @@ -57,7 +54,7 @@ export default { loader.fail(e); } }, - runAutomaticFix: async () => { + runAutomaticFix: async ({logManualInstallation}) => { logManualInstallation({ healthcheck: 'JDK', url: 'https://openjdk.java.net/', diff --git a/packages/cli/src/commands/doctor/healthchecks/nodeJS.ts b/packages/cli/src/commands/doctor/healthchecks/nodeJS.ts index c9f9dc47b..6b36cbbff 100644 --- a/packages/cli/src/commands/doctor/healthchecks/nodeJS.ts +++ b/packages/cli/src/commands/doctor/healthchecks/nodeJS.ts @@ -1,7 +1,6 @@ import versionRanges from '../versionRanges'; import {doesSoftwareNeedToBeFixed} from '../checkInstallation'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; export default { label: 'Node.js', @@ -13,7 +12,7 @@ export default { version: Binaries.Node.version, versionRange: versionRanges.NODE_JS, }), - runAutomaticFix: async ({loader}) => { + runAutomaticFix: async ({loader, logManualInstallation}) => { loader.fail(); logManualInstallation({ diff --git a/packages/cli/src/commands/doctor/healthchecks/packageManagers.ts b/packages/cli/src/commands/doctor/healthchecks/packageManagers.ts index d83af28c7..51b83a621 100644 --- a/packages/cli/src/commands/doctor/healthchecks/packageManagers.ts +++ b/packages/cli/src/commands/doctor/healthchecks/packageManagers.ts @@ -5,7 +5,7 @@ import { doesSoftwareNeedToBeFixed, } from '../checkInstallation'; import {install} from '../../../tools/install'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; const packageManager = (() => { if (fs.existsSync('yarn.lock')) { diff --git a/packages/cli/src/commands/doctor/healthchecks/python.ts b/packages/cli/src/commands/doctor/healthchecks/python.ts index fc3993a0c..087914dc1 100644 --- a/packages/cli/src/commands/doctor/healthchecks/python.ts +++ b/packages/cli/src/commands/doctor/healthchecks/python.ts @@ -1,12 +1,10 @@ import {fetchToTemp} from '@react-native-community/cli-tools'; import versionRanges from '../versionRanges'; import {doesSoftwareNeedToBeFixed} from '../checkInstallation'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; import {updateEnvironment} from '../../../tools/windows/environmentVariables'; import {join} from 'path'; -import {Ora} from 'ora'; import {executeCommand} from '../../../tools/windows/executeWinCommand'; export default { @@ -26,7 +24,7 @@ export default { : Languages.Python.version, versionRange: versionRanges.PYTHON, }), - win32AutomaticFix: async ({loader}: {loader: Ora}) => { + win32AutomaticFix: async ({loader}) => { try { const arch = process.arch === 'x64' ? 'amd64.' : ''; const installerUrl = `https://www.python.org/ftp/python/2.7.9/python-2.7.9.${arch}msi`; @@ -51,7 +49,7 @@ export default { loader.fail(e); } }, - runAutomaticFix: async () => { + runAutomaticFix: async ({logManualInstallation}) => { /** * Python is only needed on Windows so this method should never be called. * Leaving it in case that changes and as an example of how to have a diff --git a/packages/cli/src/commands/doctor/healthchecks/watchman.ts b/packages/cli/src/commands/doctor/healthchecks/watchman.ts index 7fcfa8e33..e3e8c2074 100644 --- a/packages/cli/src/commands/doctor/healthchecks/watchman.ts +++ b/packages/cli/src/commands/doctor/healthchecks/watchman.ts @@ -1,7 +1,7 @@ import versionRanges from '../versionRanges'; import {doesSoftwareNeedToBeFixed} from '../checkInstallation'; import {install} from '../../../tools/install'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; const label = 'Watchman'; diff --git a/packages/cli/src/commands/doctor/healthchecks/xcode.ts b/packages/cli/src/commands/doctor/healthchecks/xcode.ts index 2eae0da7e..9d2f8c125 100644 --- a/packages/cli/src/commands/doctor/healthchecks/xcode.ts +++ b/packages/cli/src/commands/doctor/healthchecks/xcode.ts @@ -1,7 +1,6 @@ import versionRanges from '../versionRanges'; import {doesSoftwareNeedToBeFixed} from '../checkInstallation'; -import {logManualInstallation} from './common'; -import {HealthCheckInterface} from '../types'; +import {HealthCheckInterface} from '@react-native-community/cli-types'; export default { label: 'Xcode', @@ -18,7 +17,7 @@ export default { versionRange: versionRanges.XCODE, }; }, - runAutomaticFix: async ({loader}) => { + runAutomaticFix: async ({loader, logManualInstallation}) => { loader.fail(); logManualInstallation({ diff --git a/packages/cli/src/commands/doctor/runAutomaticFix.ts b/packages/cli/src/commands/doctor/runAutomaticFix.ts index f58a25000..92d708974 100644 --- a/packages/cli/src/commands/doctor/runAutomaticFix.ts +++ b/packages/cli/src/commands/doctor/runAutomaticFix.ts @@ -2,7 +2,9 @@ import chalk from 'chalk'; import ora, {Ora} from 'ora'; import {logger} from '@react-native-community/cli-tools'; import {HEALTHCHECK_TYPES} from './healthchecks'; -import {EnvironmentInfo, HealthCheckCategoryResult} from './types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; +import {HealthCheckCategoryResult} from './types'; +import {logManualInstallation} from './healthchecks/common'; export enum AUTOMATIC_FIX_LEVELS { ALL_ISSUES = 'ALL_ISSUES', @@ -86,6 +88,7 @@ export default async function({ try { await healthcheckToRun.runAutomaticFix({ loader: spinner, + logManualInstallation, environmentInfo, }); } catch (error) { diff --git a/packages/cli/src/commands/doctor/types.ts b/packages/cli/src/commands/doctor/types.ts index 233d65cb4..ca4d7cefa 100644 --- a/packages/cli/src/commands/doctor/types.ts +++ b/packages/cli/src/commands/doctor/types.ts @@ -1,87 +1,4 @@ -import {Ora} from 'ora'; - -type NotFound = 'Not Found'; -type AvailableInformation = { - version: string; - path: string; -}; - -type Information = AvailableInformation | NotFound; - -export type EnvironmentInfo = { - System: { - OS: string; - CPU: string; - Memory: string; - Shell: AvailableInformation; - }; - Binaries: { - Node: AvailableInformation; - Yarn: AvailableInformation; - npm: AvailableInformation; - Watchman: AvailableInformation; - }; - SDKs: { - 'iOS SDK': { - Platforms: string[]; - }; - 'Android SDK': - | { - 'API Levels': string[] | NotFound; - 'Build Tools': string[] | NotFound; - 'System Images': string[] | NotFound; - 'Android NDK': string | NotFound; - } - | NotFound; - }; - IDEs: { - 'Android Studio': AvailableInformation | NotFound; - Emacs: AvailableInformation; - Nano: AvailableInformation; - VSCode: AvailableInformation; - Vim: AvailableInformation; - Xcode: AvailableInformation; - }; - Languages: { - Java: Information; - Python: Information; - }; -}; - -export type HealthCheckCategory = { - label: string; - healthchecks: HealthCheckInterface[]; -}; - -export type Healthchecks = { - common: HealthCheckCategory; - android: HealthCheckCategory; - ios?: HealthCheckCategory; -}; - -export type RunAutomaticFix = (args: { - loader: Ora; - environmentInfo: EnvironmentInfo; -}) => Promise | void; - -export type HealthCheckInterface = { - label: string; - visible?: boolean | void; - isRequired?: boolean; - description?: string; - getDiagnostics: ( - environmentInfo: EnvironmentInfo, - ) => Promise<{ - version?: string; - versions?: [string]; - versionRange?: string; - needsToBeFixed: boolean | string; - }>; - win32AutomaticFix?: RunAutomaticFix; - darwinAutomaticFix?: RunAutomaticFix; - linuxAutomaticFix?: RunAutomaticFix; - runAutomaticFix: RunAutomaticFix; -}; +import {RunAutomaticFix, NotFound} from '@react-native-community/cli-types'; export type HealthCheckResult = { label: string; diff --git a/packages/cli/src/tools/config/index.ts b/packages/cli/src/tools/config/index.ts index 3e55d99bf..7f427a44a 100644 --- a/packages/cli/src/tools/config/index.ts +++ b/packages/cli/src/tools/config/index.ts @@ -74,6 +74,7 @@ function loadConfig(projectRoot: string = findProjectRoot()): Config { get assets() { return findAssets(projectRoot, userConfig.assets); }, + healthChecks: [], platforms: userConfig.platforms, get project() { if (lazyProject) { @@ -143,6 +144,7 @@ function loadConfig(projectRoot: string = findProjectRoot()): Config { ...acc.platforms, ...config.platforms, }, + healthChecks: [...acc.healthChecks, ...config.healthChecks], }) as Config; }, initialConfig); diff --git a/packages/cli/src/tools/config/schema.ts b/packages/cli/src/tools/config/schema.ts index 6ce6d3537..3516b27a4 100644 --- a/packages/cli/src/tools/config/schema.ts +++ b/packages/cli/src/tools/config/schema.ts @@ -35,6 +35,25 @@ const command = t.object({ ), }); +/** + * Schema for HealthChecksT + */ +const healthCheck = t.object({ + label: t.string().required(), + healthchecks: t.array().items( + t.object({ + label: t.string().required(), + isRequired: t.string(), + description: t.string(), + getDiagnostics: t.func(), + win32AutomaticFix: t.func(), + darwinAutomaticFix: t.func(), + linuxAutomaticFix: t.func(), + runAutomaticFix: t.func().required(), + }), + ), +}); + /** * Schema for UserDependencyConfigT */ @@ -93,6 +112,10 @@ export const dependencyConfig = t .array() .items(command) .default([]), + healthChecks: t + .array() + .items(healthCheck) + .default([]), }) .unknown(true) .default(); diff --git a/packages/cli/src/tools/envinfo.ts b/packages/cli/src/tools/envinfo.ts index 7f903d4f6..137c2db42 100644 --- a/packages/cli/src/tools/envinfo.ts +++ b/packages/cli/src/tools/envinfo.ts @@ -1,6 +1,6 @@ // @ts-ignore import envinfo from 'envinfo'; -import {EnvironmentInfo} from '../commands/doctor/types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; /** * Returns information about the running system. From 4a8ea761437a9876fb91082113cf8276a537826a Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Fri, 31 Jul 2020 09:10:02 -0700 Subject: [PATCH 2/8] Adding some documentation --- docs/healthChecks.md | 147 +++++++++++++++++++++++++++++++++++++++++++ docs/plugins.md | 3 + 2 files changed, 150 insertions(+) create mode 100644 docs/healthChecks.md diff --git a/docs/healthChecks.md b/docs/healthChecks.md new file mode 100644 index 000000000..0c585109a --- /dev/null +++ b/docs/healthChecks.md @@ -0,0 +1,147 @@ +# Health Check Plugins + +Plugins can be used to extend the health checks that `react-native doctor` runs. This can be used to add additional checks for out of tree platforms, or other checks that are specific to a community module. + +See [`Plugins`](./plugins.md) for information about how plugins work. + + +## How does it work? + +To provide additional health checks, a package needs to have a `react-native.config.js` at the root folder in order to be discovered by the CLI as a plugin. + +```js +module.exports = { + healthChecks: [ + { + label: 'Foo', + healthchecks: [ + label: 'bar-installed', + getDiagnostics: async () => ({ + needsToBeFixed: !isBarInstalled() + }), + runAutomaticFix: async ({loader}) => { + await installBar(); + loader.succeed(); + }, + } + ], +}; +``` + +> Above is an example of a plugin that extends the healthChecks performed by `react-native doctor` to check if `bar` is installed. + +At the startup, React Native CLI reads configuration from all dependencies listed in `package.json` and reduces them into a single configuration. + +At the end, an array of health check categories is concatenated to be checked when `react-native doctor` is run. + + +## HealthCheckCategory interface + +```ts +type HealthCheckCategory = { + label: string; + healthchecks: HealthCheckInterface[]; +}; +``` + +##### `label` + +Name of the category for this health check. This will be used to group health checks in doctor. + +##### `healthChecks` + +Array of health checks to perorm in this category + + +## HealthCheckInterface interface + +```ts +type HealthCheckInterface = { + label: string; + visible?: boolean | void; + isRequired?: boolean; + description?: string; + getDiagnostics: ( + environmentInfo: EnvironmentInfo, + ) => Promise<{ + version?: string; + versions?: [string]; + versionRange?: string; + needsToBeFixed: boolean | string; + }>; + win32AutomaticFix?: RunAutomaticFix; + darwinAutomaticFix?: RunAutomaticFix; + linuxAutomaticFix?: RunAutomaticFix; + runAutomaticFix: RunAutomaticFix; +}; +``` + +##### `label` + +Name of this health check + +##### `visible` + +If set to false, doctor will ignore this health check + +##### `isRequired` + +Is this health check required or optional? + +##### `description` + +Longer description of this health check + + +##### `getDiagnostics` + +Functions which performs the actual check. Simple checks can just return `needsToBeFixed`. Checks which are looking at versions of an installed components (such as the version of node), can also return `version`, `versions` and `versionRange` to provide better information to be displayed in `react-native doctor` when running the check + +##### `win32AutomaticFix` + +This function will be used to try to fix the issue when `react-native doctor` is run on a windows machine. If this is not specified, `runAutomaticFix` will be run instead. + +##### `darwinAutomaticFix` + +This function will be used to try to fix the issue when `react-native doctor` is run on a macOS machine. If this is not specified, `runAutomaticFix` will be run instead. + +##### `linuxAutomaticFix` + +This function will be used to try to fix the issue when `react-native doctor` is run on a linux machine. If this is not specified, `runAutomaticFix` will be run instead. + +##### `runAutomaticFix` + +This function will be used to try to fix the issue when `react-native doctor` is run and no more platform specific automatic fix function was provided. + + +## RunAutomaticFix interface + +```ts +type RunAutomaticFix = (args: { + loader: Ora; + logManualInstallation: ({ + healthcheck, + url, + command, + message, + }: { + healthcheck?: string; + url?: string; + command?: string; + message?: string; + }) => void; + environmentInfo: EnvironmentInfo; +}) => Promise | void; +``` + +##### `loader` + +A reference to a [`ora`](https://www.npmjs.com/package/ora) instance which should be used to report success / failure, and progress of the fix. The fix function should always call either `loader.succeed()` or `loader.fail()` before returning. + +##### `logManualInstallation` + +If an automated fix cannot be performed, this function should be used to provide instructions to the user on how to manually fix the issue. + +##### `environmentInfo` + +Provides information about the current system \ No newline at end of file diff --git a/docs/plugins.md b/docs/plugins.md index 82d988b59..c59736205 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -27,6 +27,8 @@ At the startup, React Native CLI reads configuration from all dependencies liste At the end, an array of commands concatenated from all plugins is passed on to the CLI to be loaded after built-in commands. +> See [`healthChecks`](./healthChecks.md) for information on how plugins can provide additional health checks for `react-native doctor`. + ## Command interface ```ts @@ -107,6 +109,7 @@ String that describes this particular usage. A command with arguments and options (if applicable) that can be run in order to achieve the desired goal. + ## Migrating from `rnpm` configuration The changes are mostly cosmetic so the migration should be pretty straight-forward. From 15013e88cefae4793b486d7ce4569b9fc0e7d6a2 Mon Sep 17 00:00:00 2001 From: "Andrew Coates (REDMOND)" Date: Fri, 31 Jul 2020 09:25:53 -0700 Subject: [PATCH 3/8] Update tests --- .../__tests__/androidHomeEnvVariable.test.ts | 17 ++++++++++++++--- .../healthchecks/__tests__/androidNDK.test.ts | 9 +++++++-- .../healthchecks/__tests__/androidSDK.test.ts | 15 ++++++++++++--- .../__tests__/androidStudio.test.ts | 15 ++++++++++++--- .../doctor/healthchecks/__tests__/jdk.test.ts | 11 ++++++++--- .../healthchecks/__tests__/python.test.ts | 15 ++++++++++++--- .../__tests__/__snapshots__/index-test.ts.snap | 1 + 7 files changed, 66 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidHomeEnvVariable.test.ts b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidHomeEnvVariable.test.ts index b537ad2e7..d526c44b5 100644 --- a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidHomeEnvVariable.test.ts +++ b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidHomeEnvVariable.test.ts @@ -1,12 +1,15 @@ import androidHomeEnvVariables from '../androidHomeEnvVariable'; import {NoopLoader} from '../../../../tools/loader'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; import * as common from '../common'; const logSpy = jest.spyOn(common, 'logManualInstallation'); +const {logManualInstallation} = common; describe('androidHomeEnvVariables', () => { const OLD_ENV = process.env; + let environmentInfo: EnvironmentInfo; afterEach(() => { process.env = OLD_ENV; @@ -16,14 +19,18 @@ describe('androidHomeEnvVariables', () => { it('returns true if no ANDROID_HOME is defined', async () => { delete process.env.ANDROID_HOME; - const diagnostics = await androidHomeEnvVariables.getDiagnostics(); + const diagnostics = await androidHomeEnvVariables.getDiagnostics( + environmentInfo, + ); expect(diagnostics.needsToBeFixed).toBe(true); }); it('returns false if ANDROID_HOME is defined', async () => { process.env.ANDROID_HOME = '/fake/path/to/android/home'; - const diagnostics = await androidHomeEnvVariables.getDiagnostics(); + const diagnostics = await androidHomeEnvVariables.getDiagnostics( + environmentInfo, + ); expect(diagnostics.needsToBeFixed).toBe(false); }); @@ -31,7 +38,11 @@ describe('androidHomeEnvVariables', () => { const loader = new NoopLoader(); delete process.env.ANDROID_HOME; - androidHomeEnvVariables.runAutomaticFix({loader}); + androidHomeEnvVariables.runAutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(logSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidNDK.test.ts b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidNDK.test.ts index b5b4e0abe..f3d0cd72d 100644 --- a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidNDK.test.ts +++ b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidNDK.test.ts @@ -1,11 +1,12 @@ import androidNDK from '../androidNDK'; import getEnvironmentInfo from '../../../../tools/envinfo'; -import {EnvironmentInfo} from '../../types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; import {NoopLoader} from '../../../../tools/loader'; import * as common from '../common'; const logSpy = jest.spyOn(common, 'logManualInstallation'); +const {logManualInstallation} = common; describe('androidNDK', () => { let environmentInfo: EnvironmentInfo; @@ -57,7 +58,11 @@ describe('androidNDK', () => { it('logs manual installation steps to the screen', () => { const loader = new NoopLoader(); - androidNDK.runAutomaticFix({loader, environmentInfo}); + androidNDK.runAutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(logSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidSDK.test.ts b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidSDK.test.ts index 1b46bea12..9e6cc6830 100644 --- a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidSDK.test.ts +++ b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidSDK.test.ts @@ -5,13 +5,14 @@ import {cleanup, writeFiles} from '../../../../../../../jest/helpers'; import androidSDK from '../androidSDK'; import getEnvironmentInfo from '../../../../tools/envinfo'; import * as downloadAndUnzip from '../../../../tools/downloadAndUnzip'; -import {EnvironmentInfo} from '../../types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; import {NoopLoader} from '../../../../tools/loader'; import * as common from '../common'; import * as androidWinHelpers from '../../../../tools/windows/androidWinHelpers'; import * as environmentVariables from '../../../../tools/windows/environmentVariables'; const logSpy = jest.spyOn(common, 'logManualInstallation'); +const {logManualInstallation} = common; jest.mock('execa', () => jest.fn()); @@ -90,7 +91,11 @@ describe('androidSDK', () => { it('logs manual installation steps to the screen for the default fix', () => { const loader = new NoopLoader(); - androidSDK.runAutomaticFix({loader, environmentInfo}); + androidSDK.runAutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(logSpy).toHaveBeenCalledTimes(1); }); @@ -124,7 +129,11 @@ describe('androidSDK', () => { return Promise.resolve({hypervisor: 'WHPX', installed: true}); }); - await androidSDK.win32AutomaticFix({loader, environmentInfo}); + await androidSDK.win32AutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); // 1. Download and unzip the SDK expect(downloadAndUnzipSpy).toBeCalledTimes(1); diff --git a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidStudio.test.ts b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidStudio.test.ts index a749d000e..92d0ddcf6 100644 --- a/packages/cli/src/commands/doctor/healthchecks/__tests__/androidStudio.test.ts +++ b/packages/cli/src/commands/doctor/healthchecks/__tests__/androidStudio.test.ts @@ -1,7 +1,7 @@ import execa from 'execa'; import androidStudio from '../androidStudio'; import getEnvironmentInfo from '../../../../tools/envinfo'; -import {EnvironmentInfo} from '../../types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; import {NoopLoader} from '../../../../tools/loader'; import * as common from '../common'; import * as downloadAndUnzip from '../../../../tools/downloadAndUnzip'; @@ -9,6 +9,7 @@ import * as downloadAndUnzip from '../../../../tools/downloadAndUnzip'; jest.mock('execa', () => jest.fn()); const logSpy = jest.spyOn(common, 'logManualInstallation'); +const {logManualInstallation} = common; describe('androidStudio', () => { let environmentInfo: EnvironmentInfo; @@ -42,7 +43,11 @@ describe('androidStudio', () => { it('logs manual installation steps to the screen for the default fix', async () => { const loader = new NoopLoader(); - await androidStudio.runAutomaticFix({loader, environmentInfo}); + await androidStudio.runAutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(logSpy).toHaveBeenCalledTimes(1); }); @@ -54,7 +59,11 @@ describe('androidStudio', () => { .spyOn(downloadAndUnzip, 'downloadAndUnzip') .mockImplementation(() => Promise.resolve()); - await androidStudio.win32AutomaticFix({loader, environmentInfo}); + await androidStudio.win32AutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(loaderFailSpy).toHaveBeenCalledTimes(0); expect(logSpy).toHaveBeenCalledTimes(0); diff --git a/packages/cli/src/commands/doctor/healthchecks/__tests__/jdk.test.ts b/packages/cli/src/commands/doctor/healthchecks/__tests__/jdk.test.ts index 29e96c907..62a6e00c2 100644 --- a/packages/cli/src/commands/doctor/healthchecks/__tests__/jdk.test.ts +++ b/packages/cli/src/commands/doctor/healthchecks/__tests__/jdk.test.ts @@ -1,7 +1,7 @@ import execa from 'execa'; import jdk from '../jdk'; import getEnvironmentInfo from '../../../../tools/envinfo'; -import {EnvironmentInfo} from '../../types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; import {NoopLoader} from '../../../../tools/loader'; import * as common from '../common'; import * as unzip from '../../../../tools/unzip'; @@ -20,6 +20,7 @@ jest.mock('@react-native-community/cli-tools', () => { }); const logSpy = jest.spyOn(common, 'logManualInstallation'); +const {logManualInstallation} = common; describe('jdk', () => { let environmentInfo: EnvironmentInfo; @@ -61,7 +62,7 @@ describe('jdk', () => { it('logs manual installation steps to the screen for the default fix', async () => { const loader = new NoopLoader(); - await jdk.runAutomaticFix({loader, environmentInfo}); + await jdk.runAutomaticFix({loader, logManualInstallation, environmentInfo}); expect(logSpy).toHaveBeenCalledTimes(1); }); @@ -73,7 +74,11 @@ describe('jdk', () => { .spyOn(unzip, 'unzip') .mockImplementation(() => Promise.resolve()); - await jdk.win32AutomaticFix({loader, environmentInfo}); + await jdk.win32AutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(loaderFailSpy).toHaveBeenCalledTimes(0); expect(logSpy).toHaveBeenCalledTimes(0); diff --git a/packages/cli/src/commands/doctor/healthchecks/__tests__/python.test.ts b/packages/cli/src/commands/doctor/healthchecks/__tests__/python.test.ts index 04ac250f9..8e30fb682 100644 --- a/packages/cli/src/commands/doctor/healthchecks/__tests__/python.test.ts +++ b/packages/cli/src/commands/doctor/healthchecks/__tests__/python.test.ts @@ -1,7 +1,7 @@ import execa from 'execa'; import python from '../python'; import getEnvironmentInfo from '../../../../tools/envinfo'; -import {EnvironmentInfo} from '../../types'; +import {EnvironmentInfo} from '@react-native-community/cli-types'; import {NoopLoader} from '../../../../tools/loader'; import * as common from '../common'; @@ -11,6 +11,7 @@ jest.mock('@react-native-community/cli-tools', () => ({ })); const logSpy = jest.spyOn(common, 'logManualInstallation'); +const {logManualInstallation} = common; describe('python', () => { let environmentInfo: EnvironmentInfo; @@ -44,7 +45,11 @@ describe('python', () => { it('logs manual installation steps to the screen for the default fix', async () => { const loader = new NoopLoader(); - await python.runAutomaticFix({loader, environmentInfo}); + await python.runAutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(logSpy).toHaveBeenCalledTimes(1); }); @@ -53,7 +58,11 @@ describe('python', () => { const loaderSucceedSpy = jest.spyOn(loader, 'succeed'); const loaderFailSpy = jest.spyOn(loader, 'fail'); - await python.win32AutomaticFix({loader, environmentInfo}); + await python.win32AutomaticFix({ + loader, + logManualInstallation, + environmentInfo, + }); expect(loaderFailSpy).toHaveBeenCalledTimes(0); expect(logSpy).toHaveBeenCalledTimes(0); diff --git a/packages/cli/src/tools/config/__tests__/__snapshots__/index-test.ts.snap b/packages/cli/src/tools/config/__tests__/__snapshots__/index-test.ts.snap index 0c5415a93..b84cd250e 100644 --- a/packages/cli/src/tools/config/__tests__/__snapshots__/index-test.ts.snap +++ b/packages/cli/src/tools/config/__tests__/__snapshots__/index-test.ts.snap @@ -5,6 +5,7 @@ Object { "assets": Array [], "commands": Array [], "dependencies": Object {}, + "healthChecks": Array [], "platforms": Object {}, "project": Object {}, "reactNativePath": "<>", From 5e9e602386f47531cb92dc113ba5172e7b3b11bf Mon Sep 17 00:00:00 2001 From: Andrew Coates Date: Fri, 28 Aug 2020 09:37:46 -0700 Subject: [PATCH 4/8] remove todo --- packages/cli/src/commands/doctor/healthchecks/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/cli/src/commands/doctor/healthchecks/index.ts b/packages/cli/src/commands/doctor/healthchecks/index.ts index 159fa7e6f..9af8daed8 100644 --- a/packages/cli/src/commands/doctor/healthchecks/index.ts +++ b/packages/cli/src/commands/doctor/healthchecks/index.ts @@ -29,10 +29,6 @@ type Options = { export const getHealthchecks = ({contributor}: Options): Healthchecks => { let additionalChecks: HealthCheckCategory[] = []; - // Todo: maybe detached commands should take the config as an arg, thats null if running in detacted. - // There is already another case of a health check running loadConfig inside a try, - // so we could save some time by passing it through - // Doctor can run in a detached mode, where there isn't a config so this can fail try { let config = loadConfig(); From 6471154ace6175a0e96f14462503d3426a47c31e Mon Sep 17 00:00:00 2001 From: Andrew Coates Date: Fri, 28 Aug 2020 10:09:36 -0700 Subject: [PATCH 5/8] Add a couple of examples for RunAutomaticFix implementations --- docs/healthChecks.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/healthChecks.md b/docs/healthChecks.md index 0c585109a..c8e4e359f 100644 --- a/docs/healthChecks.md +++ b/docs/healthChecks.md @@ -95,7 +95,7 @@ Longer description of this health check ##### `getDiagnostics` -Functions which performs the actual check. Simple checks can just return `needsToBeFixed`. Checks which are looking at versions of an installed components (such as the version of node), can also return `version`, `versions` and `versionRange` to provide better information to be displayed in `react-native doctor` when running the check +Functions which performs the actual check. Simple checks can just return `needsToBeFixed`. Checks which are looking at versions of an installed component (such as the version of node), can also return `version`, `versions` and `versionRange` to provide better information to be displayed in `react-native doctor` when running the check ##### `win32AutomaticFix` @@ -144,4 +144,32 @@ If an automated fix cannot be performed, this function should be used to provide ##### `environmentInfo` -Provides information about the current system \ No newline at end of file +Provides information about the current system + + +### Examples of RunAutomaticFix implementations + +A health check that requires the user to manually go download/install something. This check will immediately display a message to notify the user how to fix the issue. + +```ts +async function needToInstallFoo({loader, logManualInstallation}) { + loader.fail(); + + return logManualInstallation({ + healthcheck: 'Foo', + url: 'https:/foo.com/download', + }); +} +``` + +A health check that runs some commands locally which may fix the issue. This check will display a spinner while the exec commands are running. Then once the commands are complete, the spinner will change to a checkmark. + +```ts + +import { exec } from 'promisify-child-process'; +async function fixFoo({loader}) { + await exec(`foo --install`); + await exec(`foo --fix`); + + loader.succeed(); +} From 27b19765ef834a6f71ceb4f55c8be6ba19a1353c Mon Sep 17 00:00:00 2001 From: Andrew Coates Date: Fri, 28 Aug 2020 10:27:55 -0700 Subject: [PATCH 6/8] update config snapshot --- __e2e__/__snapshots__/config.test.ts.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/__e2e__/__snapshots__/config.test.ts.snap b/__e2e__/__snapshots__/config.test.ts.snap index 01ee539b2..3b19df155 100644 --- a/__e2e__/__snapshots__/config.test.ts.snap +++ b/__e2e__/__snapshots__/config.test.ts.snap @@ -33,6 +33,7 @@ exports[`shows up current config without unnecessary output 1`] = ` } ], "assets": [], + "healthChecks": [], "platforms": { "ios": {}, "android": {} From e3c1d78b22b6959752826aef5dc63d6024789b4c Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Thu, 17 Sep 2020 08:46:23 -0700 Subject: [PATCH 7/8] Use real ora project for types --- packages/cli-types/package.json | 3 + packages/cli-types/src/index.ts | 3 +- packages/cli-types/src/ora.ts | 154 -------------------------------- 3 files changed, 4 insertions(+), 156 deletions(-) delete mode 100644 packages/cli-types/src/ora.ts diff --git a/packages/cli-types/package.json b/packages/cli-types/package.json index 2ebec70e9..a1454adf1 100644 --- a/packages/cli-types/package.json +++ b/packages/cli-types/package.json @@ -5,6 +5,9 @@ "publishConfig": { "access": "public" }, + "dependencies": { + "ora": "^3.4.0" + }, "files": [ "build", "!*.map" diff --git a/packages/cli-types/src/index.ts b/packages/cli-types/src/index.ts index 5863f70b7..b5b6ad3de 100644 --- a/packages/cli-types/src/index.ts +++ b/packages/cli-types/src/index.ts @@ -11,8 +11,7 @@ import { AndroidDependencyConfig, AndroidDependencyParams, } from './android'; -import {Ora} from './ora'; -export {Ora} from './ora'; +import {Ora} from 'ora'; export type InquirerPrompt = any; diff --git a/packages/cli-types/src/ora.ts b/packages/cli-types/src/ora.ts deleted file mode 100644 index fe7950684..000000000 --- a/packages/cli-types/src/ora.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * The types exported from here are all from the Ora package. - * Alternatively we could make this pacakge depend on the Ora - * pacakge just to export the actual types. - * - * If these types are ever out of sync with the actual types - * provided by Ora, the build of the CLI will break since we - * assign Ora objects to objects of these types - */ - -interface Spinner { - readonly interval?: number; - readonly frames: string[]; -} - -type Color = - | 'black' - | 'red' - | 'green' - | 'yellow' - | 'blue' - | 'magenta' - | 'cyan' - | 'white' - | 'gray'; - -// Not the full list, but should be compatible with the real Ora type -type SpinnerName = any; - -interface PersistOptions { - /** - Symbol to replace the spinner with. - - @default ' ' - */ - readonly symbol?: string; - - /** - Text to be persisted after the symbol. Default: Current `text`. - */ - readonly text?: string; - - /** - Text to be persisted before the symbol. Default: Current `prefixText`. - */ - readonly prefixText?: string; -} - -export interface Ora { - /** - A boolean of whether the instance is currently spinning. - */ - readonly isSpinning: boolean; - - /** - Change the text after the spinner. - */ - text: string; - - /** - Change the text before the spinner. - */ - prefixText: string; - - /** - Change the spinner color. - */ - color: Color; - - /** - Change the spinner. - */ - spinner: SpinnerName | Spinner; - - /** - Change the spinner indent. - */ - indent: number; - - /** - Start the spinner. - - @param text - Set the current text. - @returns The spinner instance. - */ - start(text?: string): Ora; - - /** - Stop and clear the spinner. - - @returns The spinner instance. - */ - stop(): Ora; - - /** - Stop the spinner, change it to a green `✔` and persist the current text, or `text` if provided. - - @param text - Will persist text if provided. - @returns The spinner instance. - */ - succeed(text?: string): Ora; - - /** - Stop the spinner, change it to a red `✖` and persist the current text, or `text` if provided. - - @param text - Will persist text if provided. - @returns The spinner instance. - */ - fail(text?: string): Ora; - - /** - Stop the spinner, change it to a yellow `⚠` and persist the current text, or `text` if provided. - - @param text - Will persist text if provided. - @returns The spinner instance. - */ - warn(text?: string): Ora; - - /** - Stop the spinner, change it to a blue `ℹ` and persist the current text, or `text` if provided. - - @param text - Will persist text if provided. - @returns The spinner instance. - */ - info(text?: string): Ora; - - /** - Stop the spinner and change the symbol or text. - - @returns The spinner instance. - */ - stopAndPersist(options?: PersistOptions): Ora; - - /** - Clear the spinner. - - @returns The spinner instance. - */ - clear(): Ora; - - /** - Manually render a new frame. - - @returns The spinner instance. - */ - render(): Ora; - - /** - Get a new frame. - - @returns The spinner instance. - */ - frame(): Ora; -} From aa0b67271258ad6260ee491d64ba28e744f02b97 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Thu, 17 Sep 2020 08:57:13 -0700 Subject: [PATCH 8/8] lint fix --- .../cli/src/commands/init/templateName.ts | 2 +- packages/cli/src/tools/config/schema.ts | 30 ++++--------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/packages/cli/src/commands/init/templateName.ts b/packages/cli/src/commands/init/templateName.ts index b0a7a2b98..e013d09f7 100644 --- a/packages/cli/src/commands/init/templateName.ts +++ b/packages/cli/src/commands/init/templateName.ts @@ -95,7 +95,7 @@ export function processTemplateName(templateName: string) { return handleVersionedPackage(templateName); } if ( - !['github', '@'].some(str => templateName.includes(str)) && + !['github', '@'].some((str) => templateName.includes(str)) && templateName.includes('/') ) { return handleGitHubRepo(templateName); diff --git a/packages/cli/src/tools/config/schema.ts b/packages/cli/src/tools/config/schema.ts index b00ad4e2f..aabe18c17 100644 --- a/packages/cli/src/tools/config/schema.ts +++ b/packages/cli/src/tools/config/schema.ts @@ -1,10 +1,7 @@ import t, {SchemaLike} from 'joi'; const map = (key: RegExp | SchemaLike, value: SchemaLike) => - t - .object() - .unknown(true) - .pattern(key, value); + t.object().unknown(true).pattern(key, value); /** * Schema for CommandT @@ -81,10 +78,7 @@ export const dependencyConfig = t .default({}), }) .default(), - assets: t - .array() - .items(t.string()) - .default([]), + assets: t.array().items(t.string()).default([]), hooks: map(t.string(), t.string()).default({}), params: t .array() @@ -107,14 +101,8 @@ export const dependencyConfig = t linkConfig: t.func(), }), ).default({}), - commands: t - .array() - .items(command) - .default([]), - healthChecks: t - .array() - .items(healthCheck) - .default([]), + commands: t.array().items(command).default([]), + healthChecks: t.array().items(healthCheck).default([]), }) .unknown(true) .default(); @@ -190,14 +178,8 @@ export const projectConfig = t .default({}), }) .default(), - assets: t - .array() - .items(t.string()) - .default([]), - commands: t - .array() - .items(command) - .default([]), + assets: t.array().items(t.string()).default([]), + commands: t.array().items(command).default([]), platforms: map( t.string(), t.object({