refactor: add type for driver-command,plugin-command,doctor,docutils,execute-child
MODIFY
packages/appium/lib/cli/driver-command.js
packages/appium/lib/cli/driver-command.js
@@ -8,6 +8,7 @@ const REQ_DRIVER_FIELDS = ['driverName', 'automationName', 'platformNames', 'mai
/**
* @extends {ExtensionCommand<DriverType>}
*/
+
export default class DriverCommand extends ExtensionCommand {
/**
* @param {import('./extension-command').ExtensionCommandOptions<DriverType>} opts
@@ -21,6 +22,7 @@ export default class DriverCommand extends ExtensionCommand {
* Install a driver
*
* @param {DriverInstallOpts} opts
+ * @return {Promise<ExtRecord<DriverType>>}
*/
async install({driver, installType, packageName}) {
return await super._install({
@@ -30,18 +32,40 @@ export default class DriverCommand extends ExtensionCommand {
});
}
+ /**
+ * Uninstall a driver
+ *
+ * @param {DriverUninstallOpts} opts
+ * @return {Promise<ExtRecord<DriverType>>}
+ */
async uninstall({driver}) {
return await super._uninstall({installSpec: driver});
}
+ /**
+ * Update a driver
+ *
+ * @param {DriverUpdateOpts} opts
+ * @return {Promise<import('./extension-command').ExtensionUpdateResult>}
+ */
async update({driver, unsafe}) {
return await super._update({installSpec: driver, unsafe});
}
+ /**
+ *
+ * @param {DriverRunOptions} opts
+ * @return {Promise<import('./extension-command').RunOutput>}
+ */
async run({driver, scriptName, extraArgs}) {
return await super._run({installSpec: driver, scriptName, extraArgs});
}
+ /**
+ *
+ * @param {import('./extension-command').ExtensionArgs} opts
+ * @returns {string}
+ */
getPostInstallText({extName, extData}) {
return (
`Driver ${extName}@${extData.version} successfully installed\n`.green +
@@ -75,17 +99,23 @@ export default class DriverCommand extends ExtensionCommand {
}
/**
- * @typedef DriverCommandOptions
- * @property {import('../extension/extension-config').ExtensionConfig<DriverType>} config
- * @property {boolean} json
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
+ * @typedef {import('@appium/types').DriverType} DriverType
*/
/**
- * @typedef {import('@appium/types').DriverType} DriverType
+ * @template {ExtensionType} ExtType
+ * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
+ */
+
+/**
+ * @typedef DriverCommandOptions
+ * @property {import('../extension/extension-config').ExtensionConfig<DriverType>} config
+ * @property {boolean} json
*/
/**
- * Options for {@linkcode ExtensionCommand._install}
+ * Options for {@linkcode DriverCommand.install}
* @typedef DriverInstallOpts
* @property {string} driver - the name or spec of a driver to install
* @property {InstallType} installType - how to install this driver. One of the INSTALL_TYPES
@@ -95,3 +125,24 @@ export default class DriverCommand extends ExtensionCommand {
/**
* @typedef {import('appium/types').InstallType} InstallType
*/
+
+/**
+ * Options for {@linkcode DriverCommand.uninstall}
+ * @typedef DriverUninstallOpts
+ * @property {string} driver - the name or spec of a driver to uninstall
+ */
+
+/**
+ * Options for {@linkcode DriverCommand.update}
+ * @typedef DriverUpdateOpts
+ * @property {string} driver - the name of the driver to update
+ * @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
+ */
+
+/**
+ * Options for {@linkcode DriverCommand.run}.
+ * @typedef DriverRunOptions
+ * @property {string} driver - name of the driver to run a script from
+ * @property {string} scriptName - name of the script to run
+ * @property {string[]} [extraArgs] - arguments to pass to the script
+ */

MODIFY
packages/appium/lib/cli/extension-command.js
packages/appium/lib/cli/extension-command.js
@@ -75,7 +75,7 @@ class ExtensionCommand {
* nor is something like `@returns {never}` which does not imply a thrown exception.
* @param {string} message
* @protected
- * @returns {Error}
+ * @throws {Error}
*/
_createFatalError(message) {
return new Error(this.log.decorate(message, 'error'));

MODIFY
packages/appium/lib/cli/plugin-command.js
packages/appium/lib/cli/plugin-command.js
@@ -17,6 +17,12 @@ export default class PluginCommand extends ExtensionCommand {
this.knownExtensions = KNOWN_PLUGINS;
}
+ /**
+ * Install a plugin
+ *
+ * @param {PluginInstallOpts} opts
+ * @returns {Promise<ExtRecord<PluginType>>}
+ */
async install({plugin, installType, packageName}) {
return await super._install({
installSpec: plugin,
@@ -25,26 +31,48 @@ export default class PluginCommand extends ExtensionCommand {
});
}
+ /**
+ * Uninstall a plugin
+ *
+ * @param {PluginUninstallOpts} opts
+ * @returns {Promise<ExtRecord<PluginType>>}
+ */
async uninstall({plugin}) {
return await super._uninstall({installSpec: plugin});
}
+ /**
+ * Update a plugin
+ *
+ * @param {PluginUpdateOpts} opts
+ * @returns {Promise<import('./extension-command').ExtensionUpdateResult>}
+ */
async update({plugin, unsafe}) {
return await super._update({installSpec: plugin, unsafe});
}
+ /**
+ *
+ * @param {PluginRunOptions} opts
+ * @returns {Promise<import('./extension-command').RunOutput>}
+ */
async run({plugin, scriptName, extraArgs}) {
return await super._run({installSpec: plugin, scriptName, extraArgs});
}
+ /**
+ *
+ * @param {import('./extension-command').ExtensionArgs} opts
+ * @returns {string}
+ */
getPostInstallText({extName, extData}) {
return `Plugin ${extName}@${extData.version} successfully installed`.green;
}
/**
- * Validates fields in `appium` field of `driverMetadata`
+ * Validates fields in `appium` field of `pluginMetadata`
*
- * For any `package.json` fields which a driver requires, validate the type of
+ * For any `package.json` fields which a plugin requires, validate the type of
* those fields on the `package.json` data, throwing an error if anything is
* amiss.
* @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
@@ -67,5 +95,44 @@ export default class PluginCommand extends ExtensionCommand {
}
/**
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
* @typedef {import('@appium/types').PluginType} PluginType
*/
+
+/**
+ * @template {ExtensionType} ExtType
+ * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
+ */
+
+/**
+ * Options for {@linkcode PluginCommand.install}
+ * @typedef PluginInstallOpts
+ * @property {string} plugin - the name or spec of a plugin to install
+ * @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
+ * @property {string} [packageName] - for git/github installs, the plugin node package name
+ */
+
+/**
+ * @typedef {import('appium/types').InstallType} InstallType
+ */
+
+/**
+ * Options for {@linkcode PluginCommand.uninstall}
+ * @typedef PluginUninstallOpts
+ * @property {string} plugin - the name or spec of a plugin to uninstall
+ */
+
+/**
+ * Options for {@linkcode PluginCommand.update}
+ * @typedef PluginUpdateOpts
+ * @property {string} plugin - the name of the plugin to update
+ * @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
+ */
+
+/**
+ * Options for {@linkcode PluginCommand.run}.
+ * @typedef PluginRunOptions
+ * @property {string} plugin - name of the plugin to run a script from
+ * @property {string} scriptName - name of the script to run
+ * @property {string[]} [extraArgs] - arguments to pass to the script
+ */

MODIFY
packages/doctor/lib/android.js
packages/doctor/lib/android.js
@@ -7,6 +7,9 @@ import '@colors/colors';
import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb';
import log from './logger';
+/**
+ * @type {import('./factory').DoctorCheckList}
+ */
let checks = [];
const javaHome = system.isWindows() ? '%JAVA_HOME%' : '$JAVA_HOME';

MODIFY
packages/doctor/lib/demo.js
packages/doctor/lib/demo.js
@@ -7,6 +7,9 @@ import {DoctorCheck, FixSkippedError} from './doctor';
import log from './logger';
import {fixIt} from './prompt';
+/**
+ * @type {import('./factory').DoctorCheckList}
+ */
let checks = [];
class DirCheck extends DoctorCheck {

MODIFY
packages/doctor/lib/dev.js
packages/doctor/lib/dev.js
@@ -4,6 +4,9 @@ import {fs, system} from '@appium/support';
import path from 'path';
import '@colors/colors';
+/**
+ * @type {import('./factory').DoctorCheckList}
+ */
let checks = [];
// Check PATH binaries

MODIFY
packages/doctor/lib/doctor.js
packages/doctor/lib/doctor.js
@@ -7,15 +7,29 @@ const {version} = fs.readPackageJsonFrom(__dirname);
class FixSkippedError extends Error {}
+/**
+ * Create interface for other Doctors
+ */
class DoctorCheck {
+ /**
+ * @param {DoctorOpts} opts
+ */
constructor(opts = {}) {
this.autofix = !!opts.autofix;
}
+ /**
+ * Every doctor diagnose the symptoms
+ * @throws {Error}
+ */
diagnose() {
throw new Error('Not Implemented!');
}
+ /**
+ * Every doctor suggest the solutions to fix the sickness
+ * @throws {Error}
+ */
fix() {
// return string for manual fixes.
throw new Error('Not Implemented!');
@@ -24,17 +38,28 @@ class DoctorCheck {
class Doctor {
constructor() {
+ /**
+ * All the sub check goes here after register
+ * @type {DoctorCheck[]}
+ */
this.checks = [];
this.checkOptionals = [];
this.toFix = [];
this.toFixOptionals = [];
}
+ /**
+ * Register all the sub check and combine them together
+ * @param {DoctorCheck[] | DoctorCheck} checks
+ */
register(checks) {
checks = Array.isArray(checks) ? checks : [checks];
this.checks = this.checks.concat(checks);
}
+ /**
+ * The doctor shows the report
+ */
async diagnose() {
log.info(`### Diagnostic for ${'necessary'.green} dependencies starting ###`);
this.toFix = [];
@@ -211,3 +236,8 @@ class Doctor {
}
export {Doctor, DoctorCheck, FixSkippedError};
+
+/**
+ * @typedef DoctorOpts
+ * @property {boolean?} autofix
+ */

MODIFY
packages/doctor/lib/factory.js
packages/doctor/lib/factory.js
@@ -6,6 +6,9 @@ import androidChecks from './android';
import devChecks from './dev';
import demoChecks from './demo';
+/**
+ * @type {DoctorGroup}
+ */
let checks = {generalChecks, iosChecks, androidChecks, devChecks, demoChecks};
let newDoctor = (opts) => {
@@ -19,3 +22,16 @@ let newDoctor = (opts) => {
};
export default newDoctor;
+
+/**
+ * @typedef {import('./doctor').DoctorCheck[]} DoctorCheckList
+ */
+
+/**
+ * @typedef DoctorGroup - Contain a group of Doctors
+ * @property {DoctorCheckList} generalChecks - Check AppiumHome, NodeBinary, NodeVersion, ffmpeg, mjpeg-consumer
+ * @property {DoctorCheckList} iosChecks - Check if iOS toolchains are installed
+ * @property {DoctorCheckList} androidChecks - Check if Android toolchains are installed
+ * @property {DoctorCheckList} devChecks - Check Path Binary and Android SDKs
+ * @property {DoctorCheckList} demoChecks - Check /tmp/appium-doctor/demo/*
+ */

MODIFY
packages/doctor/lib/general.js
packages/doctor/lib/general.js
@@ -6,9 +6,16 @@ import {util, env} from '@appium/support';
import {EOL} from 'os';
import '@colors/colors';
+/**
+ * @type {import('./factory').DoctorCheckList}
+ */
let checks = [];
class AppiumHomeCheck extends DoctorCheck {
+ /**
+ *
+ * @returns {UtilsResult}
+ */
async diagnose() {
return ok(`APPIUM_HOME is ${await env.resolveAppiumHome()}`);
}
@@ -59,6 +66,10 @@ class NodeVersionCheck extends DoctorCheck {
checks.push(new NodeVersionCheck());
class OptionalFfmpegCommandCheck extends DoctorCheck {
+ /**
+ *
+ * @returns {Promise<UtilsResult>}
+ */
async diagnose() {
const ffmpegPath = await resolveExecutablePath('ffmpeg');
return ffmpegPath
@@ -69,8 +80,11 @@ class OptionalFfmpegCommandCheck extends DoctorCheck {
)
: nokOptional('ffmpeg cannot be found');
}
- // eslint-disable-next-line require-await
- async fix() {
+ /**
+ *
+ * @returns {string}
+ */
+ fix() {
return `${
'ffmpeg'.bold
} is needed to record screen features. Please read https://www.ffmpeg.org/ to install it`;
@@ -107,3 +121,7 @@ export {
OptionalMjpegConsumerCommandCheck,
};
export default checks;
+
+/**
+ * @typedef {import('./utils').UtilsResult} UtilsResult
+ */

MODIFY
packages/doctor/lib/ios.js
packages/doctor/lib/ios.js
@@ -7,6 +7,9 @@ import {fixIt} from './prompt';
import EnvVarAndPathCheck from './env';
import '@colors/colors';
+/**
+ * @type {import('./factory').DoctorCheckList}
+ */
let checks = [];
let fixes = {};

MODIFY
packages/doctor/lib/prompt.js
packages/doctor/lib/prompt.js
import {inquirer} from './utils';
+/**
+ * @type {string}
+ */
let persistentResponse;
const fixItQuestion = {
@@ -7,6 +10,11 @@ const fixItQuestion = {
name: 'confirmation',
message: 'Fix it:',
choices: ['yes', 'no', 'always', 'never'],
+ /**
+ *
+ * @param {string} val
+ * @returns {string}
+ */
filter(val) {
return val.toLowerCase();
},

MODIFY
packages/doctor/lib/utils.js
packages/doctor/lib/utils.js
@@ -5,15 +5,34 @@ import {fs, system} from '@appium/support';
import {exec} from 'teen_process';
import {isFunction} from 'lodash';
+/**
+ * @param {string} message
+ * @returns {UtilsResult}
+ */
function ok(message) {
return {ok: true, optional: false, message};
}
+
+/**
+ * @param {string} message
+ * @returns {UtilsResult}
+ */
function nok(message) {
return {ok: false, optional: false, message};
}
+
+/**
+ * @param {string} message
+ * @returns {UtilsResult}
+ */
function okOptional(message) {
return {ok: true, optional: true, message};
}
+
+/**
+ * @param {string} message
+ * @returns {UtilsResult}
+ */
function nokOptional(message) {
return {ok: false, optional: true, message};
}
@@ -122,3 +141,10 @@ export {
getNpmPackageInfo,
resetLog,
};
+
+/**
+ * @typedef UtilsResult
+ * @property {boolean} ok
+ * @property {boolean} optional
+ * @property {string} message
+ */

MODIFY
packages/docutils/lib/mike.js
packages/docutils/lib/mike.js
@@ -6,11 +6,11 @@ const DEFAULT_BRANCH = 'gh-pages';
const MIKE_VER_STRING = 'mike 1.';
export class Mike {
- /** @type string */ remote;
- /** @type string */ branch;
- /** @type string? */ prefix;
- /** @type string */ configFile;
- /** @type boolean */ _mikeVerified = false;
+ /** @type {string} */ remote;
+ /** @type {string} */ branch;
+ /** @type {string?} */ prefix;
+ /** @type {string} */ configFile;
+ /** @type {boolean} */ _mikeVerified = false;
constructor(/** @type MikeOpts */ opts) {
this.remote = opts.remote || DEFAULT_REMOTE;
@@ -22,7 +22,7 @@ export class Mike {
/**
* Throw an error if the 'mike' binary cannot be found
*
- * @throws Error
+ * @throws {Error}
*/
async verifyMike() {
if (this._mikeVerified) {
@@ -46,7 +46,7 @@ export class Mike {
* @param {string} cmdName - the name of the mike command to run
* @param {string[]} cmdArgs - an array of command-specific arguments
*
- * @returns string[]
+ * @returns {string[]}
*/
getMikeArgs(cmdName, cmdArgs) {
return [
@@ -70,7 +70,7 @@ export class Mike {
* @param {string[]} [mikeArgs=[]] - the arguments to pass to the mike command
* @param {boolean?} [verify=true] - whether to verify mike exists first
*
- * @returns Promise<import('teen_process').ExecResult<string>>
+ * @returns {Promise<import('teen_process').ExecResult<string>>}
*/
async exec(mikeCmd, mikeArgs = [], verify = true) {
if (verify) {
@@ -84,7 +84,7 @@ export class Mike {
/**
* Return the list of mike deploys
*
- * @returns string[]
+ * @returns {string[]}
*/
async list() {
const {stdout} = await this.exec('list');

MODIFY
packages/docutils/lib/mkdocs.js
packages/docutils/lib/mkdocs.js
@@ -22,9 +22,9 @@ export async function verifyMkdocs() {
/**
* Run 'mkdocs build' on a project
*
- * @param {string} configPath path to mkdocs config yml
- * @param {string} outputDir directory mkdocs should build into
- * @param {?string} theme theme name
+ * @param {string} configPath - path to mkdocs config yml
+ * @param {string} outputDir - directory mkdocs should build into
+ * @param {string?} theme - theme name
*/
export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
await verifyMkdocs();
@@ -34,7 +34,7 @@ export async function mkdocsBuild(configPath, outputDir, theme = 'mkdocs') {
/**
* Run 'mkdocs serve' on a project
*
- * @param {string} configPath path to mkdocs config yml
+ * @param {string} configPath - path to mkdocs config yml
*/
export async function mkdocsServe(configPath) {
await verifyMkdocs();

MODIFY
packages/execute-driver-plugin/lib/execute-child.js
packages/execute-driver-plugin/lib/execute-child.js
@@ -5,6 +5,9 @@ import {logger, util} from 'appium/support';
import {attach} from 'webdriverio';
const log = logger.getLogger('ExecuteDriver Child');
+/**
+ * @type {Promise<void>}
+ */
let send;
// duplicate defining these keys here so we don't need to re-load a huge appium
@@ -12,12 +15,22 @@ let send;
export const W3C_ELEMENT_KEY = util.W3C_WEB_ELEMENT_IDENTIFIER;
export const MJSONWP_ELEMENT_KEY = 'ELEMENT';
-async function runScript(driverOpts, script, timeoutMs) {
+/**
+ * Run the script in a VM.
+ * @param {DriverScriptMessageEvent} eventParams
+ * @returns {Promise<RunScriptResult>}
+ * @throws {TypeError}
+ */
+async function runScript(eventParams) {
+ const {driverOpts, script, timeoutMs} = eventParams;
if (!_.isNumber(timeoutMs)) {
throw new TypeError('Timeout parameter must be a number');
}
- // set up fake logger
+ /**
+ * set up fake logger
+ * @type {string[]}
+ */
const logLevels = ['error', 'warn', 'log'];
const logs = {};
const consoleFns = {};
@@ -122,11 +135,22 @@ function coerceScriptResult(obj) {
return obj;
}
-async function main(driverOpts, script, timeoutMs) {
+/**
+ * Entry point to runScript
+ * @param {DriverScriptMessageEvent} eventParams
+ */
+async function main(eventParams) {
+ /**
+ * keep the response of runScript
+ * @type {ScriptResult}
+ */
let res;
+ log.info('Parameters received from parent process');
try {
- res = {success: await runScript(driverOpts, script, timeoutMs)};
+ res = {success: await runScript(eventParams)};
+ log.info('runScript success');
} catch (error) {
+ log.info('runScript error');
res = {error: {message: error.message, stack: error.stack}};
}
await send(res);
@@ -136,8 +160,34 @@ async function main(driverOpts, script, timeoutMs) {
if (require.main === module && _.isFunction(process.send)) {
send = B.promisify(process.send, {context: process});
log.info('Running driver execution in child process');
- process.on('message', ({driverOpts, script, timeoutMs}) => {
- log.info('Parameters received from parent process');
- main(driverOpts, script, timeoutMs);
- });
+ process.on('message', main);
}
+
+/**
+ * @typedef {import('webdriverio').AttachOptions} AttachOptions
+ */
+
+/**
+ * @typedef DriverScriptMessageEvent
+ * @property {AttachOptions} driverOpts - the driver options
+ * @property {string} script - the javascript to execute
+ * @property {number} timeoutMs - script timeout in milliseconds
+ */
+
+/**
+ * @typedef ScriptResult
+ * @property {any} success
+ * @property {ScriptResultError} error
+ */
+
+/**
+ * @typedef ScriptResultError
+ * @property {any} message
+ * @property {any} stack
+ */
+
+/**
+ * @typedef RunScriptResult
+ * @property {any} result
+ * @property {Object} logs
+ */

MODIFY
packages/execute-driver-plugin/lib/plugin.js
packages/execute-driver-plugin/lib/plugin.js
@@ -24,7 +24,7 @@ export default class ExecuteDriverPlugin extends BasePlugin {
* already been attached to the currently running session.
*
* @param {function} next - standard behaviour for executeDriverScript
- * @param {BaseDriver} driver - Appium driver handling this command
+ * @param {import('@appium/types').ExternalDriver} driver - Appium driver handling this command
* @param {string} script - the string representing the driver script to run
* @param {string} [scriptType='webdriverio'] - the name of the driver script
* library (currently only webdriverio is supported)
@@ -32,6 +32,7 @@ export default class ExecuteDriverPlugin extends BasePlugin {
*
* @returns {Object} - a JSONifiable object representing the return value of
* the script
+ * @throws {Error}
*/
async executeDriverScript(
next,

refactor: add type for driver-command,plugin-command,doctor,docutils,execute-child (#17962)