appium
Version:
Automation for Apps.
390 lines • 15.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.rootDir = exports.APPIUM_VER = void 0;
exports.updateBuildInfo = updateBuildInfo;
exports.showDebugInfo = showDebugInfo;
exports.getGitRev = getGitRev;
exports.getBuildInfo = getBuildInfo;
exports.checkNodeOk = checkNodeOk;
exports.showBuildInfo = showBuildInfo;
exports.getNonDefaultServerArgs = getNonDefaultServerArgs;
exports.showConfig = showConfig;
exports.requireDir = requireDir;
/* eslint-disable no-console */
const lodash_1 = __importDefault(require("lodash"));
const support_1 = require("@appium/support");
const axios_1 = __importDefault(require("axios"));
const teen_process_1 = require("teen_process");
const semver = __importStar(require("semver"));
const node_os_1 = __importDefault(require("node:os"));
const utils_1 = require("./utils");
const bluebird_1 = __importDefault(require("bluebird"));
const schema_1 = require("./schema/schema");
exports.APPIUM_VER = utils_1.npmPackage.version;
const ENGINES = /** @type {Record<string,string>} */ (utils_1.npmPackage.engines);
const MIN_NODE_VERSION = ENGINES.node;
exports.rootDir = support_1.fs.findRoot(__dirname);
const GIT_BINARY = `git${support_1.system.isWindows() ? '.exe' : ''}`;
const GITHUB_API = 'https://api.github.com/repos/appium/appium';
/**
* @type {import('appium/types').BuildInfo}
*/
const BUILD_INFO = {
version: exports.APPIUM_VER,
};
function getNodeVersion() {
return /** @type {import('semver').SemVer} */ (semver.coerce(process.version));
}
/**
* @param {boolean} [useGithubApiFallback]
* @returns {Promise<void>}
*/
async function updateBuildInfo(useGithubApiFallback = false) {
const sha = await getGitRev(useGithubApiFallback);
if (!sha) {
return;
}
BUILD_INFO['git-sha'] = sha;
const buildTimestamp = await getGitTimestamp(sha, useGithubApiFallback);
if (buildTimestamp) {
BUILD_INFO.built = buildTimestamp;
}
}
/** @type {() => Promise<string?>} */
const getFullGitPath = lodash_1.default.memoize(async function getFullGitPath() {
try {
return await support_1.fs.which(GIT_BINARY);
}
catch {
return null;
}
});
/**
* Prints server debug into into the console.
*
* @param {{
* driverConfig: import('./extension/driver-config').DriverConfig,
* pluginConfig: import('./extension/plugin-config').PluginConfig,
* appiumHome: string
* }} info
* @returns {Promise<void>}
*/
async function showDebugInfo({ driverConfig, pluginConfig, appiumHome }) {
const getNpmVersion = async () => {
const { stdout } = await support_1.npm.exec('--version', [], { cwd: process.cwd() });
return lodash_1.default.trim(stdout);
};
const findNpmLocation = async () => await support_1.fs.which(support_1.system.isWindows() ? 'npm.cmd' : 'npm');
const [npmVersion, npmLocation] = await bluebird_1.default.all([
...([getNpmVersion, findNpmLocation].map((f) => getSafeResult(f, 'unknown'))),
/** @type {any} */ (updateBuildInfo()),
]);
const debugInfo = {
os: {
platform: node_os_1.default.platform(),
release: node_os_1.default.release(),
arch: node_os_1.default.arch(),
homedir: node_os_1.default.homedir(),
username: node_os_1.default.userInfo().username,
},
node: {
version: process.version,
arch: process.arch,
cwd: process.cwd(),
argv: process.argv,
env: process.env,
npm: {
location: npmLocation,
version: npmVersion,
},
},
appium: {
location: exports.rootDir,
homedir: appiumHome,
build: getBuildInfo(),
drivers: driverConfig.installedExtensions,
plugins: pluginConfig.installedExtensions,
},
};
console.log(JSON.stringify(debugInfo, null, 2));
}
/**
* @param {boolean} [useGithubApiFallback]
* @returns {Promise<string?>}
*/
async function getGitRev(useGithubApiFallback = false) {
const fullGitPath = await getFullGitPath();
if (fullGitPath) {
try {
const { stdout } = await (0, teen_process_1.exec)(fullGitPath, ['rev-parse', 'HEAD'], {
cwd: __dirname,
});
return stdout.trim();
}
catch { }
}
if (!useGithubApiFallback) {
return null;
}
// If the package folder is not a valid git repository
// then fetch the corresponding tag info from GitHub
try {
return (await axios_1.default.get(`${GITHUB_API}/git/refs/tags/appium@${exports.APPIUM_VER}`, {
headers: {
'User-Agent': `Appium ${exports.APPIUM_VER}`,
},
})).data?.object?.sha;
}
catch { }
return null;
}
/**
* @param {string} commitSha
* @param {boolean} [useGithubApiFallback]
* @returns {Promise<string?>}
*/
async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
const fullGitPath = await getFullGitPath();
if (fullGitPath) {
try {
const { stdout } = await (0, teen_process_1.exec)(fullGitPath, ['show', '-s', '--format=%ci', commitSha], {
cwd: __dirname,
});
return stdout.trim();
}
catch { }
}
if (!useGithubApiFallback) {
return null;
}
try {
return (await axios_1.default.get(`${GITHUB_API}/git/tags/${commitSha}`, {
headers: {
'User-Agent': `Appium ${exports.APPIUM_VER}`,
},
})).data?.tagger?.date;
}
catch { }
return null;
}
/**
* Mutable object containing Appium build information. By default it
* only contains the Appium version, but is updated with the build timestamp
* and git commit hash asynchronously as soon as `updateBuildInfo` is called
* and succeeds.
* @returns {import('appium/types').BuildInfo}
*/
function getBuildInfo() {
return BUILD_INFO;
}
/**
* @returns {void}
* @throws {Error} If Node version is outside of the supported range
*/
function checkNodeOk() {
const version = getNodeVersion();
if (!semver.satisfies(version, MIN_NODE_VERSION)) {
throw new Error(`Node version must be at least ${MIN_NODE_VERSION}; current is ${version.version}`);
}
}
async function showBuildInfo() {
await updateBuildInfo(true);
console.log(JSON.stringify(getBuildInfo()));
}
/**
* Returns k/v pairs of server arguments which are _not_ the defaults.
* @param {Args} parsedArgs
* @returns {Args}
*/
function getNonDefaultServerArgs(parsedArgs) {
/**
* Flattens parsed args into a single level object for comparison with
* flattened defaults across server args and extension args.
* @param {Args} args
* @returns {Record<string, { value: any, argSpec: ArgSpec }>}
*/
const flatten = (args) => {
const argSpecs = (0, schema_1.getAllArgSpecs)();
const flattened = lodash_1.default.reduce([...argSpecs.values()], (acc, argSpec) => {
if (lodash_1.default.has(args, argSpec.dest)) {
acc[argSpec.dest] = { value: lodash_1.default.get(args, argSpec.dest), argSpec };
}
return acc;
},
/** @type {Record<string, { value: any, argSpec: ArgSpec }>} */ ({}));
return flattened;
};
const args = flatten(parsedArgs);
// hopefully these function names are descriptive enough
const typesDiffer = /** @param {string} dest */ (dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
const defaultValueIsArray = /** @param {string} dest */ (dest) => lodash_1.default.isArray(defaultsFromSchema[dest]);
const argsValueIsArray = /** @param {string} dest */ (dest) => lodash_1.default.isArray(args[dest].value);
const arraysDiffer = /** @param {string} dest */ (dest) => lodash_1.default.gt(lodash_1.default.size(lodash_1.default.difference(args[dest].value, defaultsFromSchema[dest])), 0);
const valuesDiffer = /** @param {string} dest */ (dest) => args[dest].value !== defaultsFromSchema[dest];
const defaultIsDefined = /** @param {string} dest */ (dest) => !lodash_1.default.isUndefined(defaultsFromSchema[dest]);
// note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
const argValueNotArrayOrArraysDiffer = lodash_1.default.overSome([lodash_1.default.negate(argsValueIsArray), arraysDiffer]);
const defaultValueNotArrayAndValuesDiffer = lodash_1.default.overEvery([
lodash_1.default.negate(defaultValueIsArray),
valuesDiffer,
]);
/**
* This used to be a hideous conditional, but it's broken up into a hideous function instead.
* hopefully this makes things a little more understandable.
* - checks if the default value is defined
* - if so, and the default is not an array:
* - ensures the types are the same
* - ensures the values are equal
* - if so, and the default is an array:
* - ensures the args value is an array
* - ensures the args values do not differ from the default values
* @type {(dest: string) => boolean}
*/
const isNotDefault = lodash_1.default.overEvery([
defaultIsDefined,
lodash_1.default.overSome([
typesDiffer,
lodash_1.default.overEvery([defaultValueIsArray, argValueNotArrayOrArraysDiffer]),
defaultValueNotArrayAndValuesDiffer,
]),
]);
const defaultsFromSchema = (0, schema_1.getDefaultsForSchema)(true);
return lodash_1.default.reduce(lodash_1.default.pickBy(args, (__, key) => isNotDefault(key)),
// explodes the flattened object back into nested one
(acc, { value, argSpec }) => lodash_1.default.set(acc, argSpec.dest, value),
/** @type {Args} */ ({}));
}
/**
* Compacts an object for {@link showConfig}:
* 1. Removes `subcommand` key/value
* 2. Removes `undefined` values
* 3. Removes empty objects (but not `false` values)
* Does not operate recursively.
*/
const compactConfig = lodash_1.default.partial(lodash_1.default.omitBy, lodash_1.default, (value, key) => key === 'subcommand' || lodash_1.default.isUndefined(value) || (lodash_1.default.isObject(value) && lodash_1.default.isEmpty(value)));
/**
* Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
*
* The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
* but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
*
* @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
* @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
* @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
* @param {ParsedArgs} parsedArgs - Entire parsed args object
*/
function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
console.log('Appium Configuration\n');
console.log('from defaults:\n');
console.dir(compactConfig(defaults));
if (configResult.config) {
console.log(`\nfrom config file at ${configResult.filepath}:\n`);
console.dir(compactConfig(configResult.config));
}
else {
console.log(`\n(no configuration file loaded)`);
}
const compactedNonDefaultPreConfigArgs = compactConfig(nonDefaultPreConfigParsedArgs);
if (lodash_1.default.isEmpty(compactedNonDefaultPreConfigArgs)) {
console.log(`\n(no CLI parameters provided)`);
}
else {
console.log('\nvia CLI or function call:\n');
console.dir(compactedNonDefaultPreConfigArgs);
}
console.log('\nfinal configuration:\n');
console.dir(compactConfig(parsedArgs));
}
/**
* @param {string} root
* @param {boolean} [requireWriteable=true]
* @param {string} [displayName='folder path']
* @throws {Error}
*/
async function requireDir(root, requireWriteable = true, displayName = 'folder path') {
let stat;
try {
stat = await support_1.fs.stat(root);
}
catch (e) {
if (e.code === 'ENOENT') {
try {
await support_1.fs.mkdir(root, { recursive: true });
return;
}
catch { }
}
throw new Error(`The ${displayName} '${root}' must exist and be a valid directory`);
}
if (stat && !stat.isDirectory()) {
throw new Error(`The ${displayName} '${root}' must be a valid directory`);
}
if (requireWriteable) {
try {
await support_1.fs.access(root, support_1.fs.constants.W_OK);
}
catch {
throw new Error(`The ${displayName} '${root}' must be ` +
`writeable for the current user account '${node_os_1.default.userInfo().username}'`);
}
}
}
/**
* Calculates the result of the given function and return its value
* or the default one if there was an exception.
*
* @template T
* @param {() => Promise<T>} f
* @param {T} defaultValue
* @returns {Promise<T>}
*/
async function getSafeResult(f, defaultValue) {
try {
return await f();
}
catch {
return defaultValue;
}
}
/**
* @typedef {import('appium/types').ParsedArgs} ParsedArgs
* @typedef {import('appium/types').Args} Args
* @typedef {import('./schema/arg-spec').ArgSpec} ArgSpec
*/
//# sourceMappingURL=config.js.map