appium 2.0.0-beta.21 → 2.0.0-beta.25

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 (93) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1 -2
  3. package/build/check-npm-pack-files.js +23 -0
  4. package/build/commands-yml/parse.js +319 -0
  5. package/build/commands-yml/validator.js +130 -0
  6. package/build/index.js +19 -0
  7. package/build/lib/appium.js +22 -7
  8. package/build/lib/cli/args.js +13 -15
  9. package/build/lib/cli/npm.js +27 -16
  10. package/build/lib/cli/parser.js +7 -3
  11. package/build/lib/config-file.js +4 -7
  12. package/build/lib/config.js +57 -48
  13. package/build/lib/extension-config.js +1 -1
  14. package/build/lib/main.js +28 -28
  15. package/build/lib/plugin-config.js +2 -2
  16. package/build/lib/plugins.js +4 -2
  17. package/build/lib/schema/appium-config-schema.js +3 -2
  18. package/build/lib/schema/arg-spec.js +5 -3
  19. package/build/lib/schema/cli-args.js +25 -16
  20. package/build/lib/schema/keywords.js +14 -4
  21. package/build/lib/schema/schema.js +86 -9
  22. package/build/lib/utils.js +16 -36
  23. package/build/postinstall.js +90 -0
  24. package/build/test/cli/cli-e2e-specs.js +221 -0
  25. package/build/test/cli/cli-helpers.js +86 -0
  26. package/build/test/cli/cli-specs.js +71 -0
  27. package/build/test/cli/fixtures/test-driver/package.json +27 -0
  28. package/build/test/cli/schema-args-specs.js +48 -0
  29. package/build/test/cli/schema-e2e-specs.js +47 -0
  30. package/build/test/config-e2e-specs.js +112 -0
  31. package/build/test/config-file-e2e-specs.js +191 -0
  32. package/build/test/config-file-specs.js +281 -0
  33. package/build/test/config-specs.js +258 -0
  34. package/build/test/driver-e2e-specs.js +435 -0
  35. package/build/test/driver-specs.js +386 -0
  36. package/build/test/ext-config-io-specs.js +181 -0
  37. package/build/test/extension-config-specs.js +365 -0
  38. package/build/test/fixtures/allow-feat.txt +5 -0
  39. package/build/test/fixtures/caps.json +3 -0
  40. package/build/test/fixtures/config/allow-insecure.txt +3 -0
  41. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
  42. package/build/test/fixtures/config/appium.config.bad.json +32 -0
  43. package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
  44. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
  45. package/build/test/fixtures/config/appium.config.good.js +40 -0
  46. package/build/test/fixtures/config/appium.config.good.json +33 -0
  47. package/build/test/fixtures/config/appium.config.good.yaml +30 -0
  48. package/build/test/fixtures/config/appium.config.invalid.json +31 -0
  49. package/build/test/fixtures/config/appium.config.security-array.json +5 -0
  50. package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
  51. package/build/test/fixtures/config/appium.config.security-path.json +5 -0
  52. package/build/test/fixtures/config/driver-fake.config.json +8 -0
  53. package/build/test/fixtures/config/nodeconfig.json +3 -0
  54. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  55. package/build/test/fixtures/default-args.js +35 -0
  56. package/build/test/fixtures/deny-feat.txt +5 -0
  57. package/build/test/fixtures/driver.schema.js +20 -0
  58. package/build/test/fixtures/extensions.yaml +27 -0
  59. package/build/test/fixtures/flattened-schema.js +532 -0
  60. package/build/test/fixtures/plugin.schema.js +20 -0
  61. package/build/test/fixtures/schema-with-extensions.js +28 -0
  62. package/build/test/grid-register-specs.js +74 -0
  63. package/build/test/helpers.js +75 -0
  64. package/build/test/logger-specs.js +76 -0
  65. package/build/test/npm-specs.js +20 -0
  66. package/build/test/parser-specs.js +319 -0
  67. package/build/test/plugin-e2e-specs.js +316 -0
  68. package/build/test/schema/arg-spec-specs.js +70 -0
  69. package/build/test/schema/cli-args-specs.js +408 -0
  70. package/build/test/schema/schema-specs.js +407 -0
  71. package/build/test/utils-specs.js +288 -0
  72. package/index.js +11 -0
  73. package/lib/appium-config.schema.json +2 -1
  74. package/lib/appium.js +51 -8
  75. package/lib/cli/args.js +17 -14
  76. package/lib/cli/npm.js +68 -6
  77. package/lib/cli/parser.js +5 -2
  78. package/lib/config-file.js +9 -12
  79. package/lib/config.js +104 -56
  80. package/lib/extension-config.js +1 -1
  81. package/lib/main.js +94 -40
  82. package/lib/plugin-config.js +1 -1
  83. package/lib/plugins.js +2 -0
  84. package/lib/schema/appium-config-schema.js +1 -0
  85. package/lib/schema/arg-spec.js +13 -3
  86. package/lib/schema/cli-args.js +22 -34
  87. package/lib/schema/keywords.js +20 -4
  88. package/lib/schema/schema.js +150 -24
  89. package/lib/utils.js +28 -29
  90. package/package.json +9 -14
  91. package/types/types.d.ts +5 -0
  92. package/build/lib/cli/argparse-actions.js +0 -104
  93. package/lib/cli/argparse-actions.js +0 -77
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 {
@@ -165,7 +165,10 @@ class ArgParser {
165
165
  */
166
166
  static _patchExit (parser) {
167
167
  parser.exit = (code, msg) => {
168
- throw new Error(msg);
168
+ if (code) {
169
+ throw new Error(msg);
170
+ }
171
+ process.exit();
169
172
  };
170
173
  }
171
174
 
@@ -113,10 +113,9 @@ export async function readConfigFile (filepath, opts = {}) {
113
113
  ? await loadConfigFile(lc, filepath)
114
114
  : await searchConfigFile(lc);
115
115
 
116
- if (result && !result.isEmpty && result.filepath) {
117
- const {normalize = true, pretty = true} = opts;
116
+ if (result?.filepath && !result?.isEmpty) {
117
+ const {pretty = true} = opts;
118
118
  try {
119
- /** @type {ReadConfigFileResult} */
120
119
  let configResult;
121
120
  const errors = validate(result.config);
122
121
  if (_.isEmpty(errors)) {
@@ -131,12 +130,10 @@ export async function readConfigFile (filepath, opts = {}) {
131
130
  : {...result, errors};
132
131
  }
133
132
 
134
- if (normalize) {
135
- // normalize (to camel case) all top-level property names of the config file
136
- configResult.config = normalizeConfig(
137
- /** @type {AppiumConfig} */ (configResult.config),
138
- );
139
- }
133
+ // normalize (to camel case) all top-level property names of the config file
134
+ configResult.config = normalizeConfig(
135
+ /** @type {AppiumConfig} */ (configResult.config),
136
+ );
140
137
 
141
138
  return configResult;
142
139
  } finally {
@@ -152,11 +149,12 @@ export async function readConfigFile (filepath, opts = {}) {
152
149
  * @param {AppiumConfig} config - Configuration object
153
150
  * @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
154
151
  */
155
- function normalizeConfig (config) {
152
+ export function normalizeConfig (config) {
156
153
  const schema = getSchema();
157
154
  /**
158
155
  * @param {AppiumConfig} config
159
156
  * @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
157
+ * @todo Rewrite as a loop
160
158
  * @returns Normalized section of config
161
159
  */
162
160
  const normalize = (config, section) => {
@@ -187,7 +185,7 @@ function normalizeConfig (config) {
187
185
  * @property {import('ajv').ErrorObject[]} [errors] - Validation errors
188
186
  * @property {string} [filepath] - The path to the config file, if found
189
187
  * @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
190
- * @property {AppiumConfig} [config] - The parsed configuration
188
+ * @property {NormalizedAppiumConfig} [config] - The parsed configuration
191
189
  * @property {string|betterAjvErrors.IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
192
190
  */
193
191
 
@@ -195,7 +193,6 @@ function normalizeConfig (config) {
195
193
  * Options for {@link readConfigFile}.
196
194
  * @typedef {Object} ReadConfigFileOptions
197
195
  * @property {boolean} [pretty=true] If `false`, do not use color and fancy formatting in the `reason` property of the {@link ReadConfigFileResult}. The value of `reason` is then suitable for machine-reading.
198
- * @property {boolean} [normalize=true] If `false`, do not normalize key names to camel case.
199
196
  */
200
197
 
201
198
  /**
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';
@@ -5,11 +8,8 @@ import { exec } from 'teen_process';
5
8
  import { rootDir } from './utils';
6
9
  import logger from './logger';
7
10
  import semver from 'semver';
8
- import {
9
- StoreDeprecatedDefaultCapabilityAction, DEFAULT_CAPS_ARG,
10
- } from './cli/argparse-actions';
11
11
  import findUp from 'find-up';
12
- import { getDefaultsFromSchema } from './schema/schema';
12
+ import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
13
13
 
14
14
  const npmPackage = fs.readPackageJsonFrom(__dirname);
15
15
 
@@ -25,12 +25,7 @@ const BUILD_INFO = {
25
25
  };
26
26
 
27
27
  function getNodeVersion () {
28
- return semver.coerce(process.version);
29
- }
30
-
31
- function isSubClass (candidateClass, superClass) {
32
- return _.isFunction(superClass) && _.isFunction(candidateClass)
33
- && (candidateClass.prototype instanceof superClass || candidateClass === superClass);
28
+ return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
34
29
  }
35
30
 
36
31
  async function updateBuildInfo (useGithubApiFallback = false) {
@@ -50,7 +45,7 @@ async function updateBuildInfo (useGithubApiFallback = false) {
50
45
  *
51
46
  * This is needed because Appium cannot assume `package.json` and `.git` are in the same
52
47
  * directory. Monorepos, see?
53
- * @returns {string|void} Path to dir or `undefined` if not found
48
+ * @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
54
49
  */
55
50
  async function findGitRoot () {
56
51
  return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
@@ -88,6 +83,11 @@ async function getGitRev (useGithubApiFallback = false) {
88
83
  return null;
89
84
  }
90
85
 
86
+ /**
87
+ * @param {string} commitSha
88
+ * @param {boolean} [useGithubApiFallback]
89
+ * @returns {Promise<number?>}
90
+ */
91
91
  async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
92
92
  const gitRoot = await findGitRoot();
93
93
  if (gitRoot) {
@@ -152,37 +152,49 @@ function warnNodeDeprecations () {
152
152
  // }
153
153
  }
154
154
 
155
- async function showConfig () {
155
+ async function showBuildInfo () {
156
156
  await updateBuildInfo(true);
157
157
  console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
158
158
  }
159
159
 
160
- function getNonDefaultServerArgs (parser, args) {
161
- // hopefully these function names are descriptive enough
160
+ /**
161
+ * Returns k/v pairs of server arguments which are _not_ the defaults.
162
+ * @param {ParsedArgs} parsedArgs
163
+ * @returns {Partial<ParsedArgs>}
164
+ */
165
+ function getNonDefaultServerArgs (parsedArgs) {
166
+ /**
167
+ * Flattens parsed args into a single level object for comparison with
168
+ * flattened defaults across server args and extension args.
169
+ * @param {ParsedArgs} args
170
+ * @returns {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>}
171
+ */
172
+ const flatten = (args) => {
173
+ const argSpecs = getAllArgSpecs();
174
+ const flattened = _.reduce([...argSpecs.values()], (acc, argSpec) => {
175
+ if (_.has(args, argSpec.dest)) {
176
+ acc[argSpec.dest] = {value: _.get(args, argSpec.dest), argSpec};
177
+ }
178
+ return acc;
179
+ }, /** @type {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>} */({}));
162
180
 
163
- function typesDiffer (dest) {
164
- return typeof args[dest] !== typeof defaultsFromSchema[dest];
165
- }
181
+ return flattened;
182
+ };
166
183
 
167
- function defaultValueIsArray (dest) {
168
- return _.isArray(defaultsFromSchema[dest]);
169
- }
184
+ const args = flatten(parsedArgs);
170
185
 
171
- function argsValueIsArray (dest) {
172
- return _.isArray(args[dest]);
173
- }
186
+ // hopefully these function names are descriptive enough
187
+ const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
174
188
 
175
- function arraysDiffer (dest) {
176
- return _.difference(args[dest], defaultsFromSchema[dest]).length > 0;
177
- }
189
+ const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
178
190
 
179
- function valuesUnequal (dest) {
180
- return args[dest] !== defaultsFromSchema[dest];
181
- }
191
+ const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
182
192
 
183
- function defaultIsDefined (dest) {
184
- return !_.isUndefined(defaultsFromSchema[dest]);
185
- }
193
+ const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
194
+
195
+ const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
196
+
197
+ const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
186
198
 
187
199
  // note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
188
200
 
@@ -191,8 +203,8 @@ function getNonDefaultServerArgs (parser, args) {
191
203
  arraysDiffer
192
204
  ]);
193
205
 
194
- const defaultValueNotArrayAndValuesUnequal = _.overEvery([
195
- _.negate(defaultValueIsArray), valuesUnequal
206
+ const defaultValueNotArrayAndValuesDiffer = _.overEvery([
207
+ _.negate(defaultValueIsArray), valuesDiffer
196
208
  ]);
197
209
 
198
210
  /**
@@ -205,8 +217,7 @@ function getNonDefaultServerArgs (parser, args) {
205
217
  * - if so, and the default is an array:
206
218
  * - ensures the args value is an array
207
219
  * - ensures the args values do not differ from the default values
208
- * @param {string} dest - argument name (`dest` value)
209
- * @returns {boolean}
220
+ * @type {(dest: string) => boolean}
210
221
  */
211
222
  const isNotDefault = _.overEvery([
212
223
  defaultIsDefined,
@@ -216,32 +227,66 @@ function getNonDefaultServerArgs (parser, args) {
216
227
  defaultValueIsArray,
217
228
  argValueNotArrayOrArraysDiffer
218
229
  ]),
219
- defaultValueNotArrayAndValuesUnequal
230
+ defaultValueNotArrayAndValuesDiffer
220
231
  ])
221
232
  ]);
222
233
 
223
- const defaultsFromSchema = getDefaultsFromSchema();
234
+ const defaultsFromSchema = getDefaultsForSchema(true);
224
235
 
225
- return _.pickBy(args, (__, key) => isNotDefault(key));
236
+ return _.reduce(
237
+ _.pickBy(args, (__, key) => isNotDefault(key)),
238
+ // explodes the flattened object back into nested one
239
+ (acc, {value, argSpec}) => _.set(acc, argSpec.dest, value), /** @type {Partial<ParsedArgs>} */({})
240
+ );
226
241
  }
227
242
 
228
- function getDeprecatedArgs (parser, args) {
229
- // go through the server command line arguments and figure
230
- // out which of the ones used are deprecated
231
- return parser.rawArgs.reduce((acc, [[name], {dest, default: defaultValue, action}]) => {
232
- if (!args[dest] || args[dest] === defaultValue) {
233
- return acc;
234
- }
243
+ /**
244
+ * Compacts an object for {@link showConfig}:
245
+ * 1. Removes `subcommand` key/value
246
+ * 2. Removes `undefined` values
247
+ * 3. Removes empty objects (but not `false` values)
248
+ * Does not operate recursively.
249
+ */
250
+ const compactConfig = _.partial(
251
+ _.omitBy,
252
+ _,
253
+ (value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
254
+ );
235
255
 
236
- if (action?.deprecated_for) {
237
- acc[name] = action.deprecated_for;
238
- } else if (isSubClass(action, StoreDeprecatedDefaultCapabilityAction)) {
239
- acc[name] = DEFAULT_CAPS_ARG;
240
- }
241
- return acc;
242
- }, {});
256
+ /**
257
+ * Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
258
+ *
259
+ * The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
260
+ * but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
261
+ *
262
+ * @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
263
+ * @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
264
+ * @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
265
+ * @param {ParsedArgs} parsedArgs - Entire parsed args object
266
+ */
267
+ function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
268
+ console.log('Appium Configuration\n');
269
+ console.log('from defaults:\n');
270
+ console.dir(compactConfig(defaults));
271
+ if (configResult.config) {
272
+ console.log(`\nfrom config file at ${configResult.filepath}:\n`);
273
+ console.dir(compactConfig(configResult.config));
274
+ } else {
275
+ console.log(`\n(no configuration file loaded)`);
276
+ }
277
+ if (_.isEmpty(nonDefaultPreConfigParsedArgs)) {
278
+ console.log(`\n(no CLI parameters provided)`);
279
+ } else {
280
+ console.log('\nvia CLI or function call:\n');
281
+ console.dir(compactConfig(nonDefaultPreConfigParsedArgs));
282
+ }
283
+ console.log('\nfinal configuration:\n');
284
+ console.dir(compactConfig(parsedArgs));
243
285
  }
244
286
 
287
+ /**
288
+ * @param {string} tmpDir
289
+ */
245
290
  async function validateTmpDir (tmpDir) {
246
291
  try {
247
292
  await mkdirp(tmpDir);
@@ -252,8 +297,11 @@ async function validateTmpDir (tmpDir) {
252
297
  }
253
298
 
254
299
  export {
255
- getBuildInfo, checkNodeOk, showConfig,
300
+ getBuildInfo, checkNodeOk, showBuildInfo,
256
301
  warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
257
- getGitRev, APPIUM_VER, updateBuildInfo,
258
- getDeprecatedArgs
302
+ getGitRev, APPIUM_VER, updateBuildInfo, showConfig
259
303
  };
304
+
305
+ /**
306
+ * @typedef {import('../types/types').ParsedArgs} ParsedArgs
307
+ */
@@ -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}`;
@@ -94,13 +104,13 @@ function logServerPort (address, port) {
94
104
 
95
105
  /**
96
106
  * Find any plugin name which has been installed, and which has been requested for activation by
97
- * using the --plugins flag, and turn each one into its class, so we can send them as objects to
98
- * the server init. We also want to send/assign them to the umbrella driver so it can use them to
99
- * wrap command execution
107
+ * using the --use-plugins flag, and turn each one into its class, so we can send them as objects
108
+ * to the server init. We also want to send/assign them to the umbrella driver so it can use them
109
+ * to wrap command execution
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,35 +215,41 @@ 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
+
203
239
  parsedArgs = _.defaultsDeep(
204
- parsedArgs,
240
+ preConfigParsedArgs,
205
241
  configResult.config?.server,
206
- getDefaultsFromSchema()
242
+ defaults
207
243
  );
208
- }
209
244
 
210
- parsedArgs = _.defaultsDeep(
211
- parsedArgs,
212
- configResult.config ?? {},
213
- );
245
+ if (preConfigParsedArgs.showConfig) {
246
+ showConfig(getNonDefaultServerArgs(preConfigParsedArgs), configResult, defaults, parsedArgs);
247
+ return {};
248
+ }
249
+
250
+ } else {
251
+ parsedArgs = preConfigParsedArgs;
252
+ }
214
253
 
215
254
  await logsinkInit(parsedArgs);
216
255
 
@@ -218,11 +257,11 @@ async function init (args) {
218
257
  // but instead pass control to the driver CLI
219
258
  if (parsedArgs.subcommand === DRIVER_TYPE) {
220
259
  await runExtensionCommand(parsedArgs, parsedArgs.subcommand, driverConfig);
221
- return {parser};
260
+ return {};
222
261
  }
223
262
  if (parsedArgs.subcommand === PLUGIN_TYPE) {
224
263
  await runExtensionCommand(parsedArgs, parsedArgs.subcommand, pluginConfig);
225
- return {parser};
264
+ return {};
226
265
  }
227
266
 
228
267
  if (parsedArgs.logFilters) {
@@ -243,16 +282,17 @@ async function init (args) {
243
282
  appiumDriver.driverConfig = driverConfig;
244
283
  await preflightChecks(parsedArgs, throwInsteadOfExit);
245
284
 
246
- return {parser, appiumDriver, parsedArgs};
285
+ return {appiumDriver, parsedArgs};
247
286
  }
248
287
 
249
288
  /**
250
- *
251
- * @param {ParsedArgs} [args]
252
- * @returns
289
+ * Initializes Appium's config. Starts server if appropriate and resolves the
290
+ * server instance if so; otherwise resolves w/ `undefined`.
291
+ * @param {ParsedArgs} [args] - Arguments from CLI or otherwise
292
+ * @returns {Promise<import('express').Express|undefined>}
253
293
  */
254
294
  async function main (args) {
255
- const {parser, appiumDriver, parsedArgs} = await init(args);
295
+ const {appiumDriver, parsedArgs} = await init(args);
256
296
 
257
297
  if (!appiumDriver || !parsedArgs) {
258
298
  // if this branch is taken, we've run a different subcommand, so there's nothing
@@ -264,7 +304,7 @@ async function main (args) {
264
304
  // set the active plugins on the umbrella driver so it can use them for commands
265
305
  appiumDriver.pluginClasses = pluginClasses;
266
306
 
267
- await logStartupInfo(parser, parsedArgs);
307
+ await logStartupInfo(parsedArgs);
268
308
  let routeConfiguringFunction = makeRouter(appiumDriver);
269
309
 
270
310
  const driverClasses = getActiveDrivers(parsedArgs, driverConfig);
@@ -298,7 +338,6 @@ async function main (args) {
298
338
  'to visit sites which could maliciously try to start Appium ' +
299
339
  'sessions on your machine');
300
340
  }
301
- // @ts-ignore
302
341
  appiumDriver.server = server;
303
342
  try {
304
343
  // configure as node on grid, if necessary
@@ -335,6 +374,9 @@ async function main (args) {
335
374
  return server;
336
375
  }
337
376
 
377
+ // NOTE: this is here for backwards compat for any scripts referencing `main.js` directly
378
+ // (more specifically, `build/lib/main.js`)
379
+ // the executable is now `../index.js`, so that module will typically be `require.main`.
338
380
  if (require.main === module) {
339
381
  asyncify(main);
340
382
  }
@@ -348,3 +390,15 @@ export { readConfigFile } from './config-file';
348
390
  /**
349
391
  * @typedef {import('../types/types').ParsedArgs} ParsedArgs
350
392
  */
393
+
394
+ /**
395
+ * @typedef {import('./appium').PluginExtensionClass} PluginExtensionClass
396
+ */
397
+
398
+ /**
399
+ * @typedef {import('./appium').DriverExtensionClass} DriverExtensionClass
400
+ */
401
+
402
+ /**
403
+ * @typedef {import('./appium').StaticExtMembers} StaticExtMembers
404
+ */
@@ -57,7 +57,7 @@ export default class PluginConfig extends ExtensionConfig {
57
57
  }
58
58
 
59
59
  if (_.isEmpty(activeNames)) {
60
- log.info('No plugins activated. Use the --plugins flag with names of plugins to activate');
60
+ log.info('No plugins activated. Use the --use-plugins flag with names of plugins to activate');
61
61
  }
62
62
  }
63
63
  }
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 {
@@ -212,6 +212,7 @@ const schema = /** @type {const} */ ({
212
212
  'Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it\'s not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using "deny-insecure"',
213
213
  title: 'relaxed-security config',
214
214
  type: 'boolean',
215
+ appiumCliDest: 'relaxedSecurityEnabled'
215
216
  },
216
217
  'session-override': {
217
218
  default: false,