appium 2.0.0-beta.9 → 2.0.0-rc.2

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 (208) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +149 -58
  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 +677 -449
  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 -300
  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 +70 -68
  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 +252 -148
  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 +262 -223
  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 +189 -184
  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 +406 -234
  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 +220 -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 +276 -0
  116. package/build/lib/utils.d.ts.map +1 -0
  117. package/build/lib/utils.js +372 -192
  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 +545 -188
  147. package/lib/cli/args.js +275 -407
  148. package/lib/cli/driver-command.js +132 -24
  149. package/lib/cli/extension-command.js +751 -272
  150. package/lib/cli/extension.js +38 -19
  151. package/lib/cli/parser.js +267 -95
  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 +243 -132
  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 +59 -36
  167. package/lib/main.js +392 -104
  168. package/lib/schema/arg-spec.js +229 -0
  169. package/lib/schema/cli-args.js +241 -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 +310 -89
  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 -3669
  190. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  191. package/build/lib/cli/argparse-actions.js +0 -104
  192. package/build/lib/cli/npm.js +0 -207
  193. package/build/lib/cli/parser-helpers.js +0 -93
  194. package/build/lib/driver-config.js +0 -77
  195. package/build/lib/drivers.js +0 -99
  196. package/build/lib/extension-config.js +0 -253
  197. package/build/lib/plugin-config.js +0 -59
  198. package/build/lib/plugins.js +0 -16
  199. package/build/postinstall.js +0 -90
  200. package/lib/cli/argparse-actions.js +0 -77
  201. package/lib/cli/npm.js +0 -183
  202. package/lib/cli/parser-helpers.js +0 -91
  203. package/lib/driver-config.js +0 -46
  204. package/lib/drivers.js +0 -84
  205. package/lib/extension-config.js +0 -209
  206. package/lib/plugin-config.js +0 -34
  207. package/lib/plugins.js +0 -11
  208. package/postinstall.js +0 -71
