appium
Version:
Automation for Apps.
241 lines (216 loc) • 7.24 kB
JavaScript
// @ts-check
/* eslint-disable no-console, promise/prefer-await-to-then */
/**
* This script is intended to be run as a `postinstall` lifecycle script,
* and will automatically install extensions if requested by the user.
*
* If the current working directory is within a project which has `appium`
* as a dependency, this script does nothing; extensions must be managed
* via `npm` or another package manager.
*
* If `CI=1` is in the environment, this script will exit with a non-zero
* code upon failure (which will typically break a build). Otherwise, it
* will always exit with code 0, even if errors occur.
*
* @module
* @example
* `npm install -g appium --drivers=uiautomator2,xcuitest --plugins=images`
*/
const B = require('bluebird');
const path = require('node:path');
const {realpath} = require('node:fs/promises');
B.config({
cancellation: true,
});
/** @type {typeof import('../lib/cli/extension').runExtensionCommand} */
let runExtensionCommand;
/** @type {typeof import('../lib/constants').DRIVER_TYPE} */
let DRIVER_TYPE;
/** @type {typeof import('../lib/constants').PLUGIN_TYPE} */
let PLUGIN_TYPE;
/** @type {typeof import('../lib/extension').loadExtensions} */
let loadExtensions;
const ora = require('ora');
/** @type {typeof import('@appium/support').env} */
let env;
/** @type {typeof import('@appium/support').util} */
let util;
/** @type {typeof import('@appium/support').logger} */
let logger;
function log(message) {
console.error(`[Appium] ${message}`);
}
/**
* This is a naive attempt at determining whether or not we are in a dev environment; in other
* words, is `postinstall` being run from within the `appium` monorepo?
*
* When we're in the monorepo, `npm_config_local_prefix` will be set to the root of the monorepo root
* dir when running this lifecycle script from an `npm install` in the monorepo root.
*
* `realpath` is necessary due to macOS omitting `/private` from paths
*/
async function isDevEnvironment() {
return (
process.env.npm_config_local_prefix &&
path.join(process.env.npm_config_local_prefix, 'packages', 'appium') ===
(await realpath(path.join(__dirname, '..')))
);
}
/**
* Setup / check environment if we should do anything here
* @returns {Promise<boolean>} `true` if Appium is built and ready to go
*/
async function init() {
if (await isDevEnvironment()) {
log('Dev environment likely; skipping automatic installation of extensions');
return false;
}
try {
({env, util, logger} = require('@appium/support'));
// @ts-ignore This is OK
({runExtensionCommand} = require('../build/lib/cli/extension'));
({DRIVER_TYPE, PLUGIN_TYPE} = require('../build/lib/constants'));
// @ts-ignore This is OK
({loadExtensions} = require('../build/lib/extension'));
logger.getLogger('Appium').level = 'error';
// if we're doing `npm install -g appium` then we will assume we don't have a local appium.
if (!process.env.npm_config_global && (await env.hasAppiumDependency(process.cwd()))) {
log(`Found local Appium installation; skipping automatic installation of extensions.`);
return false;
}
return true;
} catch {
log('Dev environment likely; skipping automatic installation of extensions');
return false;
}
}
async function main() {
if (!(await init())) {
return;
}
const driverEnv = process.env.npm_config_drivers;
const pluginEnv = process.env.npm_config_plugins;
const spinner = ora({
text: 'Looking for extensions to automatically install...',
prefixText: '[Appium]',
}).start();
if (!driverEnv && !pluginEnv) {
spinner.succeed('No drivers or plugins to automatically install.');
log(
'If desired, provide the argument "--drivers=<driver_name>[,<driver_name>...]" and/or ' +
'"--plugins=<plugin_name>[,<plugin_name>...]" to the "npm install appium" command. ' +
'The specified extensions will be installed automatically alongside Appium. ' +
'For a list of known extensions, run "appium <driver|plugin> list".'
);
return;
}
/**
* @type {[[typeof DRIVER_TYPE, string?], [typeof PLUGIN_TYPE, string?]]}
*/
const specs = [
[DRIVER_TYPE, driverEnv],
[PLUGIN_TYPE, pluginEnv],
];
spinner.start('Resolving Appium home directory...');
const appiumHome = await env.resolveAppiumHome();
spinner.succeed(`Found Appium home: ${appiumHome}`);
spinner.start('Loading extension data...');
const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
spinner.succeed('Loaded extension data.');
const installedStats = {[DRIVER_TYPE]: 0, [PLUGIN_TYPE]: 0};
for (const [type, extEnv] of specs) {
if (extEnv) {
for await (let ext of extEnv.split(',')) {
ext = ext.trim();
try {
await checkAndInstallExtension({
runExtensionCommand,
appiumHome,
type,
ext,
driverConfig,
pluginConfig,
spinner,
});
installedStats[type]++;
} catch (e) {
spinner.fail(`Could not install ${type} "${ext}": ${e.message}`);
if (process.env.CI) {
process.exitCode = 1;
}
return;
}
}
}
}
spinner.succeed(
`Done. ${installedStats[DRIVER_TYPE]} ${util.pluralize(
'driver',
installedStats[DRIVER_TYPE]
)} and ${installedStats[PLUGIN_TYPE]} ${util.pluralize(
'plugin',
installedStats[PLUGIN_TYPE]
)} are installed.`
);
}
/**
* @privateRemarks the two `@ts-ignore` directives here are because I have no idea what's wrong with
* the types and don't want to spend more time on it. regardless, it seems to work for now.
* @param {CheckAndInstallExtensionsOpts} opts
*/
async function checkAndInstallExtension({
runExtensionCommand,
appiumHome,
type,
ext,
driverConfig,
pluginConfig,
spinner,
}) {
const extList = await runExtensionCommand(
// @ts-ignore This is OK
{
appiumHome,
subcommand: type,
[`${type}Command`]: 'list',
showInstalled: true,
suppressOutput: true,
},
type === DRIVER_TYPE ? driverConfig : pluginConfig
);
if (extList[ext]) {
spinner.info(`The ${type} "${ext}" is already installed.`);
return;
}
spinner.start(`Installing ${type} "${ext}"...`);
await runExtensionCommand(
// @ts-ignore this is OK
{
subcommand: type,
appiumHome,
[`${type}Command`]: 'install',
suppressOutput: true,
[type]: ext,
},
type === DRIVER_TYPE ? driverConfig : pluginConfig
);
spinner.succeed(`Installed ${type} "${ext}".`);
}
if (require.main === module) {
main().catch((e) => {
log(e);
process.exitCode = 1;
});
}
module.exports = main;
/**
* @typedef CheckAndInstallExtensionsOpts
* @property {typeof runExtensionCommand} runExtensionCommand
* @property {string} appiumHome
* @property {DRIVER_TYPE | PLUGIN_TYPE} type
* @property {string} ext
* @property {import('../lib/extension/driver-config').DriverConfig} driverConfig
* @property {import('../lib/extension/plugin-config').PluginConfig} pluginConfig
* @property {import('ora').Ora} spinner
*/