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
package/lib/cli/parser.js CHANGED
@@ -2,21 +2,16 @@
2
2
 
3
3
  import { fs } from '@appium/support';
4
4
  import { ArgumentParser } from 'argparse';
5
- import B from 'bluebird';
6
5
  import _ from 'lodash';
7
6
  import path from 'path';
8
- import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
7
+ import { DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND } from '../constants';
9
8
  import { finalizeSchema, getArgSpec, hasArgSpec } from '../schema';
10
- import { rootDir } from '../utils';
9
+ import { rootDir } from '../config';
11
10
  import {
12
- driverConfig,
13
11
  getExtensionArgs,
14
- getServerArgs,
15
- pluginConfig
12
+ getServerArgs
16
13
  } from './args';
17
14
 
18
- export const SERVER_SUBCOMMAND = 'server';
19
-
20
15
  /**
21
16
  * If the parsed args do not contain any of these values, then we
22
17
  * will automatially inject the `server` subcommand.
@@ -146,7 +141,7 @@ class ArgParser {
146
141
  return _.reduce(
147
142
  args,
148
143
  (unpacked, value, key) => {
149
- if (hasArgSpec(key)) {
144
+ if (!_.isUndefined(value) && hasArgSpec(key)) {
150
145
  const {dest} = /** @type {import('../schema/arg-spec').ArgSpec} */(getArgSpec(key));
151
146
  _.set(unpacked, dest, value);
