appium 2.0.0-beta.8 → 2.0.0-rc.1

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.
Files changed (206) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +149 -58
  3. package/build/lib/appium.d.ts +229 -0
  4. package/build/lib/appium.d.ts.map +1 -0
  5. package/build/lib/appium.js +678 -441
  6. package/build/lib/appium.js.map +1 -0
  7. package/build/lib/cli/args.d.ts +17 -0
  8. package/build/lib/cli/args.d.ts.map +1 -0
  9. package/build/lib/cli/args.js +263 -300
  10. package/build/lib/cli/args.js.map +1 -0
  11. package/build/lib/cli/driver-command.d.ts +102 -0
  12. package/build/lib/cli/driver-command.d.ts.map +1 -0
  13. package/build/lib/cli/driver-command.js +131 -81
  14. package/build/lib/cli/driver-command.js.map +1 -0
  15. package/build/lib/cli/extension-command.d.ts +402 -0
  16. package/build/lib/cli/extension-command.d.ts.map +1 -0
  17. package/build/lib/cli/extension-command.js +799 -383
  18. package/build/lib/cli/extension-command.js.map +1 -0
  19. package/build/lib/cli/extension.d.ts +23 -0
  20. package/build/lib/cli/extension.d.ts.map +1 -0
  21. package/build/lib/cli/extension.js +71 -60
  22. package/build/lib/cli/extension.js.map +1 -0
  23. package/build/lib/cli/parser.d.ts +84 -0
  24. package/build/lib/cli/parser.d.ts.map +1 -0
  25. package/build/lib/cli/parser.js +252 -148
  26. package/build/lib/cli/parser.js.map +1 -0
  27. package/build/lib/cli/plugin-command.d.ts +99 -0
  28. package/build/lib/cli/plugin-command.d.ts.map +1 -0
  29. package/build/lib/cli/plugin-command.js +125 -81
  30. package/build/lib/cli/plugin-command.js.map +1 -0
  31. package/build/lib/cli/utils.d.ts +29 -0
  32. package/build/lib/cli/utils.d.ts.map +1 -0
  33. package/build/lib/cli/utils.js +72 -51
  34. package/build/lib/cli/utils.js.map +1 -0
  35. package/build/lib/config-file.d.ts +100 -0
  36. package/build/lib/config-file.d.ts.map +1 -0
  37. package/build/lib/config-file.js +207 -0
  38. package/build/lib/config-file.js.map +1 -0
  39. package/build/lib/config.d.ts +49 -0
  40. package/build/lib/config.d.ts.map +1 -0
  41. package/build/lib/config.js +262 -223
  42. package/build/lib/config.js.map +1 -0
  43. package/build/lib/constants.d.ts +56 -0
  44. package/build/lib/constants.d.ts.map +1 -0
  45. package/build/lib/constants.js +73 -0
  46. package/build/lib/constants.js.map +1 -0
  47. package/build/lib/extension/driver-config.d.ts +82 -0
  48. package/build/lib/extension/driver-config.d.ts.map +1 -0
  49. package/build/lib/extension/driver-config.js +210 -0
  50. package/build/lib/extension/driver-config.js.map +1 -0
  51. package/build/lib/extension/extension-config.d.ts +270 -0
  52. package/build/lib/extension/extension-config.d.ts.map +1 -0
  53. package/build/lib/extension/extension-config.js +601 -0
  54. package/build/lib/extension/extension-config.js.map +1 -0
  55. package/build/lib/extension/index.d.ts +48 -0
  56. package/build/lib/extension/index.d.ts.map +1 -0
  57. package/build/lib/extension/index.js +105 -0
  58. package/build/lib/extension/index.js.map +1 -0
  59. package/build/lib/extension/manifest-migrations.d.ts +27 -0
  60. package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
  61. package/build/lib/extension/manifest-migrations.js +134 -0
  62. package/build/lib/extension/manifest-migrations.js.map +1 -0
  63. package/build/lib/extension/manifest.d.ts +145 -0
  64. package/build/lib/extension/manifest.d.ts.map +1 -0
  65. package/build/lib/extension/manifest.js +528 -0
  66. package/build/lib/extension/manifest.js.map +1 -0
  67. package/build/lib/extension/package-changed.d.ts +11 -0
  68. package/build/lib/extension/package-changed.d.ts.map +1 -0
  69. package/build/lib/extension/package-changed.js +62 -0
  70. package/build/lib/extension/package-changed.js.map +1 -0
  71. package/build/lib/extension/plugin-config.d.ts +56 -0
  72. package/build/lib/extension/plugin-config.d.ts.map +1 -0
  73. package/build/lib/extension/plugin-config.js +102 -0
  74. package/build/lib/extension/plugin-config.js.map +1 -0
  75. package/build/lib/grid-register.d.ts +10 -0
  76. package/build/lib/grid-register.d.ts.map +1 -0
  77. package/build/lib/grid-register.js +122 -144
  78. package/build/lib/grid-register.js.map +1 -0
  79. package/build/lib/logger.d.ts +3 -0
  80. package/build/lib/logger.d.ts.map +1 -0
  81. package/build/lib/logger.js +5 -17
  82. package/build/lib/logger.js.map +1 -0
  83. package/build/lib/logsink.d.ts +4 -0
  84. package/build/lib/logsink.d.ts.map +1 -0
  85. package/build/lib/logsink.js +189 -184
  86. package/build/lib/logsink.js.map +1 -0
  87. package/build/lib/main.d.ts +62 -0
  88. package/build/lib/main.d.ts.map +1 -0
  89. package/build/lib/main.js +406 -234
  90. package/build/lib/main.js.map +1 -0
  91. package/build/lib/schema/arg-spec.d.ts +143 -0
  92. package/build/lib/schema/arg-spec.d.ts.map +1 -0
  93. package/build/lib/schema/arg-spec.js +164 -0
  94. package/build/lib/schema/arg-spec.js.map +1 -0
  95. package/build/lib/schema/cli-args.d.ts +19 -0
  96. package/build/lib/schema/cli-args.d.ts.map +1 -0
  97. package/build/lib/schema/cli-args.js +220 -0
  98. package/build/lib/schema/cli-args.js.map +1 -0
  99. package/build/lib/schema/cli-transformers.d.ts +5 -0
  100. package/build/lib/schema/cli-transformers.d.ts.map +1 -0
  101. package/build/lib/schema/cli-transformers.js +124 -0
  102. package/build/lib/schema/cli-transformers.js.map +1 -0
  103. package/build/lib/schema/index.d.ts +3 -0
  104. package/build/lib/schema/index.d.ts.map +1 -0
  105. package/build/lib/schema/index.js +19 -0
  106. package/build/lib/schema/index.js.map +1 -0
  107. package/build/lib/schema/keywords.d.ts +24 -0
  108. package/build/lib/schema/keywords.d.ts.map +1 -0
  109. package/build/lib/schema/keywords.js +128 -0
  110. package/build/lib/schema/keywords.js.map +1 -0
  111. package/build/lib/schema/schema.d.ts +260 -0
  112. package/build/lib/schema/schema.d.ts.map +1 -0
  113. package/build/lib/schema/schema.js +640 -0
  114. package/build/lib/schema/schema.js.map +1 -0
  115. package/build/lib/utils.d.ts +276 -0
  116. package/build/lib/utils.d.ts.map +1 -0
  117. package/build/lib/utils.js +373 -271
  118. package/build/lib/utils.js.map +1 -0
  119. package/build/types/cli.d.ts +134 -0
  120. package/build/types/cli.d.ts.map +1 -0
  121. package/build/types/cli.js +3 -0
  122. package/build/types/cli.js.map +1 -0
  123. package/build/types/index.d.ts +15 -0
  124. package/build/types/index.d.ts.map +1 -0
  125. package/build/types/index.js +19 -0
  126. package/build/types/index.js.map +1 -0
  127. package/build/types/manifest/base.d.ts +135 -0
  128. package/build/types/manifest/base.d.ts.map +1 -0
  129. package/build/types/manifest/base.js +3 -0
  130. package/build/types/manifest/base.js.map +1 -0
  131. package/build/types/manifest/index.d.ts +21 -0
  132. package/build/types/manifest/index.d.ts.map +1 -0
  133. package/build/types/manifest/index.js +42 -0
  134. package/build/types/manifest/index.js.map +1 -0
  135. package/build/types/manifest/v3.d.ts +139 -0
  136. package/build/types/manifest/v3.d.ts.map +1 -0
  137. package/build/types/manifest/v3.js +3 -0
  138. package/build/types/manifest/v3.js.map +1 -0
  139. package/build/types/manifest/v4.d.ts +139 -0
  140. package/build/types/manifest/v4.d.ts.map +1 -0
  141. package/build/types/manifest/v4.js +3 -0
  142. package/build/types/manifest/v4.js.map +1 -0
  143. package/driver.d.ts +1 -0
  144. package/driver.js +14 -0
  145. package/index.js +11 -0
  146. package/lib/appium.js +555 -185
  147. package/lib/cli/args.js +275 -407
  148. package/lib/cli/driver-command.js +132 -24
  149. package/lib/cli/extension-command.js +751 -272
  150. package/lib/cli/extension.js +47 -20
  151. package/lib/cli/parser.js +267 -95
  152. package/lib/cli/plugin-command.js +122 -22
  153. package/lib/cli/utils.js +24 -10
  154. package/lib/config-file.js +220 -0
  155. package/lib/config.js +243 -132
  156. package/lib/constants.js +79 -0
  157. package/lib/extension/driver-config.js +247 -0
  158. package/lib/extension/extension-config.js +709 -0
  159. package/lib/extension/index.js +116 -0
  160. package/lib/extension/manifest-migrations.js +136 -0
  161. package/lib/extension/manifest.js +580 -0
  162. package/lib/extension/package-changed.js +64 -0
  163. package/lib/extension/plugin-config.js +112 -0
  164. package/lib/grid-register.js +49 -35
  165. package/lib/logger.js +1 -2
  166. package/lib/logsink.js +59 -36
  167. package/lib/main.js +392 -104
  168. package/lib/schema/arg-spec.js +229 -0
  169. package/lib/schema/cli-args.js +241 -0
  170. package/lib/schema/cli-transformers.js +119 -0
  171. package/lib/schema/index.js +2 -0
  172. package/lib/schema/keywords.js +136 -0
  173. package/lib/schema/schema.js +725 -0
  174. package/lib/utils.js +315 -167
  175. package/package.json +84 -82
  176. package/plugin.d.ts +1 -0
  177. package/plugin.js +13 -0
  178. package/scripts/autoinstall-extensions.js +243 -0
  179. package/support.d.ts +1 -0
  180. package/support.js +13 -0
  181. package/tsconfig.json +25 -0
  182. package/types/cli.ts +193 -0
  183. package/types/index.ts +20 -0
  184. package/types/manifest/README.md +30 -0
  185. package/types/manifest/base.ts +158 -0
  186. package/types/manifest/index.ts +28 -0
  187. package/types/manifest/v3.ts +161 -0
  188. package/types/manifest/v4.ts +161 -0
  189. package/CHANGELOG.md +0 -3669
  190. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  191. package/build/lib/cli/argparse-actions.js +0 -104
  192. package/build/lib/cli/npm.js +0 -207
  193. package/build/lib/cli/parser-helpers.js +0 -93
  194. package/build/lib/driver-config.js +0 -77
  195. package/build/lib/drivers.js +0 -99
  196. package/build/lib/extension-config.js +0 -253
  197. package/build/lib/plugin-config.js +0 -59
  198. package/build/lib/plugins.js +0 -14
  199. package/lib/cli/argparse-actions.js +0 -77
  200. package/lib/cli/npm.js +0 -183
  201. package/lib/cli/parser-helpers.js +0 -91
  202. package/lib/driver-config.js +0 -46
  203. package/lib/drivers.js +0 -84
  204. package/lib/extension-config.js +0 -209
  205. package/lib/plugin-config.js +0 -34
  206. package/lib/plugins.js +0 -10
