appium 2.0.0-beta.23 → 2.0.0-beta.26

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 (125) hide show
  1. package/README.md +1 -2
  2. package/build/lib/appium-config.schema.json +278 -0
  3. package/build/lib/appium.js +62 -71
  4. package/build/lib/cli/args.js +31 -53
  5. package/build/lib/cli/driver-command.js +5 -9
  6. package/build/lib/cli/extension-command.js +73 -64
  7. package/build/lib/cli/extension.js +10 -23
  8. package/build/lib/cli/parser.js +10 -20
  9. package/build/lib/cli/plugin-command.js +5 -9
  10. package/build/lib/cli/utils.js +2 -4
  11. package/build/lib/config-file.js +5 -10
  12. package/build/lib/config.js +67 -30
  13. package/build/lib/constants.js +60 -0
  14. package/build/lib/extension/driver-config.js +190 -0
  15. package/build/lib/extension/extension-config.js +297 -0
  16. package/build/lib/extension/index.js +77 -0
  17. package/build/lib/extension/manifest.js +246 -0
  18. package/build/lib/extension/package-changed.js +68 -0
  19. package/build/lib/extension/plugin-config.js +87 -0
  20. package/build/lib/grid-register.js +2 -4
  21. package/build/lib/logger.js +2 -4
  22. package/build/lib/logsink.js +2 -4
  23. package/build/lib/main.js +64 -92
  24. package/build/lib/schema/appium-config-schema.js +2 -4
  25. package/build/lib/schema/arg-spec.js +14 -15
  26. package/build/lib/schema/cli-args.js +8 -16
  27. package/build/lib/schema/cli-transformers.js +2 -4
  28. package/build/lib/schema/index.js +2 -4
  29. package/build/lib/schema/keywords.js +2 -4
  30. package/build/lib/schema/schema.js +136 -41
  31. package/build/lib/utils.js +13 -64
  32. package/lib/appium.js +74 -55
  33. package/lib/cli/args.js +36 -37
  34. package/lib/cli/driver-command.js +10 -2
  35. package/lib/cli/extension-command.js +216 -135
  36. package/lib/cli/extension.js +7 -15
  37. package/lib/cli/parser.js +7 -15
  38. package/lib/cli/plugin-command.js +1 -2
  39. package/lib/config-file.js +12 -15
  40. package/lib/config.js +111 -36
  41. package/lib/constants.js +79 -0
  42. package/lib/extension/driver-config.js +230 -0
  43. package/lib/extension/extension-config.js +459 -0
  44. package/lib/extension/index.js +103 -0
  45. package/lib/extension/manifest.js +590 -0
  46. package/lib/extension/package-changed.js +64 -0
  47. package/lib/extension/plugin-config.js +111 -0
  48. package/lib/grid-register.js +4 -4
  49. package/lib/main.js +110 -96
  50. package/lib/schema/arg-spec.js +11 -5
  51. package/lib/schema/cli-args.js +7 -30
  52. package/lib/schema/keywords.js +1 -1
  53. package/lib/schema/schema.js +206 -48
  54. package/lib/utils.js +27 -58
  55. package/package.json +29 -21
  56. package/{postinstall.js → scripts/postinstall.js} +1 -1
  57. package/types/types.d.ts +72 -28
  58. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  59. package/build/check-npm-pack-files.js +0 -23
  60. package/build/commands-yml/parse.js +0 -319
  61. package/build/commands-yml/validator.js +0 -130
  62. package/build/index.js +0 -19
  63. package/build/lib/cli/npm.js +0 -220
  64. package/build/lib/driver-config.js +0 -100
  65. package/build/lib/drivers.js +0 -100
  66. package/build/lib/ext-config-io.js +0 -165
  67. package/build/lib/extension-config.js +0 -320
  68. package/build/lib/plugin-config.js +0 -69
  69. package/build/lib/plugins.js +0 -16
  70. package/build/postinstall.js +0 -90
  71. package/build/test/cli/cli-e2e-specs.js +0 -221
  72. package/build/test/cli/cli-helpers.js +0 -86
  73. package/build/test/cli/cli-specs.js +0 -71
  74. package/build/test/cli/fixtures/test-driver/package.json +0 -27
  75. package/build/test/cli/schema-args-specs.js +0 -48
  76. package/build/test/cli/schema-e2e-specs.js +0 -47
  77. package/build/test/config-e2e-specs.js +0 -112
  78. package/build/test/config-file-e2e-specs.js +0 -209
  79. package/build/test/config-file-specs.js +0 -281
  80. package/build/test/config-specs.js +0 -159
  81. package/build/test/driver-e2e-specs.js +0 -435
  82. package/build/test/driver-specs.js +0 -321
  83. package/build/test/ext-config-io-specs.js +0 -181
  84. package/build/test/extension-config-specs.js +0 -365
  85. package/build/test/fixtures/allow-feat.txt +0 -5
  86. package/build/test/fixtures/caps.json +0 -3
  87. package/build/test/fixtures/config/allow-insecure.txt +0 -3
  88. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +0 -5
  89. package/build/test/fixtures/config/appium.config.bad.json +0 -32
  90. package/build/test/fixtures/config/appium.config.ext-good.json +0 -9
  91. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +0 -10
  92. package/build/test/fixtures/config/appium.config.good.js +0 -40
  93. package/build/test/fixtures/config/appium.config.good.json +0 -33
  94. package/build/test/fixtures/config/appium.config.good.yaml +0 -30
  95. package/build/test/fixtures/config/appium.config.invalid.json +0 -31
  96. package/build/test/fixtures/config/appium.config.security-array.json +0 -5
  97. package/build/test/fixtures/config/appium.config.security-delimited.json +0 -5
  98. package/build/test/fixtures/config/appium.config.security-path.json +0 -5
  99. package/build/test/fixtures/config/driver-fake.config.json +0 -8
  100. package/build/test/fixtures/config/nodeconfig.json +0 -3
  101. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  102. package/build/test/fixtures/default-args.js +0 -35
  103. package/build/test/fixtures/deny-feat.txt +0 -5
  104. package/build/test/fixtures/driver.schema.js +0 -20
  105. package/build/test/fixtures/extensions.yaml +0 -27
  106. package/build/test/fixtures/flattened-schema.js +0 -504
  107. package/build/test/fixtures/plugin.schema.js +0 -20
  108. package/build/test/fixtures/schema-with-extensions.js +0 -28
  109. package/build/test/grid-register-specs.js +0 -74
  110. package/build/test/helpers.js +0 -75
  111. package/build/test/logger-specs.js +0 -76
  112. package/build/test/npm-specs.js +0 -20
  113. package/build/test/parser-specs.js +0 -314
  114. package/build/test/plugin-e2e-specs.js +0 -316
  115. package/build/test/schema/arg-spec-specs.js +0 -70
  116. package/build/test/schema/cli-args-specs.js +0 -431
  117. package/build/test/schema/schema-specs.js +0 -389
  118. package/build/test/utils-specs.js +0 -266
  119. package/lib/cli/npm.js +0 -251
  120. package/lib/driver-config.js +0 -101
  121. package/lib/drivers.js +0 -84
  122. package/lib/ext-config-io.js +0 -287
  123. package/lib/extension-config.js +0 -366
  124. package/lib/plugin-config.js +0 -63
  125. package/lib/plugins.js +0 -11
