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