@@ -1,47 +1,74 @@
1
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';
2
7
 
3
- import DriverCommand from './driver-command';
4
- import PluginCommand from './plugin-command';
5
- import DriverConfig from '../driver-config';
6
- import PluginConfig from '../plugin-config';
7
- import { DRIVER_TYPE } from '../extension-config';
8
- import { errAndQuit, log, JSON_SPACES } from './utils';
8
+ export const commandClasses = Object.freeze(
9
+ /** @type {const} */ ({
10
+ [DRIVER_TYPE]: DriverCliCommand,
11
+ [PLUGIN_TYPE]: PluginCliCommand,
12
+ })
13
+ );
9
14
 
10
15
  /**
11
16
  * Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
12
17
  * can be represented as a JS object.
13
18
  *
14
- * @param {Object} args - JS object where the key is the parameter name (as defined in
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
15
22
  * driver-parser.js)
23
+ * @param {import('../extension/extension-config').ExtensionConfig<Cmd>} config - Extension config object
16
24
  */
17
- async function runExtensionCommand (args, type) {
25
+ async function runExtensionCommand(args, config) {
18
26
  // TODO driver config file should be locked while any of these commands are
19
27
  // running to prevent weird situations
20
28
  let jsonResult = null;
21
- const extCmd = args[`${type}Command`];
22
- if (!extCmd) {
29
+ const {extensionType: type} = config; // NOTE this is the same as `args.subcommand`
30
+ if (!isExtensionCommandArgs(args)) {
23
31
  throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
24
32
  }
25
- const {json, appiumHome} = args;
26
- const logFn = (msg) => log(json, msg);
27
- const ConfigClass = type === DRIVER_TYPE ? DriverConfig : PluginConfig;
28
- const CommandClass = type === DRIVER_TYPE ? DriverCommand : PluginCommand;
29
- const config = new ConfigClass(appiumHome, logFn);
33
+ let {json, suppressOutput} = args;
34
+ json = Boolean(json);
35
+ if (suppressOutput) {
36
+ json = true;
37
+ }
38
+ const CommandClass = /** @type {ExtCommand<Cmd>} */ (commandClasses[type]);
30
39
  const cmd = new CommandClass({config, json});
31
40
  try {
32
- await config.read();
33
41
  jsonResult = await cmd.execute(args);
34
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
+ }
35
48
  errAndQuit(json, err);
36
49
  }
37
50
 
38
- if (json) {
51
+ if (json && !suppressOutput) {
39
52
  console.log(JSON.stringify(jsonResult, null, JSON_SPACES));
40
53
  }
41
54
 
42
55
  return jsonResult;
43
56
  }
44
57
 
45
- export {
46
- runExtensionCommand,
47
- };
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
+ */
package/lib/cli/parser.js CHANGED
@@ -1,117 +1,289 @@
1
- import path from 'path';
1
+ import {fs} from '@appium/support';
2
+ import {ArgumentParser} from 'argparse';
2
3
  import _ from 'lodash';
3
- import { ArgumentParser } from 'argparse';
4
- import { sharedArgs, serverArgs, extensionArgs } from './args';
5
- import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
6
- import { rootDir } from '../utils';
4
+ import path from 'path';
5
+ import {DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND} from '../constants';
6
+ import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
7
+ import {rootDir} from '../config';
8
+ import {getExtensionArgs, getServerArgs} from './args';
7
9
 
10
+ export const EXTRA_ARGS = 'extraArgs';
8
11
 
9
- function makeDebugParser (parser) {
10
- parser.exit = (status, message = undefined) => {
11
- throw new Error(message);
12
- };
13
- }
12
+ /**
13
+ * If the parsed args do not contain any of these values, then we
14
+ * will automatially inject the `server` subcommand.
15
+ */
16
+ const NON_SERVER_ARGS = Object.freeze(
17
+ new Set([DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND, '-h', '--help', '-v', '--version'])
18
+ );
19
+
20
+ const version = fs.readPackageJsonFrom(rootDir).version;
21
+
22
+ /**
23
+ * A wrapper around `argparse`
24
+ *
25
+ * - Handles instantiation, configuration, and monkeypatching of an
26
+ * `ArgumentParser` instance for Appium server and its extensions
27
+ * - Handles error conditions, messages, and exit behavior
28
+ */
29
+ class ArgParser {
30
+ /**
31
+ * @param {boolean} [debug] - If true, throw instead of exit on error.
32
+ */
33
+ constructor(debug = false) {
34
+ const prog = process.argv[1] ? path.basename(process.argv[1]) : 'appium';
35
+ const parser = new ArgumentParser({
36
+ add_help: true,
37
+ description:
38
+ 'A webdriver-compatible server that facilitates automation of web, mobile, and other types of apps across various platforms.',
39
+ prog,
40
+ });
41
+
42
+ ArgParser._patchExit(parser);
43
+
44
+ /**
45
+ * Program name (typically `appium`)
46
+ * @type {string}
47
+ */
48
+ this.prog = prog;
14
49
 
15
- function getParser (debug = false) {
16
- const parser = new ArgumentParser({
17
- add_help: true,
18
- description: 'A webdriver-compatible server for use with native and hybrid iOS and Android applications.',
19
- prog: process.argv[1] ? path.basename(process.argv[1]) : 'appium',
20
- });
21
- if (debug) {
22
- makeDebugParser(parser);
50
+ /**
51
+ * If `true`, throw an error on parse failure instead of printing help
52
+ * @type {boolean}
53
+ */
54
+ this.debug = debug;
55
+
56
+ /**
57
+ * Wrapped `ArgumentParser` instance
58
+ * @type {ArgumentParser}
59
+ */
60
+ this.parser = parser;
61
+
62
+ parser.add_argument('-v', '--version', {
63
+ action: 'version',
64
+ version,
65
+ });
66
+
67
+ const subParsers = parser.add_subparsers({dest: 'subcommand'});
68
+
69
+ // add the 'server' subcommand, and store the raw arguments on the parser
70
+ // object as a way for other parts of the code to work with the arguments
71
+ // conceptually rather than just through argparse
72
+ const serverArgs = ArgParser._addServerToParser(subParsers);
73
+
74
+ this.rawArgs = serverArgs;
75
+
76
+ // add the 'driver' and 'plugin' subcommands
77
+ ArgParser._addExtensionCommandsToParser(subParsers);
78
+
79
+ // backwards compatibility / drop-in wrapper
80
+ /**
81
+ * @type {ArgParser['parseArgs']}
82
+ */
83
+ this.parse_args = this.parseArgs;
23
84
  }
24
- parser.add_argument('-v', '--version', {
25
- action: 'version',
26
- version: require(path.resolve(rootDir, 'package.json')).version
27
- });
28
- const subParsers = parser.add_subparsers({dest: 'subcommand'});
29
-
30
- // add the 'server' subcommand, and store the raw arguments on the parser
31
- // object as a way for other parts of the code to work with the arguments
32
- // conceptually rather than just through argparse
33
- const serverArgs = addServerToParser(sharedArgs, subParsers, debug);
34
- parser.rawArgs = serverArgs;
35
-
36
- // add the 'driver' and 'plugin' subcommands
37
- addExtensionsToParser(sharedArgs, subParsers, debug);
38
-
39
- // modify the parse_args function to insert the 'server' subcommand if the
40
- // user hasn't specified a subcommand or the global help command
41
- parser._parse_args = parser.parse_args.bind(parser);
42
- parser.parse_args = function (args, namespace) {
43
- if (_.isUndefined(args)) {
44
- args = [...process.argv.slice(2)];
45
- }
46
- if (!_.includes([DRIVER_TYPE, PLUGIN_TYPE, 'server', '-h', '--help', '-v', '--version'], args[0])) {
47
- args.splice(0, 0, 'server');
85
+
86
+ /**
87
+ * Parse arguments from the command line.
88
+ *
89
+ * If no subcommand is passed in, this method will inject the `server` subcommand.
90
+ *
91
+ * `ArgParser.prototype.parse_args` is an alias of this method.
92
+ * @template {import('appium/types').CliCommand} [Cmd=import('appium/types').CliCommandServer]
93
+ * @param {string[]} [args] - Array of arguments, ostensibly from `process.argv`. Gathers args from `process.argv` if not provided.
94
+ * @returns {import('appium/types').Args<Cmd>} - The parsed arguments
95
+ */
96
+ parseArgs(args = process.argv.slice(2)) {
97
+ if (!NON_SERVER_ARGS.has(args[0])) {
98
+ args.unshift(SERVER_SUBCOMMAND);
48
99
  }
49
- return this._parse_args(args, namespace);
50
- }.bind(parser);
51
- return parser;
52
- }
53
100
 
54
- function addServerToParser (sharedArgs, subParsers, debug = false) {
55
- const serverParser = subParsers.add_parser('server', {
56
- add_help: true,
57
- help: 'Run an Appium server',
58
- });
101
+ try {
102
+ const parsed = this.parser.parse_known_args(args);
103
+ const [knownArgs, unknownArgs] = parsed;
104
+ // XXX: you'd think that argparse, when given an alias for a subcommand,
105
+ // would set this value to the original subcommand name, but it doesn't.
106
+ if (knownArgs?.driverCommand === 'ls') {
107
+ knownArgs.driverCommand = 'list';
108
+ } else if (knownArgs?.pluginCommand === 'ls') {
109
+ knownArgs.pluginCommand = 'list';
110
+ }
111
+ if (
112
+ unknownArgs?.length &&
113
+ (knownArgs.driverCommand === 'run' || knownArgs.pluginCommand === 'run')
114
+ ) {
115
+ return ArgParser._transformParsedArgs(knownArgs, unknownArgs);
116
+ } else if (unknownArgs?.length) {
117
+ throw new Error(`[ERROR] Unrecognized arguments: ${unknownArgs.join(' ')}`);
118
+ }
119
+ return ArgParser._transformParsedArgs(knownArgs);
120
+ } catch (err) {
121
+ if (this.debug) {
122
+ throw err;
123
+ }
124
+ // this isn't tested via unit tests (we use `debug: true`) so may escape coverage.
59
125
 
60
- if (debug) {
61
- makeDebugParser(serverParser);
126
+ /* istanbul ignore next */
127
+ {
128
+ // eslint-disable-next-line no-console
129
+ console.error(); // need an extra space since argparse prints usage.
130
+ // eslint-disable-next-line no-console
131
+ console.error(err.message);
132
+ process.exit(1);
133
+ }
134
+ }
62
135
  }
63
136
 
64
- for (const [flagsOrNames, opts] of [...sharedArgs, ...serverArgs]) {
65
- // add_argument mutates arguments so make copies
66
- serverParser.add_argument(...flagsOrNames, {...opts});
137
+ /**
138
+ * Given an object full of arguments as returned by `argparser.parse_args`,
139
+ * expand the ones for extensions into a nested object structure and rename
140
+ * keys to match the intended destination.
141
+ *
142
+ * E.g., `{'driver-foo-bar': baz}` becomes `{driver: {foo: {bar: 'baz'}}}`
143
+ * @param {object} args
144
+ * @param {string[]} [unknownArgs]
145
+ * @returns {object}
146
+ */
147
+ static _transformParsedArgs(args, unknownArgs = []) {
148
+ const result = _.reduce(
149
+ args,
150
+ (unpacked, value, key) => {
151
+ if (!_.isUndefined(value) && hasArgSpec(key)) {
152
+ const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */ (getArgSpec(key));
153
+ _.set(unpacked, dest, value);
154
+ } else {
155
+ // this could be anything that _isn't_ a server arg
156
+ unpacked[key] = value;
157
+ }
158
+ return unpacked;
159
+ },
160
+ {}
161
+ );
162
+ result[EXTRA_ARGS] = unknownArgs;
163
+ return result;
67
164
  }
68
165
 
69
- return serverArgs;
70
- }
71
-
72
- function getDefaultServerArgs () {
73
- let defaults = {};
74
- for (let [, arg] of serverArgs) {
75
- defaults[arg.dest] = arg.default;
166
+ /**
167
+ * Patches the `exit()` method of the parser to throw an error, so we can handle it manually.
168
+ * @param {ArgumentParser} parser
169
+ */
170
+ static _patchExit(parser) {
171
+ parser.exit = (code, msg) => {
172
+ if (code) {
173
+ throw new Error(msg);
174
+ }
175
+ process.exit();
176
+ };
76
177
  }
77
- return defaults;
78
- }
79
178
 
80
- function addExtensionsToParser (sharedArgs, subParsers, debug = false) {
81
- for (const type of [DRIVER_TYPE, PLUGIN_TYPE]) {
82
- const extParser = subParsers.add_parser(type, {
179
+ /**
180
+ *
181
+ * @param {import('argparse').SubParser} subParser
182
+ * @returns {import('./args').ArgumentDefinitions}
183
+ */
184
+ static _addServerToParser(subParser) {
185
+ const serverParser = subParser.add_parser('server', {
83
186
  add_help: true,
84
- help: `Access the ${type} management CLI commands`,
187
+ help: 'Run an Appium server',
85
188
  });
86
- if (debug) {
87
- makeDebugParser(extParser);
189
+
190
+ ArgParser._patchExit(serverParser);
191
+
192
+ const serverArgs = getServerArgs();
193
+ for (const [flagsOrNames, opts] of serverArgs) {
194
+ // TS doesn't like the spread operator here.
195
+ // @ts-ignore
196
+ serverParser.add_argument(...flagsOrNames, {...opts});
88
197
  }
89
- const extSubParsers = extParser.add_subparsers({
90
- dest: `${type}Command`,
91
- });
92
- const parserSpecs = [
93
- {command: 'list', args: extensionArgs[type].list,
94
- help: `List available and installed ${type}s`},
95
- {command: 'install', args: extensionArgs[type].install,
96
- help: `Install a ${type}`},
97
- {command: 'uninstall', args: extensionArgs[type].uninstall,
98
- help: `Uninstall a ${type}`},
99
- {command: 'update', args: extensionArgs[type].update,
100
- help: `Update installed ${type}s to the latest version`},
101
- ];
102
-
103
- for (const {command, args, help} of parserSpecs) {
104
- const parser = extSubParsers.add_parser(command, {help});
105
- if (debug) {
106
- makeDebugParser(parser);
107
- }
108
- for (const [flagsOrNames, opts] of [...sharedArgs, ...args]) {
109
- // add_argument mutates params so make sure to send in copies instead
110
- parser.add_argument(...flagsOrNames, {...opts});
198
+
199
+ return serverArgs;
200
+ }
201
+
202
+ /**
203
+ * Adds extension sub-sub-commands to `driver`/`plugin` subcommands
204
+ * @param {import('argparse').SubParser} subParsers
205
+ */
206
+ static _addExtensionCommandsToParser(subParsers) {
207
+ for (const type of /** @type {[DriverType, PluginType]} */ ([DRIVER_TYPE, PLUGIN_TYPE])) {
208
+ const extParser = subParsers.add_parser(type, {
209
+ add_help: true,
210
+ help: `Access the ${type} management CLI commands`,
211
+ });
212
+
213
+ ArgParser._patchExit(extParser);
214
+
215
+ const extSubParsers = extParser.add_subparsers({
216
+ dest: `${type}Command`,
217
+ });
218
+ const extensionArgs = getExtensionArgs();
219
+ /**
220
+ * @type { {command: import('appium/types').CliExtensionSubcommand, args: import('./args').ArgumentDefinitions, help: string, aliases?: import('argparse').SubArgumentParserOptions['aliases']}[] }
221
+ */
222
+ const parserSpecs = [
223
+ {
224
+ command: 'list',
225
+ args: extensionArgs[type].list,
226
+ help: `List available and installed ${type}s`,
227
+ aliases: ['ls'],
228
+ },
229
+ {
230
+ command: 'install',
231
+ args: extensionArgs[type].install,
232
+ help: `Install a ${type}`,
233
+ },
234
+ {
235
+ command: 'uninstall',
236
+ args: extensionArgs[type].uninstall,
237
+ help: `Uninstall a ${type}`,
238
+ },
239
+ {
240
+ command: 'update',
241
+ args: extensionArgs[type].update,
242
+ help: `Update installed ${type}s to the latest version`,
243
+ },
244
+ {
245
+ command: 'run',
246
+ args: extensionArgs[type].run,
247
+ help:
248
+ `Run a script (defined inside the ${type}'s package.json under the ` +
249
+ `“scripts” field inside the “appium” field) from an installed ${type}`,
250
+ },
251
+ ];
252
+
253
+ for (const {command, args, help, aliases} of parserSpecs) {
254
+ const parser = extSubParsers.add_parser(command, {help, aliases: aliases ?? []});
255
+
256
+ ArgParser._patchExit(parser);
257
+
258
+ for (const [flagsOrNames, opts] of args) {
259
+ // add_argument mutates params so make sure to send in copies instead
260
+ if (flagsOrNames.length === 2) {
261
+ parser.add_argument(flagsOrNames[0], flagsOrNames[1], {...opts});
262
+ } else {
263
+ parser.add_argument(flagsOrNames[0], {...opts});
264
+ }
265
+ }
111
266
  }
112
267
  }
113
268
  }
114
269
  }
115
270
 
116
- export default getParser;
117
- export { getParser, getDefaultServerArgs };
271
+ /**
272
+ * Creates a {@link ArgParser} instance; finalizes the config schema.
273
+ *
274
+ * @constructs ArgParser
275
+ * @param {boolean} [debug] - If `true`, throw instead of exit upon parsing error
276
+ * @returns {ArgParser}
277
+ */
278
+ function getParser(debug) {
279
+ finalizeSchema();
280
+
281
+ return new ArgParser(debug);
282
+ }
283
+
284
+ export {getParser, ArgParser};
285
+
286
+ /**
287
+ * @typedef {import('@appium/types').DriverType} DriverType
288
+ * @typedef {import('@appium/types').PluginType} PluginType
289
+ */
@@ -1,43 +1,143 @@
1
1
  import _ from 'lodash';
2
- import ExtensionCommand from './extension-command';
3
- import { PLUGIN_TYPE } from '../extension-config';
4
- import { KNOWN_PLUGINS } from '../plugins';
2
+ import ExtensionCliCommand from './extension-command';
3
+ import {KNOWN_PLUGINS} from '../constants';
5
4
 
6
5
  const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
7
6
 
8
- export default class PluginCommand extends ExtensionCommand {
9
-
10
- constructor ({config, json}) {
11
- super({config, json, type: PLUGIN_TYPE});
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});
12
17
  this.knownExtensions = KNOWN_PLUGINS;
13
18
  }
14
19
 
15
- async install ({plugin, installType, packageName}) {
16
- return await super.install({ext: plugin, installType, packageName});
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});
17
42
  }
18
43
 
19
- async uninstall ({plugin}) {
20
- return await super.uninstall({ext: plugin});
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});
21
52
  }
22
53
 
23
- async update ({plugin, unsafe}) {
24
- return await super.update({ext: plugin, unsafe});
54
+ /**
55
+ *
56
+ * @param {PluginRunOptions} opts
57
+ * @returns {Promise<import('./extension-command').RunOutput>}
58
+ */
59
+ async run({plugin, scriptName, extraArgs}) {
60
+ return await super._run({
61
+ installSpec: plugin,
62
+ scriptName,
63
+ extraArgs,
64
+ bufferOutput: this.isJsonOutput,
65
+ });
25
66
  }
26
67
 
27
- getPostInstallText ({extName, extData}) {
68
+ /**
69
+ *
70
+ * @param {import('./extension-command').ExtensionArgs} opts
71
+ * @returns {string}
72
+ */
73
+ getPostInstallText({extName, extData}) {
28
74
  return `Plugin ${extName}@${extData.version} successfully installed`.green;
29
75
  }
30
76
 
31
- validateExtensionFields (appiumPkgData) {
32
- const missingFields = REQ_PLUGIN_FIELDS.reduce((acc, field) => (
33
- appiumPkgData[field] ? acc : [...acc, field]
34
- ), []);
77
+ /**
78
+ * Validates fields in `appium` field of `pluginMetadata`
79
+ *
80
+ * For any `package.json` fields which a plugin requires, validate the type of
81
+ * those fields on the `package.json` data, throwing an error if anything is
82
+ * amiss.
83
+ * @param {import('appium/types').ExtMetadata<PluginType>} pluginMetadata
84
+ * @param {string} installSpec
85
+ * @returns {void}
86
+ */
87
+ validateExtensionFields(pluginMetadata, installSpec) {
88
+ const missingFields = REQ_PLUGIN_FIELDS.reduce(
89
+ (acc, field) => (pluginMetadata[field] ? acc : [...acc, field]),
90
+ []
91
+ );
35
92
 
36
93
  if (!_.isEmpty(missingFields)) {
37
- throw new Error(`Installed plugin did not expose correct fields for compability ` +
38
- `with Appium. Missing fields: ${JSON.stringify(missingFields)}`);
94
+ throw new Error(
95
+ `Installed plugin "${installSpec}" did not expose correct fields for compability ` +
96
+ `with Appium. Missing fields: ${JSON.stringify(missingFields)}`
97
+ );
39
98
  }
40
-
41
99
  }
42
-
43
100
  }
101
+
102
+ /**
103
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
104
+ * @typedef {import('@appium/types').PluginType} PluginType
105
+ */
106
+
107
+ /**
108
+ * @template {ExtensionType} ExtType
109
+ * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
110
+ */
111
+
112
+ /**
113
+ * Options for {@linkcode PluginCliCommand.install}
114
+ * @typedef PluginInstallOpts
115
+ * @property {string} plugin - the name or spec of a plugin to install
116
+ * @property {InstallType} installType - how to install this plugin. One of the INSTALL_TYPES
117
+ * @property {string} [packageName] - for git/github installs, the plugin node package name
118
+ */
119
+
120
+ /**
121
+ * @typedef {import('appium/types').InstallType} InstallType
122
+ */
123
+
124
+ /**
125
+ * Options for {@linkcode PluginCliCommand.uninstall}
126
+ * @typedef PluginUninstallOpts
127
+ * @property {string} plugin - the name or spec of a plugin to uninstall
128
+ */
129
+
130
+ /**
131
+ * Options for {@linkcode PluginCliCommand.update}
132
+ * @typedef PluginUpdateOpts
133
+ * @property {string} plugin - the name of the plugin to update
134
+ * @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
135
+ */
136
+
137
+ /**
138
+ * Options for {@linkcode PluginCliCommand.run}.
139
+ * @typedef PluginRunOptions
140
+ * @property {string} plugin - name of the plugin to run a script from
141
+ * @property {string} scriptName - name of the script to run
142
+ * @property {string[]} [extraArgs] - arguments to pass to the script
143
+ */