152
147
  } else {
@@ -258,19 +253,16 @@ class ArgParser {
258
253
  }
259
254
 
260
255
  /**
261
- * Creates a {@link ArgParser} instance. Necessarily reads extension configuration
262
- * beforehand, and finalizes the config schema.
256
+ * Creates a {@link ArgParser} instance; finalizes the config schema.
263
257
  *
264
258
  * @constructs ArgParser
265
259
  * @param {boolean} [debug] - If `true`, throw instead of exit upon parsing error
266
- * @returns {Promise<ArgParser>}
260
+ * @returns {ArgParser}
267
261
  */
268
- async function getParser (debug = false) {
269
- await B.all([driverConfig.read(), pluginConfig.read()]);
262
+ function getParser (debug) {
270
263
  finalizeSchema();
271
264
 
272
265
  return new ArgParser(debug);
273
266
  }
274
267
 
275
- export default getParser;
276
268
  export { getParser, ArgParser };
@@ -1,7 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import ExtensionCommand from './extension-command';
3
- import { PLUGIN_TYPE } from '../extension-config';
4
- import { KNOWN_PLUGINS } from '../plugins';
3
+ import { PLUGIN_TYPE, KNOWN_PLUGINS } from '../constants';
5
4
 
6
5
  const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
7
6
 
@@ -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) => {
@@ -183,19 +181,18 @@ function normalizeConfig (config) {
183
181
 
184
182
  /**
185
183
  * Result of calling {@link readConfigFile}.
186
- * @typedef {Object} ReadConfigFileResult
184
+ * @typedef ReadConfigFileResult
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
 
194
192
  /**
195
193
  * Options for {@link readConfigFile}.
196
- * @typedef {Object} ReadConfigFileOptions
194
+ * @typedef 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
  /**
@@ -220,7 +217,7 @@ function normalizeConfig (config) {
220
217
 
221
218
  /**
222
219
  * Options for {@link formatErrors}.
223
- * @typedef {Object} FormatConfigErrorsOptions
220
+ * @typedef FormatConfigErrorsOptions
224
221
  * @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
225
222
  * @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
226
223
  * @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
package/lib/config.js CHANGED
@@ -1,12 +1,14 @@
1
+ // @ts-check
2
+
3
+ /* eslint-disable no-console */
1
4
  import _ from 'lodash';
2
- import { mkdirp, system, fs } from '@appium/support';
5
+ import { system, fs } from '@appium/support';
3
6
  import axios from 'axios';
4
7
  import { exec } from 'teen_process';
5
- import { rootDir } from './utils';
6
8
  import logger from './logger';
7
9
  import semver from 'semver';
8
10
  import findUp from 'find-up';
9
- import { getDefaultsFromSchema } from './schema/schema';
11
+ import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
10
12
 
11
13
  const npmPackage = fs.readPackageJsonFrom(__dirname);
12
14
 
@@ -22,7 +24,7 @@ const BUILD_INFO = {
22
24
  };
23
25
 
24
26
  function getNodeVersion () {
25
- return semver.coerce(process.version);
27
+ return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
26
28
  }
27
29
 
28
30
  async function updateBuildInfo (useGithubApiFallback = false) {
@@ -42,7 +44,7 @@ async function updateBuildInfo (useGithubApiFallback = false) {
42
44
  *
43
45
  * This is needed because Appium cannot assume `package.json` and `.git` are in the same
44
46
  * directory. Monorepos, see?
45
- * @returns {string|void} Path to dir or `undefined` if not found
47
+ * @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
46
48
  */
47
49
  async function findGitRoot () {
48
50
  return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
@@ -80,6 +82,11 @@ async function getGitRev (useGithubApiFallback = false) {
80
82
  return null;
81
83
  }
82
84
 
85
+ /**
86
+ * @param {string} commitSha
87
+ * @param {boolean} [useGithubApiFallback]
88
+ * @returns {Promise<number?>}
89
+ */
83
90
  async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
84
91
  const gitRoot = await findGitRoot();
85
92
  if (gitRoot) {
@@ -144,37 +151,49 @@ function warnNodeDeprecations () {
144
151
  // }
145
152
  }
146
153
 
147
- async function showConfig () {
154
+ async function showBuildInfo () {
148
155
  await updateBuildInfo(true);
149
156
  console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
150
157
  }
151
158
 
152
- function getNonDefaultServerArgs (parser, args) {
153
- // hopefully these function names are descriptive enough
159
+ /**
160
+ * Returns k/v pairs of server arguments which are _not_ the defaults.
161
+ * @param {ParsedArgs} parsedArgs
162
+ * @returns {Partial<ParsedArgs>}
163
+ */
164
+ function getNonDefaultServerArgs (parsedArgs) {
165
+ /**
166
+ * Flattens parsed args into a single level object for comparison with
167
+ * flattened defaults across server args and extension args.
168
+ * @param {ParsedArgs} args
169
+ * @returns {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>}
170
+ */
171
+ const flatten = (args) => {
172
+ const argSpecs = getAllArgSpecs();
173
+ const flattened = _.reduce([...argSpecs.values()], (acc, argSpec) => {
174
+ if (_.has(args, argSpec.dest)) {
175
+ acc[argSpec.dest] = {value: _.get(args, argSpec.dest), argSpec};
176
+ }
177
+ return acc;
178
+ }, /** @type {Record<string, { value: any, argSpec: import('./schema/arg-spec').ArgSpec }>} */({}));
154
179
 
155
- function typesDiffer (dest) {
156
- return typeof args[dest] !== typeof defaultsFromSchema[dest];
157
- }
180
+ return flattened;
181
+ };
158
182
 
159
- function defaultValueIsArray (dest) {
160
- return _.isArray(defaultsFromSchema[dest]);
161
- }
183
+ const args = flatten(parsedArgs);
162
184
 
163
- function argsValueIsArray (dest) {
164
- return _.isArray(args[dest]);
165
- }
185
+ // hopefully these function names are descriptive enough
186
+ const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
166
187
 
167
- function arraysDiffer (dest) {
168
- return _.difference(args[dest], defaultsFromSchema[dest]).length > 0;
169
- }
188
+ const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
170
189
 
171
- function valuesUnequal (dest) {
172
- return args[dest] !== defaultsFromSchema[dest];
173
- }
190
+ const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
174
191
 
175
- function defaultIsDefined (dest) {
176
- return !_.isUndefined(defaultsFromSchema[dest]);
177
- }
192
+ const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
193
+
194
+ const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
195
+
196
+ const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
178
197
 
179
198
  // note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
180
199
 
@@ -183,8 +202,8 @@ function getNonDefaultServerArgs (parser, args) {
183
202
  arraysDiffer
184
203
  ]);
185
204
 
186
- const defaultValueNotArrayAndValuesUnequal = _.overEvery([
187
- _.negate(defaultValueIsArray), valuesUnequal
205
+ const defaultValueNotArrayAndValuesDiffer = _.overEvery([
206
+ _.negate(defaultValueIsArray), valuesDiffer
188
207
  ]);
189
208
 
190
209
  /**
@@ -197,8 +216,7 @@ function getNonDefaultServerArgs (parser, args) {
197
216
  * - if so, and the default is an array:
198
217
  * - ensures the args value is an array
199
218
  * - ensures the args values do not differ from the default values
200
- * @param {string} dest - argument name (`dest` value)
201
- * @returns {boolean}
219
+ * @type {(dest: string) => boolean}
202
220
  */
203
221
  const isNotDefault = _.overEvery([
204
222
  defaultIsDefined,
@@ -208,26 +226,83 @@ function getNonDefaultServerArgs (parser, args) {
208
226
  defaultValueIsArray,
209
227
  argValueNotArrayOrArraysDiffer
210
228
  ]),
211
- defaultValueNotArrayAndValuesUnequal
229
+ defaultValueNotArrayAndValuesDiffer
212
230
  ])
213
231
  ]);
214
232
 
215
- const defaultsFromSchema = getDefaultsFromSchema();
233
+ const defaultsFromSchema = getDefaultsForSchema(true);
216
234
 
217
- return _.pickBy(args, (__, key) => isNotDefault(key));
235
+ return _.reduce(
236
+ _.pickBy(args, (__, key) => isNotDefault(key)),
237
+ // explodes the flattened object back into nested one
238
+ (acc, {value, argSpec}) => _.set(acc, argSpec.dest, value), /** @type {Partial<ParsedArgs>} */({})
239
+ );
218
240
  }
219
241
 
242
+ /**
243
+ * Compacts an object for {@link showConfig}:
244
+ * 1. Removes `subcommand` key/value
245
+ * 2. Removes `undefined` values
246
+ * 3. Removes empty objects (but not `false` values)
247
+ * Does not operate recursively.
248
+ */
249
+ const compactConfig = _.partial(
250
+ _.omitBy,
251
+ _,
252
+ (value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
253
+ );
254
+
255
+ /**
256
+ * Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
257
+ *
258
+ * The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
259
+ * but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
260
+ *
261
+ * @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
262
+ * @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
263
+ * @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
264
+ * @param {ParsedArgs} parsedArgs - Entire parsed args object
265
+ */
266
+ function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
267
+ console.log('Appium Configuration\n');
268
+ console.log('from defaults:\n');
269
+ console.dir(compactConfig(defaults));
270
+ if (configResult.config) {
271
+ console.log(`\nfrom config file at ${configResult.filepath}:\n`);
272
+ console.dir(compactConfig(configResult.config));
273
+ } else {
274
+ console.log(`\n(no configuration file loaded)`);
275
+ }
276
+ if (_.isEmpty(nonDefaultPreConfigParsedArgs)) {
277
+ console.log(`\n(no CLI parameters provided)`);
278
+ } else {
279
+ console.log('\nvia CLI or function call:\n');
280
+ console.dir(compactConfig(nonDefaultPreConfigParsedArgs));
281
+ }
282
+ console.log('\nfinal configuration:\n');
283
+ console.dir(compactConfig(parsedArgs));
284
+ }
285
+
286
+ /**
287
+ * @param {string} tmpDir
288
+ */
220
289
  async function validateTmpDir (tmpDir) {
221
290
  try {
222
- await mkdirp(tmpDir);
291
+ await fs.mkdirp(tmpDir);
223
292
  } catch (e) {
224
293
  throw new Error(`We could not ensure that the temp dir you specified ` +
225
294
  `(${tmpDir}) exists. Please make sure it's writeable.`);
226
295
  }
227
296
  }
228
297
 
298
+ const rootDir = fs.findRoot(__dirname);
299
+
229
300
  export {
230
- getBuildInfo, checkNodeOk, showConfig,
301
+ getBuildInfo, checkNodeOk, showBuildInfo,
231
302
  warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
232
- getGitRev, APPIUM_VER, updateBuildInfo
303
+ getGitRev, APPIUM_VER, updateBuildInfo, showConfig, rootDir
233
304
  };
305
+
306
+ /**
307
+ * @typedef {import('../types/types').ParsedArgs} ParsedArgs
308
+ */
@@ -0,0 +1,79 @@
1
+ // @ts-check
2
+
3
+ import path from 'path';
4
+
5
+ /**
6
+ * The name of the extension type for drivers
7
+ */
8
+ export const DRIVER_TYPE = 'driver';
9
+
10
+ /**
11
+ * The name of the extension type for plugins
12
+ */
13
+ export const PLUGIN_TYPE = 'plugin';
14
+
15
+ /**
16
+ * The `server` command of the `appium` CLI
17
+ */
18
+ export const SERVER_SUBCOMMAND = 'server';
19
+
20
+ /**
21
+ * The value of `--use-plugins` if _all_ plugins should be loaded
22
+ */
23
+ export const USE_ALL_PLUGINS = 'all';
24
+
25
+ // This is a map of plugin names to npm packages representing those plugins.
26
+ // The plugins in this list will be available to the CLI so users can just
27
+ // type 'appium plugin install 'name'', rather than having to specify the full
28
+ // npm package. I.e., these are the officially recognized plugins.
29
+ export const KNOWN_PLUGINS = Object.freeze(
30
+ /** @type {const} */ ({
31
+ images: '@appium/images-plugin',
32
+ 'execute-driver': '@appium/execute-driver-plugin',
33
+ 'relaxed-caps': '@appium/relaxed-caps-plugin',
34
+ }),
35
+ );
36
+
37
+ // This is a map of driver names to npm packages representing those drivers.
38
+ // The drivers in this list will be available to the CLI so users can just
39
+ // type 'appium driver install 'name'', rather than having to specify the full
40
+ // npm package. I.e., these are the officially recognized drivers.
41
+ export const KNOWN_DRIVERS = Object.freeze(
42
+ /** @type {const} */ ({
43
+ uiautomator2: 'appium-uiautomator2-driver',
44
+ xcuitest: 'appium-xcuitest-driver',
45
+ youiengine: 'appium-youiengine-driver',
46
+ windows: 'appium-windows-driver',
47
+ mac: 'appium-mac-driver',
48
+ mac2: 'appium-mac2-driver',
49
+ espresso: 'appium-espresso-driver',
50
+ tizen: 'appium-tizen-driver',
51
+ flutter: 'appium-flutter-driver',
52
+ safari: 'appium-safari-driver',
53
+ gecko: 'appium-geckodriver',
54
+ }),
55
+ );
56
+
57
+ /**
58
+ * Relative path to directory containing any Appium internal files
59
+ */
60
+ export const CACHE_DIR_RELATIVE_PATH = path.join(
61
+ 'node_modules',
62
+ '.cache',
63
+ 'appium',
64
+ );
65
+
66
+ /**
67
+ * Relative path to hashfile (from `APPIUM_HOME`) of consuming project's `package.json` (if it exists)
68
+ */
69
+ export const PKG_HASHFILE_RELATIVE_PATH = path.join(
70
+ CACHE_DIR_RELATIVE_PATH,
71
+ 'package.hash',
72
+ );
73
+
74
+
75
+ export const EXT_SUBCOMMAND_LIST = 'list';
76
+ export const EXT_SUBCOMMAND_INSTALL = 'install';
77
+ export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
78
+ export const EXT_SUBCOMMAND_UPDATE = 'update';
79
+ export const EXT_SUBCOMMAND_RUN = 'run';
@@ -0,0 +1,230 @@
1
+ // @ts-check
2
+
3
+ import _ from 'lodash';
4
+ import { DRIVER_TYPE } from '../constants';
5
+ import log from '../logger';
6
+ import { ExtensionConfig } from './extension-config';
7
+
8
+ /**
9
+ * @extends {ExtensionConfig<DriverType>}
10
+ */
11
+ export class DriverConfig extends ExtensionConfig {
12
+
13
+ /**
14
+ * A set of unique automation names used by drivers.
15
+ * @type {Set<string>}
16
+ */
17
+ knownAutomationNames;
18
+
19
+ /**
20
+ * A mapping of {@link Manifest} instances to {@link DriverConfig} instances.
21
+ *
22
+ * `Manifest` and `ExtensionConfig` have a one-to-many relationship; each `Manifest` should be associated with a `DriverConfig` and a `PluginConfig`; no more, no less.
23
+ *
24
+ * This variable tracks the `Manifest`-to-`DriverConfig` portion.
25
+ *
26
+ * @type {WeakMap<Manifest,DriverConfig>}
27
+ * @private
28
+ */
29
+ static _instances = new WeakMap();
30
+
31
+ /**
32
+ * Call {@link DriverConfig.create} instead.
33
+ * @private
34
+ * @param {import('./manifest').Manifest} manifest - Manifest instance
35
+ * @param {DriverConfigOptions} [opts]
36
+ */
37
+ constructor (manifest, {logFn, extData} = {}) {
38
+ super(DRIVER_TYPE, manifest, logFn);
39
+
40
+ this.knownAutomationNames = new Set();
41
+
42
+ if (extData) {
43
+ this.validate(extData);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Creates a new {@link DriverConfig} instance for a {@link Manifest} instance.
49
+ *
50
+ * @param {Manifest} manifest
51
+ * @param {DriverConfigOptions} [opts]
52
+ * @throws If `manifest` already associated with a `DriverConfig`
53
+ * @returns {DriverConfig}
54
+ */
55
+ static create (manifest, {extData, logFn} = {}) {
56
+ const instance = new DriverConfig(manifest, {logFn, extData});
57
+ if (DriverConfig.getInstance(manifest)) {
58
+ throw new Error(`Manifest with APPIUM_HOME ${manifest.appiumHome} already has a DriverConfig; use DriverConfig.getInstance() to retrieve it.`);
59
+ }
60
+ DriverConfig._instances.set(manifest, instance);
61
+ return instance;
62
+ }
63
+
64
+ /**
65
+ * Returns a DriverConfig associated with a Manifest
66
+ * @param {Manifest} manifest
67
+ * @returns {DriverConfig|undefined}
68
+ */
69
+ static getInstance (manifest) {
70
+ return DriverConfig._instances.get(manifest);
71
+ }
72
+
73
+ /**
74
+ * Checks extensions for problems
75
+ * @param {ExtRecord<DriverType>} exts
76
+ */
77
+ validate (exts) {
78
+ this.knownAutomationNames.clear();
79
+ return super.validate(exts);
80
+ }
81
+
82
+ /**
83
+ * @param {ManifestDriverData} extData
84
+ * @returns {import('./extension-config').Problem[]}
85
+ */
86
+ getConfigProblems (extData) {
87
+ const problems = [];
88
+ const {platformNames, automationName} = extData;
89
+
90
+ if (!_.isArray(platformNames)) {
91
+ problems.push({
92
+ err: 'Missing or incorrect supported platformNames list.',
93
+ val: platformNames
94
+ });
95
+ } else {
96
+ if (_.isEmpty(platformNames)) {
97
+ problems.push({
98
+ err: 'Empty platformNames list.',
99
+ val: platformNames
100
+ });
101
+ } else {
102
+ for (const pName of platformNames) {
103
+ if (!_.isString(pName)) {
104
+ problems.push({err: 'Incorrectly formatted platformName.', val: pName});
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ if (!_.isString(automationName)) {
111
+ problems.push({err: 'Missing or incorrect automationName', val: automationName});
112
+ }
113
+
114
+ if (this.knownAutomationNames.has(automationName)) {
115
+ problems.push({
116
+ err: 'Multiple drivers claim support for the same automationName',
117
+ val: automationName
118
+ });
119
+ }
120
+
121
+ // should we retain the name at the end of this function, once we've checked there are no problems?
122
+ this.knownAutomationNames.add(automationName);
123
+
124
+ return problems;
125
+ }
126
+
127
+ /**
128
+ * @param {ExtName<DriverType>} driverName
129
+ * @param {ManifestDriverData} extData
130
+ * @returns {string}
131
+ */
132
+ extensionDesc (driverName, {version, automationName}) {
133
+ return `${driverName}@${version} (automationName '${automationName}')`;
134
+ }
135
+
136
+ /**
137
+ * Given capabilities, find a matching driver within the config. Load its class and return it along with version and driver name.
138
+ * @param { { automationName: string, platformName: string } } caps
139
+ * @returns { { driver: import('./manifest').DriverClass, version: string, driverName: string } }
140
+ */
141
+ findMatchingDriver ({automationName, platformName}) {
142
+ if (!_.isString(platformName)) {
143
+ throw new Error('You must include a platformName capability');
144
+ }
145
+
146
+ if (!_.isString(automationName)) {
147
+ throw new Error('You must include an automationName capability');
148
+ }
149
+
150
+ log.info(`Attempting to find matching driver for automationName ` +
151
+ `'${automationName}' and platformName '${platformName}'`);
152
+
153
+ try {
154
+ const {
155
+ driverName,
156
+ mainClass,
157
+ version,
158
+ } = this._getDriverBySupport(automationName, platformName);
159
+ log.info(`The '${driverName}' driver was installed and matched caps.`);
160
+ log.info(`Will require it at ${this.getInstallPath(driverName)}`);
161
+ const driver = this.require(driverName);
162
+ if (!driver) {
163
+ throw new Error(`Driver '${driverName}' did not export a class with name '${mainClass}'. Contact the author of the driver!`);
164
+ }
165
+ return {driver, version, driverName};
166
+ } catch (err) {
167
+ const msg = `Could not find a driver for automationName ` +
168
+ `'${automationName}' and platformName ${platformName}'. ` +
169
+ `Have you installed a driver that supports those ` +
170
+ `capabilities? Run 'appium driver list --installed' to see. ` +
171
+ `(Lower-level error: ${err.message})`;
172
+ throw new Error(msg);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Given an automation name and platform name, find a suitable driver and return its extension data.
178
+ * @param {string} matchAutomationName
179
+ * @param {string} matchPlatformName
180
+ * @returns {ManifestDriverData & { driverName: string } }
181
+ */
182
+ _getDriverBySupport (matchAutomationName, matchPlatformName) {
183
+ const drivers = this.installedExtensions;
184
+ for (const [driverName, driverData] of _.toPairs(drivers)) {
185
+ const {automationName, platformNames} = driverData;
186
+ const aNameMatches = automationName.toLowerCase() === matchAutomationName.toLowerCase();
187
+ const pNameMatches = _.includes(platformNames.map(_.toLower),
188
+ matchPlatformName.toLowerCase());
189
+
190
+ if (aNameMatches && pNameMatches) {
191
+ return {driverName, ...driverData};
192
+ }
193
+
194
+ if (aNameMatches) {
195
+ throw new Error(`Driver '${driverName}' supports automationName ` +
196
+ `'${automationName}', but Appium could not find ` +
197
+ `support for platformName '${matchPlatformName}'. Supported ` +
198
+ `platformNames are: ` +
199
+ JSON.stringify(platformNames));
200
+ }
201
+ }
202
+
203
+ throw new Error(`Could not find installed driver to support given caps`);
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @typedef DriverConfigOptions
209
+ * @property {import('./extension-config').ExtensionLogFn} [logFn] - Optional logging function
210
+ * @property {ManifestData['drivers']} [extData] - Extension data
211
+ */
212
+
213
+ /**
214
+ * @typedef {import('./manifest').ExternalData<DriverType>} ExternalDriverData
215
+ * @typedef {import('./manifest').ManifestDriverData} ManifestDriverData
216
+ * @typedef {import('./manifest').ManifestData} ManifestData
217
+ * @typedef {import('./manifest').DriverType} DriverType
218
+ * @typedef {import('./manifest').Manifest} Manifest
219
+ */
220
+
221
+ /**
222
+ * @template T
223
+ * @typedef {import('./extension-config').ExtRecord<T>} ExtRecord
224
+ */
225
+
226
+ /**
227
+ * @template T
228
+ * @typedef {import('./extension-config').ExtName<T>} ExtName
229
+ */
230
+