appium-doctor
Version:
Test environment for fitness to run Appium
196 lines (176 loc) • 5.89 kB
JavaScript
import '@colors/colors';
import _ from 'lodash';
import log from './logger';
import { version } from '../../package.json'; // eslint-disable-line import/no-unresolved
class FixSkippedError extends Error {
}
class DoctorCheck {
constructor (opts = {}) {
this.autofix = !!opts.autofix;
}
diagnose () { throw new Error('Not Implemented!'); }
fix () {
// return string for manual fixes.
throw new Error('Not Implemented!');
}
}
class Doctor {
constructor () {
this.checks = [];
this.checkOptionals = [];
this.toFix = [];
this.toFixOptionals = [];
}
register (checks) {
checks = Array.isArray(checks) ? checks : [checks];
this.checks = this.checks.concat(checks);
}
async diagnose () {
log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`);
this.toFix = [];
for (const check of this.checks) {
const res = await check.diagnose();
if (res.optional) {
this.checkOptionals.push(check);
continue;
}
await this.diagnosticResultMessage(res, this.toFix, check);
}
log.info(`### Diagnostic for necessary dependencies completed, ${await this.fixMessage(this.toFix.length)}. ###`);
log.info('');
log.info(`### Diagnostic for ${'optional'.yellow} dependencies starting ###`);
this.toFixOptionals = [];
for (const checkOptional of this.checkOptionals) {
await this.diagnosticResultMessage(await checkOptional.diagnose(), this.toFixOptionals, checkOptional);
}
log.info(`### Diagnostic for optional dependencies completed, ${await this.fixMessage(this.toFixOptionals.length, true)}. ###`);
log.info('');
}
async reportManualFixes (fix, fixOptioal) {
const manualFixes = _.filter(fix, (f) => !f?.check?.autofix);
const manualFixesOptional = _.filter(fixOptioal, (f) => !f?.check?.autofix);
if (manualFixes.length > 0) {
log.info('### Manual Fixes Needed ###');
log.info('The configuration cannot be automatically fixed, please do the following first:');
// for manual fixes, the fix method always return a string
const fixMessages = [];
for (const f of manualFixes) {
fixMessages.push(await f.check.fix());
}
for (const m of _.uniq(fixMessages)) {
log.warn(` \u279C ${m}`);
}
log.info('');
}
if (manualFixesOptional.length > 0) {
log.info('### Optional Manual Fixes ###');
log.info('The configuration can install optionally. Please do the following manually:');
// for manual fixes, the fix method always return a string
const fixMessages = [];
for (const f of manualFixesOptional) {
fixMessages.push(await f.check.fix());
}
for (const m of _.uniq(fixMessages)) {
log.warn(` \u279C ${m}`);
}
log.info('');
}
if (manualFixes.length > 0 || manualFixesOptional.length > 0) {
log.info('###');
log.info('');
log.info('Bye! Run appium-doctor again when all manual fixes have been applied!');
log.info('');
return true;
}
return false;
}
async runAutoFix (f) {
log.info(`### Fixing: ${f.error} ###`);
try {
await f.check.fix();
} catch (err) {
if (err instanceof FixSkippedError) {
log.info(`### Skipped fix ###`);
return;
} else {
log.warn(`${err}`.replace(/\n$/g, ''));
log.info(`### Fix did not succeed ###`);
return;
}
}
log.info('Checking if this was fixed:');
let res = await f.check.diagnose();
if (res.ok) {
f.fixed = true;
log.info(` ${'\u2714'.green} ${res.message}`);
log.info(`### Fix was successfully applied ###`);
} else {
log.info(` ${'\u2716'.red} ${res.message}`);
log.info(`### Fix was applied but issue remains ###`);
}
}
async runAutoFixes () {
let autoFixes = _.filter(this.toFix, (f) => f?.check?.autofix);
for (let f of autoFixes) {
await this.runAutoFix(f);
log.info('');
}
if (_.find(autoFixes, (f) => !f.fixed)) {
// a few issues remain.
log.info('Bye! A few issues remain, fix manually and/or rerun appium-doctor!');
} else {
// nothing left to fix.
log.info('Bye! All issues have been fixed!');
}
log.info('');
}
async run () {
log.warn('[Deprecated] Please use appium-doctor installed with "npm install @appium/doctor --location=global"');
log.info(`Appium Doctor v.${version}`);
await this.diagnose();
if (await this.reportSuccess(this.toFix.length, this.toFixOptionals.length)) {
return;
}
if (await this.reportManualFixes(this.toFix, this.toFixOptionals)) {
return;
}
await this.runAutoFixes();
}
//// generating messages
async diagnosticResultMessage (result, toFixList, check) { // eslint-disable-line require-await
if (result.ok) {
log.info(` ${'\u2714'.green} ${result.message}`);
} else {
const errorMessage = result.optional ? ` ${'\u2716'.yellow} ${result.message}` : ` ${'\u2716'.red} ${result.message}`;
log.warn(errorMessage);
toFixList.push({
error: errorMessage,
check
});
}
}
async fixMessage (length, optional = false) { // eslint-disable-line require-await
let message;
switch (length) {
case 0:
message = 'no fix';
break;
case 1:
message = 'one fix';
break;
default:
message = `${length} fixes`;
}
return `${message} ${optional ? 'possible' : 'needed'}`;
}
async reportSuccess (length, lengthOptional) { // eslint-disable-line require-await
if (length === 0 && lengthOptional === 0) {
log.info('Everything looks good, bye!');
log.info('');
return true;
} else {
return false;
}
}
}
export { Doctor, DoctorCheck, FixSkippedError };