appium 2.0.0-beta.8 → 2.0.0-rc.1
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/LICENSE +1 -1
- package/README.md +149 -58
- package/build/lib/appium.d.ts +229 -0
- package/build/lib/appium.d.ts.map +1 -0
- package/build/lib/appium.js +678 -441
- package/build/lib/appium.js.map +1 -0
- package/build/lib/cli/args.d.ts +17 -0
- package/build/lib/cli/args.d.ts.map +1 -0
- package/build/lib/cli/args.js +263 -300
- package/build/lib/cli/args.js.map +1 -0
- package/build/lib/cli/driver-command.d.ts +102 -0
- package/build/lib/cli/driver-command.d.ts.map +1 -0
- package/build/lib/cli/driver-command.js +131 -81
- package/build/lib/cli/driver-command.js.map +1 -0
- package/build/lib/cli/extension-command.d.ts +402 -0
- package/build/lib/cli/extension-command.d.ts.map +1 -0
- package/build/lib/cli/extension-command.js +799 -383
- package/build/lib/cli/extension-command.js.map +1 -0
- package/build/lib/cli/extension.d.ts +23 -0
- package/build/lib/cli/extension.d.ts.map +1 -0
- package/build/lib/cli/extension.js +71 -60
- package/build/lib/cli/extension.js.map +1 -0
- package/build/lib/cli/parser.d.ts +84 -0
- package/build/lib/cli/parser.d.ts.map +1 -0
- package/build/lib/cli/parser.js +252 -148
- package/build/lib/cli/parser.js.map +1 -0
- package/build/lib/cli/plugin-command.d.ts +99 -0
- package/build/lib/cli/plugin-command.d.ts.map +1 -0
- package/build/lib/cli/plugin-command.js +125 -81
- package/build/lib/cli/plugin-command.js.map +1 -0
- package/build/lib/cli/utils.d.ts +29 -0
- package/build/lib/cli/utils.d.ts.map +1 -0
- package/build/lib/cli/utils.js +72 -51
- package/build/lib/cli/utils.js.map +1 -0
- package/build/lib/config-file.d.ts +100 -0
- package/build/lib/config-file.d.ts.map +1 -0
- package/build/lib/config-file.js +207 -0
- package/build/lib/config-file.js.map +1 -0
- package/build/lib/config.d.ts +49 -0
- package/build/lib/config.d.ts.map +1 -0
- package/build/lib/config.js +262 -223
- package/build/lib/config.js.map +1 -0
- package/build/lib/constants.d.ts +56 -0
- package/build/lib/constants.d.ts.map +1 -0
- package/build/lib/constants.js +73 -0
- package/build/lib/constants.js.map +1 -0
- package/build/lib/extension/driver-config.d.ts +82 -0
- package/build/lib/extension/driver-config.d.ts.map +1 -0
- package/build/lib/extension/driver-config.js +210 -0
- package/build/lib/extension/driver-config.js.map +1 -0
- package/build/lib/extension/extension-config.d.ts +270 -0
- package/build/lib/extension/extension-config.d.ts.map +1 -0
- package/build/lib/extension/extension-config.js +601 -0
- package/build/lib/extension/extension-config.js.map +1 -0
- package/build/lib/extension/index.d.ts +48 -0
- package/build/lib/extension/index.d.ts.map +1 -0
- package/build/lib/extension/index.js +105 -0
- package/build/lib/extension/index.js.map +1 -0
- package/build/lib/extension/manifest-migrations.d.ts +27 -0
- package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
- package/build/lib/extension/manifest-migrations.js +134 -0
- package/build/lib/extension/manifest-migrations.js.map +1 -0
- package/build/lib/extension/manifest.d.ts +145 -0
- package/build/lib/extension/manifest.d.ts.map +1 -0
- package/build/lib/extension/manifest.js +528 -0
- package/build/lib/extension/manifest.js.map +1 -0
- package/build/lib/extension/package-changed.d.ts +11 -0
- package/build/lib/extension/package-changed.d.ts.map +1 -0
- package/build/lib/extension/package-changed.js +62 -0
- package/build/lib/extension/package-changed.js.map +1 -0
- package/build/lib/extension/plugin-config.d.ts +56 -0
- package/build/lib/extension/plugin-config.d.ts.map +1 -0
- package/build/lib/extension/plugin-config.js +102 -0
- package/build/lib/extension/plugin-config.js.map +1 -0
- package/build/lib/grid-register.d.ts +10 -0
- package/build/lib/grid-register.d.ts.map +1 -0
- package/build/lib/grid-register.js +122 -144
- package/build/lib/grid-register.js.map +1 -0
- package/build/lib/logger.d.ts +3 -0
- package/build/lib/logger.d.ts.map +1 -0
- package/build/lib/logger.js +5 -17
- package/build/lib/logger.js.map +1 -0
- package/build/lib/logsink.d.ts +4 -0
- package/build/lib/logsink.d.ts.map +1 -0
- package/build/lib/logsink.js +189 -184
- package/build/lib/logsink.js.map +1 -0
- package/build/lib/main.d.ts +62 -0
- package/build/lib/main.d.ts.map +1 -0
- package/build/lib/main.js +406 -234
- package/build/lib/main.js.map +1 -0
- package/build/lib/schema/arg-spec.d.ts +143 -0
- package/build/lib/schema/arg-spec.d.ts.map +1 -0
- package/build/lib/schema/arg-spec.js +164 -0
- package/build/lib/schema/arg-spec.js.map +1 -0
- package/build/lib/schema/cli-args.d.ts +19 -0
- package/build/lib/schema/cli-args.d.ts.map +1 -0
- package/build/lib/schema/cli-args.js +220 -0
- package/build/lib/schema/cli-args.js.map +1 -0
- package/build/lib/schema/cli-transformers.d.ts +5 -0
- package/build/lib/schema/cli-transformers.d.ts.map +1 -0
- package/build/lib/schema/cli-transformers.js +124 -0
- package/build/lib/schema/cli-transformers.js.map +1 -0
- package/build/lib/schema/index.d.ts +3 -0
- package/build/lib/schema/index.d.ts.map +1 -0
- package/build/lib/schema/index.js +19 -0
- package/build/lib/schema/index.js.map +1 -0
- package/build/lib/schema/keywords.d.ts +24 -0
- package/build/lib/schema/keywords.d.ts.map +1 -0
- package/build/lib/schema/keywords.js +128 -0
- package/build/lib/schema/keywords.js.map +1 -0
- package/build/lib/schema/schema.d.ts +260 -0
- package/build/lib/schema/schema.d.ts.map +1 -0
- package/build/lib/schema/schema.js +640 -0
- package/build/lib/schema/schema.js.map +1 -0
- package/build/lib/utils.d.ts +276 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +373 -271
- package/build/lib/utils.js.map +1 -0
- package/build/types/cli.d.ts +134 -0
- package/build/types/cli.d.ts.map +1 -0
- package/build/types/cli.js +3 -0
- package/build/types/cli.js.map +1 -0
- package/build/types/index.d.ts +15 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +19 -0
- package/build/types/index.js.map +1 -0
- package/build/types/manifest/base.d.ts +135 -0
- package/build/types/manifest/base.d.ts.map +1 -0
- package/build/types/manifest/base.js +3 -0
- package/build/types/manifest/base.js.map +1 -0
- package/build/types/manifest/index.d.ts +21 -0
- package/build/types/manifest/index.d.ts.map +1 -0
- package/build/types/manifest/index.js +42 -0
- package/build/types/manifest/index.js.map +1 -0
- package/build/types/manifest/v3.d.ts +139 -0
- package/build/types/manifest/v3.d.ts.map +1 -0
- package/build/types/manifest/v3.js +3 -0
- package/build/types/manifest/v3.js.map +1 -0
- package/build/types/manifest/v4.d.ts +139 -0
- package/build/types/manifest/v4.d.ts.map +1 -0
- package/build/types/manifest/v4.js +3 -0
- package/build/types/manifest/v4.js.map +1 -0
- package/driver.d.ts +1 -0
- package/driver.js +14 -0
- package/index.js +11 -0
- package/lib/appium.js +555 -185
- package/lib/cli/args.js +275 -407
- package/lib/cli/driver-command.js +132 -24
- package/lib/cli/extension-command.js +751 -272
- package/lib/cli/extension.js +47 -20
- package/lib/cli/parser.js +267 -95
- package/lib/cli/plugin-command.js +122 -22
- package/lib/cli/utils.js +24 -10
- package/lib/config-file.js +220 -0
- package/lib/config.js +243 -132
- package/lib/constants.js +79 -0
- package/lib/extension/driver-config.js +247 -0
- package/lib/extension/extension-config.js +709 -0
- package/lib/extension/index.js +116 -0
- package/lib/extension/manifest-migrations.js +136 -0
- package/lib/extension/manifest.js +580 -0
- package/lib/extension/package-changed.js +64 -0
- package/lib/extension/plugin-config.js +112 -0
- package/lib/grid-register.js +49 -35
- package/lib/logger.js +1 -2
- package/lib/logsink.js +59 -36
- package/lib/main.js +392 -104
- package/lib/schema/arg-spec.js +229 -0
- package/lib/schema/cli-args.js +241 -0
- package/lib/schema/cli-transformers.js +119 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +136 -0
- package/lib/schema/schema.js +725 -0
- package/lib/utils.js +315 -167
- package/package.json +84 -82
- package/plugin.d.ts +1 -0
- package/plugin.js +13 -0
- package/scripts/autoinstall-extensions.js +243 -0
- package/support.d.ts +1 -0
- package/support.js +13 -0
- package/tsconfig.json +25 -0
- package/types/cli.ts +193 -0
- package/types/index.ts +20 -0
- package/types/manifest/README.md +30 -0
- package/types/manifest/base.ts +158 -0
- package/types/manifest/index.ts +28 -0
- package/types/manifest/v3.ts +161 -0
- package/types/manifest/v4.ts +161 -0
- package/CHANGELOG.md +0 -3669
- package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
- package/build/lib/cli/argparse-actions.js +0 -104
- package/build/lib/cli/npm.js +0 -207
- package/build/lib/cli/parser-helpers.js +0 -93
- package/build/lib/driver-config.js +0 -77
- package/build/lib/drivers.js +0 -99
- package/build/lib/extension-config.js +0 -253
- package/build/lib/plugin-config.js +0 -59
- package/build/lib/plugins.js +0 -14
- package/lib/cli/argparse-actions.js +0 -77
- package/lib/cli/npm.js +0 -183
- package/lib/cli/parser-helpers.js +0 -91
- package/lib/driver-config.js +0 -46
- package/lib/drivers.js +0 -84
- package/lib/extension-config.js +0 -209
- package/lib/plugin-config.js +0 -34
- package/lib/plugins.js +0 -10
|
@@ -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
|
+
*/
|