appium 3.2.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/lib/cli/args.d.ts +16 -12
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +15 -35
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/driver-command.d.ts +51 -93
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +11 -66
- package/build/lib/cli/driver-command.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +211 -415
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +384 -653
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/extension.d.ts +11 -16
- package/build/lib/cli/extension.d.ts.map +1 -1
- package/build/lib/cli/extension.js +10 -28
- package/build/lib/cli/extension.js.map +1 -1
- package/build/lib/cli/parser.d.ts +40 -69
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +24 -59
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/cli/plugin-command.d.ts +50 -90
- package/build/lib/cli/plugin-command.d.ts.map +1 -1
- package/build/lib/cli/plugin-command.js +11 -63
- package/build/lib/cli/plugin-command.js.map +1 -1
- package/build/lib/cli/setup-command.d.ts +21 -26
- package/build/lib/cli/setup-command.d.ts.map +1 -1
- package/build/lib/cli/setup-command.js +13 -55
- package/build/lib/cli/setup-command.js.map +1 -1
- package/build/lib/cli/utils.d.ts +27 -29
- package/build/lib/cli/utils.d.ts.map +1 -1
- package/build/lib/cli/utils.js +29 -31
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/config-file.d.ts +24 -67
- package/build/lib/config-file.d.ts.map +1 -1
- package/build/lib/config-file.js +56 -115
- package/build/lib/config-file.js.map +1 -1
- package/build/lib/config.d.ts +42 -44
- package/build/lib/config.d.ts.map +1 -1
- package/build/lib/config.js +75 -107
- package/build/lib/config.js.map +1 -1
- package/build/lib/constants.d.ts +23 -23
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +10 -15
- package/build/lib/constants.js.map +1 -1
- package/build/lib/doctor/doctor.d.ts +40 -57
- package/build/lib/doctor/doctor.d.ts.map +1 -1
- package/build/lib/doctor/doctor.js +29 -60
- package/build/lib/doctor/doctor.js.map +1 -1
- package/build/lib/grid-register.d.ts +32 -7
- package/build/lib/grid-register.d.ts.map +1 -1
- package/build/lib/grid-register.js +84 -48
- package/build/lib/grid-register.js.map +1 -1
- package/build/lib/logsink.d.ts +13 -22
- package/build/lib/logsink.d.ts.map +1 -1
- package/build/lib/logsink.js +48 -103
- package/build/lib/logsink.js.map +1 -1
- package/build/lib/main.js +1 -1
- package/build/lib/main.js.map +1 -1
- package/build/lib/schema/arg-spec.d.ts +32 -107
- package/build/lib/schema/arg-spec.d.ts.map +1 -1
- package/build/lib/schema/arg-spec.js +11 -107
- package/build/lib/schema/arg-spec.js.map +1 -1
- package/build/lib/schema/cli-args.d.ts +3 -15
- package/build/lib/schema/cli-args.d.ts.map +1 -1
- package/build/lib/schema/cli-args.js +15 -105
- package/build/lib/schema/cli-args.js.map +1 -1
- package/build/lib/schema/cli-transformers.d.ts +15 -12
- package/build/lib/schema/cli-transformers.d.ts.map +1 -1
- package/build/lib/schema/cli-transformers.js +15 -45
- package/build/lib/schema/cli-transformers.js.map +1 -1
- package/build/lib/schema/index.d.ts +2 -2
- package/build/lib/schema/index.d.ts.map +1 -1
- package/build/lib/schema/index.js.map +1 -1
- package/build/lib/schema/keywords.d.ts +12 -20
- package/build/lib/schema/keywords.d.ts.map +1 -1
- package/build/lib/schema/keywords.js +6 -51
- package/build/lib/schema/keywords.js.map +1 -1
- package/build/lib/schema/schema.d.ts +106 -231
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +75 -345
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.d.ts +59 -238
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +55 -207
- package/build/lib/utils.js.map +1 -1
- package/lib/cli/{args.js → args.ts} +40 -51
- package/lib/cli/driver-command.ts +122 -0
- package/lib/cli/{extension-command.js → extension-command.ts} +610 -689
- package/lib/cli/extension.ts +65 -0
- package/lib/cli/{parser.js → parser.ts} +48 -71
- package/lib/cli/plugin-command.ts +117 -0
- package/lib/cli/{setup-command.js → setup-command.ts} +57 -72
- package/lib/cli/utils.ts +97 -0
- package/lib/config-file.ts +212 -0
- package/lib/{config.js → config.ts} +129 -141
- package/lib/{constants.js → constants.ts} +30 -41
- package/lib/doctor/{doctor.js → doctor.ts} +81 -91
- package/lib/grid-register.ts +250 -0
- package/lib/{logsink.js → logsink.ts} +91 -137
- package/lib/main.js +1 -1
- package/lib/schema/arg-spec.ts +131 -0
- package/lib/schema/cli-args.ts +171 -0
- package/lib/schema/cli-transformers.ts +83 -0
- package/lib/schema/keywords.ts +96 -0
- package/lib/schema/schema.ts +449 -0
- package/lib/utils.ts +404 -0
- package/package.json +16 -16
- package/lib/cli/driver-command.js +0 -174
- package/lib/cli/extension.js +0 -74
- package/lib/cli/plugin-command.js +0 -164
- package/lib/cli/utils.js +0 -91
- package/lib/config-file.js +0 -228
- package/lib/grid-register.js +0 -146
- package/lib/schema/arg-spec.js +0 -229
- package/lib/schema/cli-args.js +0 -254
- package/lib/schema/cli-transformers.js +0 -113
- package/lib/schema/keywords.js +0 -136
- package/lib/schema/schema.js +0 -725
- package/lib/utils.js +0 -512
- /package/lib/schema/{index.js → index.ts} +0 -0
package/lib/cli/extension.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
|
3
|
-
import {isExtensionCommandArgs} from '../utils';
|
|
4
|
-
import DriverCliCommand from './driver-command';
|
|
5
|
-
import PluginCliCommand from './plugin-command';
|
|
6
|
-
import {errAndQuit, JSON_SPACES} from './utils';
|
|
7
|
-
|
|
8
|
-
export const commandClasses = Object.freeze(
|
|
9
|
-
/** @type {const} */ ({
|
|
10
|
-
[DRIVER_TYPE]: DriverCliCommand,
|
|
11
|
-
[PLUGIN_TYPE]: PluginCliCommand,
|
|
12
|
-
})
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
|
|
17
|
-
* can be represented as a JS object.
|
|
18
|
-
*
|
|
19
|
-
* @template {import('appium/types').CliExtensionCommand} Cmd
|
|
20
|
-
* @template {import('appium/types').CliExtensionSubcommand} SubCmd
|
|
21
|
-
* @param {import('appium/types').Args<Cmd, SubCmd>} args - JS object where the key is the parameter name (as defined in
|
|
22
|
-
* driver-parser.js)
|
|
23
|
-
* @param {import('../extension/extension-config').ExtensionConfig<Cmd>} config - Extension config object
|
|
24
|
-
*/
|
|
25
|
-
async function runExtensionCommand(args, config) {
|
|
26
|
-
// TODO driver config file should be locked while any of these commands are
|
|
27
|
-
// running to prevent weird situations
|
|
28
|
-
let jsonResult = null;
|
|
29
|
-
const {extensionType: type} = config; // NOTE this is the same as `args.subcommand`
|
|
30
|
-
if (!isExtensionCommandArgs(args)) {
|
|
31
|
-
throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
|
|
32
|
-
}
|
|
33
|
-
let {json, suppressOutput} = args;
|
|
34
|
-
json = Boolean(json);
|
|
35
|
-
if (suppressOutput) {
|
|
36
|
-
json = true;
|
|
37
|
-
}
|
|
38
|
-
const CommandClass = /** @type {ExtCommand<Cmd>} */ (commandClasses[type]);
|
|
39
|
-
const cmd = new CommandClass({config, json});
|
|
40
|
-
try {
|
|
41
|
-
jsonResult = await cmd.execute(args);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
// in the suppress output case, we are calling this function internally and should
|
|
44
|
-
// just throw instead of printing an error and ending the process
|
|
45
|
-
if (suppressOutput) {
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
48
|
-
errAndQuit(json, err);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (json && !suppressOutput) {
|
|
52
|
-
console.log(JSON.stringify(jsonResult, null, JSON_SPACES));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return jsonResult;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export {runExtensionCommand};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @template {ExtensionType} ExtType
|
|
62
|
-
* @typedef {ExtType extends DriverType ? Class<DriverCliCommand> : ExtType extends PluginType ? Class<PluginCliCommand> : never} ExtCommand
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
67
|
-
* @typedef {import('@appium/types').DriverType} DriverType
|
|
68
|
-
* @typedef {import('@appium/types').PluginType} PluginType
|
|
69
|
-
*/
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @template T
|
|
73
|
-
* @typedef {import('@appium/types').Class<T>} Class
|
|
74
|
-
*/
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import ExtensionCliCommand from './extension-command';
|
|
3
|
-
import {KNOWN_PLUGINS} from '../constants';
|
|
4
|
-
|
|
5
|
-
const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @extends {ExtensionCliCommand<PluginType>}
|
|
9
|
-
*/
|
|
10
|
-
export default class PluginCliCommand extends ExtensionCliCommand {
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
13
|
-
* @param {import('./extension-command').ExtensionCommandOptions<PluginType>} opts
|
|
14
|
-
*/
|
|
15
|
-
constructor({config, json}) {
|
|
16
|
-
super({config, json});
|
|
17
|
-
this.knownExtensions = KNOWN_PLUGINS;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Install a plugin
|
|
22
|
-
*
|
|
23
|
-
* @param {PluginInstallOpts} opts
|
|
24
|
-
* @returns {Promise<ExtRecord<PluginType>>}
|
|
25
|
-
*/
|
|
26
|
-
async install({plugin, installType, packageName}) {
|
|
27
|
-
return await super._install({
|
|
28
|
-
installSpec: plugin,
|
|
29
|
-
installType,
|
|
30
|
-
packageName,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Uninstall a plugin
|
|
36
|
-
*
|
|
37
|
-
* @param {PluginUninstallOpts} opts
|
|
38
|
-
* @returns {Promise<ExtRecord<PluginType>>}
|
|
39
|
-
*/
|
|
40
|
-
async uninstall({plugin}) {
|
|
41
|
-
return await super._uninstall({installSpec: plugin});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Update a plugin
|
|
46
|
-
*
|
|
47
|
-
* @param {PluginUpdateOpts} opts
|
|
48
|
-
* @returns {Promise<import('./extension-command').ExtensionUpdateResult>}
|
|
49
|
-
*/
|
|
50
|
-
async update({plugin, unsafe}) {
|
|
51
|
-
return await super._update({installSpec: plugin, unsafe});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Run a script from a plugin
|
|
56
|
-
*
|
|
57
|
-
* @param {PluginRunOptions} opts
|
|
58
|
-
* @returns {Promise<import('./extension-command').RunOutput>}
|
|
59
|
-
* @throws {Error} if the script fails to run
|
|
60
|
-
*/
|
|
61
|
-
async run({plugin, scriptName, extraArgs}) {
|
|
62
|
-
return await super._run({
|
|
63
|
-
installSpec: plugin,
|
|
64
|
-
scriptName,
|
|
65
|
-
extraArgs,
|
|
66
|
-
bufferOutput: this.isJsonOutput,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Runs doctor checks for the given plugin
|
|
72
|
-
*
|
|
73
|
-
* @param {PluginDoctorOptions} opts
|
|
74
|
-
* @returns {Promise<number>} The amount of executed doctor checks.
|
|
75
|
-
* @throws {Error} If any of the mandatory Doctor checks fails.
|
|
76
|
-
*/
|
|
77
|
-
async doctor({plugin}) {
|
|
78
|
-
return await super._doctor({
|
|
79
|
-
installSpec: plugin,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
*
|
|
85
|
-
* @param {import('./extension-command').ExtensionArgs} opts
|
|
86
|
-
* @returns {string}
|
|
87
|
-
*/
|
|
88
|
-
getPostInstallText({extName, extData}) {
|
|
89
|
-
return `Plugin ${extName}@${extData.version} successfully installed`.green;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Validates fields in `appium` field of `pluginMetadata`
|
|
94
|
-
*
|
|
95
|
-
* For any `package.json` fields which a plugin requires, validate the type of
|
|
96
|
-
* those fields on the `package.json` data, throwing an error if anything is
|
|
97
|
-
* amiss.
|
|
98
|
-
* @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
|
|
99
|
-
* @param {string} installSpec
|
|
100
|
-
* @returns {void}
|
|
101
|
-
*/
|
|
102
|
-
validateExtensionFields(pluginMetadata, installSpec) {
|
|
103
|
-
const missingFields = REQ_PLUGIN_FIELDS.reduce(
|
|
104
|
-
(acc, field) => (pluginMetadata[field] ? acc : [...acc, field]),
|
|
105
|
-
[]
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if (!_.isEmpty(missingFields)) {
|
|
109
|
-
throw new Error(
|
|
110
|
-
`Installed plugin "${installSpec}" did not expose correct fields for compatibility ` +
|
|
111
|
-
`with Appium. Missing fields: ${JSON.stringify(missingFields)}`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
119
|
-
* @typedef {import('@appium/types').PluginType} PluginType
|
|
120
|
-
*/
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @template {ExtensionType} ExtType
|
|
124
|
-
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
|
125
|
-
*/
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Options for {@linkcode PluginCliCommand.install}
|
|
129
|
-
* @typedef PluginInstallOpts
|
|
130
|
-
* @property {string} plugin - the name or spec of a plugin to install
|
|
131
|
-
* @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
|
|
132
|
-
* @property {string} [packageName] - for git/github installs, the plugin node package name
|
|
133
|
-
*/
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @typedef {import('appium/types').InstallType} InstallType
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Options for {@linkcode PluginCliCommand.uninstall}
|
|
141
|
-
* @typedef PluginUninstallOpts
|
|
142
|
-
* @property {string} plugin - the name or spec of a plugin to uninstall
|
|
143
|
-
*/
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Options for {@linkcode PluginCliCommand.update}
|
|
147
|
-
* @typedef PluginUpdateOpts
|
|
148
|
-
* @property {string} plugin - the name of the plugin to update
|
|
149
|
-
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
|
|
150
|
-
*/
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Options for {@linkcode PluginCliCommand.run}.
|
|
154
|
-
* @typedef PluginRunOptions
|
|
155
|
-
* @property {string} plugin - name of the plugin to run a script from
|
|
156
|
-
* @property {string} scriptName - name of the script to run
|
|
157
|
-
* @property {string[]} [extraArgs] - arguments to pass to the script
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Options for {@linkcode PluginCliCommand.doctor}.
|
|
162
|
-
* @typedef PluginDoctorOptions
|
|
163
|
-
* @property {string} plugin - name of the plugin to run doctor checks for
|
|
164
|
-
*/
|
package/lib/cli/utils.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
|
|
5
|
-
export const JSON_SPACES = 4;
|
|
6
|
-
|
|
7
|
-
/***
|
|
8
|
-
* Log an error to the console and exit the process.
|
|
9
|
-
* @param {boolean} json - whether we should log json or text
|
|
10
|
-
* @param {any} msg - error message, object, Error instance, etc.
|
|
11
|
-
*/
|
|
12
|
-
export function errAndQuit(json, msg) {
|
|
13
|
-
if (json) {
|
|
14
|
-
console.log(JSON.stringify({error: `${msg}`}, null, JSON_SPACES));
|
|
15
|
-
} else {
|
|
16
|
-
console.error(`${msg}`.red);
|
|
17
|
-
if (msg.stderr) {
|
|
18
|
-
console.error(`${msg.stderr}`.red);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Conditionally log something to the console
|
|
26
|
-
* @param {boolean} json - whether we are in json mode (and should therefore not log)
|
|
27
|
-
* @param {string} msg - string to log
|
|
28
|
-
*/
|
|
29
|
-
export function log(json, msg) {
|
|
30
|
-
if (!json) {
|
|
31
|
-
console.log(msg);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Start a spinner, execute an async function, and then stop the spinner
|
|
37
|
-
* @param {boolean} json - whether we are in json mode (and should therefore not log)
|
|
38
|
-
* @param {string} msg - string to log
|
|
39
|
-
* @param {function} fn - function to wrap with spinning
|
|
40
|
-
*/
|
|
41
|
-
export async function spinWith(json, msg, fn) {
|
|
42
|
-
if (json) {
|
|
43
|
-
return await fn();
|
|
44
|
-
}
|
|
45
|
-
const spinner = ora(msg).start();
|
|
46
|
-
let res;
|
|
47
|
-
try {
|
|
48
|
-
res = await fn();
|
|
49
|
-
spinner.succeed();
|
|
50
|
-
return res;
|
|
51
|
-
} catch (err) {
|
|
52
|
-
spinner.fail();
|
|
53
|
-
throw err;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export class RingBuffer {
|
|
58
|
-
constructor(size = 50) {
|
|
59
|
-
this.size = size;
|
|
60
|
-
this.buffer = [];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get the current buffer contents
|
|
65
|
-
*
|
|
66
|
-
* @returns {any[]}
|
|
67
|
-
*/
|
|
68
|
-
getBuff() {
|
|
69
|
-
return this.buffer;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Remove the oldest item from the buffer
|
|
74
|
-
*
|
|
75
|
-
* @returns {void}
|
|
76
|
-
*/
|
|
77
|
-
dequeue() {
|
|
78
|
-
this.buffer.shift();
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Add an item to the buffer
|
|
82
|
-
*
|
|
83
|
-
* @param {any} item
|
|
84
|
-
*/
|
|
85
|
-
enqueue(item) {
|
|
86
|
-
if (this.buffer.length >= this.size) {
|
|
87
|
-
this.dequeue();
|
|
88
|
-
}
|
|
89
|
-
this.buffer.push(item);
|
|
90
|
-
}
|
|
91
|
-
}
|
package/lib/config-file.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import betterAjvErrors from '@sidvind/better-ajv-errors';
|
|
2
|
-
import {lilconfig} from 'lilconfig';
|
|
3
|
-
import _ from 'lodash';
|
|
4
|
-
import * as yaml from 'yaml';
|
|
5
|
-
import {getSchema, validate} from './schema/schema';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* lilconfig loader to handle `.yaml` files
|
|
9
|
-
* @type {import('lilconfig').LoaderSync}
|
|
10
|
-
*/
|
|
11
|
-
function yamlLoader(filepath, content) {
|
|
12
|
-
try {
|
|
13
|
-
return yaml.parse(content);
|
|
14
|
-
} catch (e) {
|
|
15
|
-
throw new Error(`The YAML config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A cache of the raw config file (a JSON string) at a filepath.
|
|
21
|
-
* This is used for better error reporting.
|
|
22
|
-
* Note that config files needn't be JSON, but it helps if they are.
|
|
23
|
-
* @type {Map<string,RawJson>}
|
|
24
|
-
*/
|
|
25
|
-
const rawConfig = new Map();
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Custom JSON loader that caches the raw config file (for use with `better-ajv-errors`).
|
|
29
|
-
* If it weren't for this cache, this would be unnecessary.
|
|
30
|
-
* @type {import('lilconfig').LoaderSync}
|
|
31
|
-
*/
|
|
32
|
-
function jsonLoader(filepath, content) {
|
|
33
|
-
rawConfig.set(filepath, content);
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(content);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
throw new Error(`The JSON config at '${filepath}' cannot be loaded. Original error: ${e.message}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Loads a config file from an explicit path
|
|
43
|
-
* @param {LilconfigAsyncSearcher} lc - lilconfig instance
|
|
44
|
-
* @param {string} filepath - Path to config file
|
|
45
|
-
* @returns {Promise<import('lilconfig').LilconfigResult>}
|
|
46
|
-
*/
|
|
47
|
-
async function loadConfigFile(lc, filepath) {
|
|
48
|
-
try {
|
|
49
|
-
// removing "await" will cause any rejection to _not_ be caught in this block!
|
|
50
|
-
return await lc.load(filepath);
|
|
51
|
-
} catch (/** @type {unknown} */ err) {
|
|
52
|
-
if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ENOENT') {
|
|
53
|
-
/** @type {NodeJS.ErrnoException} */ (
|
|
54
|
-
err
|
|
55
|
-
).message = `Config file not found at user-provided path: ${filepath}`;
|
|
56
|
-
throw err;
|
|
57
|
-
} else if (err instanceof SyntaxError) {
|
|
58
|
-
// generally invalid JSON
|
|
59
|
-
err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`;
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
62
|
-
throw err;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Searches for a config file
|
|
68
|
-
* @param {LilconfigAsyncSearcher} lc - lilconfig instance
|
|
69
|
-
* @returns {Promise<import('lilconfig').LilconfigResult>}
|
|
70
|
-
*/
|
|
71
|
-
async function searchConfigFile(lc) {
|
|
72
|
-
return await lc.search();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Given an array of errors and the result of loading a config file, generate a
|
|
77
|
-
* helpful string for the user.
|
|
78
|
-
*
|
|
79
|
-
* - If `opts` contains a `json` property, this should be the original JSON
|
|
80
|
-
* _string_ of the config file. This is only applicable if the config file
|
|
81
|
-
* was in JSON format. If present, it will associate line numbers with errors.
|
|
82
|
-
* - If `errors` happens to be empty, this will throw.
|
|
83
|
-
* @param {import('ajv').ErrorObject[]} errors - Non-empty array of errors. Required.
|
|
84
|
-
* @param {ReadConfigFileResult['config']|any} [config] -
|
|
85
|
-
* Configuration & metadata
|
|
86
|
-
* @param {FormatConfigErrorsOptions} [opts]
|
|
87
|
-
* @throws {TypeError} If `errors` is empty
|
|
88
|
-
* @returns {string}
|
|
89
|
-
*/
|
|
90
|
-
export function formatErrors(errors = [], config = {}, opts = {}) {
|
|
91
|
-
if (errors && !errors.length) {
|
|
92
|
-
throw new TypeError('Array of errors must be non-empty');
|
|
93
|
-
}
|
|
94
|
-
return betterAjvErrors(getSchema(opts.schemaId), config, errors, {
|
|
95
|
-
json: opts.json,
|
|
96
|
-
format: 'cli',
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Given an optional path, read a config file. Validates the config file.
|
|
102
|
-
*
|
|
103
|
-
* Call {@link validate} if you already have a config object.
|
|
104
|
-
* @param {string} [filepath] - Path to config file, if we have one
|
|
105
|
-
* @param {ReadConfigFileOptions} [opts] - Options
|
|
106
|
-
* @public
|
|
107
|
-
* @returns {Promise<ReadConfigFileResult>} Contains config and filepath, if found, and any errors
|
|
108
|
-
*/
|
|
109
|
-
export async function readConfigFile(filepath, opts = {}) {
|
|
110
|
-
const lc = lilconfig('appium', {
|
|
111
|
-
loaders: {
|
|
112
|
-
'.yaml': yamlLoader,
|
|
113
|
-
'.yml': yamlLoader,
|
|
114
|
-
'.json': jsonLoader,
|
|
115
|
-
noExt: jsonLoader,
|
|
116
|
-
},
|
|
117
|
-
packageProp: 'appiumConfig',
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const result = filepath ? await loadConfigFile(lc, filepath) : await searchConfigFile(lc);
|
|
121
|
-
|
|
122
|
-
if (result?.filepath && !result?.isEmpty) {
|
|
123
|
-
const {pretty = true} = opts;
|
|
124
|
-
try {
|
|
125
|
-
let configResult;
|
|
126
|
-
const errors = validate(result.config);
|
|
127
|
-
if (_.isEmpty(errors)) {
|
|
128
|
-
configResult = {...result, errors};
|
|
129
|
-
} else {
|
|
130
|
-
const reason = formatErrors(errors, result.config, {
|
|
131
|
-
json: rawConfig.get(result.filepath),
|
|
132
|
-
pretty,
|
|
133
|
-
});
|
|
134
|
-
configResult = reason ? {...result, errors, reason} : {...result, errors};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// normalize (to camel case) all top-level property names of the config file
|
|
138
|
-
configResult.config = normalizeConfig(/** @type {AppiumConfig} */ (configResult.config));
|
|
139
|
-
|
|
140
|
-
return configResult;
|
|
141
|
-
} finally {
|
|
142
|
-
// clean up the raw config file cache, which is only kept to better report errors.
|
|
143
|
-
rawConfig.delete(result.filepath);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return result ?? {};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Convert schema property names to either a) the value of the `appiumCliDest` property, if any; or b) camel-case
|
|
151
|
-
* @param {AppiumConfig} config - Configuration object
|
|
152
|
-
* @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
|
|
153
|
-
*/
|
|
154
|
-
export function normalizeConfig(config) {
|
|
155
|
-
const schema = getSchema();
|
|
156
|
-
/**
|
|
157
|
-
* @param {AppiumConfig} config
|
|
158
|
-
* @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
|
|
159
|
-
* @todo Rewrite as a loop
|
|
160
|
-
* @returns Normalized section of config
|
|
161
|
-
*/
|
|
162
|
-
const normalize = (config, section) => {
|
|
163
|
-
const obj = _.isUndefined(section) ? config : _.get(config, section, config);
|
|
164
|
-
|
|
165
|
-
const mappedObj = _.mapKeys(obj, (__, prop) =>
|
|
166
|
-
_.get(schema, `properties.server.properties[${prop}].appiumCliDest`, _.camelCase(prop))
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
return _.mapValues(mappedObj, (value, property) => {
|
|
170
|
-
const nextSection = section ? `${section}.${property}` : property;
|
|
171
|
-
return isSchemaTypeObject(schema.properties?.[property])
|
|
172
|
-
? normalize(config, nextSection)
|
|
173
|
-
: value;
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Returns `true` if the schema prop references an object, or if it's an object itself
|
|
179
|
-
* @param {import('ajv').SchemaObject|object} schema - Referencing schema object
|
|
180
|
-
*/
|
|
181
|
-
const isSchemaTypeObject = (schema) => Boolean(schema?.properties || schema?.type === 'object');
|
|
182
|
-
|
|
183
|
-
return normalize(config);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Result of calling {@link readConfigFile}.
|
|
188
|
-
* @typedef ReadConfigFileResult
|
|
189
|
-
* @property {import('ajv').ErrorObject[]} [errors] - Validation errors
|
|
190
|
-
* @property {string} [filepath] - The path to the config file, if found
|
|
191
|
-
* @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
|
|
192
|
-
* @property {NormalizedAppiumConfig} [config] - The parsed configuration
|
|
193
|
-
* @property {string|import('@sidvind/better-ajv-errors').IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
|
|
194
|
-
*/
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Options for {@link readConfigFile}.
|
|
198
|
-
* @typedef ReadConfigFileOptions
|
|
199
|
-
* @property {boolean} [pretty=true] If `false`, do not use color and fancy formatting in the `reason` property of the {@link ReadConfigFileResult}. The value of `reason` is then suitable for machine-reading.
|
|
200
|
-
*/
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
|
|
204
|
-
* @typedef {ReturnType<import('lilconfig')["lilconfig"]>} LilconfigAsyncSearcher
|
|
205
|
-
*/
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* The contents of an Appium config file. Generated from schema
|
|
209
|
-
* @typedef {import('@appium/types').AppiumConfig} AppiumConfig
|
|
210
|
-
*/
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* The contents of an Appium config file with camelcased property names (and using `appiumCliDest` value if present). Generated from {@link AppiumConfig}
|
|
214
|
-
* @typedef {import('@appium/types').NormalizedAppiumConfig} NormalizedAppiumConfig
|
|
215
|
-
*/
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* The string should be a raw JSON string.
|
|
219
|
-
* @typedef {string} RawJson
|
|
220
|
-
*/
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Options for {@link formatErrors}.
|
|
224
|
-
* @typedef FormatConfigErrorsOptions
|
|
225
|
-
* @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
|
|
226
|
-
* @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
|
|
227
|
-
* @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
|
|
228
|
-
*/
|
package/lib/grid-register.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import {fs} from '@appium/support';
|
|
3
|
-
import logger from './logger';
|
|
4
|
-
import _ from 'lodash';
|
|
5
|
-
|
|
6
|
-
const hubUri = (config) => {
|
|
7
|
-
const protocol = config.hubProtocol || 'http';
|
|
8
|
-
return `${protocol}://${config.hubHost}:${config.hubPort}`;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Registers a new node with a selenium grid
|
|
13
|
-
* @param {string|object} data - Path or object representing selenium grid node config file. If a `string`, all subsequent arguments are required!
|
|
14
|
-
* @param {string} [addr] - Bind to this address
|
|
15
|
-
* @param {number} [port] - Bind to this port
|
|
16
|
-
* @param {string} [basePath] - Base path for the grid
|
|
17
|
-
*/
|
|
18
|
-
async function registerNode(data, addr, port, basePath) {
|
|
19
|
-
let configFilePath;
|
|
20
|
-
if (_.isString(data)) {
|
|
21
|
-
configFilePath = data;
|
|
22
|
-
try {
|
|
23
|
-
data = await fs.readFile(data, 'utf-8');
|
|
24
|
-
} catch (err) {
|
|
25
|
-
logger.error(
|
|
26
|
-
`Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}`
|
|
27
|
-
);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
data = JSON.parse(data);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
throw logger.errorWithException(
|
|
34
|
-
`Syntax error in node configuration file ${configFilePath}: ${err.message}`
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
postRequest(data, addr, port, basePath);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function registerToGrid(postOptions, configHolder) {
|
|
43
|
-
try {
|
|
44
|
-
const {status} = await axios(postOptions);
|
|
45
|
-
if (status !== 200) {
|
|
46
|
-
throw new Error(`Request failed with code ${status}`);
|
|
47
|
-
}
|
|
48
|
-
logger.debug(
|
|
49
|
-
`Appium successfully registered with the the grid on ` + hubUri(configHolder.configuration)
|
|
50
|
-
);
|
|
51
|
-
} catch (err) {
|
|
52
|
-
logger.error(`An attempt to register with the grid was unsuccessful: ${err.message}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function postRequest(configHolder, addr, port, basePath) {
|
|
57
|
-
// Move Selenium 3 configuration properties to configuration object
|
|
58
|
-
if (!_.has(configHolder, 'configuration')) {
|
|
59
|
-
let configuration = {};
|
|
60
|
-
for (const property in /** @type {import('@appium/types').StringRecord} */ (configHolder)) {
|
|
61
|
-
if (_.has(configHolder, property) && property !== 'capabilities') {
|
|
62
|
-
configuration[property] = configHolder[property];
|
|
63
|
-
delete configHolder[property];
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/** @type {import('@appium/types').StringRecord} */ (configHolder).configuration = configuration;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// if the node config does not have the appium/webdriver url, host, and port,
|
|
70
|
-
// automatically add it based on how appium was initialized
|
|
71
|
-
// otherwise, we will take whatever the user setup
|
|
72
|
-
// because we will always set localhost/127.0.0.1. this won't work if your
|
|
73
|
-
// node and grid aren't in the same place
|
|
74
|
-
if (
|
|
75
|
-
!configHolder.configuration.url ||
|
|
76
|
-
!configHolder.configuration.host ||
|
|
77
|
-
!configHolder.configuration.port
|
|
78
|
-
) {
|
|
79
|
-
configHolder.configuration.url = `http://${addr}:${port}${basePath}`;
|
|
80
|
-
configHolder.configuration.host = addr;
|
|
81
|
-
configHolder.configuration.port = port;
|
|
82
|
-
}
|
|
83
|
-
// if the node config does not have id automatically add it
|
|
84
|
-
if (!configHolder.configuration.id) {
|
|
85
|
-
configHolder.configuration.id = `http://${configHolder.configuration.host}:${configHolder.configuration.port}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// the post options
|
|
89
|
-
const regRequest = {
|
|
90
|
-
url: `${hubUri(configHolder.configuration)}/grid/register`,
|
|
91
|
-
method: 'POST',
|
|
92
|
-
data: configHolder,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
if (configHolder.configuration.register !== true) {
|
|
96
|
-
logger.debug(`No registration sent (${configHolder.configuration.register} = false)`);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const registerCycleInterval = configHolder.configuration.registerCycle;
|
|
101
|
-
if (isNaN(registerCycleInterval) || registerCycleInterval <= 0) {
|
|
102
|
-
logger.warn(
|
|
103
|
-
`'registerCycle' is not a valid positive number. ` +
|
|
104
|
-
`No registration request will be sent to the grid.`
|
|
105
|
-
);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// initiate a new Thread
|
|
109
|
-
let first = true;
|
|
110
|
-
logger.debug(
|
|
111
|
-
`Starting auto register thread for the grid. ` +
|
|
112
|
-
`Will try to register every ${registerCycleInterval} ms.`
|
|
113
|
-
);
|
|
114
|
-
setInterval(async function registerRetry() {
|
|
115
|
-
if (first) {
|
|
116
|
-
first = false;
|
|
117
|
-
await registerToGrid(regRequest, configHolder);
|
|
118
|
-
} else if (!(await isAlreadyRegistered(configHolder))) {
|
|
119
|
-
// make the http POST to the grid for registration
|
|
120
|
-
await registerToGrid(regRequest, configHolder);
|
|
121
|
-
}
|
|
122
|
-
}, registerCycleInterval);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function isAlreadyRegistered(configHolder) {
|
|
126
|
-
//check if node is already registered
|
|
127
|
-
const id = configHolder.configuration.id;
|
|
128
|
-
try {
|
|
129
|
-
const {data, status} = await axios({
|
|
130
|
-
url: `${hubUri(configHolder.configuration)}/grid/api/proxy?id=${id}`,
|
|
131
|
-
timeout: 10000,
|
|
132
|
-
});
|
|
133
|
-
if (status !== 200) {
|
|
134
|
-
throw new Error(`Request failed with code ${status}`);
|
|
135
|
-
}
|
|
136
|
-
if (!data.success) {
|
|
137
|
-
// if register fail, print the debug msg
|
|
138
|
-
logger.debug(`Grid registration error: ${data.msg}`);
|
|
139
|
-
}
|
|
140
|
-
return data.success;
|
|
141
|
-
} catch (err) {
|
|
142
|
-
logger.debug(`Hub down or not responding: ${err.message}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export default registerNode;
|