appium 2.0.0-beta.6 → 2.0.0-beta.60

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