appium 2.0.0-beta.3 → 2.0.0-beta.34

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 (149) hide show
  1. package/README.md +10 -11
  2. package/build/lib/appium.d.ts +215 -0
  3. package/build/lib/appium.d.ts.map +1 -0
  4. package/build/lib/appium.js +241 -132
  5. package/build/lib/cli/args.d.ts +20 -0
  6. package/build/lib/cli/args.d.ts.map +1 -0
  7. package/build/lib/cli/args.js +96 -282
  8. package/build/lib/cli/driver-command.d.ts +36 -0
  9. package/build/lib/cli/driver-command.d.ts.map +1 -0
  10. package/build/lib/cli/driver-command.js +19 -12
  11. package/build/lib/cli/extension-command.d.ts +345 -0
  12. package/build/lib/cli/extension-command.d.ts.map +1 -0
  13. package/build/lib/cli/extension-command.js +171 -96
  14. package/build/lib/cli/extension.d.ts +14 -0
  15. package/build/lib/cli/extension.d.ts.map +1 -0
  16. package/build/lib/cli/extension.js +31 -16
  17. package/build/lib/cli/parser.d.ts +80 -0
  18. package/build/lib/cli/parser.d.ts.map +1 -0
  19. package/build/lib/cli/parser.js +152 -95
  20. package/build/lib/cli/plugin-command.d.ts +39 -0
  21. package/build/lib/cli/plugin-command.d.ts.map +1 -0
  22. package/build/lib/cli/plugin-command.js +18 -13
  23. package/build/lib/cli/utils.d.ts +29 -0
  24. package/build/lib/cli/utils.d.ts.map +1 -0
  25. package/build/lib/cli/utils.js +27 -3
  26. package/build/lib/config-file.d.ts +100 -0
  27. package/build/lib/config-file.d.ts.map +1 -0
  28. package/build/lib/config-file.js +136 -0
  29. package/build/lib/config.d.ts +41 -0
  30. package/build/lib/config.d.ts.map +1 -0
  31. package/build/lib/config.js +92 -67
  32. package/build/lib/constants.d.ts +48 -0
  33. package/build/lib/constants.d.ts.map +1 -0
  34. package/build/lib/constants.js +60 -0
  35. package/build/lib/extension/driver-config.d.ts +84 -0
  36. package/build/lib/extension/driver-config.d.ts.map +1 -0
  37. package/build/lib/extension/driver-config.js +190 -0
  38. package/build/lib/extension/extension-config.d.ts +170 -0
  39. package/build/lib/extension/extension-config.d.ts.map +1 -0
  40. package/build/lib/extension/extension-config.js +297 -0
  41. package/build/lib/extension/index.d.ts +39 -0
  42. package/build/lib/extension/index.d.ts.map +1 -0
  43. package/build/lib/extension/index.js +77 -0
  44. package/build/lib/extension/manifest.d.ts +174 -0
  45. package/build/lib/extension/manifest.d.ts.map +1 -0
  46. package/build/lib/extension/manifest.js +246 -0
  47. package/build/lib/extension/package-changed.d.ts +11 -0
  48. package/build/lib/extension/package-changed.d.ts.map +1 -0
  49. package/build/lib/extension/package-changed.js +68 -0
  50. package/build/lib/extension/plugin-config.d.ts +62 -0
  51. package/build/lib/extension/plugin-config.d.ts.map +1 -0
  52. package/build/lib/extension/plugin-config.js +87 -0
  53. package/build/lib/grid-register.d.ts +10 -0
  54. package/build/lib/grid-register.d.ts.map +1 -0
  55. package/build/lib/grid-register.js +21 -25
  56. package/build/lib/logger.d.ts +3 -0
  57. package/build/lib/logger.d.ts.map +1 -0
  58. package/build/lib/logger.js +4 -6
  59. package/build/lib/logsink.d.ts +4 -0
  60. package/build/lib/logsink.d.ts.map +1 -0
  61. package/build/lib/logsink.js +12 -16
  62. package/build/lib/main.d.ts +54 -0
  63. package/build/lib/main.d.ts.map +1 -0
  64. package/build/lib/main.js +189 -90
  65. package/build/lib/schema/arg-spec.d.ts +143 -0
  66. package/build/lib/schema/arg-spec.d.ts.map +1 -0
  67. package/build/lib/schema/arg-spec.js +119 -0
  68. package/build/lib/schema/cli-args.d.ts +19 -0
  69. package/build/lib/schema/cli-args.d.ts.map +1 -0
  70. package/build/lib/schema/cli-args.js +180 -0
  71. package/build/lib/schema/cli-transformers.d.ts +5 -0
  72. package/build/lib/schema/cli-transformers.d.ts.map +1 -0
  73. package/build/lib/schema/cli-transformers.js +74 -0
  74. package/build/lib/schema/index.d.ts +3 -0
  75. package/build/lib/schema/index.d.ts.map +1 -0
  76. package/build/lib/schema/index.js +34 -0
  77. package/build/lib/schema/keywords.d.ts +24 -0
  78. package/build/lib/schema/keywords.d.ts.map +1 -0
  79. package/build/lib/schema/keywords.js +70 -0
  80. package/build/lib/schema/schema.d.ts +259 -0
  81. package/build/lib/schema/schema.d.ts.map +1 -0
  82. package/build/lib/schema/schema.js +452 -0
  83. package/build/lib/utils.d.ts +66 -0
  84. package/build/lib/utils.d.ts.map +1 -0
  85. package/build/lib/utils.js +35 -139
  86. package/build/tsconfig.tsbuildinfo +1 -0
  87. package/build/types/appium-manifest.d.ts +40 -0
  88. package/build/types/appium-manifest.d.ts.map +1 -0
  89. package/build/types/cli.d.ts +112 -0
  90. package/build/types/cli.d.ts.map +1 -0
  91. package/build/types/extension.d.ts +43 -0
  92. package/build/types/extension.d.ts.map +1 -0
  93. package/build/types/external-manifest.d.ts +47 -0
  94. package/build/types/external-manifest.d.ts.map +1 -0
  95. package/build/types/index.d.ts +15 -0
  96. package/build/types/index.d.ts.map +1 -0
  97. package/index.js +11 -0
  98. package/lib/appium-config.schema.json +278 -0
  99. package/lib/appium.js +402 -155
  100. package/lib/cli/args.js +174 -377
  101. package/lib/cli/driver-command.js +22 -7
  102. package/lib/cli/extension-command.js +372 -177
  103. package/lib/cli/extension.js +32 -10
  104. package/lib/cli/parser.js +253 -83
  105. package/lib/cli/plugin-command.js +19 -6
  106. package/lib/cli/utils.js +22 -2
  107. package/lib/config-file.js +223 -0
  108. package/lib/config.js +170 -69
  109. package/lib/constants.js +78 -0
  110. package/lib/extension/driver-config.js +249 -0
  111. package/lib/extension/extension-config.js +458 -0
  112. package/lib/extension/index.js +102 -0
  113. package/lib/extension/manifest.js +484 -0
  114. package/lib/extension/package-changed.js +63 -0
  115. package/lib/extension/plugin-config.js +113 -0
  116. package/lib/grid-register.js +25 -22
  117. package/lib/logger.js +1 -1
  118. package/lib/logsink.js +14 -7
  119. package/lib/main.js +255 -90
  120. package/lib/schema/arg-spec.js +232 -0
  121. package/lib/schema/cli-args.js +261 -0
  122. package/lib/schema/cli-transformers.js +122 -0
  123. package/lib/schema/index.js +2 -0
  124. package/lib/schema/keywords.js +134 -0
  125. package/lib/schema/schema.js +734 -0
  126. package/lib/utils.js +85 -129
  127. package/package.json +68 -85
  128. package/scripts/postinstall.js +71 -0
  129. package/types/appium-manifest.ts +61 -0
  130. package/types/cli.ts +153 -0
  131. package/types/extension.ts +56 -0
  132. package/types/external-manifest.ts +58 -0
  133. package/types/index.ts +14 -0
  134. package/CHANGELOG.md +0 -3515
  135. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  136. package/build/lib/cli/npm.js +0 -206
  137. package/build/lib/cli/parser-helpers.js +0 -82
  138. package/build/lib/driver-config.js +0 -77
  139. package/build/lib/drivers.js +0 -96
  140. package/build/lib/extension-config.js +0 -251
  141. package/build/lib/plugin-config.js +0 -59
  142. package/build/lib/plugins.js +0 -14
  143. package/lib/cli/npm.js +0 -183
  144. package/lib/cli/parser-helpers.js +0 -79
  145. package/lib/driver-config.js +0 -46
  146. package/lib/drivers.js +0 -81
  147. package/lib/extension-config.js +0 -208
  148. package/lib/plugin-config.js +0 -34
  149. package/lib/plugins.js +0 -10