@@ -0,0 +1,709 @@
1
+ import {util} from '@appium/support';
2
+ import B from 'bluebird';
3
+ import _ from 'lodash';
4
+ import path from 'path';
5
+ import resolveFrom from 'resolve-from';
6
+ import {satisfies} from 'semver';
7
+ import {commandClasses} from '../cli/extension';
8
+ import {APPIUM_VER} from '../config';
9
+ import log from '../logger';
10
+ import {
11
+ ALLOWED_SCHEMA_EXTENSIONS,
12
+ isAllowedSchemaFileExtension,
13
+ registerSchema,
14
+ } from '../schema/schema';
15
+
16
+ /**
17
+ * "npm" install type
18
+ * Used when extension was installed by npm package name
19
+ * @remarks _All_ extensions are installed _by_ `npm`, but only this one means the package name was
20
+ * used to specify it
21
+ */
22
+ export const INSTALL_TYPE_NPM = 'npm';
23
+ /**
24
+ * "local" install type
25
+ * Used when extension was installed from a local path
26
+ */
27
+ export const INSTALL_TYPE_LOCAL = 'local';
28
+ /**
29
+ * "github" install type
30
+ * Used when extension was installed via GitHub URL
31
+ */
32
+ export const INSTALL_TYPE_GITHUB = 'github';
33
+ /**
34
+ * "git" install type
35
+ * Used when extensions was installed via Git URL
36
+ */
37
+ export const INSTALL_TYPE_GIT = 'git';
38
+ /**
39
+ * "dev" install type
40
+ * Used when automatically detected as a working copy
41
+ */
42
+ export const INSTALL_TYPE_DEV = 'dev';
43
+
44
+ /** @type {Set<InstallType>} */
45
+ export const INSTALL_TYPES = new Set([
46
+ INSTALL_TYPE_GIT,
47
+ INSTALL_TYPE_GITHUB,
48
+ INSTALL_TYPE_LOCAL,
49
+ INSTALL_TYPE_NPM,
50
+ INSTALL_TYPE_DEV,
51
+ ]);
52
+
53
+ /**
54
+ * This class is abstract. It should not be instantiated directly.
55
+ *
56
+ * Subclasses should provide the generic parameter to implement.
57
+ * @template {ExtensionType} ExtType
58
+ */
59
+ export class ExtensionConfig {
60
+ /**
61
+ * The type of extension this class is responsible for.
62
+ * @type {ExtType}
63
+ */
64
+ extensionType;
65
+
66
+ /**
67
+ * Manifest data for the extensions of this type.
68
+ *
69
+ * This data should _not_ be written to by anything but {@linkcode Manifest}.
70
+ * @type {Readonly<ExtRecord<ExtType>>}
71
+ */
72
+ installedExtensions;
73
+
74
+ /** @type {import('@appium/types').AppiumLogger} */
75
+ log;
76
+
77
+ /** @type {Manifest} */
78
+ manifest;
79
+
80
+ /**
81
+ * @type {import('../cli/extension-command').ExtensionList<ExtType>|undefined}
82
+ */
83
+ #listDataCache;
84
+
85
+ /**
86
+ * @protected
87
+ * @param {ExtType} extensionType - Type of extension
88
+ * @param {Manifest} manifest - `Manifest` instance
89
+ */
90
+ constructor(extensionType, manifest) {
91
+ this.extensionType = extensionType;
92
+ this.installedExtensions = manifest.getExtensionData(extensionType);
93
+ this.manifest = manifest;
94
+ }
95
+
96
+ get manifestPath() {
97
+ return this.manifest.manifestPath;
98
+ }
99
+
100
+ get appiumHome() {
101
+ return this.manifest.appiumHome;
102
+ }
103
+
104
+ /**
105
+ * Returns a list of errors for a given extension.
106
+ *
107
+ * @param {ExtName<ExtType>} extName
108
+ * @param {ExtManifest<ExtType>} extManifest
109
+ * @returns {ExtManifestProblem[]}
110
+ */
111
+ getProblems(extName, extManifest) {
112
+ return [
113
+ ...this.getGenericConfigProblems(extManifest, extName),
114
+ ...this.getConfigProblems(extManifest, extName),
115
+ ...this.getSchemaProblems(extManifest, extName),
116
+ ];
117
+ }
118
+
119
+ /**
120
+ * Returns a list of warnings for a given extension.
121
+ *
122
+ * @param {ExtName<ExtType>} extName
123
+ * @param {ExtManifest<ExtType>} extManifest
124
+ * @returns {Promise<string[]>}
125
+ */
126
+ async getWarnings(extName, extManifest) {
127
+ const [genericConfigWarnings, configWarnings] = await B.all([
128
+ this.getGenericConfigWarnings(extManifest, extName),
129
+ this.getConfigWarnings(extManifest, extName),
130
+ ]);
131
+
132
+ return [...genericConfigWarnings, ...configWarnings];
133
+ }
134
+
135
+ /**
136
+ * Returns a list of extension-type-specific issues. To be implemented by subclasses.
137
+ * @abstract
138
+ * @param {ExtManifest<ExtType>} extManifest
139
+ * @param {ExtName<ExtType>} extName
140
+ * @returns {Promise<string[]>}
141
+ */
142
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
143
+ async getConfigWarnings(extManifest, extName) {
144
+ return [];
145
+ }
146
+
147
+ /**
148
+ *
149
+ * @param {Map<ExtName<ExtType>,ExtManifestProblem[]>} [errorMap]
150
+ * @param {Map<ExtName<ExtType>,string[]>} [warningMap]
151
+ */
152
+ getValidationResultSummaries(errorMap = new Map(), warningMap = new Map()) {
153
+ /**
154
+ * Array of computed strings
155
+ * @type {string[]}
156
+ */
157
+ const errorSummaries = [];
158
+ for (const [extName, problems] of errorMap.entries()) {
159
+ if (_.isEmpty(problems)) {
160
+ continue;
161
+ }
162
+ // remove this extension from the list since it's not valid
163
+ errorSummaries.push(
164
+ `${this.extensionType} "${extName}" had ${util.pluralize(
165
+ 'error',
166
+ problems.length
167
+ )} and will not be available:`
168
+ );
169
+ for (const problem of problems) {
170
+ errorSummaries.push(
171
+ ` - ${problem.err} (Actual value: ` + `${JSON.stringify(problem.val)})`
172
+ );
173
+ }
174
+ }
175
+ /** @type {string[]} */
176
+ const warningSummaries = [];
177
+ for (const [extName, warnings] of warningMap.entries()) {
178
+ if (_.isEmpty(warnings)) {
179
+ continue;
180
+ }
181
+ const extTypeText = _.capitalize(this.extensionType);
182
+ const problemEnumerationText = util.pluralize('potential problem', warnings.length, true);
183
+ warningSummaries.push(`${extTypeText} "${extName}" has ${problemEnumerationText}: `);
184
+ for (const warning of warnings) {
185
+ warningSummaries.push(` - ${warning}`);
186
+ }
187
+ }
188
+
189
+ return {errorSummaries, warningSummaries};
190
+ }
191
+
192
+ /**
193
+ * Checks extensions for problems. To be called by subclasses' `validate` method.
194
+ *
195
+ * Errors and warnings will be displayed to the user.
196
+ *
197
+ * This method mutates `exts`.
198
+ *
199
+ * @protected
200
+ * @param {ExtRecord<ExtType>} exts - Lookup of extension names to {@linkcode ExtManifest} objects
201
+ * @returns {Promise<ExtRecord<ExtType>>} The same lookup, but picking only error-free extensions
202
+ */
203
+ async _validate(exts) {
204
+ /**
205
+ * Lookup of extension names to {@linkcode ExtManifestProblem ExtManifestProblems}
206
+ * @type {Map<ExtName<ExtType>,ExtManifestProblem[]>}
207
+ */
208
+ const errorMap = new Map();
209
+ /**
210
+ * Lookup of extension names to warnings.
211
+ * @type {Map<ExtName<ExtType>,string[]>}
212
+ */
213
+ const warningMap = new Map();
214
+
215
+ for (const [extName, extManifest] of _.toPairs(exts)) {
216
+ const [errors, warnings] = await B.all([
217
+ this.getProblems(extName, extManifest),
218
+ this.getWarnings(extName, extManifest),
219
+ ]);
220
+ if (errors.length) {
221
+ delete exts[extName];
222
+ }
223
+ errorMap.set(extName, errors);
224
+ warningMap.set(extName, warnings);
225
+ }
226
+
227
+ const {errorSummaries, warningSummaries} = this.getValidationResultSummaries(
228
+ errorMap,
229
+ warningMap
230
+ );
231
+
232
+ if (!_.isEmpty(errorSummaries)) {
233
+ log.error(
234
+ `Appium encountered ${util.pluralize('error', errorMap.size, true)} while validating ${
235
+ this.extensionType
236
+ }s found in manifest ${this.manifestPath}`
237
+ );
238
+ for (const summary of errorSummaries) {
239
+ log.error(summary);
240
+ }
241
+ } else {
242
+ // only display warnings if there are no errors!
243
+
244
+ if (!_.isEmpty(warningSummaries)) {
245
+ log.warn(
246
+ `Appium encountered ${util.pluralize(
247
+ 'warning',
248
+ warningMap.size,
249
+ true
250
+ )} while validating ${this.extensionType}s found in manifest ${this.manifestPath}`
251
+ );
252
+ for (const summary of warningSummaries) {
253
+ log.warn(summary);
254
+ }
255
+ }
256
+ }
257
+ return exts;
258
+ }
259
+
260
+ /**
261
+ * Retrieves listing data for extensions via command class.
262
+ *
263
+ * This is an expensive operation, so the result is cached. Currently, there is no
264
+ * use case for invalidating the cache.
265
+ * @protected
266
+ * @returns {Promise<import('../cli/extension-command').ExtensionList<ExtType>>}
267
+ */
268
+ async getListData() {
269
+ if (this.#listDataCache) {
270
+ return this.#listDataCache;
271
+ }
272
+ const CommandClass = /** @type {ExtCommand<ExtType>} */ (commandClasses[this.extensionType]);
273
+ const cmd = new CommandClass({config: this, json: true});
274
+ const listData = await cmd.list({showInstalled: true, showUpdates: true});
275
+ this.#listDataCache = listData;
276
+ return listData;
277
+ }
278
+
279
+ /**
280
+ * Returns a list of warnings for a particular extension.
281
+ *
282
+ * By definition, a non-empty list of warnings does _not_ imply the extension cannot be loaded,
283
+ * but it may not work as expected or otherwise throw an exception at runtime.
284
+ *
285
+ * @param {ExtManifest<ExtType>} extManifest
286
+ * @param {ExtName<ExtType>} extName
287
+ * @returns {Promise<string[]>}
288
+ */
289
+ async getGenericConfigWarnings(extManifest, extName) {
290
+ const {appiumVersion, installSpec, installType, pkgName} = extManifest;
291
+ const warnings = [];
292
+
293
+ const invalidFields = [];
294
+ if (!_.isString(installSpec)) {
295
+ invalidFields.push('installSpec');
296
+ }
297
+
298
+ if (!INSTALL_TYPES.has(installType)) {
299
+ invalidFields.push('installType');
300
+ }
301
+
302
+ const extTypeText = _.capitalize(this.extensionType);
303
+
304
+ if (invalidFields.length) {
305
+ const invalidFieldsEnumerationText = util.pluralize(
306
+ 'invalid or missing field',
307
+ invalidFields.length,
308
+ true
309
+ );
310
+ const invalidFieldsText = invalidFields.map((field) => `"${field}"`).join(', ');
311
+
312
+ warnings.push(
313
+ `${extTypeText} "${extName}" (package \`${pkgName}\`) has ${invalidFieldsEnumerationText} (${invalidFieldsText}) in \`extensions.yaml\`; this may cause upgrades done via the \`appium\` CLI tool to fail. Please reinstall with \`appium ${this.extensionType} uninstall ${extName}\` and \`appium ${this.extensionType} install ${extName}\` to attempt a fix.`
314
+ );
315
+ }
316
+
317
+ /**
318
+ * Helps concatenate warning messages related to peer dependencies
319
+ * @param {string} reason
320
+ * @returns string
321
+ */
322
+ const createPeerWarning = (reason) =>
323
+ `${extTypeText} "${extName}" (package \`${pkgName}\`) may be incompatible with the current version of Appium (v${APPIUM_VER}) due to ${reason}`;
324
+
325
+ if (_.isString(appiumVersion) && !satisfies(APPIUM_VER, appiumVersion)) {
326
+ const listData = await this.getListData();
327
+ const extListData =
328
+ /** @type {import('../cli/extension-command').ExtensionListData<ExtType>} */ (
329
+ listData[extName]
330
+ );
331
+ if (extListData?.installed) {
332
+ const {updateVersion, upToDate} = extListData;
333
+ if (!upToDate) {
334
+ warnings.push(
335
+ createPeerWarning(
336
+ `its peer dependency on older Appium v${appiumVersion}. Please upgrade \`${pkgName}\` to v${updateVersion} or newer.`
337
+ )
338
+ );
339
+ } else {
340
+ warnings.push(
341
+ createPeerWarning(
342
+ `its peer dependency on older Appium v${appiumVersion}. Please ask the developer of \`${pkgName}\` to update the peer dependency on Appium to v${APPIUM_VER}.`
343
+ )
344
+ );
345
+ }
346
+ }
347
+ } else if (!_.isString(appiumVersion)) {
348
+ const listData = await this.getListData();
349
+ const extListData =
350
+ /** @type {import('../cli/extension-command').InstalledExtensionListData<ExtType>} */ (
351
+ listData[extName]
352
+ );
353
+ if (!extListData?.upToDate && extListData?.updateVersion) {
354
+ warnings.push(
355
+ createPeerWarning(
356
+ `an invalid or missing peer dependency on Appium. A newer version of \`${pkgName}\` is available; please attempt to upgrade "${extName}" to v${extListData.updateVersion} or newer.`
357
+ )
358
+ );
359
+ } else {
360
+ warnings.push(
361
+ createPeerWarning(
362
+ `an invalid or missing peer dependency on Appium. Please ask the developer of \`${pkgName}\` to add a peer dependency on \`^appium@${APPIUM_VER}\`.`
363
+ )
364
+ );
365
+ }
366
+ }
367
+ return warnings;
368
+ }
369
+ /**
370
+ * Returns list of unrecoverable errors (if any) for the given extension _if_ it has a `schema` property.
371
+ *
372
+ * @param {ExtManifest<ExtType>} extManifest - Extension data (from manifest)
373
+ * @param {ExtName<ExtType>} extName - Extension name (from manifest)
374
+ * @returns {ExtManifestProblem[]}
375
+ */
376
+ getSchemaProblems(extManifest, extName) {
377
+ /** @type {ExtManifestProblem[]} */
378
+ const problems = [];
379
+ const {schema: argSchemaPath} = extManifest;
380
+ if (ExtensionConfig.extDataHasSchema(extManifest)) {
381
+ if (_.isString(argSchemaPath)) {
382
+ if (isAllowedSchemaFileExtension(argSchemaPath)) {
383
+ try {
384
+ this.readExtensionSchema(extName, extManifest);
385
+ } catch (err) {
386
+ problems.push({
387
+ err: `Unable to register schema at path ${argSchemaPath}; ${err.message}`,
388
+ val: argSchemaPath,
389
+ });
390
+ }
391
+ } else {
392
+ problems.push({
393
+ err: `Schema file has unsupported extension. Allowed: ${[
394
+ ...ALLOWED_SCHEMA_EXTENSIONS,
395
+ ].join(', ')}`,
396
+ val: argSchemaPath,
397
+ });
398
+ }
399
+ } else if (_.isPlainObject(argSchemaPath)) {
400
+ try {
401
+ this.readExtensionSchema(extName, extManifest);
402
+ } catch (err) {
403
+ problems.push({
404
+ err: `Unable to register embedded schema; ${err.message}`,
405
+ val: argSchemaPath,
406
+ });
407
+ }
408
+ } else {
409
+ problems.push({
410
+ err: 'Incorrectly formatted schema field; must be a path to a schema file or a schema object.',
411
+ val: argSchemaPath,
412
+ });
413
+ }
414
+ }
415
+ return problems;
416
+ }
417
+
418
+ /**
419
+ * Return a list of generic unrecoverable errors for the given extension
420
+ * @param {ExtManifest<ExtType>} extManifest - Extension data (from manifest)
421
+ * @param {ExtName<ExtType>} extName - Extension name (from manifest)
422
+ * @returns {ExtManifestProblem[]}
423
+ */
424
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
425
+ getGenericConfigProblems(extManifest, extName) {
426
+ const {version, pkgName, mainClass} = extManifest;
427
+ const problems = [];
428
+
429
+ if (!_.isString(version)) {
430
+ problems.push({
431
+ err: `Invalid or missing \`version\` field in my \`package.json\` and/or \`extensions.yaml\` (must be a string)`,
432
+ val: version,
433
+ });
434
+ }
435
+
436
+ if (!_.isString(pkgName)) {
437
+ problems.push({
438
+ err: `Invalid or missing \`name\` field in my \`package.json\` and/or \`extensions.yaml\` (must be a string)`,
439
+ val: pkgName,
440
+ });
441
+ }
442
+
443
+ if (!_.isString(mainClass)) {
444
+ problems.push({
445
+ err: `Invalid or missing \`appium.mainClass\` field in my \`package.json\` and/or \`mainClass\` field in \`extensions.yaml\` (must be a string)`,
446
+ val: mainClass,
447
+ });
448
+ }
449
+
450
+ return problems;
451
+ }
452
+
453
+ /**
454
+ * @abstract
455
+ * @param {ExtManifest<ExtType>} extManifest
456
+ * @param {ExtName<ExtType>} extName
457
+ * @returns {ExtManifestProblem[]}
458
+ */
459
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
460
+ getConfigProblems(extManifest, extName) {
461
+ // shoud override this method if special validation is necessary for this extension type
462
+ return [];
463
+ }
464
+
465
+ /**
466
+ * @param {string} extName
467
+ * @param {ExtManifest<ExtType>} extManifest
468
+ * @param {ExtensionConfigMutationOpts} opts
469
+ * @returns {Promise<void>}
470
+ */
471
+ async addExtension(extName, extManifest, {write = true} = {}) {
472
+ this.manifest.setExtension(this.extensionType, extName, extManifest);
473
+ if (write) {
474
+ await this.manifest.write();
475
+ }
476
+ }
477
+
478
+ /**
479
+ * @param {ExtName<ExtType>} extName
480
+ * @param {ExtManifest<ExtType>} extManifest
481
+ * @param {ExtensionConfigMutationOpts} opts
482
+ * @returns {Promise<void>}
483
+ */
484
+ async updateExtension(extName, extManifest, {write = true} = {}) {
485
+ this.manifest.setExtension(this.extensionType, extName, {
486
+ ...this.installedExtensions[extName],
487
+ ...extManifest,
488
+ });
489
+ if (write) {
490
+ await this.manifest.write();
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Remove an extension from the list of installed extensions, and optionally avoid a write to the manifest file.
496
+ *
497
+ * @param {ExtName<ExtType>} extName
498
+ * @param {ExtensionConfigMutationOpts} opts
499
+ * @returns {Promise<void>}
500
+ */
501
+ async removeExtension(extName, {write = true} = {}) {
502
+ this.manifest.deleteExtension(this.extensionType, extName);
503
+ if (write) {
504
+ await this.manifest.write();
505
+ }
506
+ }
507
+
508
+ /**
509
+ * @param {ExtName<ExtType>[]} [activeNames]
510
+ * @returns {void}
511
+ */
512
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
513
+ print(activeNames) {
514
+ if (_.isEmpty(this.installedExtensions)) {
515
+ log.info(
516
+ `No ${this.extensionType}s have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
517
+ 'command to install the one(s) you want to use.'
518
+ );
519
+ return;
520
+ }
521
+
522
+ log.info(`Available ${this.extensionType}s:`);
523
+ for (const [extName, extManifest] of /** @type {[string, ExtManifest<ExtType>][]} */ (
524
+ _.toPairs(this.installedExtensions)
525
+ )) {
526
+ log.info(` - ${this.extensionDesc(extName, extManifest)}`);
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Returns a string describing the extension. Subclasses must implement.
532
+ * @param {ExtName<ExtType>} extName - Extension name
533
+ * @param {ExtManifest<ExtType>} extManifest - Extension data
534
+ * @returns {string}
535
+ * @abstract
536
+ */
537
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
538
+ extensionDesc(extName, extManifest) {
539
+ throw new Error('This must be implemented in a subclass');
540
+ }
541
+
542
+ /**
543
+ * Returns--with reasonable accuracy--the path on disk to the extension.
544
+ *
545
+ * If `installPath` is present in the manifest, then it is used; otherwise we just guess.
546
+ * @param {keyof typeof this.installedExtensions} extName
547
+ * @returns {string}
548
+ */
549
+ getInstallPath(extName) {
550
+ return (
551
+ this.installedExtensions[extName]?.installPath ??
552
+ path.join(this.appiumHome, 'node_modules', this.installedExtensions[extName].pkgName)
553
+ );
554
+ }
555
+
556
+ /**
557
+ * Loads extension and returns its main class (constructor)
558
+ * @param {ExtName<ExtType>} extName
559
+ * @returns {ExtClass<ExtType>}
560
+ */
561
+ require(extName) {
562
+ const {mainClass} = this.installedExtensions[extName];
563
+ const reqPath = this.getInstallPath(extName);
564
+ /** @type {string} */
565
+ let reqResolved;
566
+ try {
567
+ reqResolved = require.resolve(reqPath);
568
+ } catch (err) {
569
+ throw new ReferenceError(`Could not find a ${this.extensionType} installed at ${reqPath}`);
570
+ }
571
+ // note: this will only reload the entry point
572
+ if (process.env.APPIUM_RELOAD_EXTENSIONS && require.cache[reqResolved]) {
573
+ log.debug(`Removing ${reqResolved} from require cache`);
574
+ delete require.cache[reqResolved];
575
+ }
576
+ log.debug(`Requiring ${this.extensionType} at ${reqPath}`);
577
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
578
+ const MainClass = require(reqPath)[mainClass];
579
+ if (!MainClass) {
580
+ throw new ReferenceError(
581
+ `Could not find a class named "${mainClass}" exported by ${this.extensionType} "${extName}"`
582
+ );
583
+ }
584
+ return MainClass;
585
+ }
586
+
587
+ /**
588
+ * @param {string} extName
589
+ * @returns {boolean}
590
+ */
591
+ isInstalled(extName) {
592
+ return extName in this.installedExtensions;
593
+ }
594
+
595
+ /**
596
+ * Intended to be called by corresponding instance methods of subclass.
597
+ * @private
598
+ * @template {ExtensionType} ExtType
599
+ * @param {string} appiumHome
600
+ * @param {ExtType} extType
601
+ * @param {ExtName<ExtType>} extName - Extension name (unique to its type)
602
+ * @param {ExtManifestWithSchema<ExtType>} extManifest - Extension config
603
+ * @returns {import('ajv').SchemaObject|undefined}
604
+ */
605
+ static _readExtensionSchema(appiumHome, extType, extName, extManifest) {
606
+ const {pkgName, schema: argSchemaPath} = extManifest;
607
+ if (!argSchemaPath) {
608
+ throw new TypeError(
609
+ `No \`schema\` property found in config for ${extType} ${pkgName} -- why is this function being called?`
610
+ );
611
+ }
612
+ let moduleObject;
613
+ if (_.isString(argSchemaPath)) {
614
+ const schemaPath = resolveFrom(appiumHome, path.join(pkgName, argSchemaPath));
615
+ moduleObject = require(schemaPath);
616
+ } else {
617
+ moduleObject = argSchemaPath;
618
+ }
619
+ // this sucks. default exports should be destroyed
620
+ const schema = moduleObject.__esModule ? moduleObject.default : moduleObject;
621
+ registerSchema(extType, extName, schema);
622
+ return schema;
623
+ }
624
+
625
+ /**
626
+ * Returns `true` if a specific {@link ExtManifest} object has a `schema` prop.
627
+ * The {@link ExtManifest} object becomes a {@link ExtManifestWithSchema} object.
628
+ * @template {ExtensionType} ExtType
629
+ * @param {ExtManifest<ExtType>} extManifest
630
+ * @returns {extManifest is ExtManifestWithSchema<ExtType>}
631
+ */
632
+ static extDataHasSchema(extManifest) {
633
+ return _.isString(extManifest?.schema) || _.isObject(extManifest?.schema);
634
+ }
635
+
636
+ /**
637
+ * If an extension provides a schema, this will load the schema and attempt to
638
+ * register it with the schema registrar.
639
+ * @param {ExtName<ExtType>} extName - Name of extension
640
+ * @param {ExtManifestWithSchema<ExtType>} extManifest - Extension data
641
+ * @returns {import('ajv').SchemaObject|undefined}
642
+ */
643
+ readExtensionSchema(extName, extManifest) {
644
+ return ExtensionConfig._readExtensionSchema(
645
+ this.appiumHome,
646
+ this.extensionType,
647
+ extName,
648
+ extManifest
649
+ );
650
+ }
651
+ }
652
+
653
+ /**
654
+ * An issue with the {@linkcode ExtManifest} for a particular extension.
655
+ *
656
+ * The existance of such an object implies that the extension cannot be loaded.
657
+ * @typedef ExtManifestProblem
658
+ * @property {string} err - Error message
659
+ * @property {any} val - Associated value
660
+ */
661
+
662
+ /**
663
+ * An optional logging function provided to an {@link ExtensionConfig} subclass.
664
+ * @callback ExtensionLogFn
665
+ * @param {...any} args
666
+ * @returns {void}
667
+ */
668
+
669
+ /**
670
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
671
+ * @typedef {import('./manifest').Manifest} Manifest
672
+ * @typedef {import('appium/types').InstallType} InstallType
673
+ */
674
+
675
+ /**
676
+ * @template {ExtensionType} ExtType
677
+ * @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
678
+ */
679
+
680
+ /**
681
+ * @template {ExtensionType} ExtType
682
+ * @typedef {ExtManifest<ExtType> & {schema: NonNullable<ExtManifest<ExtType>['schema']>}} ExtManifestWithSchema
683
+ */
684
+
685
+ /**
686
+ * @template {ExtensionType} ExtType
687
+ * @typedef {import('appium/types').ExtName<ExtType>} ExtName
688
+ */
689
+
690
+ /**
691
+ * @template {ExtensionType} ExtType
692
+ * @typedef {import('appium/types').ExtClass<ExtType>} ExtClass
693
+ */
694
+
695
+ /**
696
+ * @template {ExtensionType} ExtType
697
+ * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
698
+ */
699
+
700
+ /**
701
+ * @template {ExtensionType} ExtType
702
+ * @typedef {import('../cli/extension').ExtCommand<ExtType>} ExtCommand
703
+ */
704
+
705
+ /**
706
+ * Options for various methods in {@link ExtensionConfig}
707
+ * @typedef ExtensionConfigMutationOpts
708
+ * @property {boolean} [write=true] Whether or not to write the manifest to disk after a mutation operation
709
+ */