appium 2.0.0-beta.34 → 2.0.0-beta.38
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 +5 -5
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +8 -8
- package/build/lib/cli/extension-command.d.ts +78 -51
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +135 -80
- 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 +9 -15
- package/build/lib/cli/plugin-command.d.ts.map +1 -1
- package/build/lib/cli/plugin-command.js +8 -8
- 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 +46 -26
- package/lib/cli/extension-command.js +314 -157
- package/lib/cli/extension.js +15 -19
- package/lib/cli/parser.js +19 -31
- package/lib/cli/plugin-command.js +39 -24
- 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/lib/appium-config.schema.json +0 -278
- package/scripts/postinstall.js +0 -71
- package/types/extension.ts +0 -56
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
|
|
2
|
+
import B from 'bluebird';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import {npm, util, env, console} from '@appium/support';
|
|
6
|
+
import {spinWith, RingBuffer} from './utils';
|
|
7
|
+
import {SubProcess} from 'teen_process';
|
|
8
|
+
import {
|
|
9
|
+
INSTALL_TYPE_NPM,
|
|
10
|
+
INSTALL_TYPE_GIT,
|
|
11
|
+
INSTALL_TYPE_GITHUB,
|
|
12
|
+
INSTALL_TYPE_LOCAL,
|
|
13
|
+
} from '../extension/extension-config';
|
|
14
|
+
import {packageDidChange} from '../extension/package-changed';
|
|
11
15
|
|
|
12
16
|
const UPDATE_ALL = 'installed';
|
|
13
17
|
|
|
@@ -40,64 +44,78 @@ class ExtensionCommand {
|
|
|
40
44
|
* Build an ExtensionCommand
|
|
41
45
|
* @param {ExtensionCommandOptions<ExtType>} opts
|
|
42
46
|
*/
|
|
43
|
-
constructor
|
|
47
|
+
constructor({config, json}) {
|
|
44
48
|
this.config = config;
|
|
45
|
-
this.
|
|
49
|
+
this.log = new console.CliConsole({jsonMode: json});
|
|
50
|
+
this.isJsonOutput = Boolean(json);
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
49
54
|
* `driver` or `plugin`, depending on the `ExtensionConfig`.
|
|
50
55
|
*/
|
|
51
|
-
get type
|
|
56
|
+
get type() {
|
|
52
57
|
return this.config.extensionType;
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Logs a message and returns an {@linkcode Error} to throw.
|
|
62
|
+
*
|
|
63
|
+
* For TS to understand that a function throws an exception, it must actually throw an exception--
|
|
64
|
+
* in other words, _calling_ a function which is guaranteed to throw an exception is not enough--
|
|
65
|
+
* nor is something like `@returns {never}` which does not imply a thrown exception.
|
|
66
|
+
* @param {string} message
|
|
67
|
+
* @protected
|
|
68
|
+
* @returns {Error}
|
|
69
|
+
*/
|
|
70
|
+
_createFatalError(message) {
|
|
71
|
+
return new Error(this.log.decorate(message, 'error'));
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Take a CLI parse and run an extension command based on its type
|
|
57
76
|
*
|
|
58
77
|
* @param {object} args - a key/value object with CLI flags and values
|
|
59
78
|
* @return {Promise<object>} the result of the specific command which is executed
|
|
60
79
|
*/
|
|
61
|
-
async execute
|
|
80
|
+
async execute(args) {
|
|
62
81
|
const cmd = args[`${this.type}Command`];
|
|
63
82
|
if (!_.isFunction(this[cmd])) {
|
|
64
|
-
throw
|
|
83
|
+
throw this._createFatalError(`Cannot handle ${this.type} command ${cmd}`);
|
|
65
84
|
}
|
|
66
85
|
const executeCmd = this[cmd].bind(this);
|
|
67
86
|
return await executeCmd(args);
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
/**
|
|
71
|
-
* @typedef ListOptions
|
|
72
|
-
* @property {boolean} showInstalled - whether should show only installed extensions
|
|
73
|
-
* @property {boolean} showUpdates - whether should show available updates
|
|
74
|
-
*/
|
|
75
|
-
|
|
76
89
|
/**
|
|
77
90
|
* List extensions
|
|
78
91
|
*
|
|
79
92
|
* @param {ListOptions} opts
|
|
80
93
|
* @return {Promise<ExtensionListData>} map of extension names to extension data
|
|
81
94
|
*/
|
|
82
|
-
async list
|
|
95
|
+
async list({showInstalled, showUpdates}) {
|
|
83
96
|
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
|
|
84
97
|
const installedNames = Object.keys(this.config.installedExtensions);
|
|
85
98
|
const knownNames = Object.keys(this.knownExtensions);
|
|
86
|
-
const exts = [...installedNames, ...knownNames].reduce(
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
const exts = [...installedNames, ...knownNames].reduce(
|
|
100
|
+
(acc, name) => {
|
|
101
|
+
if (!acc[name]) {
|
|
102
|
+
if (installedNames.includes(name)) {
|
|
103
|
+
acc[name] = {
|
|
104
|
+
...this.config.installedExtensions[name],
|
|
105
|
+
installed: true,
|
|
106
|
+
};
|
|
107
|
+
} else if (!showInstalled) {
|
|
108
|
+
acc[name] = {pkgName: this.knownExtensions[name], installed: false};
|
|
109
|
+
}
|
|
92
110
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
return acc;
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* This accumulator contains either {@linkcode UninstalledExtensionLIstData} _or_
|
|
115
|
+
* {@linkcode InstalledExtensionListData} without upgrade information (which is added by the below code block)
|
|
116
|
+
* @type {Record<string,Partial<InstalledExtensionListData>|UninstalledExtensionListData>}
|
|
117
|
+
*/ ({})
|
|
118
|
+
);
|
|
101
119
|
|
|
102
120
|
// if we want to show whether updates are available, put that behind a spinner
|
|
103
121
|
await spinWith(this.isJsonOutput, lsMsg, async () => {
|
|
@@ -117,7 +135,7 @@ class ExtensionCommand {
|
|
|
117
135
|
}
|
|
118
136
|
});
|
|
119
137
|
|
|
120
|
-
const listData = /** @type {ExtensionListData} */(exts);
|
|
138
|
+
const listData = /** @type {ExtensionListData} */ (exts);
|
|
121
139
|
|
|
122
140
|
// if we're just getting the data, short circuit return here since we don't need to do any
|
|
123
141
|
// formatting logic
|
|
@@ -125,16 +143,14 @@ class ExtensionCommand {
|
|
|
125
143
|
return listData;
|
|
126
144
|
}
|
|
127
145
|
|
|
128
|
-
for (const [
|
|
129
|
-
name,
|
|
130
|
-
data
|
|
131
|
-
] of _.toPairs(listData)) {
|
|
146
|
+
for (const [name, data] of _.toPairs(listData)) {
|
|
132
147
|
let installTxt = ' [not installed]'.grey;
|
|
133
148
|
let updateTxt = '';
|
|
134
149
|
let upToDateTxt = '';
|
|
135
150
|
let unsafeUpdateTxt = '';
|
|
136
151
|
if (data.installed) {
|
|
137
|
-
const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} =
|
|
152
|
+
const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} =
|
|
153
|
+
data;
|
|
138
154
|
let typeTxt;
|
|
139
155
|
switch (installType) {
|
|
140
156
|
case INSTALL_TYPE_GIT:
|
|
@@ -162,7 +178,7 @@ class ExtensionCommand {
|
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
|
|
165
|
-
|
|
181
|
+
this.log.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`);
|
|
166
182
|
}
|
|
167
183
|
|
|
168
184
|
return listData;
|
|
@@ -174,29 +190,53 @@ class ExtensionCommand {
|
|
|
174
190
|
* @param {InstallArgs} args
|
|
175
191
|
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
|
|
176
192
|
*/
|
|
177
|
-
async _install
|
|
193
|
+
async _install({installSpec, installType, packageName}) {
|
|
194
|
+
/** @type {ExtensionFields<ExtType>} */
|
|
178
195
|
let extData;
|
|
179
|
-
let installSpec = ext;
|
|
180
196
|
|
|
181
197
|
if (packageName && [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)) {
|
|
182
|
-
throw
|
|
198
|
+
throw this._createFatalError(`When using --source=${installType}, cannot also use --package`);
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
if (!packageName && [INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType)) {
|
|
186
|
-
throw
|
|
202
|
+
throw this._createFatalError(`When using --source=${installType}, must also use --package`);
|
|
187
203
|
}
|
|
188
204
|
|
|
205
|
+
/**
|
|
206
|
+
* @type {InstallViaNpmArgs}
|
|
207
|
+
*/
|
|
208
|
+
let installOpts;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The probable (?) name of the extension derived from the install spec.
|
|
212
|
+
*
|
|
213
|
+
* If using a local install type, this will remain empty.
|
|
214
|
+
* @type {string}
|
|
215
|
+
*/
|
|
216
|
+
let probableExtName = '';
|
|
217
|
+
|
|
218
|
+
// depending on `installType`, build the options to pass into `installViaNpm`
|
|
189
219
|
if (installType === INSTALL_TYPE_GITHUB) {
|
|
190
220
|
if (installSpec.split('/').length !== 2) {
|
|
191
|
-
throw
|
|
192
|
-
|
|
221
|
+
throw this._createFatalError(
|
|
222
|
+
`Github ${this.type} spec ${installSpec} appeared to be invalid; ` +
|
|
223
|
+
'it should be of the form <org>/<repo>'
|
|
224
|
+
);
|
|
193
225
|
}
|
|
194
|
-
|
|
226
|
+
installOpts = {
|
|
227
|
+
installSpec,
|
|
228
|
+
pkgName: /** @type {string} */ (packageName),
|
|
229
|
+
};
|
|
230
|
+
probableExtName = installSpec;
|
|
195
231
|
} else if (installType === INSTALL_TYPE_GIT) {
|
|
196
232
|
// git urls can have '.git' at the end, but this is not necessary and would complicate the
|
|
197
233
|
// way we download and name directories, so we can just remove it
|
|
198
234
|
installSpec = installSpec.replace(/\.git$/, '');
|
|
199
|
-
|
|
235
|
+
installOpts = {
|
|
236
|
+
installSpec,
|
|
237
|
+
pkgName: /** @type {string} */ (packageName),
|
|
238
|
+
};
|
|
239
|
+
probableExtName = installSpec;
|
|
200
240
|
} else {
|
|
201
241
|
let pkgName, pkgVer;
|
|
202
242
|
if (installType === INSTALL_TYPE_LOCAL) {
|
|
@@ -227,30 +267,71 @@ class ExtensionCommand {
|
|
|
227
267
|
// check it exists and get the correct package
|
|
228
268
|
const knownNames = Object.keys(this.knownExtensions);
|
|
229
269
|
if (!_.includes(knownNames, name)) {
|
|
230
|
-
const msg =
|
|
231
|
-
|
|
232
|
-
|
|
270
|
+
const msg =
|
|
271
|
+
`Could not resolve ${this.type}; are you sure it's in the list ` +
|
|
272
|
+
`of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
|
|
273
|
+
throw this._createFatalError(msg);
|
|
233
274
|
}
|
|
275
|
+
probableExtName = name;
|
|
234
276
|
pkgName = this.knownExtensions[name];
|
|
235
277
|
// given that we'll use the install type in the driver json, store it as
|
|
236
278
|
// 'npm' now
|
|
237
279
|
installType = INSTALL_TYPE_NPM;
|
|
238
280
|
}
|
|
239
281
|
}
|
|
282
|
+
installOpts = {installSpec, pkgName, pkgVer};
|
|
283
|
+
}
|
|
240
284
|
|
|
241
|
-
|
|
285
|
+
// fail fast here if we can
|
|
286
|
+
if (probableExtName && this.config.isInstalled(probableExtName)) {
|
|
287
|
+
throw this._createFatalError(
|
|
288
|
+
`A ${this.type} named "${probableExtName}" is already installed. ` +
|
|
289
|
+
`Did you mean to update? Run "appium ${this.type} update". See ` +
|
|
290
|
+
`installed ${this.type}s with "appium ${this.type} list --installed".`
|
|
291
|
+
);
|
|
242
292
|
}
|
|
243
293
|
|
|
244
|
-
|
|
245
|
-
|
|
294
|
+
extData = await this.installViaNpm(installOpts);
|
|
295
|
+
|
|
296
|
+
// this _should_ be the same as `probablyExtName` as the one derived above unless
|
|
297
|
+
// install type is local.
|
|
298
|
+
const extName = extData[/** @type {string} */ (`${this.type}Name`)];
|
|
246
299
|
|
|
300
|
+
// check _a second time_ with the more-accurate extName
|
|
247
301
|
if (this.config.isInstalled(extName)) {
|
|
248
|
-
throw
|
|
249
|
-
|
|
250
|
-
|
|
302
|
+
throw this._createFatalError(
|
|
303
|
+
`A ${this.type} named "${extName}" is already installed. ` +
|
|
304
|
+
`Did you mean to update? Run "appium ${this.type} update". See ` +
|
|
305
|
+
`installed ${this.type}s with "appium ${this.type} list --installed".`
|
|
306
|
+
);
|
|
251
307
|
}
|
|
252
308
|
|
|
309
|
+
// this field does not exist as such in the manifest (it's used as a property name instead)
|
|
310
|
+
// so that's why it's being removed here.
|
|
311
|
+
delete extData[/** @type {string} */ (`${this.type}Name`)];
|
|
312
|
+
|
|
313
|
+
/** @type {ExtManifest<ExtType>} */
|
|
253
314
|
const extManifest = {...extData, installType, installSpec};
|
|
315
|
+
const [errors, warnings] = await B.all([
|
|
316
|
+
this.config.getProblems(extName, extManifest),
|
|
317
|
+
this.config.getWarnings(extName, extManifest),
|
|
318
|
+
]);
|
|
319
|
+
const errorMap = new Map([[extName, errors]]);
|
|
320
|
+
const warningMap = new Map([[extName, warnings]]);
|
|
321
|
+
const {errorSummaries, warningSummaries} = this.config.getValidationResultSummaries(
|
|
322
|
+
errorMap,
|
|
323
|
+
warningMap
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (!_.isEmpty(errorSummaries)) {
|
|
327
|
+
throw this._createFatalError(errorSummaries.join('\n'));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// note that we won't show any warnings if there were errors.
|
|
331
|
+
if (!_.isEmpty(warningSummaries)) {
|
|
332
|
+
this.log.warn(warningSummaries.join('\n'));
|
|
333
|
+
}
|
|
334
|
+
|
|
254
335
|
await this.config.addExtension(extName, extManifest);
|
|
255
336
|
|
|
256
337
|
// update the if we've changed the local `package.json`
|
|
@@ -259,7 +340,7 @@ class ExtensionCommand {
|
|
|
259
340
|
}
|
|
260
341
|
|
|
261
342
|
// log info for the user
|
|
262
|
-
log(this.
|
|
343
|
+
this.log.info(this.getPostInstallText({extName, extData}));
|
|
263
344
|
|
|
264
345
|
return this.config.installedExtensions;
|
|
265
346
|
}
|
|
@@ -269,19 +350,22 @@ class ExtensionCommand {
|
|
|
269
350
|
*
|
|
270
351
|
* @param {InstallViaNpmArgs} args
|
|
271
352
|
*/
|
|
272
|
-
async installViaNpm
|
|
353
|
+
async installViaNpm({installSpec, pkgName, pkgVer}) {
|
|
273
354
|
const npmSpec = `${pkgName}${pkgVer ? '@' + pkgVer : ''}`;
|
|
274
|
-
const specMsg = npmSpec ===
|
|
275
|
-
const msg = `Installing '${
|
|
355
|
+
const specMsg = npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`;
|
|
356
|
+
const msg = `Installing '${installSpec}'${specMsg}`;
|
|
276
357
|
try {
|
|
277
|
-
const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () =>
|
|
278
|
-
await npm.installPackage(this.config.appiumHome, pkgName, {
|
|
279
|
-
pkgVer
|
|
280
|
-
})
|
|
281
|
-
|
|
358
|
+
const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () => {
|
|
359
|
+
const pkgJsonData = await npm.installPackage(this.config.appiumHome, pkgName, {
|
|
360
|
+
pkgVer,
|
|
361
|
+
});
|
|
362
|
+
this.validatePackageJson(pkgJsonData, installSpec);
|
|
363
|
+
return pkgJsonData;
|
|
364
|
+
});
|
|
365
|
+
|
|
282
366
|
return this.getExtensionFields(pkgJsonData);
|
|
283
367
|
} catch (err) {
|
|
284
|
-
throw
|
|
368
|
+
throw this._createFatalError(`Encountered an error when installing package: ${err.message}`);
|
|
285
369
|
}
|
|
286
370
|
}
|
|
287
371
|
|
|
@@ -293,8 +377,8 @@ class ExtensionCommand {
|
|
|
293
377
|
* @returns {string}
|
|
294
378
|
*/
|
|
295
379
|
// eslint-disable-next-line no-unused-vars
|
|
296
|
-
getPostInstallText
|
|
297
|
-
throw
|
|
380
|
+
getPostInstallText(args) {
|
|
381
|
+
throw this._createFatalError('Must be implemented in final class');
|
|
298
382
|
}
|
|
299
383
|
|
|
300
384
|
/**
|
|
@@ -303,51 +387,95 @@ class ExtensionCommand {
|
|
|
303
387
|
* load as the main driver class, or to be able to detect incompatibilities between driver and
|
|
304
388
|
* appium versions.
|
|
305
389
|
*
|
|
306
|
-
* @param {ExtPackageJson<ExtType>}
|
|
307
|
-
* straightforwardly 'require'd
|
|
390
|
+
* @param {ExtPackageJson<ExtType>} pkgJson - the package.json data for a driver module, as if it had been straightforwardly 'require'd
|
|
308
391
|
* @returns {ExtensionFields<ExtType>}
|
|
309
392
|
*/
|
|
310
|
-
getExtensionFields
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
`package.json file as expected`);
|
|
314
|
-
}
|
|
315
|
-
const {appium, name, version} = pkgJsonData;
|
|
316
|
-
this.validateExtensionFields(appium);
|
|
393
|
+
getExtensionFields(pkgJson) {
|
|
394
|
+
const {appium, name, version, peerDependencies} = pkgJson;
|
|
395
|
+
|
|
317
396
|
/** @type {unknown} */
|
|
318
|
-
const result = {
|
|
319
|
-
|
|
397
|
+
const result = {
|
|
398
|
+
...appium,
|
|
399
|
+
pkgName: name,
|
|
400
|
+
version,
|
|
401
|
+
appiumVersion: peerDependencies?.appium,
|
|
402
|
+
};
|
|
403
|
+
return /** @type {ExtensionFields<ExtType>} */ (result);
|
|
320
404
|
}
|
|
321
405
|
|
|
322
406
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
407
|
+
* Validates the _required_ root fields of an extension's `package.json` file.
|
|
408
|
+
*
|
|
409
|
+
* These required fields are:
|
|
410
|
+
* - `name`
|
|
411
|
+
* - `version`
|
|
412
|
+
* - `appium`
|
|
413
|
+
* @param {import('type-fest').PackageJson} pkgJson - `package.json` of extension
|
|
414
|
+
* @param {string} installSpec - Extension name/spec
|
|
415
|
+
* @throws {ReferenceError} If `package.json` has a missing or invalid field
|
|
416
|
+
* @returns {pkgJson is ExtPackageJson<ExtType>}
|
|
417
|
+
*/
|
|
418
|
+
validatePackageJson(pkgJson, installSpec) {
|
|
419
|
+
const {appium, name, version} = /** @type {ExtPackageJson<ExtType>} */ (pkgJson);
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
*
|
|
423
|
+
* @param {string} field
|
|
424
|
+
* @returns {ReferenceError}
|
|
425
|
+
*/
|
|
426
|
+
const createMissingFieldError = (field) =>
|
|
427
|
+
new ReferenceError(
|
|
428
|
+
`${this.type} "${installSpec}" invalid; missing a \`${field}\` field of its \`package.json\``
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
if (!name) {
|
|
432
|
+
throw createMissingFieldError('name');
|
|
433
|
+
}
|
|
434
|
+
if (!version) {
|
|
435
|
+
throw createMissingFieldError('version');
|
|
436
|
+
}
|
|
437
|
+
if (!appium) {
|
|
438
|
+
throw createMissingFieldError('appium');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.validateExtensionFields(appium, installSpec);
|
|
442
|
+
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* For any `package.json` fields which a particular type of extension requires, validate the
|
|
448
|
+
* presence and form of those fields on the `package.json` data, throwing an error if anything is
|
|
325
449
|
* amiss.
|
|
326
450
|
*
|
|
327
|
-
* @param {ExtMetadata<ExtType>}
|
|
451
|
+
* @param {ExtMetadata<ExtType>} extMetadata - the data in the "appium" field of `package.json` for an extension
|
|
452
|
+
* @param {string} installSpec - Extension name/spec
|
|
328
453
|
*/
|
|
329
454
|
// eslint-disable-next-line no-unused-vars
|
|
330
|
-
validateExtensionFields
|
|
331
|
-
throw
|
|
455
|
+
validateExtensionFields(extMetadata, installSpec) {
|
|
456
|
+
throw this._createFatalError('Must be implemented in final class');
|
|
332
457
|
}
|
|
333
458
|
|
|
334
459
|
/**
|
|
335
|
-
* Uninstall an extension
|
|
460
|
+
* Uninstall an extension.
|
|
461
|
+
*
|
|
462
|
+
* First tries to do this via `npm uninstall`, but if that fails, just `rm -rf`'s the extension dir.
|
|
463
|
+
*
|
|
464
|
+
* Will only remove the extension from the manifest if it has been successfully removed.
|
|
336
465
|
*
|
|
337
466
|
* @param {UninstallOpts} opts
|
|
338
|
-
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
|
|
467
|
+
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data (without the extension just uninstalled)
|
|
339
468
|
*/
|
|
340
|
-
async _uninstall
|
|
341
|
-
if (!this.config.isInstalled(
|
|
342
|
-
throw
|
|
469
|
+
async _uninstall({installSpec}) {
|
|
470
|
+
if (!this.config.isInstalled(installSpec)) {
|
|
471
|
+
throw this._createFatalError(
|
|
472
|
+
`Can't uninstall ${this.type} '${installSpec}'; it is not installed`
|
|
473
|
+
);
|
|
343
474
|
}
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
await this.config.removeExtension(ext);
|
|
349
|
-
}
|
|
350
|
-
log(this.isJsonOutput, `Successfully uninstalled ${this.type} '${ext}'`.green);
|
|
475
|
+
const pkgName = this.config.installedExtensions[installSpec].pkgName;
|
|
476
|
+
await npm.uninstallPackage(this.config.appiumHome, pkgName);
|
|
477
|
+
await this.config.removeExtension(installSpec);
|
|
478
|
+
this.log.ok(`Successfully uninstalled ${this.type} '${installSpec}'`.green);
|
|
351
479
|
return this.config.installedExtensions;
|
|
352
480
|
}
|
|
353
481
|
|
|
@@ -357,13 +485,17 @@ class ExtensionCommand {
|
|
|
357
485
|
* @param {ExtensionUpdateOpts} updateSpec
|
|
358
486
|
* @return {Promise<ExtensionUpdateResult>}
|
|
359
487
|
*/
|
|
360
|
-
async _update
|
|
361
|
-
const shouldUpdateAll =
|
|
488
|
+
async _update({installSpec, unsafe}) {
|
|
489
|
+
const shouldUpdateAll = installSpec === UPDATE_ALL;
|
|
362
490
|
// if we're specifically requesting an update for an extension, make sure it's installed
|
|
363
|
-
if (!shouldUpdateAll && !this.config.isInstalled(
|
|
364
|
-
throw
|
|
491
|
+
if (!shouldUpdateAll && !this.config.isInstalled(installSpec)) {
|
|
492
|
+
throw this._createFatalError(
|
|
493
|
+
`The ${this.type} "${installSpec}" was not installed, so can't be updated`
|
|
494
|
+
);
|
|
365
495
|
}
|
|
366
|
-
const extsToUpdate = shouldUpdateAll
|
|
496
|
+
const extsToUpdate = shouldUpdateAll
|
|
497
|
+
? Object.keys(this.config.installedExtensions)
|
|
498
|
+
: [installSpec];
|
|
367
499
|
|
|
368
500
|
// 'errors' will have ext names as keys and error objects as values
|
|
369
501
|
/** @type {Record<string,Error>} */
|
|
@@ -381,17 +513,23 @@ class ExtensionCommand {
|
|
|
381
513
|
throw new NotUpdatableError();
|
|
382
514
|
}
|
|
383
515
|
});
|
|
384
|
-
const update = await spinWith(
|
|
385
|
-
|
|
386
|
-
if
|
|
387
|
-
|
|
516
|
+
const update = await spinWith(
|
|
517
|
+
this.isJsonOutput,
|
|
518
|
+
`Checking if ${this.type} '${e}' needs an update`,
|
|
519
|
+
async () => {
|
|
520
|
+
const update = await this.checkForExtensionUpdate(e);
|
|
521
|
+
if (!(update.safeUpdate || update.unsafeUpdate)) {
|
|
522
|
+
throw new NoUpdatesAvailableError();
|
|
523
|
+
}
|
|
524
|
+
return update;
|
|
388
525
|
}
|
|
389
|
-
|
|
390
|
-
});
|
|
526
|
+
);
|
|
391
527
|
if (!unsafe && !update.safeUpdate) {
|
|
392
|
-
throw
|
|
393
|
-
|
|
394
|
-
|
|
528
|
+
throw this._createFatalError(
|
|
529
|
+
`The ${this.type} '${e}' has a major revision update ` +
|
|
530
|
+
`(${update.current} => ${update.unsafeUpdate}), which could include ` +
|
|
531
|
+
`breaking changes. If you want to apply this update, re-run with --unsafe`
|
|
532
|
+
);
|
|
395
533
|
}
|
|
396
534
|
const updateVer = unsafe && update.unsafeUpdate ? update.unsafeUpdate : update.safeUpdate;
|
|
397
535
|
await spinWith(
|
|
@@ -405,22 +543,24 @@ class ExtensionCommand {
|
|
|
405
543
|
}
|
|
406
544
|
}
|
|
407
545
|
|
|
408
|
-
log(
|
|
546
|
+
this.log.info('Update report:');
|
|
547
|
+
|
|
409
548
|
for (const [e, update] of _.toPairs(updates)) {
|
|
410
|
-
log(
|
|
549
|
+
this.log.ok(` - ${this.type} ${e} updated: ${update.from} => ${update.to}`.green);
|
|
411
550
|
}
|
|
551
|
+
|
|
412
552
|
for (const [e, err] of _.toPairs(errors)) {
|
|
413
553
|
if (err instanceof NotUpdatableError) {
|
|
414
|
-
log(
|
|
415
|
-
|
|
554
|
+
this.log.warn(
|
|
555
|
+
` - '${e}' was not installed via npm, so we could not check ` + `for updates`.yellow
|
|
556
|
+
);
|
|
416
557
|
} else if (err instanceof NoUpdatesAvailableError) {
|
|
417
|
-
log(
|
|
558
|
+
this.log.info(` - '${e}' had no updates available`.yellow);
|
|
418
559
|
} else {
|
|
419
560
|
// otherwise, make it pop with red!
|
|
420
|
-
log(
|
|
561
|
+
this.log.error(` - '${e}' failed to update: ${err}`.red);
|
|
421
562
|
}
|
|
422
563
|
}
|
|
423
|
-
|
|
424
564
|
return {updates, errors};
|
|
425
565
|
}
|
|
426
566
|
|
|
@@ -431,13 +571,17 @@ class ExtensionCommand {
|
|
|
431
571
|
* @param {string} ext - name of extension
|
|
432
572
|
* @return {Promise<PossibleUpdates>}
|
|
433
573
|
*/
|
|
434
|
-
async checkForExtensionUpdate
|
|
574
|
+
async checkForExtensionUpdate(ext) {
|
|
435
575
|
// TODO decide how we want to handle beta versions?
|
|
436
576
|
// this is a helper method, 'ext' is assumed to already be installed here, and of the npm
|
|
437
577
|
// install type
|
|
438
578
|
const {version, pkgName} = this.config.installedExtensions[ext];
|
|
439
579
|
let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
|
|
440
|
-
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
|
580
|
+
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
|
581
|
+
this.config.appiumHome,
|
|
582
|
+
pkgName,
|
|
583
|
+
version
|
|
584
|
+
);
|
|
441
585
|
if (!util.compareVersions(unsafeUpdate, '>', version)) {
|
|
442
586
|
// the latest version is not greater than the current version, so there's no possible update
|
|
443
587
|
unsafeUpdate = null;
|
|
@@ -458,16 +602,19 @@ class ExtensionCommand {
|
|
|
458
602
|
* Actually update an extension installed by NPM, using the NPM cli. And update the installation
|
|
459
603
|
* manifest.
|
|
460
604
|
*
|
|
461
|
-
* @param {string}
|
|
605
|
+
* @param {string} installSpec - name of extension to update
|
|
462
606
|
* @param {string} version - version string identifier to update extension to
|
|
463
607
|
* @returns {Promise<void>}
|
|
464
608
|
*/
|
|
465
|
-
async updateExtension
|
|
466
|
-
const {pkgName} = this.config.installedExtensions[
|
|
467
|
-
await
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
609
|
+
async updateExtension(installSpec, version) {
|
|
610
|
+
const {pkgName} = this.config.installedExtensions[installSpec];
|
|
611
|
+
const extData = await this.installViaNpm({
|
|
612
|
+
installSpec,
|
|
613
|
+
pkgName,
|
|
614
|
+
pkgVer: version,
|
|
615
|
+
});
|
|
616
|
+
delete extData[/** @type {string} */ (`${this.type}Name`)];
|
|
617
|
+
await this.config.updateExtension(installSpec, extData);
|
|
471
618
|
}
|
|
472
619
|
|
|
473
620
|
/**
|
|
@@ -481,50 +628,54 @@ class ExtensionCommand {
|
|
|
481
628
|
* @param {RunOptions} opts
|
|
482
629
|
* @return {Promise<RunOutput>}
|
|
483
630
|
*/
|
|
484
|
-
async _run
|
|
485
|
-
if (!
|
|
486
|
-
throw
|
|
631
|
+
async _run({installSpec, scriptName}) {
|
|
632
|
+
if (!this.config.isInstalled(installSpec)) {
|
|
633
|
+
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
|
487
634
|
}
|
|
488
635
|
|
|
489
|
-
const extConfig = this.config.installedExtensions[
|
|
636
|
+
const extConfig = this.config.installedExtensions[installSpec];
|
|
490
637
|
|
|
491
638
|
// note: TS cannot understand that _.has() is a type guard
|
|
492
639
|
if (!extConfig.scripts) {
|
|
493
|
-
throw
|
|
494
|
-
|
|
640
|
+
throw this._createFatalError(
|
|
641
|
+
`The ${this.type} named '${installSpec}' does not contain the ` +
|
|
642
|
+
`"scripts" field underneath the "appium" field in its package.json`
|
|
643
|
+
);
|
|
495
644
|
}
|
|
496
645
|
|
|
497
646
|
const extScripts = extConfig.scripts;
|
|
498
647
|
|
|
499
648
|
if (!_.isPlainObject(extScripts)) {
|
|
500
|
-
throw
|
|
649
|
+
throw this._createFatalError(
|
|
650
|
+
`The ${this.type} named '${installSpec}' "scripts" field must be a plain object`
|
|
651
|
+
);
|
|
501
652
|
}
|
|
502
653
|
|
|
503
654
|
if (!_.has(extScripts, scriptName)) {
|
|
504
|
-
throw
|
|
655
|
+
throw this._createFatalError(
|
|
656
|
+
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
|
657
|
+
);
|
|
505
658
|
}
|
|
506
659
|
|
|
507
|
-
const runner = new SubProcess(process.execPath, [
|
|
508
|
-
|
|
509
|
-
], {
|
|
510
|
-
cwd: this.config.getInstallPath(ext)
|
|
660
|
+
const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
|
|
661
|
+
cwd: this.config.getInstallPath(installSpec),
|
|
511
662
|
});
|
|
512
663
|
|
|
513
664
|
const output = new RingBuffer(50);
|
|
514
665
|
|
|
515
666
|
runner.on('stream-line', (line) => {
|
|
516
667
|
output.enqueue(line);
|
|
517
|
-
log(
|
|
668
|
+
this.log.log(line);
|
|
518
669
|
});
|
|
519
670
|
|
|
520
671
|
await runner.start(0);
|
|
521
672
|
|
|
522
673
|
try {
|
|
523
674
|
await runner.join();
|
|
524
|
-
log(
|
|
675
|
+
this.log.ok(`${scriptName} successfully ran`.green);
|
|
525
676
|
return {output: output.getBuff()};
|
|
526
677
|
} catch (err) {
|
|
527
|
-
log(
|
|
678
|
+
this.log.error(`Encountered an error when running '${scriptName}': ${err.message}`.red);
|
|
528
679
|
return {error: err.message, output: output.getBuff()};
|
|
529
680
|
}
|
|
530
681
|
}
|
|
@@ -552,14 +703,14 @@ export {ExtensionCommand};
|
|
|
552
703
|
*/
|
|
553
704
|
|
|
554
705
|
/**
|
|
555
|
-
* @typedef {import('
|
|
556
|
-
* @typedef {import('
|
|
557
|
-
* @typedef {import('
|
|
706
|
+
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
707
|
+
* @typedef {import('@appium/types').DriverType} DriverType
|
|
708
|
+
* @typedef {import('@appium/types').PluginType} PluginType
|
|
558
709
|
*/
|
|
559
710
|
|
|
560
711
|
/**
|
|
561
712
|
* @template {ExtensionType} ExtType
|
|
562
|
-
* @typedef {import('
|
|
713
|
+
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
|
563
714
|
*/
|
|
564
715
|
|
|
565
716
|
/**
|
|
@@ -569,17 +720,17 @@ export {ExtensionCommand};
|
|
|
569
720
|
|
|
570
721
|
/**
|
|
571
722
|
* @template {ExtensionType} ExtType
|
|
572
|
-
* @typedef {import('
|
|
723
|
+
* @typedef {import('appium/types').ExtMetadata<ExtType>} ExtMetadata
|
|
573
724
|
*/
|
|
574
725
|
|
|
575
726
|
/**
|
|
576
727
|
* @template {ExtensionType} ExtType
|
|
577
|
-
* @typedef {import('
|
|
728
|
+
* @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
|
|
578
729
|
*/
|
|
579
730
|
|
|
580
731
|
/**
|
|
581
732
|
* @template {ExtensionType} ExtType
|
|
582
|
-
* @typedef {import('
|
|
733
|
+
* @typedef {import('appium/types').ExtPackageJson<ExtType>} ExtPackageJson
|
|
583
734
|
*/
|
|
584
735
|
|
|
585
736
|
/**
|
|
@@ -591,7 +742,7 @@ export {ExtensionCommand};
|
|
|
591
742
|
|
|
592
743
|
/**
|
|
593
744
|
* Possible return value for {@linkcode ExtensionCommand.list}
|
|
594
|
-
* @typedef {import('
|
|
745
|
+
* @typedef {import('appium/types').InternalMetadata & ExtensionMetadata} InstalledExtensionListData
|
|
595
746
|
*/
|
|
596
747
|
|
|
597
748
|
/**
|
|
@@ -602,7 +753,7 @@ export {ExtensionCommand};
|
|
|
602
753
|
/**
|
|
603
754
|
* Options for {@linkcode ExtensionCommand._run}.
|
|
604
755
|
* @typedef RunOptions
|
|
605
|
-
* @property {string}
|
|
756
|
+
* @property {string} installSpec - name of the extension to run a script from
|
|
606
757
|
* @property {string} scriptName - name of the script to run
|
|
607
758
|
*/
|
|
608
759
|
|
|
@@ -617,7 +768,7 @@ export {ExtensionCommand};
|
|
|
617
768
|
/**
|
|
618
769
|
* Options for {@linkcode ExtensionCommand._update}.
|
|
619
770
|
* @typedef ExtensionUpdateOpts
|
|
620
|
-
* @property {string}
|
|
771
|
+
* @property {string} installSpec - the name of the extension to update
|
|
621
772
|
* @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
|
|
622
773
|
*/
|
|
623
774
|
|
|
@@ -638,7 +789,7 @@ export {ExtensionCommand};
|
|
|
638
789
|
/**
|
|
639
790
|
* Options for {@linkcode ExtensionCommand._uninstall}.
|
|
640
791
|
* @typedef UninstallOpts
|
|
641
|
-
* @property {string}
|
|
792
|
+
* @property {string} installSpec - the name or spec of an extension to uninstall
|
|
642
793
|
*/
|
|
643
794
|
|
|
644
795
|
/**
|
|
@@ -651,7 +802,7 @@ export {ExtensionCommand};
|
|
|
651
802
|
/**
|
|
652
803
|
* Options for {@linkcode ExtensionCommand.installViaNpm}
|
|
653
804
|
* @typedef InstallViaNpmArgs
|
|
654
|
-
* @property {string}
|
|
805
|
+
* @property {string} installSpec - the name or spec of an extension to install
|
|
655
806
|
* @property {string} pkgName - the NPM package name of the extension
|
|
656
807
|
* @property {string} [pkgVer] - the specific version of the NPM package
|
|
657
808
|
*/
|
|
@@ -667,18 +818,24 @@ export {ExtensionCommand};
|
|
|
667
818
|
/**
|
|
668
819
|
* Options for {@linkcode ExtensionCommand._install}
|
|
669
820
|
* @typedef InstallArgs
|
|
670
|
-
* @property {string}
|
|
671
|
-
* @property {import('
|
|
821
|
+
* @property {string} installSpec - the name or spec of an extension to install
|
|
822
|
+
* @property {import('appium/types').InstallType} installType - how to install this extension. One of the INSTALL_TYPES
|
|
672
823
|
* @property {string} [packageName] - for git/github installs, the extension node package name
|
|
673
824
|
*/
|
|
674
825
|
|
|
675
826
|
/**
|
|
676
827
|
* Returned by {@linkcode ExtensionCommand.getExtensionFields}
|
|
677
828
|
* @template {ExtensionType} ExtType
|
|
678
|
-
* @typedef {ExtMetadata<ExtType> & { pkgName: string, version: string } & import('
|
|
829
|
+
* @typedef {ExtMetadata<ExtType> & { pkgName: string, version: string, appiumVersion: string } & import('appium/types').CommonExtMetadata} ExtensionFields
|
|
679
830
|
*/
|
|
680
831
|
|
|
681
832
|
/**
|
|
682
833
|
* @template {ExtensionType} ExtType
|
|
683
834
|
* @typedef {ExtType extends DriverType ? typeof import('../constants').KNOWN_DRIVERS : ExtType extends PluginType ? typeof import('../constants').KNOWN_PLUGINS : never} KnownExtensions
|
|
684
835
|
*/
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* @typedef ListOptions
|
|
839
|
+
* @property {boolean} showInstalled - whether should show only installed extensions
|
|
840
|
+
* @property {boolean} showUpdates - whether should show available updates
|
|
841
|
+
*/
|