@@ -0,0 +1,223 @@
1
+
2
+ import betterAjvErrors from '@sidvind/better-ajv-errors';
3
+ import { lilconfig } from 'lilconfig';
4
+ import _ from 'lodash';
5
+ import yaml from 'yaml';
6
+ import { getSchema, validate } from './schema/schema';
7
+
8
+ /**
9
+ * lilconfig loader to handle `.yaml` files
10
+ * @type {import('lilconfig').LoaderSync}
11
+ */
12
+ function yamlLoader (filepath, content) {
13
+ return yaml.parse(content);
14
+ }
15
+
16
+ /**
17
+ * A cache of the raw config file (a JSON string) at a filepath.
18
+ * This is used for better error reporting.
19
+ * Note that config files needn't be JSON, but it helps if they are.
20
+ * @type {Map<string,RawJson>}
21
+ */
22
+ const rawConfig = new Map();
23
+
24
+ /**
25
+ * Custom JSON loader that caches the raw config file (for use with `better-ajv-errors`).
26
+ * If it weren't for this cache, this would be unnecessary.
27
+ * @type {import('lilconfig').LoaderSync}
28
+ */
29
+ function jsonLoader (filepath, content) {
30
+ rawConfig.set(filepath, content);
31
+ return JSON.parse(content);
32
+ }
33
+
34
+ /**
35
+ * Loads a config file from an explicit path
36
+ * @param {LilconfigAsyncSearcher} lc - lilconfig instance
37
+ * @param {string} filepath - Path to config file
38
+ * @returns {Promise<import('lilconfig').LilconfigResult>}
39
+ */
40
+ async function loadConfigFile (lc, filepath) {
41
+ try {
42
+ // removing "await" will cause any rejection to _not_ be caught in this block!
43
+ return await lc.load(filepath);
44
+ } catch (/** @type {unknown} */err) {
45
+ if (/** @type {NodeJS.ErrnoException} */(err).code === 'ENOENT') {
46
+ /** @type {NodeJS.ErrnoException} */(err).message = `Config file not found at user-provided path: ${filepath}`;
47
+ throw err;
48
+ } else if (err instanceof SyntaxError) {
49
+ // generally invalid JSON
50
+ err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`;
51
+ throw err;
52
+ }
53
+ throw err;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Searches for a config file
59
+ * @param {LilconfigAsyncSearcher} lc - lilconfig instance
60
+ * @returns {Promise<import('lilconfig').LilconfigResult>}
61
+ */
62
+ async function searchConfigFile (lc) {
63
+ return await lc.search();
64
+ }
65
+
66
+ /**
67
+ * Given an array of errors and the result of loading a config file, generate a
68
+ * helpful string for the user.
69
+ *
70
+ * - If `opts` contains a `json` property, this should be the original JSON
71
+ * _string_ of the config file. This is only applicable if the config file
72
+ * was in JSON format. If present, it will associate line numbers with errors.
73
+ * - If `errors` happens to be empty, this will throw.
74
+ * @param {import('ajv').ErrorObject[]} errors - Non-empty array of errors. Required.
75
+ * @param {ReadConfigFileResult['config']|any} [config] -
76
+ * Configuration & metadata
77
+ * @param {FormatConfigErrorsOptions} [opts]
78
+ * @throws {TypeError} If `errors` is empty
79
+ * @returns {string}
80
+ */
81
+ export function formatErrors (errors = [], config = {}, opts = {}) {
82
+ if (errors && !errors.length) {
83
+ throw new TypeError('Array of errors must be non-empty');
84
+ }
85
+ return betterAjvErrors(getSchema(opts.schemaId), config, errors, {
86
+ json: opts.json,
87
+ format: 'cli',
88
+ });
89
+ }
90
+
91
+ /**
92
+ * Given an optional path, read a config file. Validates the config file.
93
+ *
94
+ * Call {@link validate} if you already have a config object.
95
+ * @param {string} [filepath] - Path to config file, if we have one
96
+ * @param {ReadConfigFileOptions} [opts] - Options
97
+ * @public
98
+ * @returns {Promise<ReadConfigFileResult>} Contains config and filepath, if found, and any errors
99
+ */
100
+ export async function readConfigFile (filepath, opts = {}) {
101
+ const lc = lilconfig('appium', {
102
+ loaders: {
103
+ '.yaml': yamlLoader,
104
+ '.yml': yamlLoader,
105
+ '.json': jsonLoader,
106
+ noExt: jsonLoader,
107
+ },
108
+ packageProp: 'appiumConfig'
109
+ });
110
+
111
+ const result = filepath
112
+ ? await loadConfigFile(lc, filepath)
113
+ : await searchConfigFile(lc);
114
+
115
+ if (result?.filepath && !result?.isEmpty) {
116
+ const {pretty = true} = opts;
117
+ try {
118
+ let configResult;
119
+ const errors = validate(result.config);
120
+ if (_.isEmpty(errors)) {
121
+ configResult = {...result, errors};
122
+ } else {
123
+ const reason = formatErrors(errors, result.config, {
124
+ json: rawConfig.get(result.filepath),
125
+ pretty,
126
+ });
127
+ configResult = reason
128
+ ? {...result, errors, reason}
129
+ : {...result, errors};
130
+ }
131
+
132
+ // normalize (to camel case) all top-level property names of the config file
133
+ configResult.config = normalizeConfig(
134
+ /** @type {AppiumConfig} */ (configResult.config),
135
+ );
136
+
137
+ return configResult;
138
+ } finally {
139
+ // clean up the raw config file cache, which is only kept to better report errors.
140
+ rawConfig.delete(result.filepath);
141
+ }
142
+ }
143
+ return result ?? {};
144
+ }
145
+
146
+ /**
147
+ * Convert schema property names to either a) the value of the `appiumCliDest` property, if any; or b) camel-case
148
+ * @param {AppiumConfig} config - Configuration object
149
+ * @returns {NormalizedAppiumConfig} New object with camel-cased keys (or `dest` keys).
150
+ */
151
+ export function normalizeConfig (config) {
152
+ const schema = getSchema();
153
+ /**
154
+ * @param {AppiumConfig} config
155
+ * @param {string} [section] - Keypath (lodash `_.get()` style) to section of config. If omitted, assume root Appium config schema
156
+ * @todo Rewrite as a loop
157
+ * @returns Normalized section of config
158
+ */
159
+ const normalize = (config, section) => {
160
+ const obj = _.isUndefined(section) ? config : _.get(config, section, config);
161
+
162
+ const mappedObj = _.mapKeys(obj, (__, prop) =>
163
+ schema.properties[prop]?.appiumCliDest ?? _.camelCase(prop),
164
+ );
165
+
166
+ return _.mapValues(mappedObj, (value, property) => {
167
+ const nextSection = section ? `${section}.${property}` : property;
168
+ return isSchemaTypeObject(value) ? normalize(config, nextSection) : value;
169
+ });
170
+ };
171
+
172
+ /**
173
+ * Returns `true` if the schema prop references an object, or if it's an object itself
174
+ * @param {import('ajv').SchemaObject|object} schema - Referencing schema object
175
+ */
176
+ const isSchemaTypeObject = (schema) => Boolean(schema.properties);
177
+
178
+ return normalize(config);
179
+ }
180
+
181
+ /**
182
+ * Result of calling {@link readConfigFile}.
183
+ * @typedef ReadConfigFileResult
184
+ * @property {import('ajv').ErrorObject[]} [errors] - Validation errors
185
+ * @property {string} [filepath] - The path to the config file, if found
186
+ * @property {boolean} [isEmpty] - If `true`, the config file exists but is empty
187
+ * @property {NormalizedAppiumConfig} [config] - The parsed configuration
188
+ * @property {string|import('@sidvind/better-ajv-errors').IOutputError[]} [reason] - Human-readable error messages and suggestions. If the `pretty` option is `true`, this will be a nice string to print.
189
+ */
190
+
191
+ /**
192
+ * Options for {@link readConfigFile}.
193
+ * @typedef ReadConfigFileOptions
194
+ * @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.
195
+ */
196
+
197
+ /**
198
+ * This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
199
+ * @typedef {ReturnType<import('lilconfig')["lilconfig"]>} LilconfigAsyncSearcher
200
+ */
201
+
202
+ /**
203
+ * The contents of an Appium config file. Generated from schema
204
+ * @typedef {import('@appium/types').AppiumConfig} AppiumConfig
205
+ */
206
+
207
+ /**
208
+ * The contents of an Appium config file with camelcased property names (and using `appiumCliDest` value if present). Generated from {@link AppiumConfig}
209
+ * @typedef {import('@appium/types').NormalizedAppiumConfig} NormalizedAppiumConfig
210
+ */
211
+
212
+ /**
213
+ * The string should be a raw JSON string.
214
+ * @typedef {string} RawJson
215
+ */
216
+
217
+ /**
218
+ * Options for {@link formatErrors}.
219
+ * @typedef FormatConfigErrorsOptions
220
+ * @property {import('./config-file').RawJson} [json] - Raw JSON config (as string)
221
+ * @property {boolean} [pretty=true] - Whether to format errors as a CLI-friendly string
222
+ * @property {string} [schemaId] - Specific ID of a prop; otherwise entire schema
223
+ */
package/lib/config.js CHANGED
@@ -1,14 +1,15 @@
1
+ /* eslint-disable no-console */
1
2
  import _ from 'lodash';
2
- import path from 'path';
3
- import { mkdirp, fs, system } from 'appium-support';
3
+ import { system, fs } from '@appium/support';
4
4
  import axios from 'axios';
5
5
  import { exec } from 'teen_process';
6
- import { rootDir } from './utils';
7
6
  import logger from './logger';
8
7
  import semver from 'semver';
8
+ import findUp from 'find-up';
9
+ import { getDefaultsForSchema, getAllArgSpecs } from './schema/schema';
9
10
 
11
+ const npmPackage = fs.readPackageJsonFrom(__dirname);
10
12
 
11
- const npmPackage = require(path.resolve(rootDir, 'package.json'));
12
13
  const APPIUM_VER = npmPackage.version;
13
14
  const MIN_NODE_VERSION = npmPackage.engines.node;
14
15
 
@@ -16,12 +17,15 @@ const GIT_META_ROOT = '.git';
16
17
  const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
17
18
  const GITHUB_API = 'https://api.github.com/repos/appium/appium';
18
19
 
20
+ /**
21
+ * @type {import('../types/cli').BuildInfo}
22
+ */
19
23
  const BUILD_INFO = {
20
24
  version: APPIUM_VER,
21
25
  };
22
26
 
23
27
  function getNodeVersion () {
24
- return semver.coerce(process.version);
28
+ return /** @type {import('semver').SemVer} */(semver.coerce(process.version));
25
29
  }
26
30
 
27
31
  async function updateBuildInfo (useGithubApiFallback = false) {
@@ -31,16 +35,28 @@ async function updateBuildInfo (useGithubApiFallback = false) {
31
35
  }
32
36
  BUILD_INFO['git-sha'] = sha;
33
37
  const built = await getGitTimestamp(sha, useGithubApiFallback);
34
- if (!_.isEmpty(built)) {
38
+ if (built) {
35
39
  BUILD_INFO.built = built;
36
40
  }
37
41
  }
38
42
 
43
+ /**
44
+ * Finds the Git metadata dir (see `GIT_META_ROOT`)
45
+ *
46
+ * This is needed because Appium cannot assume `package.json` and `.git` are in the same
47
+ * directory. Monorepos, see?
48
+ * @returns {Promise<string|undefined>} Path to dir or `undefined` if not found
49
+ */
50
+ async function findGitRoot () {
51
+ return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
52
+ }
53
+
39
54
  async function getGitRev (useGithubApiFallback = false) {
40
- if (await fs.exists(path.resolve(rootDir, GIT_META_ROOT))) {
55
+ const gitRoot = await findGitRoot();
56
+ if (gitRoot) {
41
57
  try {
42
58
  const {stdout} = await exec(GIT_BINARY, ['rev-parse', 'HEAD'], {
43
- cwd: rootDir
59
+ cwd: gitRoot
44
60
  });
45
61
  return stdout.trim();
46
62
  } catch (ign) {}
@@ -67,11 +83,17 @@ async function getGitRev (useGithubApiFallback = false) {
67
83
  return null;
68
84
  }
69
85
 
86
+ /**
87
+ * @param {string} commitSha
88
+ * @param {boolean} [useGithubApiFallback]
89
+ * @returns {Promise<string?>}
90
+ */
70
91
  async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
71
- if (await fs.exists(path.resolve(rootDir, GIT_META_ROOT))) {
92
+ const gitRoot = await findGitRoot();
93
+ if (gitRoot) {
72
94
  try {
73
95
  const {stdout} = await exec(GIT_BINARY, ['show', '-s', '--format=%ci', commitSha], {
74
- cwd: rootDir
96
+ cwd: gitRoot
75
97
  });
76
98
  return stdout.trim();
77
99
  } catch (ign) {}
@@ -100,10 +122,11 @@ async function getGitTimestamp (commitSha, useGithubApiFallback = false) {
100
122
  }
101
123
 
102
124
  /**
103
- * @return Mutable object containing Appium build information. By default it
125
+ * Mutable object containing Appium build information. By default it
104
126
  * only contains the Appium version, but is updated with the build timestamp
105
127
  * and git commit hash asynchronously as soon as `updateBuildInfo` is called
106
128
  * and succeeds.
129
+ * @returns {import('../types/cli').BuildInfo}
107
130
  */
108
131
  function getBuildInfo () {
109
132
  return BUILD_INFO;
@@ -130,83 +153,161 @@ function warnNodeDeprecations () {
130
153
  // }
131
154
  }
132
155
 
133
- async function showConfig () {
156
+ async function showBuildInfo () {
134
157
  await updateBuildInfo(true);
135
158
  console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
136
159
  }
137
160
 
138
- function getNonDefaultArgs (parser, args) {
139
- let nonDefaults = {};
140
- for (let rawArg of parser.rawArgs) {
141
- let arg = rawArg[1].dest;
142
- if (args[arg] && args[arg] !== rawArg[1].defaultValue) {
143
- nonDefaults[arg] = args[arg];
144
- }
145
- }
146
- return nonDefaults;
147
- }
148
-
149
- function checkValidPort (port, portName) {
150
- if (port > 0 && port < 65536) return true; // eslint-disable-line curly
151
- logger.error(`Port '${portName}' must be greater than 0 and less than 65536. Currently ${port}`);
152
- return false;
153
- }
154
-
155
- function validateServerArgs (parser, args) {
156
- // arguments that cannot both be set
157
- let exclusives = [
158
- ['noReset', 'fullReset'],
159
- ['ipa', 'safari'],
160
- ['app', 'safari'],
161
- ['forceIphone', 'forceIpad'],
162
- ['deviceName', 'defaultDevice']
163
- ];
164
-
165
- for (let exSet of exclusives) {
166
- let numFoundInArgs = 0;
167
- for (let opt of exSet) {
168
- if (_.has(args, opt) && args[opt]) {
169
- numFoundInArgs++;
161
+ /**
162
+ * Returns k/v pairs of server arguments which are _not_ the defaults.
163
+ * @param {Args} parsedArgs
164
+ * @returns {Args}
165
+ */
166
+ function getNonDefaultServerArgs (parsedArgs) {
167
+ /**
168
+ * Flattens parsed args into a single level object for comparison with
169
+ * flattened defaults across server args and extension args.
170
+ * @param {Args} args
171
+ * @returns {Record<string, { value: any, argSpec: ArgSpec }>}
172
+ */
173
+ const flatten = (args) => {
174
+ const argSpecs = getAllArgSpecs();
175
+ const flattened = _.reduce([...argSpecs.values()], (acc, argSpec) => {
176
+ if (_.has(args, argSpec.dest)) {
177
+ acc[argSpec.dest] = {value: _.get(args, argSpec.dest), argSpec};
170
178
  }
171
- }
172
- if (numFoundInArgs > 1) {
173
- throw new Error(`You can't pass in more than one argument from the ` +
174
- `set ${JSON.stringify(exSet)}, since they are ` +
175
- `mutually exclusive`);
176
- }
177
- }
179
+ return acc;
180
+ }, /** @type {Record<string, { value: any, argSpec: ArgSpec }>} */({}));
178
181
 
