appium 2.0.0-beta.23 → 2.0.0-beta.24

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/lib/appium.js CHANGED
@@ -3,10 +3,12 @@ import log from './logger';
3
3
  import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
4
4
  import { findMatchingDriver } from './drivers';
5
5
  import { BaseDriver, errors, isSessionCommand,
6
- CREATE_SESSION_COMMAND } from '@appium/base-driver';
6
+ CREATE_SESSION_COMMAND, DELETE_SESSION_COMMAND, GET_STATUS_COMMAND
7
+ } from '@appium/base-driver';
7
8
  import AsyncLock from 'async-lock';
8
9
  import { parseCapsForInnerDriver, pullSettings } from './utils';
9
10
  import { util } from '@appium/support';
11
+ import { getDefaultsForExtension } from './schema';
10
12
 
11
13
  const desiredCapabilityConstraints = {
12
14
  automationName: {
@@ -51,6 +53,7 @@ class AppiumDriver extends BaseDriver {
51
53
  // It is not recommended to access this property directly from the outside
52
54
  this.pendingDrivers = {};
53
55
 
56
+ /** @type {PluginExtensionClass[]} */
54
57
  this.pluginClasses = []; // list of which plugins are active
55
58
  this.sessionPlugins = {}; // map of sessions to actual plugin instances per session
56
59
  this.sessionlessPlugins = []; // some commands are sessionless, so we need a set of plugins for them
@@ -59,9 +62,12 @@ class AppiumDriver extends BaseDriver {
59
62
  updateBuildInfo();
60
63
  }
61
64
 
62
- /** @type {DriverConfig|undefined} */
65
+ /** @type {import('./driver-config').default|undefined} */
63
66
  driverConfig;
64
67
 
68
+ /** @type {import('express').Express|undefined} */
69
+ server;
70
+
65
71
  /**
66
72
  * Cancel commands queueing for the umbrella Appium driver
67
73
  */
@@ -118,14 +124,22 @@ class AppiumDriver extends BaseDriver {
118
124
  * Validate and assign CLI args for a driver or plugin
119
125
  *
120
126
  * If the extension has provided a schema, validation has already happened.
127
+ *
128
+ * Any arg which is equal to its default value will not be assigned to the extension.
121
129
  * @param {import('./ext-config-io').ExtensionType} extType 'driver' or 'plugin'
122
130
  * @param {string} extName the name of the extension
123
131
  * @param {Object} extInstance the driver or plugin instance
124
132
  */
125
133
  assignCliArgsToExtension (extType, extName, extInstance) {
126
- const cliArgs = this.args[extType]?.[extName];
127
- if (!_.isEmpty(cliArgs)) {
128
- extInstance.cliArgs = cliArgs;
134
+ const allCliArgsForExt = this.args[extType]?.[extName];
135
+ if (!_.isEmpty(allCliArgsForExt)) {
136
+ const defaults = getDefaultsForExtension(extType, extName);
137
+ const cliArgs = _.isEmpty(defaults)
138
+ ? allCliArgsForExt
139
+ : _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value));
140
+ if (!_.isEmpty(cliArgs)) {
141
+ extInstance.cliArgs = cliArgs;
142
+ }
129
143
  }
130
144
  }
131
145
 
@@ -443,9 +457,10 @@ class AppiumDriver extends BaseDriver {
443
457
  // The tricky part is that because we support command plugins, we need to wrap any of these
444
458
  // cases with plugin handling.
445
459
 
446
- const isGetStatus = cmd === 'getStatus';
460
+ const isGetStatus = cmd === GET_STATUS_COMMAND;
461
+ const isDeleteSession = cmd === DELETE_SESSION_COMMAND;
447
462
  const isUmbrellaCmd = !isGetStatus && isAppiumDriverCommand(cmd);
448
- const isSessionCmd = !isGetStatus && !isUmbrellaCmd;
463
+ const isSessionCmd = !isUmbrellaCmd || isDeleteSession;
449
464
 
450
465
  // if a plugin override proxying for this command and that is why we are here instead of just
451
466
  // letting the protocol proxy the command entirely, determine that, get the request object for
@@ -470,7 +485,9 @@ class AppiumDriver extends BaseDriver {
470
485
  }
471
486
  // now save the response protocol given that the session driver's protocol might differ
472
487
  protocol = dstSession.protocol;
473
- driver = dstSession;
488
+ if (!isUmbrellaCmd) {
489
+ driver = dstSession;
490
+ }
474
491
  }
475
492
 
476
493
  // get any plugins which are registered as handling this command
@@ -657,3 +674,23 @@ export class NoDriverProxyCommandError extends Error {
657
674
  }
658
675
 
659
676
  export { AppiumDriver };
677
+
678
+
679
+ /**
680
+ * @typedef {Object} StaticExtMembers
681
+ * @property {(app: import('express').Express, httpServer: import('http').Server) => import('type-fest').Promisable<void>} [updateServer]
682
+ * @property {import('@appium/base-driver').MethodMap} [newMethodMap]
683
+ */
684
+
685
+ /**
686
+ * @typedef {Object} StaticPluginMembers
687
+ * @property {string} pluginName
688
+ */
689
+
690
+ /**
691
+ * @typedef {import('type-fest').Class<unknown> & StaticPluginMembers & StaticExtMembers} PluginExtensionClass
692
+ */
693
+
694
+ /**
695
+ * @typedef {import('type-fest').Class<unknown> & StaticExtMembers} DriverExtensionClass
696
+ */
package/lib/cli/args.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // @ts-check
2
2
 
3
3
  // @ts-ignore
4
- import { DEFAULT_BASE_PATH } from '@appium/base-driver';
5
4
  import _ from 'lodash';
6
5
  import DriverConfig from '../driver-config';
7
6
  import { APPIUM_HOME, DRIVER_TYPE, INSTALL_TYPES, PLUGIN_TYPE } from '../extension-config';
@@ -175,13 +174,7 @@ function makeRunArgs (type) {
175
174
  */
176
175
  function getServerArgs () {
177
176
  return new Map([
178
- ...toParserArgs({
179
- overrides: {
180
- basePath: {
181
- default: DEFAULT_BASE_PATH
182
- },
183
- }
184
- }),
177
+ ...toParserArgs(),
185
178
  ...serverArgsDisallowedInConfig,
186
179
  ]);
187
180
  }
@@ -195,21 +188,31 @@ const serverArgsDisallowedInConfig = new Map([
195
188
  ['--shell'],
196
189
  {
197
190
  required: false,
198
- default: null,
199
191
  help: 'Enter REPL mode',
200
- action: 'store_true',
192
+ action: 'store_const',
193
+ const: true,
201
194
  dest: 'shell',
202
195
  },
203
196
  ],
197
+ [
198
+ ['--show-build-info'],
199
+ {
200
+ dest: 'showBuildInfo',
201
+ action: 'store_const',
202
+ const: true,
203
+ required: false,
204
+ help: 'Show info about the Appium build and exit',
205
+ },
206
+ ],
204
207
  [
205
208
  ['--show-config'],
206
209
  {
207
- default: false,
208
210
  dest: 'showConfig',
209
- action: 'store_true',
211
+ action: 'store_const',
212
+ const: true,
210
213
  required: false,
211
- help: 'Show info about the appium server configuration and exit',
212
- },
214
+ help: 'Show the current Appium configuration and exit',
215
+ }
213
216
  ],
214
217
  [
215
218
  ['--config'],
package/lib/cli/parser.js CHANGED
@@ -146,7 +146,7 @@ class ArgParser {
146
146
  return _.reduce(
147
147
  args,
148
148
  (unpacked, value, key) => {
149
- if (hasArgSpec(key)) {
149
+ if (!_.isUndefined(value) && hasArgSpec(key)) {
150
150
  const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */(getArgSpec(key));
151
151
  _.set(unpacked, dest, value);
152
152
  } else {
package/lib/config.js CHANGED
@@ -1,3 +1,6 @@
1
+ // @ts-check
2
+
3
+ /* eslint-disable no-console */
1
4
  import _ from 'lodash';
2
5
  import { mkdirp, system, fs } from '@appium/support';
3
6
  import axios from 'axios';
@@ -6,7 +9,7 @@ import { rootDir } from './utils';
6
9
  import logger from './logger';
7
10
  import semver from 'semver';
8
11
  import findUp from 'find-up';
9
- import { getDefaultsFromSchema } from './schema/schema';
12
+ import { getDefaultsForSchema } from './schema/schema';
10
13
 
11
14
  const npmPackage = fs.readPackageJsonFrom(__dirname);
12
15
 
@@ -22,7 +25,7 @@ const BUILD_INFO = {
22
25
  };
23
26
 
24
27
  function getNodeVersion () {
25
- return semver.coerce(process.version);
28
+ return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
26
29
  }
27
30
 
28
31
  async function updateBuildInfo (useGithubApiFallback = false) {
@@ -42,7 +45,7 @@ async function updateBuildInfo (useGithubApiFallback = false) {
42
45
  *
43
46
  * This is needed because Appium cannot assume `package.json` and `.git` are in the same
44
47
  * directory. Monorepos, see?
45
- * @returns {string|void} Path to dir or `undefined` if not found
48
+ * @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
46
49
  */
47
50
  async function findGitRoot () {
48
51
  return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
@@ -80,6 +83,11 @@ async function getGitRev (useGithubApiFallback = false) {
80
83
  return null;
81
84
  }
82
85
 
86
+ /**
87
+ * @param {string} commitSha
88
+ * @param {boolean} [useGithubApiFallback]
89
+ * @returns {Promise<number?>}
90
+ */
83
91
  async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
84
92
  const gitRoot = await findGitRoot();
85
93
  if (gitRoot) {
@@ -144,37 +152,30 @@ function warnNodeDeprecations () {
144
152
  // }
145
153
  }
146
154
 
147
- async function showConfig () {
155
+ async function showBuildInfo () {
148
156
  await updateBuildInfo(true);
149
157
  console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
150
158
  }
151
159
 
152
- function getNonDefaultServerArgs (parser, args) {
160
+ /**
161
+ * Returns k/v pairs of server arguments which are _not_ the defaults.
162
+ * @param {ParsedArgs} args
163
+ * @returns {Partial<ParsedArgs>}
164
+ */
165
+ function getNonDefaultServerArgs (args) {
153
166
  // hopefully these function names are descriptive enough
154
167
 
155
- function typesDiffer (dest) {
156
- return typeof args[dest] !== typeof defaultsFromSchema[dest];
157
- }
168
+ const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest] !== typeof defaultsFromSchema[dest];
158
169
 
159
- function defaultValueIsArray (dest) {
160
- return _.isArray(defaultsFromSchema[dest]);
161
- }
170
+ const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
162
171
 
163
- function argsValueIsArray (dest) {
164
- return _.isArray(args[dest]);
165
- }
172
+ const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest]);
166
173
 
167
- function arraysDiffer (dest) {
168
- return _.difference(args[dest], defaultsFromSchema[dest]).length > 0;
169
- }
174
+ const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest], defaultsFromSchema[dest])), 0);
170
175
 
171
- function valuesUnequal (dest) {
172
- return args[dest] !== defaultsFromSchema[dest];
173
- }
176
+ const valuesDiffer = /** @param {string} dest */(dest) => args[dest] !== defaultsFromSchema[dest];
174
177
 
175
- function defaultIsDefined (dest) {
176
- return !_.isUndefined(defaultsFromSchema[dest]);
177
- }
178
+ const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
178
179
 
179
180
  // note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
180
181
 
@@ -183,8 +184,8 @@ function getNonDefaultServerArgs (parser, args) {
183
184
  arraysDiffer
184
185
  ]);
185
186
 
186
- const defaultValueNotArrayAndValuesUnequal = _.overEvery([
187
- _.negate(defaultValueIsArray), valuesUnequal
187
+ const defaultValueNotArrayAndValuesDiffer = _.overEvery([
188
+ _.negate(defaultValueIsArray), valuesDiffer
188
189
  ]);
189
190
 
190
191
  /**
@@ -197,8 +198,7 @@ function getNonDefaultServerArgs (parser, args) {
197
198
  * - if so, and the default is an array:
198
199
  * - ensures the args value is an array
199
200
  * - ensures the args values do not differ from the default values
200
- * @param {string} dest - argument name (`dest` value)
201
- * @returns {boolean}
201
+ * @type {(dest: string) => boolean}
202
202
  */
203
203
  const isNotDefault = _.overEvery([
204
204
  defaultIsDefined,
@@ -208,15 +208,55 @@ function getNonDefaultServerArgs (parser, args) {
208
208
  defaultValueIsArray,
209
209
  argValueNotArrayOrArraysDiffer
210
210
  ]),
211
- defaultValueNotArrayAndValuesUnequal
211
+ defaultValueNotArrayAndValuesDiffer
212
212
  ])
213
213
  ]);
214
214
 
215
- const defaultsFromSchema = getDefaultsFromSchema();
215
+ const defaultsFromSchema = getDefaultsForSchema();
216
216
 
217
217
  return _.pickBy(args, (__, key) => isNotDefault(key));
218
218
  }
219
219
 
220
+ /**
221
+ * Compacts an object for {@link showConfig}:
222
+ * 1. Removes `subcommand` key/value
223
+ * 2. Removes `undefined` values
224
+ * 3. Removes empty objects (but not `false` values)
225
+ * Does not operate recursively.
226
+ */
227
+ const compactConfig = _.partial(
228
+ _.omitBy,
229
+ _,
230
+ (value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
231
+ );
232
+
233
+ /**
234
+ * Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
235
+ *
236
+ * The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
237
+ * but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
238
+ *
239
+ * @param {object} preConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
240
+ * @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file
241
+ * @param {object} defaults - Configuration defaults from schemas
242
+ */
243
+ function showConfig (preConfigParsedArgs, configResult, defaults) {
244
+ console.log('Appium Configuration\n');
245
+ if (configResult.config) {
246
+ console.log(`via config file at ${configResult.filepath}:\n`);
247
+ console.dir(compactConfig(configResult.config));
248
+ } else {
249
+ console.log(`(no configuration file loaded)\n`);
250
+ }
251
+ console.log('via CLI or function call:\n');
252
+ console.dir(compactConfig(preConfigParsedArgs));
253
+ console.log('\nvia defaults:\n');
254
+ console.dir(compactConfig(defaults));
255
+ }
256
+
257
+ /**
258
+ * @param {string} tmpDir
259
+ */
220
260
  async function validateTmpDir (tmpDir) {
221
261
  try {
222
262
  await mkdirp(tmpDir);
@@ -227,7 +267,11 @@ async function validateTmpDir (tmpDir) {
227
267
  }
228
268
 
229
269
  export {
230
- getBuildInfo, checkNodeOk, showConfig,
270
+ getBuildInfo, checkNodeOk, showBuildInfo,
231
271
  warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
232
- getGitRev, APPIUM_VER, updateBuildInfo
272
+ getGitRev, APPIUM_VER, updateBuildInfo, showConfig
233
273
  };
274
+
275
+ /**
276
+ * @typedef {import('../types/types').ParsedArgs} ParsedArgs
277
+ */
@@ -270,7 +270,7 @@ export default class ExtensionConfig {
270
270
  /**
271
271
  * Loads extension and returns its main class
272
272
  * @param {string} extName
273
- * @returns {(...args: any[]) => object }
273
+ * @returns {import('type-fest').Class<unknown>}
274
274
  */
275
275
  require (extName) {
276
276
  const {mainClass} = this.installedExtensions[extName];
package/lib/main.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // transpile:main
4
4
  // @ts-check
5
5
 
6
+ import logger from './logger'; // logger needs to remain first of imports
6
7
  // @ts-ignore
7
8
  import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
8
9
  import { logger as logFactory, util } from '@appium/support';
@@ -12,14 +13,13 @@ import { AppiumDriver } from './appium';
12
13
  import { driverConfig, pluginConfig, USE_ALL_PLUGINS } from './cli/args';
13
14
  import { runExtensionCommand } from './cli/extension';
14
15
  import { default as getParser, SERVER_SUBCOMMAND } from './cli/parser';
15
- import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showConfig, validateTmpDir, warnNodeDeprecations } from './config';
16
+ import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showBuildInfo, validateTmpDir, warnNodeDeprecations, showConfig } from './config';
16
17
  import { readConfigFile } from './config-file';
17
18
  import { DRIVER_TYPE, PLUGIN_TYPE } from './extension-config';
18
19
  import registerNode from './grid-register';
19
- import logger from './logger'; // logger needs to remain first of imports
20
20
  import { init as logsinkInit } from './logsink';
21
- import { getDefaultsFromSchema, validate } from './schema/schema';
22
- import { inspectObject } from './utils';
21
+ import { getDefaultsForSchema } from './schema/schema';
22
+ import { inspect } from './utils';
23
23
 
24
24
  /**
25
25
  *
@@ -32,14 +32,12 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
32
32
  if (args.longStacktrace) {
33
33
  require('longjohn').async_trace_limit = -1;
34
34
  }
35
- if (args.showConfig) {
36
- await showConfig();
35
+ if (args.showBuildInfo) {
36
+ await showBuildInfo();
37
37
  process.exit(0);
38
38
  }
39
39
  warnNodeDeprecations();
40
40
 
41
- validate(args);
42
-
43
41
  if (args.tmpDir) {
44
42
  await validateTmpDir(args.tmpDir);
45
43
  }
@@ -53,18 +51,24 @@ async function preflightChecks (args, throwInsteadOfExit = false) {
53
51
  }
54
52
  }
55
53
 
54
+ /**
55
+ * @param {Partial<ParsedArgs>} args
56
+ */
56
57
  function logNonDefaultArgsWarning (args) {
57
58
  logger.info('Non-default server args:');
58
- inspectObject(args);
59
+ inspect(args);
59
60
  }
60
61
 
61
62
  function logDefaultCapabilitiesWarning (caps) {
62
63
  logger.info('Default capabilities, which will be added to each request ' +
63
64
  'unless overridden by desired capabilities:');
64
- inspectObject(caps);
65
+ inspect(caps);
65
66
  }
66
67
 
67
- async function logStartupInfo (parser, args) {
68
+ /**
69
+ * @param {ParsedArgs} args
70
+ */
71
+ async function logStartupInfo (args) {
68
72
  let welcome = `Welcome to Appium v${APPIUM_VER}`;
69
73
  let appiumRev = await getGitRev();
70
74
  if (appiumRev) {
@@ -72,7 +76,7 @@ async function logStartupInfo (parser, args) {
72
76
  }
73
77
  logger.info(welcome);
74
78
 
75
- let showArgs = getNonDefaultServerArgs(parser, args);
79
+ let showArgs = getNonDefaultServerArgs(args);
76
80
  if (_.size(showArgs)) {
77
81
  logNonDefaultArgsWarning(showArgs);
78
82
  }
@@ -86,6 +90,12 @@ async function logStartupInfo (parser, args) {
86
90
  // }
87
91
  }
88
92
 
93
+ /**
94
+ * Logs the address and port the server is listening on
95
+ * @param {string} address - Address
96
+ * @param {number} port - Port
97
+ * @returns {void}
98
+ */
89
99
  function logServerPort (address, port) {
90
100
  let logMessage = `Appium REST http interface listener started on ` +
91
101
  `${address}:${port}`;
@@ -100,7 +110,7 @@ function logServerPort (address, port) {
100
110
  *
101
111
  * @param {Object} args - argparser parsed dict
102
112
  * @param {import('./plugin-config').default} pluginConfig - a plugin extension config
103
- * @returns {({pluginName: string} & ((...args: any[]) => unknown))[]}
113
+ * @returns {PluginExtensionClass[]}
104
114
  */
105
115
  function getActivePlugins (args, pluginConfig) {
106
116
  return _.compact(Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
@@ -109,7 +119,7 @@ function getActivePlugins (args, pluginConfig) {
109
119
  ).map((pluginName) => {
110
120
  try {
111
121
  logger.info(`Attempting to load plugin ${pluginName}...`);
112
- const PluginClass = /** @type {{pluginName: string} & ((...args: any[]) => unknown)} */(pluginConfig.require(pluginName));
122
+ const PluginClass = /** @type {PluginExtensionClass} */(pluginConfig.require(pluginName));
113
123
 
114
124
  PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
115
125
  return PluginClass;
@@ -144,10 +154,22 @@ function getActiveDrivers (args, driverConfig) {
144
154
  }));
145
155
  }
146
156
 
157
+ /**
158
+ * Gets a list of `updateServer` functions from all extensions
159
+ * @param {DriverExtensionClass[]} driverClasses
160
+ * @param {PluginExtensionClass[]} pluginClasses
161
+ * @returns {StaticExtMembers['updateServer'][]}
162
+ */
147
163
  function getServerUpdaters (driverClasses, pluginClasses) {
148
- return [...driverClasses, ...pluginClasses].map((klass) => klass.updateServer).filter(Boolean);
164
+ return _.compact(_.map([...driverClasses, ...pluginClasses], 'updateServer'));
149
165
  }
150
166
 
167
+ /**
168
+ * Makes a big `MethodMap` from all the little `MethodMap`s in the extensions
169
+ * @param {DriverExtensionClass[]} driverClasses
170
+ * @param {PluginExtensionClass[]} pluginClasses
171
+ * @returns {import('@appium/base-driver').MethodMap}
172
+ */
151
173
  function getExtraMethodMap (driverClasses, pluginClasses) {
152
174
  return [...driverClasses, ...pluginClasses].reduce(
153
175
  (map, klass) => ({...map, ...klass.newMethodMap}),
@@ -160,19 +182,30 @@ function getExtraMethodMap (driverClasses, pluginClasses) {
160
182
  *
161
183
  * Use this to get at the configuration schema.
162
184
  *
185
+ * If `args` contains a non-empty `subcommand` which is not `server`, this function
186
+ * will resolve with an empty object.
187
+ *
188
+ * @param {ParsedArgs} [args] - Parsed args
189
+ * @returns {Promise<Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
163
190
  * @example
164
191
  * import {init, getSchema} from 'appium';
165
192
  * const options = {}; // config object
166
193
  * await init(options);
167
194
  * 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
195
  */
171
196
  async function init (args) {
172
197
  const parser = await getParser();
173
198
  let throwInsteadOfExit = false;
174
199
  /** @type {ParsedArgs} */
200
+ let preConfigParsedArgs;
201
+ /** @type {typeof preConfigParsedArgs & import('type-fest').AsyncReturnType<readConfigFile>['config']} */
175
202
  let parsedArgs;
203
+ /**
204
+ * This is a definition (instead of declaration) because TS can't figure out
205
+ * the value will be defined when it's used.
206
+ * @type {ReturnType<getDefaultsForSchema>}
207
+ */
208
+ let defaults = {};
176
209
  if (args) {
177
210
  // if we have a containing package instead of running as a CLI process,
178
211
  // that package might not appreciate us calling 'process.exit' willy-
@@ -182,47 +215,52 @@ async function init (args) {
182
215
  // but remove it since it's not a real server arg per se
183
216
  delete args.throwInsteadOfExit;
184
217
  }
185
- parsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
218
+ preConfigParsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
186
219
  } else {
187
220
  // otherwise parse from CLI
188
- parsedArgs = parser.parseArgs();
221
+ preConfigParsedArgs = parser.parseArgs();
189
222
  }
190
223
 
191
- const configResult = await readConfigFile(parsedArgs.configFile);
224
+ const configResult = await readConfigFile(preConfigParsedArgs.configFile);
192
225
 
193
226
  if (!_.isEmpty(configResult.errors)) {
194
227
  throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
195
228
  }
196
229
 
230
+
197
231
  // merge config and apply defaults.
198
232
  // the order of precendece is:
199
233
  // 1. command line args
200
234
  // 2. config file
201
235
  // 3. defaults from config file.
202
- if (parsedArgs.subcommand === SERVER_SUBCOMMAND) {
236
+ if (preConfigParsedArgs.subcommand === SERVER_SUBCOMMAND) {
237
+ defaults = getDefaultsForSchema(false);
238
+
239
+ if (preConfigParsedArgs.showConfig) {
240
+ showConfig(preConfigParsedArgs, configResult, defaults);
241
+ return {};
242
+ }
243
+
203
244
  parsedArgs = _.defaultsDeep(
204
- parsedArgs,
245
+ preConfigParsedArgs,
205
246
  configResult.config?.server,
206
- getDefaultsFromSchema()
247
+ defaults
207
248
  );
249
+ } else {
250
+ parsedArgs = preConfigParsedArgs;
208
251
  }
209
252
 
210
- parsedArgs = _.defaultsDeep(
211
- parsedArgs,
212
- configResult.config ?? {},
213
- );
214
-
215
253
  await logsinkInit(parsedArgs);
216
254
 
217
255
  // if the user has requested the 'driver' CLI, don't run the normal server,
218
256
  // but instead pass control to the driver CLI
219
257
  if (parsedArgs.subcommand === DRIVER_TYPE) {
220
258
  await runExtensionCommand(parsedArgs, parsedArgs.subcommand, driverConfig);
221
- return {parser};
259
+ return {};
222
260
  }
223
261
  if (parsedArgs.subcommand === PLUGIN_TYPE) {
224
262
  await runExtensionCommand(parsedArgs, parsedArgs.subcommand, pluginConfig);
225
- return {parser};
263
+ return {};
226
264
  }
227
265
 
228
266
  if (parsedArgs.logFilters) {
@@ -243,16 +281,17 @@ async function init (args) {
243
281
  appiumDriver.driverConfig = driverConfig;
244
282
  await preflightChecks(parsedArgs, throwInsteadOfExit);
245
283
 
246
- return {parser, appiumDriver, parsedArgs};
284
+ return {appiumDriver, parsedArgs};
247
285
  }
248
286
 
249
287
  /**
250
- *
251
- * @param {ParsedArgs} [args]
252
- * @returns
288
+ * Initializes Appium's config. Starts server if appropriate and resolves the
289
+ * server instance if so; otherwise resolves w/ `undefined`.
290
+ * @param {ParsedArgs} [args] - Arguments from CLI or otherwise
291
+ * @returns {Promise<import('express').Express|undefined>}
253
292
  */
254
293
  async function main (args) {
255
- const {parser, appiumDriver, parsedArgs} = await init(args);
294
+ const {appiumDriver, parsedArgs} = await init(args);
256
295
 
257
296
  if (!appiumDriver || !parsedArgs) {
258
297
  // if this branch is taken, we've run a different subcommand, so there's nothing
@@ -264,7 +303,7 @@ async function main (args) {
264
303
  // set the active plugins on the umbrella driver so it can use them for commands
265
304
  appiumDriver.pluginClasses = pluginClasses;
266
305
 
267
- await logStartupInfo(parser, parsedArgs);
306
+ await logStartupInfo(parsedArgs);
268
307
  let routeConfiguringFunction = makeRouter(appiumDriver);
269
308
 
270
309
  const driverClasses = getActiveDrivers(parsedArgs, driverConfig);
@@ -298,7 +337,6 @@ async function main (args) {
298
337
  'to visit sites which could maliciously try to start Appium ' +
299
338
  'sessions on your machine');
300
339
  }
301
- // @ts-ignore
302
340
  appiumDriver.server = server;
303
341
  try {
304
342
  // configure as node on grid, if necessary
@@ -351,3 +389,15 @@ export { readConfigFile } from './config-file';
351
389
  /**
352
390
  * @typedef {import('../types/types').ParsedArgs} ParsedArgs
353
391
  */
392
+
393
+ /**
394
+ * @typedef {import('./appium').PluginExtensionClass} PluginExtensionClass
395
+ */
396
+
397
+ /**
398
+ * @typedef {import('./appium').DriverExtensionClass} DriverExtensionClass
399
+ */
400
+
401
+ /**
402
+ * @typedef {import('./appium').StaticExtMembers} StaticExtMembers
403
+ */
package/lib/plugins.js CHANGED
@@ -4,6 +4,8 @@
4
4
  // npm package. I.e., these are the officially recognized plugins.
5
5
  const KNOWN_PLUGINS = {
6
6
  images: '@appium/images-plugin',
7
+ 'execute-driver': '@appium/execute-driver-plugin',
8
+ 'relaxed-caps': '@appium/relaxed-caps-plugin',
7
9
  };
8
10
 
9
11
  export {