diff --git a/doctor/package.json b/doctor/package.json index d97d1119e..5486a0b02 100644 --- a/doctor/package.json +++ b/doctor/package.json @@ -1,6 +1,6 @@ { "name": "flipper-doctor", - "version": "0.6.1", + "version": "0.7.0", "description": "Utility for checking for issues with a flipper installation", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/doctor/src/environmentInfo.ts b/doctor/src/environmentInfo.ts index 65b0b8b6f..bdbcaef5e 100644 --- a/doctor/src/environmentInfo.ts +++ b/doctor/src/environmentInfo.ts @@ -35,35 +35,14 @@ async function retrieveAndParseEnvInfo(): Promise { return JSON.parse( await run( { - SDKs: ['iOS SDK', 'Android SDK'], + SDKs: ['iOS SDK'], IDEs: ['Xcode'], - Languages: ['Java'], }, {json: true, showNotFound: true}, ), ); } -// Temporary workaround for https://github.com/facebook/flipper/issues/667 until it properly fixed in 'envinfo'. -async function workaroundForNewerJavaVersions(envInfo: any) { - try { - if (envInfo.Languages.Java && envInfo.Languages.Java.version) { - const [majorVersion] = envInfo.Languages.Java.version - .split('.') - .slice(0, 1) - .map((x: string) => parseInt(x, 10)); - if (8 < majorVersion && majorVersion < 11) { - process.env.JAVA_OPTS = - '-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee'; - return await retrieveAndParseEnvInfo(); - } - } - } catch (e) { - console.error(e); - } - return envInfo; -} - export async function getEnvInfo(): Promise { - return workaroundForNewerJavaVersions(await retrieveAndParseEnvInfo()); + return await retrieveAndParseEnvInfo(); } diff --git a/doctor/src/index.ts b/doctor/src/index.ts index 19ac3b415..8cbc7706e 100644 --- a/doctor/src/index.ts +++ b/doctor/src/index.ts @@ -10,8 +10,10 @@ import {exec} from 'child_process'; import {promisify} from 'util'; import {EnvironmentInfo, getEnvInfo} from './environmentInfo'; -export {getEnvInfo} from './environmentInfo'; +export {EnvironmentInfo, getEnvInfo} from './environmentInfo'; import * as watchman from 'fb-watchman'; +import * as fs from 'fs'; +import * as path from 'path'; export type HealthcheckCategory = { label: string; @@ -36,12 +38,12 @@ export type Healthcheck = { key: string; label: string; isRequired?: boolean; - run: ( - env: EnvironmentInfo, - ) => Promise<{ - hasProblem: boolean; - helpUrl?: string; - }>; + run: (env: EnvironmentInfo) => Promise; +}; + +export type HealthchecRunResult = { + hasProblem: boolean; + message: string; }; export type CategoryResult = [ @@ -68,9 +70,14 @@ export function getHealthchecks(): Healthchecks { key: 'common.openssl', label: 'OpenSSL Installed', run: async (_: EnvironmentInfo) => { - const isAvailable = await commandSucceeds('openssl version'); + const result = await tryExecuteCommand('openssl version'); + const hasProblem = result.hasProblem; + const message = hasProblem + ? `OpenSSL (https://wiki.openssl.org/index.php/Binaries) is not installed or not added to PATH. ${result.message}.` + : `OpenSSL (https://wiki.openssl.org/index.php/Binaries) is installed and added to PATH. ${result.message}.`; return { - hasProblem: !isAvailable, + hasProblem, + message, }; }, }, @@ -81,6 +88,9 @@ export function getHealthchecks(): Healthchecks { const isAvailable = await isWatchmanAvailable(); return { hasProblem: !isAvailable, + message: isAvailable + ? 'Watchman file watching service (https://facebook.github.io/watchman/) is installed and added to PATH. Live reloading after changes during Flipper plugin development is enabled.' + : 'Watchman file watching service (https://facebook.github.io/watchman/) is not installed or not added to PATH. Live reloading after changes during Flipper plugin development is disabled.', }; }, }, @@ -95,9 +105,28 @@ export function getHealthchecks(): Healthchecks { key: 'android.sdk', label: 'SDK Installed', isRequired: true, - run: async (e: EnvironmentInfo) => ({ - hasProblem: e.SDKs['Android SDK'] === 'Not Found', - }), + run: async (_: EnvironmentInfo) => { + if (process.env.ANDROID_HOME) { + const androidHome = process.env.ANDROID_HOME; + if (!fs.existsSync(androidHome)) { + return { + hasProblem: true, + message: `ANDROID_HOME points to a folder which does not exist: ${androidHome}.`, + }; + } + const platformToolsDir = path.join(androidHome, 'platform-tools'); + if (!fs.existsSync(path.join(androidHome, 'platform-tools'))) { + return { + hasProblem: true, + message: `Android SDK Platform Tools not found at the expected location "${platformToolsDir}". Probably they are not installed.`, + }; + } + return await tryExecuteCommand( + path.join(platformToolsDir, 'adb') + ' version', + ); + } + return await tryExecuteCommand('adb version'); + }, }, ], }, @@ -112,40 +141,66 @@ export function getHealthchecks(): Healthchecks { key: 'ios.sdk', label: 'SDK Installed', isRequired: true, - run: async (e: EnvironmentInfo) => ({ - hasProblem: + run: async (e: EnvironmentInfo) => { + const hasProblem = !e.SDKs['iOS SDK'] || !e.SDKs['iOS SDK'].Platforms || - !e.SDKs['iOS SDK'].Platforms.length, - }), + !e.SDKs['iOS SDK'].Platforms.length; + const message = hasProblem + ? 'iOS SDK is not installed. You can install it using Xcode (https://developer.apple.com/xcode/).' + : `iOS SDK is installed for the following platforms: ${JSON.stringify( + e.SDKs['iOS SDK'].Platforms, + )}.`; + return { + hasProblem, + message, + }; + }, }, { key: 'ios.xcode', label: 'XCode Installed', isRequired: true, - run: async (e: EnvironmentInfo) => ({ - hasProblem: e.IDEs == null || e.IDEs.Xcode == null, - }), + run: async (e: EnvironmentInfo) => { + const hasProblem = e.IDEs == null || e.IDEs.Xcode == null; + const message = hasProblem + ? 'Xcode (https://developer.apple.com/xcode/) is not installed.' + : `Xcode version ${e.IDEs.Xcode.version} is installed at "${e.IDEs.Xcode.path}".`; + return { + hasProblem, + message, + }; + }, }, { key: 'ios.xcode-select', label: 'xcode-select set', isRequired: true, - run: async (_: EnvironmentInfo) => ({ - hasProblem: !(await commandSucceeds('xcode-select -p')), - }), + run: async (_: EnvironmentInfo) => { + const result = await tryExecuteCommand('xcode-select -p'); + const hasProblem = result.hasProblem; + const message = hasProblem + ? `Xcode version is not selected. You can select it using command "sudo xcode-select -switch Xcode.app". ${result.message}.` + : `Xcode version is selected. ${result.message}.`; + return { + hasProblem, + message, + }; + }, }, { key: 'ios.instruments', label: 'Instruments exists', isRequired: true, run: async (_: EnvironmentInfo) => { - const hasInstruments = await commandSucceeds( - 'which instruments', - ); - + const result = await tryExecuteCommand('which instruments'); + const hasProblem = result.hasProblem; + const message = hasProblem + ? `Instruments not found. Please try to re-install Xcode (https://developer.apple.com/xcode/). ${result.message}.` + : `Instruments are installed. ${result.message}.`; return { - hasProblem: !hasInstruments, + hasProblem, + message, }; }, }, @@ -153,7 +208,7 @@ export function getHealthchecks(): Healthchecks { } : { isSkipped: true, - skipReason: `Healthcheck is skipped, because iOS development is not supported on the current platform "${process.platform}"`, + skipReason: `Healthcheck is skipped, because iOS development is not supported on the current platform "${process.platform}".`, }), }, }; @@ -199,10 +254,21 @@ export async function runHealthchecks(): Promise< return results; } -async function commandSucceeds(command: string): Promise { - return await promisify(exec)(command) - .then(() => true) - .catch(() => false); +async function tryExecuteCommand( + command: string, +): Promise { + try { + const output = await promisify(exec)(command); + return { + hasProblem: false, + message: `Command "${command}" successfully executed with output: ${output.stdout}`, + }; + } catch (err) { + return { + hasProblem: true, + message: `Command "${command}" failed to execute with output: ${err.message}`, + }; + } } async function isWatchmanAvailable(): Promise { diff --git a/package.json b/package.json index 51840a66d..752ff293e 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "expand-tilde": "^2.0.2", "express": "^4.15.2", "fb-watchman": "^2.0.0", - "flipper-doctor": "^0.6.1", + "flipper-doctor": "^0.7.0", "fs-extra": "^8.0.1", "immer": "^5.2.1", "immutable": "^4.0.0-rc.12", diff --git a/src/chrome/DoctorSheet.tsx b/src/chrome/DoctorSheet.tsx index 9824801ef..f6d251804 100644 --- a/src/chrome/DoctorSheet.tsx +++ b/src/chrome/DoctorSheet.tsx @@ -88,6 +88,7 @@ const SideContainer = styled(FlexBox)({ const SideContainerText = styled(Text)({ display: 'block', wordWrap: 'break-word', + overflow: 'auto', }); const HealthcheckLabel = styled(Text)({ @@ -170,6 +171,7 @@ function HealthcheckIcon(props: {checkResult: HealthcheckResult}) { function HealthcheckDisplay(props: { label: string; result: HealthcheckResult; + selected?: boolean; onClick?: () => void; }) { return ( @@ -177,6 +179,7 @@ function HealthcheckDisplay(props: { @@ -187,27 +190,25 @@ function HealthcheckDisplay(props: { ); } -function SideMessageDisplay(props: { - isHealthcheckInProgress: boolean; - hasProblems: boolean; -}) { - if (props.isHealthcheckInProgress) { +function SideMessageDisplay(props: {children: React.ReactNode}) { + return {props.children}; +} + +function ResultMessage(props: {result: HealthcheckResult}) { + if (status === 'IN_PROGRESS') { + return

Doctor is running healthchecks...

; + } else if (hasProblems(props.result)) { return ( - - Doctor is running healthchecks... - - ); - } else if (props.hasProblems) { - return ( - - Doctor has discovered problems with your installation. - +

+ Doctor has discovered problems with your installation. Please click to + each item to get details. +

); } else { return ( - +

All good! Doctor has not discovered any issues with your installation. - +

); } } @@ -224,6 +225,7 @@ function hasNewProblems(result: HealthcheckResult) { export type State = { acknowledgeCheckboxVisible: boolean; acknowledgeOnClose?: boolean; + selectedCheckKey?: string; }; type Props = OwnProps & StateFromProps & DispatchFromProps; @@ -296,10 +298,20 @@ class DoctorSheet extends Component { helpUrl && shell.openExternal(helpUrl); } - async runHealthchecks() { + async runHealthchecks(): Promise { await runHealthchecks(this.props); } + getCheckMessage(checkKey: string): string { + for (const cat of Object.values(this.props.healthcheckReport.categories)) { + const check = Object.values(cat.checks).find(chk => chk.key === checkKey); + if (check) { + return check.result.message || ''; + } + } + return ''; + } + render() { return ( @@ -319,12 +331,17 @@ class DoctorSheet extends Component { {Object.values(category.checks).map(check => ( this.openHelpUrl(check.result.helpUrl) - : undefined + onClick={() => + this.setState({ + ...this.state, + selectedCheckKey: + this.state.selectedCheckKey === check.key + ? undefined + : check.key, + }) } /> ))} @@ -344,12 +361,16 @@ class DoctorSheet extends Component { - + + + {this.state.selectedCheckKey && ( +

{this.getCheckMessage(this.state.selectedCheckKey)}

+ )} + {!this.state.selectedCheckKey && ( + + )} +
+
diff --git a/src/reducers/__tests__/healthchecks.node.tsx b/src/reducers/__tests__/healthchecks.node.tsx index 2541aa3b1..ce8c0d685 100644 --- a/src/reducers/__tests__/healthchecks.node.tsx +++ b/src/reducers/__tests__/healthchecks.node.tsx @@ -14,8 +14,7 @@ import { updateHealthcheckResult, acknowledgeProblems, } from '../healthchecks'; -import {Healthchecks} from 'flipper-doctor'; -import {EnvironmentInfo} from 'flipper-doctor/lib/environmentInfo'; +import {Healthchecks, EnvironmentInfo} from 'flipper-doctor'; const HEALTHCHECKS: Healthchecks = { ios: { @@ -27,7 +26,7 @@ const HEALTHCHECKS: Healthchecks = { key: 'ios.sdk', label: 'SDK Installed', run: async (_env: EnvironmentInfo) => { - return {hasProblem: false}; + return {hasProblem: false, message: ''}; }, }, ], @@ -41,7 +40,7 @@ const HEALTHCHECKS: Healthchecks = { key: 'android.sdk', label: 'SDK Installed', run: async (_env: EnvironmentInfo) => { - return {hasProblem: true}; + return {hasProblem: true, message: 'Error'}; }, }, ], @@ -55,7 +54,7 @@ const HEALTHCHECKS: Healthchecks = { key: 'common.openssl', label: 'OpenSSL Istalled', run: async (_env: EnvironmentInfo) => { - return {hasProblem: false}; + return {hasProblem: false, message: ''}; }, }, ], diff --git a/src/reducers/healthchecks.tsx b/src/reducers/healthchecks.tsx index f305d90ab..b32fb0e4f 100644 --- a/src/reducers/healthchecks.tsx +++ b/src/reducers/healthchecks.tsx @@ -60,7 +60,6 @@ export type HealthcheckResult = { status: HealthcheckStatus; isAcknowledged?: boolean; message?: string; - helpUrl?: string; }; export type HealthcheckReportItem = { diff --git a/src/utils/runHealthchecks.tsx b/src/utils/runHealthchecks.tsx index d404b7769..9cae8f3ea 100644 --- a/src/utils/runHealthchecks.tsx +++ b/src/utils/runHealthchecks.tsx @@ -66,14 +66,14 @@ async function launchHealthchecks(options: HealthcheckOptions): Promise { checkResult.hasProblem && h.isRequired ? { status: 'FAILED', - helpUrl: checkResult.helpUrl, + message: checkResult.message, } : checkResult.hasProblem && !h.isRequired ? { status: 'WARNING', - helpUrl: checkResult.helpUrl, + message: checkResult.message, } - : {status: 'SUCCESS'}; + : {status: 'SUCCESS', message: checkResult.message}; options.updateHealthcheckResult(categoryKey, h.key, result); } } diff --git a/yarn.lock b/yarn.lock index 4f17e6ad1..839741ffe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4192,10 +4192,10 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== -flipper-doctor@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/flipper-doctor/-/flipper-doctor-0.6.1.tgz#7a10cbe655293332c509d3ca37611bc32ee5d514" - integrity sha512-XRN5LqTK9J+2K5ixPwEODHYRyfL3hs9qDsKzcPoecoHP2DxPBOJNm2d2+J03lqFMedYE63a6+WhWUXOQoBRyEQ== +flipper-doctor@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/flipper-doctor/-/flipper-doctor-0.7.0.tgz#642aca4004add6e94e29fa69e5ea58b5ab7b724a" + integrity sha512-cdT/nXiRkJH3Y2HYr3rCZyjOgk/+hLry4QYxp7gvKhKA6Nvy4SiPgSCae2aBTdFSe1KoQeqD3mFURrkVlZcsWg== dependencies: "@types/node" "^12.12.12" envinfo "^7.4.0"