179
- const validations = {
180
- port: checkValidPort,
181
- callbackPort: checkValidPort,
182
- bootstrapPort: checkValidPort,
183
- chromedriverPort: checkValidPort,
184
- robotPort: checkValidPort,
185
- backendRetries: (r) => r >= 0
182
+ return flattened;
186
183
  };
187
184
 
188
- const nonDefaultArgs = getNonDefaultArgs(parser, args);
185
+ const args = flatten(parsedArgs);
189
186
 
190
- for (let [arg, validator] of _.toPairs(validations)) {
191
- if (_.has(nonDefaultArgs, arg)) {
192
- if (!validator(args[arg], arg)) {
193
- throw new Error(`Invalid argument for param ${arg}: ${args[arg]}`);
194
- }
195
- }
187
+ // hopefully these function names are descriptive enough
188
+ const typesDiffer = /** @param {string} dest */(dest) => typeof args[dest].value !== typeof defaultsFromSchema[dest];
189
+
190
+ const defaultValueIsArray = /** @param {string} dest */(dest) => _.isArray(defaultsFromSchema[dest]);
191
+
192
+ const argsValueIsArray = /** @param {string} dest */(dest) => _.isArray(args[dest].value);
193
+
194
+ const arraysDiffer = /** @param {string} dest */(dest) => _.gt(_.size(_.difference(args[dest].value, defaultsFromSchema[dest])), 0);
195
+
196
+ const valuesDiffer = /** @param {string} dest */(dest) => args[dest].value !== defaultsFromSchema[dest];
197
+
198
+ const defaultIsDefined = /** @param {string} dest */(dest) => !_.isUndefined(defaultsFromSchema[dest]);
199
+
200
+ // note that `_.overEvery` is like an "AND", and `_.overSome` is like an "OR"
201
+
202
+ const argValueNotArrayOrArraysDiffer = _.overSome([
203
+ _.negate(argsValueIsArray),
204
+ arraysDiffer
205
+ ]);
206
+
207
+ const defaultValueNotArrayAndValuesDiffer = _.overEvery([
208
+ _.negate(defaultValueIsArray), valuesDiffer
209
+ ]);
210
+
211
+ /**
212
+ * This used to be a hideous conditional, but it's broken up into a hideous function instead.
213
+ * hopefully this makes things a little more understandable.
214
+ * - checks if the default value is defined
215
+ * - if so, and the default is not an array:
216
+ * - ensures the types are the same
217
+ * - ensures the values are equal
218
+ * - if so, and the default is an array:
219
+ * - ensures the args value is an array
220
+ * - ensures the args values do not differ from the default values
221
+ * @type {(dest: string) => boolean}
222
+ */
223
+ const isNotDefault = _.overEvery([
224
+ defaultIsDefined,
225
+ _.overSome([
226
+ typesDiffer,
227
+ _.overEvery([
228
+ defaultValueIsArray,
229
+ argValueNotArrayOrArraysDiffer
230
+ ]),
231
+ defaultValueNotArrayAndValuesDiffer
232
+ ])
233
+ ]);
234
+
235
+ const defaultsFromSchema = getDefaultsForSchema(true);
236
+
237
+ return _.reduce(
238
+ _.pickBy(args, (__, key) => isNotDefault(key)),
239
+ // explodes the flattened object back into nested one
240
+ (acc, {value, argSpec}) => _.set(acc, argSpec.dest, value), /** @type {Args} */({})
241
+ );
242
+ }
243
+
244
+ /**
245
+ * Compacts an object for {@link showConfig}:
246
+ * 1. Removes `subcommand` key/value
247
+ * 2. Removes `undefined` values
248
+ * 3. Removes empty objects (but not `false` values)
249
+ * Does not operate recursively.
250
+ */
251
+ const compactConfig = _.partial(
252
+ _.omitBy,
253
+ _,
254
+ (value, key) => key === 'subcommand' || _.isUndefined(value) || (_.isObject(value) && _.isEmpty(value))
255
+ );
256
+
257
+ /**
258
+ * Shows a breakdown of the current config after CLI params, config file loaded & defaults applied.
259
+ *
260
+ * The actual shape of `preConfigParsedArgs` and `defaults` does not matter for the purposes of this function,
261
+ * but it's intended to be called with values of type {@link ParsedArgs} and `DefaultValues<true>`, respectively.
262
+ *
263
+ * @param {Partial<ParsedArgs>} nonDefaultPreConfigParsedArgs - Parsed CLI args (or param to `init()`) before config & defaults applied
264
+ * @param {import('./config-file').ReadConfigFileResult} configResult - Result of attempting to load a config file. _Must_ be normalized
265
+ * @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
266
+ * @param {ParsedArgs} parsedArgs - Entire parsed args object
267
+ */
268
+ function showConfig (nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
269
+ console.log('Appium Configuration\n');
270
+ console.log('from defaults:\n');
271
+ console.dir(compactConfig(defaults));
272
+ if (configResult.config) {
273
+ console.log(`\nfrom config file at ${configResult.filepath}:\n`);
274
+ console.dir(compactConfig(configResult.config));
275
+ } else {
276
+ console.log(`\n(no configuration file loaded)`);
277
+ }
278
+ if (_.isEmpty(nonDefaultPreConfigParsedArgs)) {
279
+ console.log(`\n(no CLI parameters provided)`);
280
+ } else {
281
+ console.log('\nvia CLI or function call:\n');
282
+ console.dir(compactConfig(nonDefaultPreConfigParsedArgs));
196
283
  }
284
+ console.log('\nfinal configuration:\n');
285
+ console.dir(compactConfig(parsedArgs));
197
286
  }
