appium 2.0.0-beta.23 → 2.0.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/build/lib/appium-config.schema.json +278 -0
- package/build/lib/appium.js +62 -71
- package/build/lib/cli/args.js +31 -53
- package/build/lib/cli/driver-command.js +5 -9
- package/build/lib/cli/extension-command.js +73 -64
- package/build/lib/cli/extension.js +10 -23
- package/build/lib/cli/parser.js +10 -20
- package/build/lib/cli/plugin-command.js +5 -9
- package/build/lib/cli/utils.js +2 -4
- package/build/lib/config-file.js +5 -10
- package/build/lib/config.js +67 -30
- package/build/lib/constants.js +60 -0
- package/build/lib/extension/driver-config.js +190 -0
- package/build/lib/extension/extension-config.js +297 -0
- package/build/lib/extension/index.js +77 -0
- package/build/lib/extension/manifest.js +246 -0
- package/build/lib/extension/package-changed.js +68 -0
- package/build/lib/extension/plugin-config.js +87 -0
- package/build/lib/grid-register.js +2 -4
- package/build/lib/logger.js +2 -4
- package/build/lib/logsink.js +2 -4
- package/build/lib/main.js +64 -92
- package/build/lib/schema/appium-config-schema.js +2 -4
- package/build/lib/schema/arg-spec.js +14 -15
- package/build/lib/schema/cli-args.js +8 -16
- package/build/lib/schema/cli-transformers.js +2 -4
- package/build/lib/schema/index.js +2 -4
- package/build/lib/schema/keywords.js +2 -4
- package/build/lib/schema/schema.js +136 -41
- package/build/lib/utils.js +13 -64
- package/lib/appium.js +74 -55
- package/lib/cli/args.js +36 -37
- package/lib/cli/driver-command.js +10 -2
- package/lib/cli/extension-command.js +216 -135
- package/lib/cli/extension.js +7 -15
- package/lib/cli/parser.js +7 -15
- package/lib/cli/plugin-command.js +1 -2
- package/lib/config-file.js +12 -15
- package/lib/config.js +111 -36
- package/lib/constants.js +79 -0
- package/lib/extension/driver-config.js +230 -0
- package/lib/extension/extension-config.js +459 -0
- package/lib/extension/index.js +103 -0
- package/lib/extension/manifest.js +590 -0
- package/lib/extension/package-changed.js +64 -0
- package/lib/extension/plugin-config.js +111 -0
- package/lib/grid-register.js +4 -4
- package/lib/main.js +110 -96
- package/lib/schema/arg-spec.js +11 -5
- package/lib/schema/cli-args.js +7 -30
- package/lib/schema/keywords.js +1 -1
- package/lib/schema/schema.js +206 -48
- package/lib/utils.js +27 -58
- package/package.json +29 -21
- package/{postinstall.js → scripts/postinstall.js} +1 -1
- package/types/types.d.ts +72 -28
- package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
- package/build/check-npm-pack-files.js +0 -23
- package/build/commands-yml/parse.js +0 -319
- package/build/commands-yml/validator.js +0 -130
- package/build/index.js +0 -19
- package/build/lib/cli/npm.js +0 -220
- package/build/lib/driver-config.js +0 -100
- package/build/lib/drivers.js +0 -100
- package/build/lib/ext-config-io.js +0 -165
- package/build/lib/extension-config.js +0 -320
- package/build/lib/plugin-config.js +0 -69
- package/build/lib/plugins.js +0 -16
- package/build/postinstall.js +0 -90
- package/build/test/cli/cli-e2e-specs.js +0 -221
- package/build/test/cli/cli-helpers.js +0 -86
- package/build/test/cli/cli-specs.js +0 -71
- package/build/test/cli/fixtures/test-driver/package.json +0 -27
- package/build/test/cli/schema-args-specs.js +0 -48
- package/build/test/cli/schema-e2e-specs.js +0 -47
- package/build/test/config-e2e-specs.js +0 -112
- package/build/test/config-file-e2e-specs.js +0 -209
- package/build/test/config-file-specs.js +0 -281
- package/build/test/config-specs.js +0 -159
- package/build/test/driver-e2e-specs.js +0 -435
- package/build/test/driver-specs.js +0 -321
- package/build/test/ext-config-io-specs.js +0 -181
- package/build/test/extension-config-specs.js +0 -365
- package/build/test/fixtures/allow-feat.txt +0 -5
- package/build/test/fixtures/caps.json +0 -3
- package/build/test/fixtures/config/allow-insecure.txt +0 -3
- package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +0 -5
- package/build/test/fixtures/config/appium.config.bad.json +0 -32
- package/build/test/fixtures/config/appium.config.ext-good.json +0 -9
- package/build/test/fixtures/config/appium.config.ext-unknown-props.json +0 -10
- package/build/test/fixtures/config/appium.config.good.js +0 -40
- package/build/test/fixtures/config/appium.config.good.json +0 -33
- package/build/test/fixtures/config/appium.config.good.yaml +0 -30
- package/build/test/fixtures/config/appium.config.invalid.json +0 -31
- package/build/test/fixtures/config/appium.config.security-array.json +0 -5
- package/build/test/fixtures/config/appium.config.security-delimited.json +0 -5
- package/build/test/fixtures/config/appium.config.security-path.json +0 -5
- package/build/test/fixtures/config/driver-fake.config.json +0 -8
- package/build/test/fixtures/config/nodeconfig.json +0 -3
- package/build/test/fixtures/config/plugin-fake.config.json +0 -0
- package/build/test/fixtures/default-args.js +0 -35
- package/build/test/fixtures/deny-feat.txt +0 -5
- package/build/test/fixtures/driver.schema.js +0 -20
- package/build/test/fixtures/extensions.yaml +0 -27
- package/build/test/fixtures/flattened-schema.js +0 -504
- package/build/test/fixtures/plugin.schema.js +0 -20
- package/build/test/fixtures/schema-with-extensions.js +0 -28
- package/build/test/grid-register-specs.js +0 -74
- package/build/test/helpers.js +0 -75
- package/build/test/logger-specs.js +0 -76
- package/build/test/npm-specs.js +0 -20
- package/build/test/parser-specs.js +0 -314
- package/build/test/plugin-e2e-specs.js +0 -316
- package/build/test/schema/arg-spec-specs.js +0 -70
- package/build/test/schema/cli-args-specs.js +0 -431
- package/build/test/schema/schema-specs.js +0 -389
- package/build/test/utils-specs.js +0 -266
- package/lib/cli/npm.js +0 -251
- package/lib/driver-config.js +0 -101
- package/lib/drivers.js +0 -84
- package/lib/ext-config-io.js +0 -287
- package/lib/extension-config.js +0 -366
- package/lib/plugin-config.js +0 -63
- package/lib/plugins.js +0 -11
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import resolveFrom from 'resolve-from';
|
|
6
|
+
import log from '../logger';
|
|
7
|
+
import {
|
|
8
|
+
ALLOWED_SCHEMA_EXTENSIONS,
|
|
9
|
+
isAllowedSchemaFileExtension,
|
|
10
|
+
registerSchema,
|
|
11
|
+
} from '../schema/schema';
|
|
12
|
+
|
|
13
|
+
const INSTALL_TYPE_NPM = 'npm';
|
|
14
|
+
const INSTALL_TYPE_LOCAL = 'local';
|
|
15
|
+
const INSTALL_TYPE_GITHUB = 'github';
|
|
16
|
+
const INSTALL_TYPE_GIT = 'git';
|
|
17
|
+
|
|
18
|
+
/** @type {Set<InstallType>} */
|
|
19
|
+
const INSTALL_TYPES = new Set([
|
|
20
|
+
INSTALL_TYPE_GIT,
|
|
21
|
+
INSTALL_TYPE_GITHUB,
|
|
22
|
+
INSTALL_TYPE_LOCAL,
|
|
23
|
+
INSTALL_TYPE_NPM,
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This class is abstract. It should not be instantiated directly.
|
|
28
|
+
*
|
|
29
|
+
* Subclasses should provide the generic parameter to implement.
|
|
30
|
+
* @template {ExtensionType} ExtType
|
|
31
|
+
*/
|
|
32
|
+
export class ExtensionConfig {
|
|
33
|
+
/** @type {ExtType} */
|
|
34
|
+
extensionType;
|
|
35
|
+
|
|
36
|
+
/** @type {`${ExtType}s`} */
|
|
37
|
+
configKey;
|
|
38
|
+
|
|
39
|
+
/** @type {ExtRecord<ExtType>} */
|
|
40
|
+
installedExtensions;
|
|
41
|
+
|
|
42
|
+
/** @type {ExtensionLogFn} */
|
|
43
|
+
log;
|
|
44
|
+
|
|
45
|
+
/** @type {Manifest} */
|
|
46
|
+
manifest;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @protected
|
|
50
|
+
* @param {ExtType} extensionType - Type of extension
|
|
51
|
+
* @param {Manifest} manifest - `Manifest` instance
|
|
52
|
+
* @param {ExtensionLogFn} [logFn]
|
|
53
|
+
*/
|
|
54
|
+
constructor (extensionType, manifest, logFn) {
|
|
55
|
+
const logger = _.isFunction(logFn) ? logFn : log.error.bind(log);
|
|
56
|
+
this.extensionType = extensionType;
|
|
57
|
+
this.configKey = `${extensionType}s`;
|
|
58
|
+
this.installedExtensions = manifest.getExtensionData(extensionType);
|
|
59
|
+
this.log = logger;
|
|
60
|
+
this.manifest = manifest;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get manifestPath () {
|
|
64
|
+
return this.manifest.manifestPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get appiumHome () {
|
|
68
|
+
return this.manifest.appiumHome;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks extensions for problems
|
|
73
|
+
* @param {ExtRecord<ExtType>} exts - Extension data
|
|
74
|
+
*/
|
|
75
|
+
validate (exts) {
|
|
76
|
+
const foundProblems =
|
|
77
|
+
/** @type {Record<ExtName<ExtType>,Problem[]>} */ ({});
|
|
78
|
+
for (const [
|
|
79
|
+
extName,
|
|
80
|
+
extData,
|
|
81
|
+
] of /** @type {[ExtName<ExtType>, ExtData<ExtType>][]} */ (
|
|
82
|
+
_.toPairs(exts)
|
|
83
|
+
)) {
|
|
84
|
+
foundProblems[extName] = [
|
|
85
|
+
...this.getGenericConfigProblems(extData, extName),
|
|
86
|
+
...this.getConfigProblems(extData),
|
|
87
|
+
...this.getSchemaProblems(extData, extName),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const problemSummaries = [];
|
|
92
|
+
for (const [extName, problems] of _.toPairs(foundProblems)) {
|
|
93
|
+
if (_.isEmpty(problems)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// remove this extension from the list since it's not valid
|
|
97
|
+
delete exts[extName];
|
|
98
|
+
problemSummaries.push(
|
|
99
|
+
`${this.extensionType} ${extName} had errors and will not ` +
|
|
100
|
+
`be available. Errors:`,
|
|
101
|
+
);
|
|
102
|
+
for (const problem of problems) {
|
|
103
|
+
problemSummaries.push(
|
|
104
|
+
` - ${problem.err} (Actual value: ` +
|
|
105
|
+
`${JSON.stringify(problem.val)})`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!_.isEmpty(problemSummaries)) {
|
|
111
|
+
this.log(
|
|
112
|
+
`Appium encountered one or more errors while validating ` +
|
|
113
|
+
`the ${this.configKey} extension file (${this.manifestPath}):`,
|
|
114
|
+
);
|
|
115
|
+
for (const summary of problemSummaries) {
|
|
116
|
+
this.log(summary);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return exts;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {ExtData<ExtType>} extData
|
|
125
|
+
* @param {ExtName<ExtType>} extName
|
|
126
|
+
* @returns {Problem[]}
|
|
127
|
+
*/
|
|
128
|
+
getSchemaProblems (extData, extName) {
|
|
129
|
+
const problems = [];
|
|
130
|
+
const {schema: argSchemaPath} = extData;
|
|
131
|
+
if (ExtensionConfig.extDataHasSchema(extData)) {
|
|
132
|
+
if (_.isString(argSchemaPath)) {
|
|
133
|
+
if (isAllowedSchemaFileExtension(argSchemaPath)) {
|
|
134
|
+
try {
|
|
135
|
+
this.readExtensionSchema(extName, extData);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
problems.push({
|
|
138
|
+
err: `Unable to register schema at path ${argSchemaPath}; ${err.message}`,
|
|
139
|
+
val: argSchemaPath,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
problems.push({
|
|
144
|
+
err: `Schema file has unsupported extension. Allowed: ${[
|
|
145
|
+
...ALLOWED_SCHEMA_EXTENSIONS,
|
|
146
|
+
].join(', ')}`,
|
|
147
|
+
val: argSchemaPath,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else if (_.isPlainObject(argSchemaPath)) {
|
|
151
|
+
try {
|
|
152
|
+
this.readExtensionSchema(extName, extData);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
problems.push({
|
|
155
|
+
err: `Unable to register embedded schema; ${err.message}`,
|
|
156
|
+
val: argSchemaPath,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
problems.push({
|
|
161
|
+
err: 'Incorrectly formatted schema field; must be a path to a schema file or a schema object.',
|
|
162
|
+
val: argSchemaPath,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return problems;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {ExtData<ExtType>} extData
|
|
171
|
+
* @param {ExtName<ExtType>} extName
|
|
172
|
+
* @returns {Problem[]}
|
|
173
|
+
*/
|
|
174
|
+
// eslint-disable-next-line no-unused-vars
|
|
175
|
+
getGenericConfigProblems (extData, extName) {
|
|
176
|
+
const {version, pkgName, installSpec, installType, mainClass} =
|
|
177
|
+
extData;
|
|
178
|
+
const problems = [];
|
|
179
|
+
|
|
180
|
+
if (!_.isString(version)) {
|
|
181
|
+
problems.push({err: 'Missing or incorrect version', val: version});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!_.isString(pkgName)) {
|
|
185
|
+
problems.push({
|
|
186
|
+
err: 'Missing or incorrect NPM package name',
|
|
187
|
+
val: pkgName,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!_.isString(installSpec)) {
|
|
192
|
+
problems.push({
|
|
193
|
+
err: 'Missing or incorrect installation spec',
|
|
194
|
+
val: installSpec,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!INSTALL_TYPES.has(installType)) {
|
|
199
|
+
problems.push({
|
|
200
|
+
err: 'Missing or incorrect install type',
|
|
201
|
+
val: installType,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!_.isString(mainClass)) {
|
|
206
|
+
problems.push({
|
|
207
|
+
err: 'Missing or incorrect driver class name',
|
|
208
|
+
val: mainClass,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return problems;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @abstract
|
|
217
|
+
* @param {ExtData<ExtType>} extData
|
|
218
|
+
* @returns {Problem[]}
|
|
219
|
+
*/
|
|
220
|
+
// eslint-disable-next-line no-unused-vars
|
|
221
|
+
getConfigProblems (extData) {
|
|
222
|
+
// shoud override this method if special validation is necessary for this extension type
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @param {string} extName
|
|
228
|
+
* @param {ExtData<ExtType>} extData
|
|
229
|
+
* @param {ExtensionConfigMutationOpts} [opts]
|
|
230
|
+
* @returns {Promise<void>}
|
|
231
|
+
*/
|
|
232
|
+
async addExtension (extName, extData, {write = true} = {}) {
|
|
233
|
+
await this.manifest.addExtension(this.extensionType, extName, extData);
|
|
234
|
+
if (write) {
|
|
235
|
+
await this.manifest.write();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {ExtName<ExtType>} extName
|
|
241
|
+
* @param {ExtData<ExtType>} extData
|
|
242
|
+
* @param {ExtensionConfigMutationOpts} [opts]
|
|
243
|
+
* @returns {Promise<void>}
|
|
244
|
+
*/
|
|
245
|
+
async updateExtension (extName, extData, {write = true} = {}) {
|
|
246
|
+
this.installedExtensions[extName] = {
|
|
247
|
+
...this.installedExtensions[extName],
|
|
248
|
+
...extData,
|
|
249
|
+
};
|
|
250
|
+
if (write) {
|
|
251
|
+
await this.manifest.write();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @param {ExtName<ExtType>} extName
|
|
257
|
+
* @param {ExtensionConfigMutationOpts} [opts]
|
|
258
|
+
* @returns {Promise<void>}
|
|
259
|
+
*/
|
|
260
|
+
async removeExtension (extName, {write = true} = {}) {
|
|
261
|
+
delete this.installedExtensions[extName];
|
|
262
|
+
if (write) {
|
|
263
|
+
await this.manifest.write();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {ExtName<ExtType>[]} [activeNames]
|
|
269
|
+
* @returns {void}
|
|
270
|
+
*/
|
|
271
|
+
// eslint-disable-next-line no-unused-vars
|
|
272
|
+
print (activeNames) {
|
|
273
|
+
if (_.isEmpty(this.installedExtensions)) {
|
|
274
|
+
log.info(
|
|
275
|
+
`No ${this.configKey} have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
|
|
276
|
+
'command to install the one(s) you want to use.',
|
|
277
|
+
);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
log.info(`Available ${this.configKey}:`);
|
|
282
|
+
for (const [
|
|
283
|
+
extName,
|
|
284
|
+
extData,
|
|
285
|
+
] of /** @type {[string, ExtData<ExtType>][]} */ (
|
|
286
|
+
_.toPairs(this.installedExtensions)
|
|
287
|
+
)) {
|
|
288
|
+
log.info(` - ${this.extensionDesc(extName, extData)}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Returns a string describing the extension. Subclasses must implement.
|
|
294
|
+
* @param {ExtName<ExtType>} extName - Extension name
|
|
295
|
+
* @param {ExtData<ExtType>} extData - Extension data
|
|
296
|
+
* @returns {string}
|
|
297
|
+
* @abstract
|
|
298
|
+
*/
|
|
299
|
+
// eslint-disable-next-line no-unused-vars
|
|
300
|
+
extensionDesc (extName, extData) {
|
|
301
|
+
throw new Error('This must be implemented in a subclass');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @param {string} extName
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
getInstallPath (extName) {
|
|
309
|
+
return path.join(this.appiumHome, 'node_modules', this.installedExtensions[extName].pkgName);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Loads extension and returns its main class (constructor)
|
|
314
|
+
* @param {ExtName<ExtType>} extName
|
|
315
|
+
* @returns {ExtClass<ExtType>}
|
|
316
|
+
*/
|
|
317
|
+
require (extName) {
|
|
318
|
+
const {mainClass} = this.installedExtensions[extName];
|
|
319
|
+
const reqPath = this.getInstallPath(extName);
|
|
320
|
+
const reqResolved = require.resolve(reqPath);
|
|
321
|
+
// note: this will only reload the entry point
|
|
322
|
+
if (process.env.APPIUM_RELOAD_EXTENSIONS && require.cache[reqResolved]) {
|
|
323
|
+
log.debug(`Removing ${reqResolved} from require cache`);
|
|
324
|
+
delete require.cache[reqResolved];
|
|
325
|
+
}
|
|
326
|
+
log.debug(`Requiring ${this.extensionType} at ${reqPath}`);
|
|
327
|
+
return require(reqPath)[mainClass];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @param {string} extName
|
|
332
|
+
* @returns {boolean}
|
|
333
|
+
*/
|
|
334
|
+
isInstalled (extName) {
|
|
335
|
+
return _.includes(Object.keys(this.installedExtensions), extName);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Intended to be called by corresponding instance methods of subclass.
|
|
340
|
+
* @private
|
|
341
|
+
* @template {ExtensionType} ExtType
|
|
342
|
+
* @param {string} appiumHome
|
|
343
|
+
* @param {ExtType} extType
|
|
344
|
+
* @param {ExtName<ExtType>} extName - Extension name (unique to its type)
|
|
345
|
+
* @param {ExtDataWithSchema<ExtType>} extData - Extension config
|
|
346
|
+
* @returns {import('ajv').SchemaObject|undefined}
|
|
347
|
+
*/
|
|
348
|
+
static _readExtensionSchema (appiumHome, extType, extName, extData) {
|
|
349
|
+
const {pkgName, schema: argSchemaPath} = extData;
|
|
350
|
+
if (!argSchemaPath) {
|
|
351
|
+
throw new TypeError(
|
|
352
|
+
`No \`schema\` property found in config for ${extType} ${pkgName} -- why is this function being called?`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
let moduleObject;
|
|
356
|
+
if (_.isString(argSchemaPath)) {
|
|
357
|
+
const schemaPath = resolveFrom(appiumHome, path.join(pkgName, argSchemaPath));
|
|
358
|
+
moduleObject = require(schemaPath);
|
|
359
|
+
} else {
|
|
360
|
+
moduleObject = argSchemaPath;
|
|
361
|
+
}
|
|
362
|
+
// this sucks. default exports should be destroyed
|
|
363
|
+
const schema = moduleObject.__esModule
|
|
364
|
+
? moduleObject.default
|
|
365
|
+
: moduleObject;
|
|
366
|
+
registerSchema(extType, extName, schema);
|
|
367
|
+
return schema;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Returns `true` if a specific {@link ExtData} object has a `schema` prop.
|
|
372
|
+
* The {@link ExtData} object becomes a {@link ExtDataWithSchema} object.
|
|
373
|
+
* @template {ExtensionType} ExtType
|
|
374
|
+
* @param {ExtData<ExtType>} extData
|
|
375
|
+
* @returns {extData is ExtDataWithSchema<ExtType>}
|
|
376
|
+
*/
|
|
377
|
+
static extDataHasSchema (extData) {
|
|
378
|
+
return _.isString(extData?.schema) || _.isObject(extData?.schema);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* If an extension provides a schema, this will load the schema and attempt to
|
|
383
|
+
* register it with the schema registrar.
|
|
384
|
+
* @param {ExtName<ExtType>} extName - Name of extension
|
|
385
|
+
* @param {ExtDataWithSchema<ExtType>} extData - Extension data
|
|
386
|
+
* @returns {import('ajv').SchemaObject|undefined}
|
|
387
|
+
*/
|
|
388
|
+
readExtensionSchema (extName, extData) {
|
|
389
|
+
return ExtensionConfig._readExtensionSchema(
|
|
390
|
+
this.appiumHome,
|
|
391
|
+
this.extensionType,
|
|
392
|
+
extName,
|
|
393
|
+
extData,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export {
|
|
399
|
+
INSTALL_TYPE_NPM,
|
|
400
|
+
INSTALL_TYPE_GIT,
|
|
401
|
+
INSTALL_TYPE_LOCAL,
|
|
402
|
+
INSTALL_TYPE_GITHUB,
|
|
403
|
+
INSTALL_TYPES,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Config problem
|
|
408
|
+
* @typedef Problem
|
|
409
|
+
* @property {string} err - Error message
|
|
410
|
+
* @property {any} val - Associated value
|
|
411
|
+
*/
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* An optional logging function provided to an {@link ExtensionConfig} subclass.
|
|
415
|
+
* @callback ExtensionLogFn
|
|
416
|
+
* @param {...any} args
|
|
417
|
+
* @returns {void}
|
|
418
|
+
*/
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @typedef {import('./manifest').ExtensionType} ExtensionType
|
|
422
|
+
* @typedef {import('./manifest').Manifest} Manifest
|
|
423
|
+
*/
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @template T
|
|
427
|
+
* @typedef {import('./manifest').ExtData<T>} ExtData
|
|
428
|
+
*/
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @template T
|
|
432
|
+
* @typedef {import('./manifest').ExtDataWithSchema<T>} ExtDataWithSchema
|
|
433
|
+
*/
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @template T
|
|
437
|
+
* @typedef {import('./manifest').ExtName<T>} ExtName
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* @template T
|
|
442
|
+
* @typedef {import('./manifest').ExtClass<T>} ExtClass
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @template T
|
|
447
|
+
* @typedef {import('./manifest').ExtRecord<T>} ExtRecord
|
|
448
|
+
*/
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Options for various methods in {@link ExtensionConfig}
|
|
452
|
+
* @typedef ExtensionConfigMutationOpts
|
|
453
|
+
* @property {boolean} [write=true] Whether or not to write the manifest to disk after a mutation operation
|
|
454
|
+
*/
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* A valid install type
|
|
458
|
+
* @typedef {typeof INSTALL_TYPE_NPM | typeof INSTALL_TYPE_GIT | typeof INSTALL_TYPE_LOCAL | typeof INSTALL_TYPE_GITHUB} InstallType
|
|
459
|
+
*/
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import { USE_ALL_PLUGINS } from '../constants';
|
|
5
|
+
import log from '../logger';
|
|
6
|
+
import { DriverConfig } from './driver-config';
|
|
7
|
+
import { Manifest } from './manifest';
|
|
8
|
+
import { PluginConfig } from './plugin-config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Loads extensions and creates `ExtensionConfig` instances.
|
|
12
|
+
*
|
|
13
|
+
* - Reads the manifest file, creating if necessary
|
|
14
|
+
* - Using the parsed extension data, creates/gets the `ExtensionConfig` subclass instances
|
|
15
|
+
* - Returns these instances
|
|
16
|
+
*
|
|
17
|
+
* If `appiumHome` is needed, use `resolveAppiumHome` from the `env` module in `@appium/support`.
|
|
18
|
+
* @param {string} appiumHome
|
|
19
|
+
* @returns {Promise<ExtensionConfigs>}
|
|
20
|
+
*/
|
|
21
|
+
export async function loadExtensions (appiumHome) {
|
|
22
|
+
const manifest = Manifest.getInstance(appiumHome);
|
|
23
|
+
const {drivers, plugins} = await manifest.read();
|
|
24
|
+
const driverConfig =
|
|
25
|
+
DriverConfig.getInstance(manifest) ??
|
|
26
|
+
DriverConfig.create(manifest, {extData: drivers});
|
|
27
|
+
const pluginConfig =
|
|
28
|
+
PluginConfig.getInstance(manifest) ??
|
|
29
|
+
PluginConfig.create(manifest, {extData: plugins});
|
|
30
|
+
return {driverConfig, pluginConfig};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Find any plugin name which has been installed, and which has been requested for activation by
|
|
35
|
+
* using the --use-plugins flag, and turn each one into its class, so we can send them as objects
|
|
36
|
+
* to the server init. We also want to send/assign them to the umbrella driver so it can use them
|
|
37
|
+
* to wrap command execution
|
|
38
|
+
*
|
|
39
|
+
* @param {import('./plugin-config').PluginConfig} pluginConfig - a plugin extension config
|
|
40
|
+
* @param {string[]} usePlugins
|
|
41
|
+
* @returns {import('./manifest').PluginClass[]}
|
|
42
|
+
*/
|
|
43
|
+
export function getActivePlugins (pluginConfig, usePlugins = []) {
|
|
44
|
+
return _.compact(
|
|
45
|
+
Object.keys(pluginConfig.installedExtensions)
|
|
46
|
+
.filter(
|
|
47
|
+
(pluginName) =>
|
|
48
|
+
_.includes(usePlugins, pluginName) ||
|
|
49
|
+
(usePlugins.length === 1 && usePlugins[0] === USE_ALL_PLUGINS),
|
|
50
|
+
)
|
|
51
|
+
.map((pluginName) => {
|
|
52
|
+
try {
|
|
53
|
+
log.info(`Attempting to load plugin ${pluginName}...`);
|
|
54
|
+
const PluginClass = pluginConfig.require(pluginName);
|
|
55
|
+
|
|
56
|
+
PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
|
|
57
|
+
return PluginClass;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
log.error(
|
|
60
|
+
`Could not load plugin '${pluginName}', so it will not be available. Error ` +
|
|
61
|
+
`in loading the plugin was: ${err.message}`,
|
|
62
|
+
);
|
|
63
|
+
log.debug(err.stack);
|
|
64
|
+
}
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Find any driver name which has been installed, and turn each one into its class, so we can send
|
|
71
|
+
* them as objects to the server init in case they need to add methods/routes or update the server.
|
|
72
|
+
* If the --drivers flag was given, this method only loads the given drivers.
|
|
73
|
+
*
|
|
74
|
+
* @param {import('./driver-config').DriverConfig} driverConfig - a driver extension config
|
|
75
|
+
* @param {string[]} [useDrivers] - optional list of drivers to load
|
|
76
|
+
*/
|
|
77
|
+
export function getActiveDrivers (driverConfig, useDrivers = []) {
|
|
78
|
+
return _.compact(
|
|
79
|
+
Object.keys(driverConfig.installedExtensions)
|
|
80
|
+
.filter(
|
|
81
|
+
(driverName) =>
|
|
82
|
+
_.includes(useDrivers, driverName) || useDrivers.length === 0,
|
|
83
|
+
)
|
|
84
|
+
.map((driverName) => {
|
|
85
|
+
try {
|
|
86
|
+
log.info(`Attempting to load driver ${driverName}...`);
|
|
87
|
+
return driverConfig.require(driverName);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
log.error(
|
|
90
|
+
`Could not load driver '${driverName}', so it will not be available. Error ` +
|
|
91
|
+
`in loading the driver was: ${err.message}`,
|
|
92
|
+
);
|
|
93
|
+
log.debug(err.stack);
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @typedef ExtensionConfigs
|
|
101
|
+
* @property {DriverConfig} driverConfig
|
|
102
|
+
* @property {PluginConfig} pluginConfig
|
|
103
|
+
*/
|