@@ -0,0 +1,111 @@
1
+ // @ts-check
2
+
3
+ import _ from 'lodash';
4
+ import {ExtensionConfig} from './extension-config';
5
+ import log from '../logger';
6
+ import {PLUGIN_TYPE} from '../constants';
7
+
8
+ /**
9
+ * @extends {ExtensionConfig<PluginType>}
10
+ */
11
+ export class PluginConfig extends ExtensionConfig {
12
+
13
+ /**
14
+ * A mapping of {@link Manifest} instances to {@link PluginConfig} instances.
15
+ *
16
+ * `Manifest` and {@link ExtensionConfig} have a one-to-many relationship; each `Manifest` should be associated with a `DriverConfig` and a `PluginConfig`; no more, no less.
17
+ *
18
+ * This variable tracks the `Manifest`-to-`PluginConfig` portion.
19
+ *
20
+ * @type {WeakMap<Manifest,PluginConfig>}
21
+ * @private
22
+ */
23
+ static _instances = new WeakMap();
24
+
25
+ /**
26
+ * Call {@link PluginConfig.create} instead.
27
+ *
28
+ * Just calls the superclass' constructor with the correct extension type
29
+ * @private
30
+ * @param {import('./manifest').Manifest} manifest - IO object
31
+ * @param {PluginConfigOptions} [opts]
32
+ */
33
+ constructor (manifest, {extData, logFn} = {}) {
34
+ super(PLUGIN_TYPE, manifest, logFn);
35
+
36
+ if (extData) {
37
+ this.validate(extData);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Creates a new {@link PluginConfig} instance for a {@link Manifest} instance.
43
+ *
44
+ * @param {Manifest} manifest
45
+ * @param {PluginConfigOptions} [opts]
46
+ * @throws If `manifest` already associated with a `PluginConfig`
47
+ * @returns {PluginConfig}
48
+ */
49
+ static create (manifest, {extData, logFn} = {}) {
50
+ const instance = new PluginConfig(manifest, {logFn, extData});
51
+ if (PluginConfig.getInstance(manifest)) {
52
+ throw new Error(`Manifest with APPIUM_HOME ${manifest.appiumHome} already has a PluginConfig; use PluginConfig.getInstance() to retrieve it.`);
53
+ }
54
+ PluginConfig._instances.set(manifest, instance);
55
+ return instance;
56
+ }
57
+
58
+ /**
59
+ * Returns a PluginConfig associated with a Manifest
60
+ * @param {Manifest} manifest
61
+ * @returns {PluginConfig|undefined}
62
+ */
63
+ static getInstance (manifest) {
64
+ return PluginConfig._instances.get(manifest);
65
+ }
66
+ /**
67
+ * @param {string} pluginName
68
+ * @param {PluginData} pluginData
69
+ * @returns {string}
70
+ */
71
+ extensionDesc (pluginName, {version}) {
72
+ return `${pluginName}@${version}`;
73
+ }
74
+
75
+ /**
76
+ *
77
+ * @param {(keyof import('./manifest').ExtRecord<PluginType>)[]} activeNames
78
+ * @returns {void}
79
+ */
80
+ print (activeNames) {
81
+ const pluginNames = Object.keys(this.installedExtensions);
82
+
83
+ if (_.isEmpty(pluginNames)) {
84
+ log.info(`No plugins have been installed. Use the "appium plugin" ` +
85
+ 'command to install the one(s) you want to use.');
86
+ return;
87
+ }
88
+
89
+ log.info(`Available plugins:`);
90
+ for (const [pluginName, pluginData] of _.toPairs(this.installedExtensions)) {
91
+ const activeTxt = _.includes(activeNames, pluginName) ? ' (ACTIVE)' : '';
92
+ log.info(` - ${this.extensionDesc(pluginName, pluginData)}${activeTxt}`);
93
+ }
94
+
95
+ if (_.isEmpty(activeNames)) {
96
+ log.info('No plugins activated. Use the --use-plugins flag with names of plugins to activate');
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @typedef PluginConfigOptions
103
+ * @property {import('./extension-config').ExtensionLogFn} [logFn] - Optional logging function
104
+ * @property {import('./manifest').ExtRecord<PluginType>} [extData] - Extension data
105
+ */
106
+
107
+ /**
108
+ * @typedef {import('./manifest').PluginType} PluginType
109
+ * @typedef {import('./manifest').ExtData<PluginType>} PluginData
110
+ * @typedef {import('./manifest').Manifest} Manifest
111
+ */
@@ -11,10 +11,10 @@ const hubUri = (config) => {
11
11
 
12
12
  /**
13
13
  * Registers a new node with a selenium grid
14
- * @param {string|object} data - Path or object representing selenium grid node config file
15
- * @param {string} addr - Bind to this address
16
- * @param {number} port - Bind to this port
17
- * @param {string} basePath - Base path for the grid
14
+ * @param {string|object} data - Path or object representing selenium grid node config file. If a `string`, all subsequent arguments are required!
15
+ * @param {string} [addr] - Bind to this address
16
+ * @param {number} [port] - Bind to this port
17
+ * @param {string} [basePath] - Base path for the grid
18
18
  */
19
19
  async function registerNode (data, addr, port, basePath) {
20
20
  let configFilePath;
package/lib/main.js CHANGED
@@ -3,23 +3,25 @@
3
3
  // transpile:main
4
4
  // @ts-check
5
5
 
6
+ import { init as logsinkInit } from './logsink'; // this import needs to come first since it sets up global npmlog
7
+ import logger from './logger'; // logger needs to remain second
6
8
  // @ts-ignore
7
9
  import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
8
- import { logger as logFactory, util } from '@appium/support';
10
+ import { logger as logFactory, util, env } from '@appium/support';
9
11
  import { asyncify } from 'asyncbox';
10
12
  import _ from 'lodash';
11
13
  import { AppiumDriver } from './appium';
12
- import { driverConfig, pluginConfig, USE_ALL_PLUGINS } from './cli/args';
13
14
  import { runExtensionCommand } from './cli/extension';
14
- import { default as getParser, SERVER_SUBCOMMAND } from './cli/parser';
15
- import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showConfig, validateTmpDir, warnNodeDeprecations } from './config';
15
+ import { getParser } from './cli/parser';
16
+ import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showConfig, showBuildInfo, validateTmpDir, warnNodeDeprecations } from './config';
16
17
  import { readConfigFile } from './config-file';
17
- import { DRIVER_TYPE, PLUGIN_TYPE } from './extension-config';
18
+ import { loadExtensions, getActivePlugins, getActiveDrivers } from './extension';
19
+ import { DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND } from './constants';
18
20
  import registerNode from './grid-register';
19
- import logger from './logger'; // logger needs to remain first of imports
20
- import { init as logsinkInit } from './logsink';
21
- import { getDefaultsFromSchema, validate } from './schema/schema';
22
- import { inspectObject } from './utils';
21
+ import { getDefaultsForSchema, validate } from './schema/schema';
22
+ import { inspect } from './utils';
23
+
24
+ const {resolveAppiumHome} = env;
23
25
 
24
26
  /**
25
27
  *
@@ -32,8 +34,8 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
32
34
  if (args.longStacktrace) {
33
35
  require('longjohn').async_trace_limit = -1;
34
36
  }
35
- if (args.showConfig) {
36
- await showConfig();
37
+ if (args.showBuildInfo) {
38
+ await showBuildInfo();
37
39
  process.exit(0);
38
40
  }
39
41
  warnNodeDeprecations();
@@ -53,18 +55,24 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
53
55
  }
54
56
  }
55
57
 
58
+ /**
59
+ * @param {Partial<ParsedArgs>} args
60
+ */
56
61
  function logNonDefaultArgsWarning (args) {
57
62
  logger.info('Non-default server args:');
58
- inspectObject(args);
63
+ inspect(args);
59
64
  }
60
65
 
61
66
  function logDefaultCapabilitiesWarning (caps) {
62
67
  logger.info('Default capabilities, which will be added to each request ' +
63
68
  'unless overridden by desired capabilities:');
64
- inspectObject(caps);
69
+ inspect(caps);
65
70
  }
66
71
 
67
- async function logStartupInfo (parser, args) {
72
+ /**
73
+ * @param {ParsedArgs} args
74
+ */
75
+ async function logStartupInfo (args) {
68
76
  let welcome = `Welcome to Appium v${APPIUM_VER}`;
69
77
  let appiumRev = await getGitRev();
70
78
  if (appiumRev) {
@@ -72,7 +80,7 @@ async function logStartupInfo (parser, args) {
72
80
  }
73
81
  logger.info(welcome);
74
82
 
75
- let showArgs = getNonDefaultServerArgs(parser, args);
83
+ let showArgs = getNonDefaultServerArgs(args);
76
84
  if (_.size(showArgs)) {
77
85
  logNonDefaultArgsWarning(showArgs);
78
86
  }
@@ -86,6 +94,12 @@ async function logStartupInfo (parser, args) {
86
94
  // }
87
95
  }
88
96
 
97
+ /**
98
+ * Logs the address and port the server is listening on
99
+ * @param {string} address - Address
100
+ * @param {number} port - Port
101
+ * @returns {void}
102
+ */
89
103
  function logServerPort (address, port) {
90
104
  let logMessage = `Appium REST http interface listener started on ` +
91
105
  `${address}:${port}`;
@@ -93,61 +107,21 @@ function logServerPort (address, port) {
93
107
  }
94
108
 
95
109
  /**
96
- * Find any plugin name which has been installed, and which has been requested for activation by
97
- * using the --use-plugins flag, and turn each one into its class, so we can send them as objects
98
- * to the server init. We also want to send/assign them to the umbrella driver so it can use them
99
- * to wrap command execution
100
- *
101
- * @param {Object} args - argparser parsed dict
102
- * @param {import('./plugin-config').default} pluginConfig - a plugin extension config
103
- * @returns {({pluginName: string} & ((...args: any[]) => unknown))[]}
110
+ * Gets a list of `updateServer` functions from all extensions
111
+ * @param {DriverClass[]} driverClasses
112
+ * @param {PluginClass[]} pluginClasses
113
+ * @returns {import('./extension/manifest').UpdateServerFn[]}
104
114
  */
105
- function getActivePlugins (args, pluginConfig) {
106
- return _.compact(Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
107
- _.includes(args.usePlugins, pluginName) ||
108
- (args.usePlugins.length === 1 && args.usePlugins[0] === USE_ALL_PLUGINS)
109
- ).map((pluginName) => {
110
- try {
111
- logger.info(`Attempting to load plugin ${pluginName}...`);
112
- const PluginClass = /** @type {{pluginName: string} & ((...args: any[]) => unknown)} */(pluginConfig.require(pluginName));
113
-
114
- PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
115
- return PluginClass;
116
- } catch (err) {
117
- logger.error(`Could not load plugin '${pluginName}', so it will not be available. Error ` +
118
- `in loading the plugin was: ${err.message}`);
119
- logger.debug(err.stack);
120
- }
121
- }));
115
+ function getServerUpdaters (driverClasses, pluginClasses) {
116
+ return _.compact(_.map([...driverClasses, ...pluginClasses], 'updateServer'));
122
117
  }
123
118
 
124
119
  /**
125
- * Find any driver name which has been installed, and turn each one into its class, so we can send
126
- * them as objects to the server init in case they need to add methods/routes or update the server.
127
- * If the --drivers flag was given, this method only loads the given drivers.
128
- *
129
- * @param {Object} args - argparser parsed dict
130
- * @param {import('./driver-config').default} driverConfig - a driver extension config
120
+ * Makes a big `MethodMap` from all the little `MethodMap`s in the extensions
121
+ * @param {DriverClass[]} driverClasses
122
+ * @param {PluginClass[]} pluginClasses
123
+ * @returns {import('@appium/base-driver').MethodMap}
131
124
  */
132
- function getActiveDrivers (args, driverConfig) {
133
- return _.compact(Object.keys(driverConfig.installedExtensions).filter((driverName) =>
134
- _.includes(args.useDrivers, driverName) || args.useDrivers.length === 0
135
- ).map((driverName) => {
136
- try {
137
- logger.info(`Attempting to load driver ${driverName}...`);
138
- return driverConfig.require(driverName);
139
- } catch (err) {
140
- logger.error(`Could not load driver '${driverName}', so it will not be available. Error ` +
141
- `in loading the driver was: ${err.message}`);
142
- logger.debug(err.stack);
143
- }
144
- }));
145
- }
146
-
147
- function getServerUpdaters (driverClasses, pluginClasses) {
148
- return [...driverClasses, ...pluginClasses].map((klass) => klass.updateServer).filter(Boolean);
149
- }
150
-
151
125
  function getExtraMethodMap (driverClasses, pluginClasses) {
152
126
  return [...driverClasses, ...pluginClasses].reduce(
153
127
  (map, klass) => ({...map, ...klass.newMethodMap}),
@@ -160,19 +134,33 @@ function getExtraMethodMap (driverClasses, pluginClasses) {
160
134
  *
161
135
  * Use this to get at the configuration schema.
162
136
  *
137
+ * If `args` contains a non-empty `subcommand` which is not `server`, this function will return an empty object.
138
+ *
139
+ * @param {PartialArgs} [args] - Partial args (progammatic usage only)
140
+ * @returns {Promise<ServerInitResult | ExtCommandInitResult>}
163
141
  * @example
164
142
  * import {init, getSchema} from 'appium';
165
143
  * const options = {}; // config object
166
144
  * await init(options);
167
145
  * const schema = getSchema(); // entire config schema including plugins and drivers
168
- * @param {ParsedArgs} [args] - Parsed args
169
- * @returns {Promise<{parser: import('./cli/parser').ArgParser} & Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
170
146
  */
171
147
  async function init (args) {
172
- const parser = await getParser();
148
+ const appiumHome = args?.appiumHome ?? await resolveAppiumHome();
149
+
150
+ const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
151
+
152
+ const parser = getParser();
173
153
  let throwInsteadOfExit = false;
174
154
  /** @type {ParsedArgs} */
155
+ let preConfigParsedArgs;
156
+ /** @type {ParsedArgs} */
175
157
  let parsedArgs;
158
+ /**
159
+ * This is a definition (instead of declaration) because TS can't figure out
160
+ * the value will be defined when it's used.
161
+ * @type {ReturnType<getDefaultsForSchema>}
162
+ */
163
+ let defaults = {};
176
164
  if (args) {
177
165
  // if we have a containing package instead of running as a CLI process,
178
166
  // that package might not appreciate us calling 'process.exit' willy-
@@ -182,13 +170,13 @@ async function init (args) {
182
170
  // but remove it since it's not a real server arg per se
183
171
  delete args.throwInsteadOfExit;
184
172
  }
185
- parsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
173
+ preConfigParsedArgs = /** @type {ParsedArgs} */({...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND});
186
174
  } else {
187
175
  // otherwise parse from CLI
188
- parsedArgs = parser.parseArgs();
176
+ preConfigParsedArgs = parser.parseArgs();
189
177
  }
190
178
 
191
- const configResult = await readConfigFile(parsedArgs.configFile);
179
+ const configResult = await readConfigFile(preConfigParsedArgs.configFile);
192
180
 
193
181
  if (!_.isEmpty(configResult.errors)) {
194
182
  throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
@@ -199,30 +187,35 @@ async function init (args) {
199
187
  // 1. command line args
200
188
  // 2. config file
201
189
  // 3. defaults from config file.
202
- if (parsedArgs.subcommand === SERVER_SUBCOMMAND) {
190
+ if (preConfigParsedArgs.subcommand === SERVER_SUBCOMMAND) {
191
+ defaults = getDefaultsForSchema(false);
192
+
203
193
  parsedArgs = _.defaultsDeep(
204
- parsedArgs,
194
+ preConfigParsedArgs,
205
195
  configResult.config?.server,
206
- getDefaultsFromSchema()
196
+ defaults
207
197
  );
208
- }
209
198
 
210
- parsedArgs = _.defaultsDeep(
211
- parsedArgs,
212
- configResult.config ?? {},
213
- );
199
+ if (preConfigParsedArgs.showConfig) {
200
+ showConfig(getNonDefaultServerArgs(preConfigParsedArgs), configResult, defaults, parsedArgs);
201
+ return {};
202
+ }
203
+
204
+ } else {
205
+ parsedArgs = preConfigParsedArgs;
206
+ }
214
207
 
215
208
  await logsinkInit(parsedArgs);
216
209
 
217
210
  // if the user has requested the 'driver' CLI, don't run the normal server,
218
211
  // but instead pass control to the driver CLI
219
212
  if (parsedArgs.subcommand === DRIVER_TYPE) {
220
- await runExtensionCommand(parsedArgs, parsedArgs.subcommand, driverConfig);
221
- return {parser};
213
+ await runExtensionCommand(parsedArgs, driverConfig);
214
+ return {};
222
215
  }
223
216
  if (parsedArgs.subcommand === PLUGIN_TYPE) {
224
- await runExtensionCommand(parsedArgs, parsedArgs.subcommand, pluginConfig);
225
- return {parser};
217
+ await runExtensionCommand(parsedArgs, pluginConfig);
218
+ return {};
226
219
  }
227
220
 
228
221
  if (parsedArgs.logFilters) {
@@ -243,31 +236,32 @@ async function init (args) {
243
236
  appiumDriver.driverConfig = driverConfig;
244
237
  await preflightChecks(parsedArgs, throwInsteadOfExit);
245
238
 
246
- return {parser, appiumDriver, parsedArgs};
239
+ return /** @type {ServerInitResult} */({appiumDriver, parsedArgs, driverConfig, pluginConfig});
247
240
  }
248
241
 
249
242
  /**
250
- *
251
- * @param {ParsedArgs} [args]
252
- * @returns
243
+ * Initializes Appium's config. Starts server if appropriate and resolves the
244
+ * server instance if so; otherwise resolves w/ `undefined`.
245
+ * @param {PartialArgs} [args] - Arguments from CLI or otherwise
246
+ * @returns {Promise<import('http').Server|undefined>}
253
247
  */
254
248
  async function main (args) {
255
- const {parser, appiumDriver, parsedArgs} = await init(args);
249
+ const {appiumDriver, parsedArgs, pluginConfig, driverConfig} = /** @type {ServerInitResult} */(await init(args));
256
250
 
257
- if (!appiumDriver || !parsedArgs) {
251
+ if (!appiumDriver || !parsedArgs || !pluginConfig || !driverConfig) {
258
252
  // if this branch is taken, we've run a different subcommand, so there's nothing
259
253
  // left to do here.
260
254
  return;
261
255
  }
262
256
 
263
- const pluginClasses = getActivePlugins(parsedArgs, pluginConfig);
257
+ const pluginClasses = getActivePlugins(pluginConfig, parsedArgs.usePlugins);
264
258
  // set the active plugins on the umbrella driver so it can use them for commands
265
259
  appiumDriver.pluginClasses = pluginClasses;
266
260
 
267
- await logStartupInfo(parser, parsedArgs);
261
+ await logStartupInfo(parsedArgs);
268
262
  let routeConfiguringFunction = makeRouter(appiumDriver);
269
263
 
270
- const driverClasses = getActiveDrivers(parsedArgs, driverConfig);
264
+ const driverClasses = getActiveDrivers(driverConfig, parsedArgs.useDrivers);
271
265
  const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
272
266
  const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
273
267
 
@@ -298,7 +292,6 @@ async function main (args) {
298
292
  'to visit sites which could maliciously try to start Appium ' +
299
293
  'sessions on your machine');
300
294
  }
301
- // @ts-ignore
302
295
  appiumDriver.server = server;
303
296
  try {
304
297
  // configure as node on grid, if necessary
@@ -343,11 +336,32 @@ if (require.main === module) {
343
336
  }
344
337
 
345
338
  // everything below here is intended to be a public API.
346
- export { main, init };
347
- export { APPIUM_HOME } from './extension-config';
348
- export { getSchema, validate, finalizeSchema } from './schema/schema';
349
339
  export { readConfigFile } from './config-file';
340
+ export { finalizeSchema, getSchema, validate } from './schema/schema';
341
+ export { main, init, resolveAppiumHome };
350
342
 
351
343
  /**
352
344
  * @typedef {import('../types/types').ParsedArgs} ParsedArgs
353
345
  */
346
+
347
+ /**
348
+ * @typedef {import('../types/types').PartialArgs} PartialArgs
349
+ * @typedef {import('./extension/manifest').DriverType} DriverType
350
+ * @typedef {import('./extension/manifest').PluginType} PluginType
351
+ * @typedef {import('./extension/manifest').DriverClass} DriverClass
352
+ * @typedef {import('./extension/manifest').PluginClass} PluginClass
353
+ */
354
+
355
+ /**
356
+ * @typedef { {} } ExtCommandInitResult
357
+ */
358
+
359
+ /**
360
+ * @typedef ServerInitData
361
+ * @property {AppiumDriver} appiumDriver - The Appium driver
362
+ * @property {ParsedArgs} parsedArgs - The parsed arguments
363
+ */
364
+
365
+ /**
366
+ * @typedef {ServerInitData & import('./extension').ExtensionConfigs} ServerInitResult
367
+ */
@@ -73,6 +73,11 @@ export class ArgSpec {
73
73
  */
74
74
  dest;
75
75
 
76
+ /**
77
+ * The same as {@link ArgSpec.dest} but without the leading `<extType>.<extName>.` prefix.
78
+ */
79
+ rawDest;
80
+
76
81
  /**
77
82
  * Whatever the default value of this argument is, as specified by the
78
83
  * `default` property of the schema.
@@ -100,10 +105,10 @@ export class ArgSpec {
100
105
 
101
106
  // if no explicit `dest` provided, just camelCase the name to avoid needing
102
107
  // to use bracket syntax when accessing props on the parsed args object.
103
- const baseDest = _.camelCase(dest ?? name);
108
+ const rawDest = _.camelCase(dest ?? name);
104
109
 
105
110
  const destKeypath =
106
- extType && extName ? [extType, extName, baseDest].join('.') : baseDest;
111
+ extType && extName ? [extType, extName, rawDest].join('.') : rawDest;
107
112
 
108
113
  this.defaultValue = defaultValue;
109
114
  this.name = name;
@@ -112,6 +117,7 @@ export class ArgSpec {
112
117
  this.arg = arg;
113
118
  this.dest = destKeypath;
114
119
  this.ref = ref;
120
+ this.rawDest = rawDest;
115
121
  }
116
122
 
117
123
  /**
@@ -170,7 +176,7 @@ export class ArgSpec {
170
176
  /**
171
177
  * When given the root ID of a schema for an extension (`<extType>-<normalizedExtName>.json`) Returns an object containing the extension type and the _normalized_ extension name.
172
178
  * @param {string} schemaId - Root schema ID
173
- * @returns {{extType: ExtensionType|undefined, normalizedExtName: string|undefined}}
179
+ * @returns { {extType: ExtensionType|undefined, normalizedExtName: string|undefined} }
174
180
  */
175
181
  static extensionInfoFromRootSchemaId (schemaId) {
176
182
  const matches = schemaId.match(SCHEMA_ID_REGEXP);
@@ -210,7 +216,7 @@ export class ArgSpec {
210
216
  /**
211
217
  * Options for {@link ArgSpec.create}
212
218
  * @template D
213
- * @typedef {Object} ArgSpecOptions
219
+ * @typedef ArgSpecOptions
214
220
  * @property {string} [extName]
215
221
  * @property {ExtensionType} [extType]
216
222
  * @property {string} [dest]
@@ -218,5 +224,5 @@ export class ArgSpec {
218
224
  */
219
225
 
220
226
  /**
221
- * @typedef {import('../ext-config-io').ExtensionType} ExtensionType
227
+ * @typedef {import('../manifest').ExtensionType} ExtensionType
222
228
  */
@@ -98,11 +98,9 @@ function makeDescription (schema) {
98
98
  * as understood by `argparse`.
99
99
  * @param {AppiumJSONSchema} subSchema - JSON schema for the option
100
100
  * @param {ArgSpec} argSpec - Argument spec tuple
101
- * @param {SubSchemaToArgDefOptions} [opts] - Options
102
101
  * @returns {[string[], import('argparse').ArgumentOptions]} Tuple of flag and options
103
102
  */
104
- function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
105
- const {overrides = {}} = opts;
103
+ function subSchemaToArgDef (subSchema, argSpec) {
106
104
  let {
107
105
  type,
108
106
  appiumCliAliases,
@@ -110,7 +108,7 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
110
108
  enum: enumValues,
111
109
  } = subSchema;
112
110
 
113
- const {name, arg, dest} = argSpec;
111
+ const {name, arg} = argSpec;
114
112
 
115
113
  const aliases = [
116
114
  aliasToFlag(argSpec),
@@ -141,8 +139,10 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
141
139
  // handle special cases for various types
142
140
  switch (type) {
143
141
  // booleans do not have a type per `ArgumentOptions`, just an "action"
142
+ // NOTE: due to limitations of `argparse`, we cannot provide fancy help text, and must rely on its internal error messaging.
144
143
  case TYPENAMES.BOOLEAN: {
145
- argOpts.action = 'store_true';
144
+ argOpts.action = 'store_const';
145
+ argOpts.const = true;
146
146
  break;
147
147
  }
148
148
 
@@ -227,16 +227,6 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
227
227
  }
228
228
  }
229
229
 
230
- // overrides override anything we computed here. usually this involves "custom types",
231
- // which are really just transform functions.
232
- argOpts = _.merge(
233
- argOpts,
234
- /** should the override keys correspond to the prop name or the prop dest?
235
- * the prop dest is computed by {@link aliasToDest}.
236
- */
237
- overrides[dest] ?? {},
238
- );
239
-
240
230
  return [aliases, argOpts];
241
231
  }
242
232
 
@@ -244,32 +234,19 @@ function subSchemaToArgDef (subSchema, argSpec, opts = {}) {
244
234
  * Converts the finalized, flattened schema representation into
245
235
  * ArgumentDefinitions for handoff to `argparse`.
246
236
  *
247
- * @param {ToParserArgsOptions} opts - Options
248
237
  * @throws If schema has not been added to ajv (via `finalizeSchema()`)
249
238
  * @returns {import('../cli/args').ArgumentDefinitions} A map of arryas of
250
239
  * aliases to `argparse` arguments; empty if no schema found
251
240
  */
252
- export function toParserArgs (opts = {}) {
241
+ export function toParserArgs () {
253
242
  const flattened = flattenSchema().filter(({schema}) => !schema.appiumCliIgnored);
254
243
  return new Map(
255
244
  _.map(flattened, ({schema, argSpec}) =>
256
- subSchemaToArgDef(schema, argSpec, opts),
245
+ subSchemaToArgDef(schema, argSpec),
257
246
  ),
258
247
  );
259
248
  }
260
249
 
261
- /**
262
- * Options for {@link toParserArgs}
263
- * @typedef {SubSchemaToArgDefOptions} ToParserArgsOptions
264
- */
265
-
266
- /**
267
- * Options for {@link subSchemaToArgDef}.
268
- * @typedef {Object} SubSchemaToArgDefOptions
269
- * @property {string} [prefix] - The prefix to use for the flag, if any
270
- * @property {{[key: string]: import('argparse').ArgumentOptions}} [overrides] - An object of key/value pairs to override the default values
271
- */
272
-
273
250
  /**
274
251
  * @template T
275
252
  * @typedef {import('ajv/dist/types').FormatValidator<T>} FormatValidator<T>
@@ -120,7 +120,7 @@ export const keywords = {
120
120
  /**
121
121
  * These are the custom keywords that Appium recognizes.
122
122
  *
123
- * @typedef {Object} AppiumJSONSchemaKeywords
123
+ * @typedef AppiumJSONSchemaKeywords
124
124
  * @property {string} [appiumCliDest]
125
125
  * @property {string} [appiumCliDescription]
126
126
  * @property {string[]} [appiumCliAliases]