198
287
 
288
+ /**
289
+ * @param {string} tmpDir
290
+ */
199
291
  async function validateTmpDir (tmpDir) {
200
292
  try {
201
- await mkdirp(tmpDir);
293
+ await fs.mkdirp(tmpDir);
202
294
  } catch (e) {
203
295
  throw new Error(`We could not ensure that the temp dir you specified ` +
204
296
  `(${tmpDir}) exists. Please make sure it's writeable.`);
205
297
  }
206
298
  }
207
299
 
300
+ const rootDir = fs.findRoot(__dirname);
301
+
208
302
  export {
209
- getBuildInfo, validateServerArgs, checkNodeOk, showConfig,
210
- warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
211
- getGitRev, checkValidPort, APPIUM_VER, updateBuildInfo,
303
+ getBuildInfo, checkNodeOk, showBuildInfo,
304
+ warnNodeDeprecations, validateTmpDir, getNonDefaultServerArgs,
305
+ getGitRev, APPIUM_VER, updateBuildInfo, showConfig, rootDir
212
306
  };
307
+
308
+ /**
309
+ * @typedef {import('../types').ParsedArgs} ParsedArgs
310
+ * @typedef {import('../types').Args} Args
311
+ * @typedef {import('./schema/arg-spec').ArgSpec} ArgSpec
312
+ */
313
+
@@ -0,0 +1,78 @@
1
+
2
+ import path from 'path';
3
+
4
+ /**
5
+ * The name of the extension type for drivers
6
+ */
7
+ export const DRIVER_TYPE = 'driver';
8
+
9
+ /**
10
+ * The name of the extension type for plugins
11
+ */
12
+ export const PLUGIN_TYPE = 'plugin';
13
+
14
+ /**
15
+ * The `server` command of the `appium` CLI
16
+ */
17
+ export const SERVER_SUBCOMMAND = 'server';
18
+
19
+ /**
20
+ * The value of `--use-plugins` if _all_ plugins should be loaded
21
+ */
22
+ export const USE_ALL_PLUGINS = 'all';
23
+
24
+ // This is a map of plugin names to npm packages representing those plugins.
25
+ // The plugins in this list will be available to the CLI so users can just
26
+ // type 'appium plugin install 'name'', rather than having to specify the full
27
+ // npm package. I.e., these are the officially recognized plugins.
28
+ export const KNOWN_PLUGINS = Object.freeze(
29
+ /** @type {const} */ ({
30
+ images: '@appium/images-plugin',
31
+ 'execute-driver': '@appium/execute-driver-plugin',
32
+ 'relaxed-caps': '@appium/relaxed-caps-plugin',
33
+ }),
34
+ );
35
+
36
+ // This is a map of driver names to npm packages representing those drivers.
37
+ // The drivers in this list will be available to the CLI so users can just
38
+ // type 'appium driver install 'name'', rather than having to specify the full
39
+ // npm package. I.e., these are the officially recognized drivers.
40
+ export const KNOWN_DRIVERS = Object.freeze(
41
+ /** @type {const} */ ({
42
+ uiautomator2: 'appium-uiautomator2-driver',
43
+ xcuitest: 'appium-xcuitest-driver',
44
+ youiengine: 'appium-youiengine-driver',
45
+ windows: 'appium-windows-driver',
46
+ mac: 'appium-mac-driver',
47
+ mac2: 'appium-mac2-driver',
48
+ espresso: 'appium-espresso-driver',
49
+ tizen: 'appium-tizen-driver',
50
+ flutter: 'appium-flutter-driver',
51
+ safari: 'appium-safari-driver',
52
+ gecko: 'appium-geckodriver',
53
+ }),
54
+ );
55
+
56
+ /**
57
+ * Relative path to directory containing any Appium internal files
58
+ */
59
+ export const CACHE_DIR_RELATIVE_PATH = path.join(
60
+ 'node_modules',
61
+ '.cache',
62
+ 'appium',
63
+ );
64
+
65
+ /**
66
+ * Relative path to hashfile (from `APPIUM_HOME`) of consuming project's `package.json` (if it exists)
67
+ */
68
+ export const PKG_HASHFILE_RELATIVE_PATH = path.join(
69
+ CACHE_DIR_RELATIVE_PATH,
70
+ 'package.hash',
71
+ );
72
+
73
+
74
+ export const EXT_SUBCOMMAND_LIST = 'list';
75
+ export const EXT_SUBCOMMAND_INSTALL = 'install';
76
+ export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
77
+ export const EXT_SUBCOMMAND_UPDATE = 'update';
78
+ export const EXT_SUBCOMMAND_RUN = 'run';