appium 2.0.0-beta.35 → 2.0.0-beta.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/lib/appium.d.ts +41 -52
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +32 -15
- package/build/lib/cli/args.d.ts +1 -1
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +1 -1
- package/build/lib/cli/driver-command.d.ts +3 -3
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +1 -1
- package/build/lib/cli/extension-command.d.ts +60 -38
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +115 -59
- package/build/lib/cli/extension.d.ts +9 -5
- package/build/lib/cli/extension.d.ts.map +1 -1
- package/build/lib/cli/extension.js +5 -7
- package/build/lib/cli/parser.d.ts +3 -3
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +1 -1
- package/build/lib/cli/plugin-command.d.ts +1 -1
- package/build/lib/cli/plugin-command.d.ts.map +1 -1
- package/build/lib/cli/plugin-command.js +1 -1
- package/build/lib/cli/utils.js +1 -1
- package/build/lib/config-file.d.ts.map +1 -1
- package/build/lib/config-file.js +1 -1
- package/build/lib/config.d.ts +4 -4
- package/build/lib/config.d.ts.map +1 -1
- package/build/lib/config.js +1 -1
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +1 -1
- package/build/lib/extension/driver-config.d.ts +29 -32
- package/build/lib/extension/driver-config.d.ts.map +1 -1
- package/build/lib/extension/driver-config.js +7 -20
- package/build/lib/extension/extension-config.d.ts +108 -36
- package/build/lib/extension/extension-config.d.ts.map +1 -1
- package/build/lib/extension/extension-config.js +199 -60
- package/build/lib/extension/index.d.ts +16 -7
- package/build/lib/extension/index.d.ts.map +1 -1
- package/build/lib/extension/index.js +15 -18
- package/build/lib/extension/manifest.d.ts +12 -12
- package/build/lib/extension/manifest.d.ts.map +1 -1
- package/build/lib/extension/manifest.js +13 -3
- package/build/lib/extension/package-changed.d.ts.map +1 -1
- package/build/lib/extension/package-changed.js +1 -1
- package/build/lib/extension/plugin-config.d.ts +19 -24
- package/build/lib/extension/plugin-config.d.ts.map +1 -1
- package/build/lib/extension/plugin-config.js +9 -18
- package/build/lib/grid-register.d.ts.map +1 -1
- package/build/lib/grid-register.js +1 -1
- package/build/lib/logger.d.ts +1 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js +1 -1
- package/build/lib/logsink.d.ts.map +1 -1
- package/build/lib/logsink.js +3 -2
- package/build/lib/main.d.ts +13 -12
- package/build/lib/main.d.ts.map +1 -1
- package/build/lib/main.js +4 -4
- package/build/lib/schema/arg-spec.d.ts +4 -4
- package/build/lib/schema/arg-spec.d.ts.map +1 -1
- package/build/lib/schema/arg-spec.js +1 -1
- package/build/lib/schema/cli-args.d.ts.map +1 -1
- package/build/lib/schema/cli-args.js +1 -1
- package/build/lib/schema/cli-transformers.d.ts.map +1 -1
- package/build/lib/schema/cli-transformers.js +1 -1
- package/build/lib/schema/keywords.d.ts.map +1 -1
- package/build/lib/schema/keywords.js +1 -1
- package/build/lib/schema/schema.d.ts +2 -2
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/appium-manifest.d.ts +23 -4
- package/build/types/appium-manifest.d.ts.map +1 -1
- package/build/types/cli.d.ts.map +1 -1
- package/build/types/{external-manifest.d.ts → extension-manifest.d.ts} +15 -7
- package/build/types/extension-manifest.d.ts.map +1 -0
- package/build/types/index.d.ts +6 -5
- package/build/types/index.d.ts.map +1 -1
- package/driver.d.ts +1 -0
- package/driver.js +14 -0
- package/lib/appium.js +208 -124
- package/lib/cli/args.js +143 -93
- package/lib/cli/driver-command.js +10 -15
- package/lib/cli/extension-command.js +226 -175
- package/lib/cli/extension.js +15 -19
- package/lib/cli/parser.js +19 -31
- package/lib/cli/plugin-command.js +8 -8
- package/lib/cli/utils.js +8 -14
- package/lib/config-file.js +21 -25
- package/lib/config.js +82 -64
- package/lib/constants.js +4 -13
- package/lib/extension/driver-config.js +171 -171
- package/lib/extension/extension-config.js +347 -126
- package/lib/extension/index.js +72 -58
- package/lib/extension/manifest.js +48 -57
- package/lib/extension/package-changed.js +9 -8
- package/lib/extension/plugin-config.js +62 -62
- package/lib/grid-register.js +29 -18
- package/lib/logger.js +1 -2
- package/lib/logsink.js +29 -31
- package/lib/main.js +111 -73
- package/lib/schema/arg-spec.js +10 -13
- package/lib/schema/cli-args.js +14 -37
- package/lib/schema/cli-transformers.js +7 -14
- package/lib/schema/keywords.js +15 -13
- package/lib/schema/schema.js +58 -75
- package/lib/utils.js +50 -25
- package/package.json +25 -18
- package/plugin.d.ts +1 -0
- package/plugin.js +13 -0
- package/scripts/autoinstall-extensions.js +177 -0
- package/support.d.ts +1 -0
- package/support.js +13 -0
- package/types/appium-manifest.ts +27 -15
- package/types/cli.ts +2 -9
- package/types/{external-manifest.ts → extension-manifest.ts} +21 -15
- package/types/index.ts +12 -5
- package/build/types/extension.d.ts +0 -43
- package/build/types/extension.d.ts.map +0 -1
- package/build/types/external-manifest.d.ts.map +0 -1
- package/scripts/postinstall.js +0 -71
- package/types/extension.ts +0 -56
|
@@ -1,9 +1,9 @@
|
|
|
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 {npm,
|
|
6
|
-
import {
|
|
5
|
+
import {npm, util, env, console} from '@appium/support';
|
|
6
|
+
import {spinWith, RingBuffer} from './utils';
|
|
7
7
|
import {SubProcess} from 'teen_process';
|
|
8
8
|
import {
|
|
9
9
|
INSTALL_TYPE_NPM,
|
|
@@ -44,49 +44,56 @@ class ExtensionCommand {
|
|
|
44
44
|
* Build an ExtensionCommand
|
|
45
45
|
* @param {ExtensionCommandOptions<ExtType>} opts
|
|
46
46
|
*/
|
|
47
|
-
constructor
|
|
47
|
+
constructor({config, json}) {
|
|
48
48
|
this.config = config;
|
|
49
|
-
this.
|
|
49
|
+
this.log = new console.CliConsole({jsonMode: json});
|
|
50
|
+
this.isJsonOutput = Boolean(json);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* `driver` or `plugin`, depending on the `ExtensionConfig`.
|
|
54
55
|
*/
|
|
55
|
-
get type
|
|
56
|
+
get type() {
|
|
56
57
|
return this.config.extensionType;
|
|
57
58
|
}
|
|
58
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
|
+
|
|
59
74
|
/**
|
|
60
75
|
* Take a CLI parse and run an extension command based on its type
|
|
61
76
|
*
|
|
62
77
|
* @param {object} args - a key/value object with CLI flags and values
|
|
63
78
|
* @return {Promise<object>} the result of the specific command which is executed
|
|
64
79
|
*/
|
|
65
|
-
async execute
|
|
80
|
+
async execute(args) {
|
|
66
81
|
const cmd = args[`${this.type}Command`];
|
|
67
82
|
if (!_.isFunction(this[cmd])) {
|
|
68
|
-
throw
|
|
83
|
+
throw this._createFatalError(`Cannot handle ${this.type} command ${cmd}`);
|
|
69
84
|
}
|
|
70
85
|
const executeCmd = this[cmd].bind(this);
|
|
71
86
|
return await executeCmd(args);
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
/**
|
|
75
|
-
* @typedef ListOptions
|
|
76
|
-
* @property {boolean} showInstalled - whether should show only installed extensions
|
|
77
|
-
* @property {boolean} showUpdates - whether should show available updates
|
|
78
|
-
*/
|
|
79
|
-
|
|
80
89
|
/**
|
|
81
90
|
* List extensions
|
|
82
91
|
*
|
|
83
92
|
* @param {ListOptions} opts
|
|
84
93
|
* @return {Promise<ExtensionListData>} map of extension names to extension data
|
|
85
94
|
*/
|
|
86
|
-
async list
|
|
87
|
-
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${
|
|
88
|
-
this.type
|
|
89
|
-
}s`;
|
|
95
|
+
async list({showInstalled, showUpdates}) {
|
|
96
|
+
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
|
|
90
97
|
const installedNames = Object.keys(this.config.installedExtensions);
|
|
91
98
|
const knownNames = Object.keys(this.knownExtensions);
|
|
92
99
|
const exts = [...installedNames, ...knownNames].reduce(
|
|
@@ -124,8 +131,7 @@ class ExtensionCommand {
|
|
|
124
131
|
const updates = await this.checkForExtensionUpdate(ext);
|
|
125
132
|
data.updateVersion = updates.safeUpdate;
|
|
126
133
|
data.unsafeUpdateVersion = updates.unsafeUpdate;
|
|
127
|
-
data.upToDate =
|
|
128
|
-
updates.safeUpdate === null && updates.unsafeUpdate === null;
|
|
134
|
+
data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null;
|
|
129
135
|
}
|
|
130
136
|
});
|
|
131
137
|
|
|
@@ -143,14 +149,8 @@ class ExtensionCommand {
|
|
|
143
149
|
let upToDateTxt = '';
|
|
144
150
|
let unsafeUpdateTxt = '';
|
|
145
151
|
if (data.installed) {
|
|
146
|
-
const {
|
|
147
|
-
|
|
148
|
-
installSpec,
|
|
149
|
-
updateVersion,
|
|
150
|
-
unsafeUpdateVersion,
|
|
151
|
-
version,
|
|
152
|
-
upToDate,
|
|
153
|
-
} = data;
|
|
152
|
+
const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} =
|
|
153
|
+
data;
|
|
154
154
|
let typeTxt;
|
|
155
155
|
switch (installType) {
|
|
156
156
|
case INSTALL_TYPE_GIT:
|
|
@@ -163,9 +163,7 @@ class ExtensionCommand {
|
|
|
163
163
|
default:
|
|
164
164
|
typeTxt = '(NPM)';
|
|
165
165
|
}
|
|
166
|
-
installTxt = `@${version.yellow} ${
|
|
167
|
-
('[installed ' + typeTxt + ']').green
|
|
168
|
-
}`;
|
|
166
|
+
installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
|
|
169
167
|
|
|
170
168
|
if (showUpdates) {
|
|
171
169
|
if (updateVersion) {
|
|
@@ -175,15 +173,12 @@ class ExtensionCommand {
|
|
|
175
173
|
upToDateTxt = ` [Up to date]`.green;
|
|
176
174
|
}
|
|
177
175
|
if (unsafeUpdateVersion) {
|
|
178
|
-
unsafeUpdateTxt =
|
|
179
|
-
` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
|
|
176
|
+
unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
|
|
180
177
|
}
|
|
181
178
|
}
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
|
|
185
|
-
`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`
|
|
186
|
-
);
|
|
181
|
+
this.log.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`);
|
|
187
182
|
}
|
|
188
183
|
|
|
189
184
|
return listData;
|
|
@@ -195,53 +190,57 @@ class ExtensionCommand {
|
|
|
195
190
|
* @param {InstallArgs} args
|
|
196
191
|
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
|
|
197
192
|
*/
|
|
198
|
-
async _install
|
|
199
|
-
/** @type {ExtensionFields<
|
|
193
|
+
async _install({installSpec, installType, packageName}) {
|
|
194
|
+
/** @type {ExtensionFields<ExtType>} */
|
|
200
195
|
let extData;
|
|
201
196
|
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
[INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)
|
|
205
|
-
) {
|
|
206
|
-
throw new Error(
|
|
207
|
-
`When using --source=${installType}, cannot also use --package`
|
|
208
|
-
);
|
|
197
|
+
if (packageName && [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)) {
|
|
198
|
+
throw this._createFatalError(`When using --source=${installType}, cannot also use --package`);
|
|
209
199
|
}
|
|
210
200
|
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
[INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType)
|
|
214
|
-
) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`When using --source=${installType}, must also use --package`
|
|
217
|
-
);
|
|
201
|
+
if (!packageName && [INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB].includes(installType)) {
|
|
202
|
+
throw this._createFatalError(`When using --source=${installType}, must also use --package`);
|
|
218
203
|
}
|
|
219
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`
|
|
220
219
|
if (installType === INSTALL_TYPE_GITHUB) {
|
|
221
220
|
if (installSpec.split('/').length !== 2) {
|
|
222
|
-
throw
|
|
221
|
+
throw this._createFatalError(
|
|
223
222
|
`Github ${this.type} spec ${installSpec} appeared to be invalid; ` +
|
|
224
223
|
'it should be of the form <org>/<repo>'
|
|
225
224
|
);
|
|
226
225
|
}
|
|
227
|
-
|
|
226
|
+
installOpts = {
|
|
228
227
|
installSpec,
|
|
229
228
|
pkgName: /** @type {string} */ (packageName),
|
|
230
|
-
}
|
|
229
|
+
};
|
|
230
|
+
probableExtName = installSpec;
|
|
231
231
|
} else if (installType === INSTALL_TYPE_GIT) {
|
|
232
232
|
// git urls can have '.git' at the end, but this is not necessary and would complicate the
|
|
233
233
|
// way we download and name directories, so we can just remove it
|
|
234
234
|
installSpec = installSpec.replace(/\.git$/, '');
|
|
235
|
-
|
|
235
|
+
installOpts = {
|
|
236
236
|
installSpec,
|
|
237
237
|
pkgName: /** @type {string} */ (packageName),
|
|
238
|
-
}
|
|
238
|
+
};
|
|
239
|
+
probableExtName = installSpec;
|
|
239
240
|
} else {
|
|
240
241
|
let pkgName, pkgVer;
|
|
241
242
|
if (installType === INSTALL_TYPE_LOCAL) {
|
|
242
|
-
pkgName = path.isAbsolute(installSpec)
|
|
243
|
-
? installSpec
|
|
244
|
-
: path.resolve(installSpec);
|
|
243
|
+
pkgName = path.isAbsolute(installSpec) ? installSpec : path.resolve(installSpec);
|
|
245
244
|
} else {
|
|
246
245
|
// at this point we have either an npm package or an appium verified extension
|
|
247
246
|
// name or a local path. both of which will be installed via npm.
|
|
@@ -271,30 +270,68 @@ class ExtensionCommand {
|
|
|
271
270
|
const msg =
|
|
272
271
|
`Could not resolve ${this.type}; are you sure it's in the list ` +
|
|
273
272
|
`of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
|
|
274
|
-
throw
|
|
273
|
+
throw this._createFatalError(msg);
|
|
275
274
|
}
|
|
275
|
+
probableExtName = name;
|
|
276
276
|
pkgName = this.knownExtensions[name];
|
|
277
277
|
// given that we'll use the install type in the driver json, store it as
|
|
278
278
|
// 'npm' now
|
|
279
279
|
installType = INSTALL_TYPE_NPM;
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
|
+
installOpts = {installSpec, pkgName, pkgVer};
|
|
283
|
+
}
|
|
282
284
|
|
|
283
|
-
|
|
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
|
+
);
|
|
284
292
|
}
|
|
285
293
|
|
|
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.
|
|
286
298
|
const extName = extData[/** @type {string} */ (`${this.type}Name`)];
|
|
287
|
-
delete extData[/** @type {string} */ (`${this.type}Name`)];
|
|
288
299
|
|
|
300
|
+
// check _a second time_ with the more-accurate extName
|
|
289
301
|
if (this.config.isInstalled(extName)) {
|
|
290
|
-
throw
|
|
291
|
-
`A ${this.type} named
|
|
292
|
-
`Did you mean to update?
|
|
293
|
-
`installed ${this.type}s with
|
|
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".`
|
|
294
306
|
);
|
|
295
307
|
}
|
|
296
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>} */
|
|
297
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
|
+
|
|
298
335
|
await this.config.addExtension(extName, extManifest);
|
|
299
336
|
|
|
300
337
|
// update the if we've changed the local `package.json`
|
|
@@ -303,7 +340,7 @@ class ExtensionCommand {
|
|
|
303
340
|
}
|
|
304
341
|
|
|
305
342
|
// log info for the user
|
|
306
|
-
log(this.
|
|
343
|
+
this.log.info(this.getPostInstallText({extName, extData}));
|
|
307
344
|
|
|
308
345
|
return this.config.installedExtensions;
|
|
309
346
|
}
|
|
@@ -313,25 +350,22 @@ class ExtensionCommand {
|
|
|
313
350
|
*
|
|
314
351
|
* @param {InstallViaNpmArgs} args
|
|
315
352
|
*/
|
|
316
|
-
async installViaNpm
|
|
353
|
+
async installViaNpm({installSpec, pkgName, pkgVer}) {
|
|
317
354
|
const npmSpec = `${pkgName}${pkgVer ? '@' + pkgVer : ''}`;
|
|
318
|
-
const specMsg =
|
|
319
|
-
npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`;
|
|
355
|
+
const specMsg = npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`;
|
|
320
356
|
const msg = `Installing '${installSpec}'${specMsg}`;
|
|
321
357
|
try {
|
|
322
|
-
const pkgJsonData = await spinWith(
|
|
323
|
-
this.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return this.getExtensionFields(pkgJsonData
|
|
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
|
+
|
|
366
|
+
return this.getExtensionFields(pkgJsonData);
|
|
331
367
|
} catch (err) {
|
|
332
|
-
throw
|
|
333
|
-
`Encountered an error when installing package: ${err.message}`
|
|
334
|
-
);
|
|
368
|
+
throw this._createFatalError(`Encountered an error when installing package: ${err.message}`);
|
|
335
369
|
}
|
|
336
370
|
}
|
|
337
371
|
|
|
@@ -343,8 +377,8 @@ class ExtensionCommand {
|
|
|
343
377
|
* @returns {string}
|
|
344
378
|
*/
|
|
345
379
|
// eslint-disable-next-line no-unused-vars
|
|
346
|
-
getPostInstallText
|
|
347
|
-
throw
|
|
380
|
+
getPostInstallText(args) {
|
|
381
|
+
throw this._createFatalError('Must be implemented in final class');
|
|
348
382
|
}
|
|
349
383
|
|
|
350
384
|
/**
|
|
@@ -353,27 +387,61 @@ class ExtensionCommand {
|
|
|
353
387
|
* load as the main driver class, or to be able to detect incompatibilities between driver and
|
|
354
388
|
* appium versions.
|
|
355
389
|
*
|
|
356
|
-
* @param {ExtPackageJson<ExtType>}
|
|
357
|
-
* @param {string} installSpec
|
|
390
|
+
* @param {ExtPackageJson<ExtType>} pkgJson - the package.json data for a driver module, as if it had been straightforwardly 'require'd
|
|
358
391
|
* @returns {ExtensionFields<ExtType>}
|
|
359
392
|
*/
|
|
360
|
-
getExtensionFields
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
`Installed driver did not have an 'appium' section in its ` +
|
|
364
|
-
`package.json file as expected`
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
const {appium, name, version} = pkgJsonData;
|
|
368
|
-
this.validateExtensionFields(appium, installSpec);
|
|
393
|
+
getExtensionFields(pkgJson) {
|
|
394
|
+
const {appium, name, version, peerDependencies} = pkgJson;
|
|
395
|
+
|
|
369
396
|
/** @type {unknown} */
|
|
370
|
-
const result = {
|
|
397
|
+
const result = {
|
|
398
|
+
...appium,
|
|
399
|
+
pkgName: name,
|
|
400
|
+
version,
|
|
401
|
+
appiumVersion: peerDependencies?.appium,
|
|
402
|
+
};
|
|
371
403
|
return /** @type {ExtensionFields<ExtType>} */ (result);
|
|
372
404
|
}
|
|
373
405
|
|
|
374
406
|
/**
|
|
375
|
-
*
|
|
376
|
-
*
|
|
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
|
+
}
|
|
377
445
|
|
|
378
446
|
/**
|
|
379
447
|
* For any `package.json` fields which a particular type of extension requires, validate the
|
|
@@ -384,32 +452,30 @@ class ExtensionCommand {
|
|
|
384
452
|
* @param {string} installSpec - Extension name/spec
|
|
385
453
|
*/
|
|
386
454
|
// eslint-disable-next-line no-unused-vars
|
|
387
|
-
validateExtensionFields
|
|
388
|
-
throw
|
|
455
|
+
validateExtensionFields(extMetadata, installSpec) {
|
|
456
|
+
throw this._createFatalError('Must be implemented in final class');
|
|
389
457
|
}
|
|
390
458
|
|
|
391
459
|
/**
|
|
392
|
-
* 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.
|
|
393
465
|
*
|
|
394
466
|
* @param {UninstallOpts} opts
|
|
395
|
-
* @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)
|
|
396
468
|
*/
|
|
397
|
-
async _uninstall
|
|
469
|
+
async _uninstall({installSpec}) {
|
|
398
470
|
if (!this.config.isInstalled(installSpec)) {
|
|
399
|
-
throw
|
|
471
|
+
throw this._createFatalError(
|
|
400
472
|
`Can't uninstall ${this.type} '${installSpec}'; it is not installed`
|
|
401
473
|
);
|
|
402
474
|
}
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
await this.config.removeExtension(installSpec);
|
|
408
|
-
}
|
|
409
|
-
log(
|
|
410
|
-
this.isJsonOutput,
|
|
411
|
-
`Successfully uninstalled ${this.type} '${installSpec}'`.green
|
|
412
|
-
);
|
|
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);
|
|
413
479
|
return this.config.installedExtensions;
|
|
414
480
|
}
|
|
415
481
|
|
|
@@ -419,12 +485,12 @@ class ExtensionCommand {
|
|
|
419
485
|
* @param {ExtensionUpdateOpts} updateSpec
|
|
420
486
|
* @return {Promise<ExtensionUpdateResult>}
|
|
421
487
|
*/
|
|
422
|
-
async _update
|
|
488
|
+
async _update({installSpec, unsafe}) {
|
|
423
489
|
const shouldUpdateAll = installSpec === UPDATE_ALL;
|
|
424
490
|
// if we're specifically requesting an update for an extension, make sure it's installed
|
|
425
491
|
if (!shouldUpdateAll && !this.config.isInstalled(installSpec)) {
|
|
426
|
-
throw
|
|
427
|
-
`The ${this.type}
|
|
492
|
+
throw this._createFatalError(
|
|
493
|
+
`The ${this.type} "${installSpec}" was not installed, so can't be updated`
|
|
428
494
|
);
|
|
429
495
|
}
|
|
430
496
|
const extsToUpdate = shouldUpdateAll
|
|
@@ -442,18 +508,11 @@ class ExtensionCommand {
|
|
|
442
508
|
|
|
443
509
|
for (const e of extsToUpdate) {
|
|
444
510
|
try {
|
|
445
|
-
await spinWith(
|
|
446
|
-
this.
|
|
447
|
-
|
|
448
|
-
() => {
|
|
449
|
-
if (
|
|
450
|
-
this.config.installedExtensions[e].installType !==
|
|
451
|
-
INSTALL_TYPE_NPM
|
|
452
|
-
) {
|
|
453
|
-
throw new NotUpdatableError();
|
|
454
|
-
}
|
|
511
|
+
await spinWith(this.isJsonOutput, `Checking if ${this.type} '${e}' is updatable`, () => {
|
|
512
|
+
if (this.config.installedExtensions[e].installType !== INSTALL_TYPE_NPM) {
|
|
513
|
+
throw new NotUpdatableError();
|
|
455
514
|
}
|
|
456
|
-
);
|
|
515
|
+
});
|
|
457
516
|
const update = await spinWith(
|
|
458
517
|
this.isJsonOutput,
|
|
459
518
|
`Checking if ${this.type} '${e}' needs an update`,
|
|
@@ -466,16 +525,13 @@ class ExtensionCommand {
|
|
|
466
525
|
}
|
|
467
526
|
);
|
|
468
527
|
if (!unsafe && !update.safeUpdate) {
|
|
469
|
-
throw
|
|
528
|
+
throw this._createFatalError(
|
|
470
529
|
`The ${this.type} '${e}' has a major revision update ` +
|
|
471
530
|
`(${update.current} => ${update.unsafeUpdate}), which could include ` +
|
|
472
531
|
`breaking changes. If you want to apply this update, re-run with --unsafe`
|
|
473
532
|
);
|
|
474
533
|
}
|
|
475
|
-
const updateVer =
|
|
476
|
-
unsafe && update.unsafeUpdate
|
|
477
|
-
? update.unsafeUpdate
|
|
478
|
-
: update.safeUpdate;
|
|
534
|
+
const updateVer = unsafe && update.unsafeUpdate ? update.unsafeUpdate : update.safeUpdate;
|
|
479
535
|
await spinWith(
|
|
480
536
|
this.isJsonOutput,
|
|
481
537
|
`Updating driver '${e}' from ${update.current} to ${updateVer}`,
|
|
@@ -487,28 +543,24 @@ class ExtensionCommand {
|
|
|
487
543
|
}
|
|
488
544
|
}
|
|
489
545
|
|
|
490
|
-
log(
|
|
546
|
+
this.log.info('Update report:');
|
|
547
|
+
|
|
491
548
|
for (const [e, update] of _.toPairs(updates)) {
|
|
492
|
-
log(
|
|
493
|
-
this.isJsonOutput,
|
|
494
|
-
`- ${this.type} ${e} updated: ${update.from} => ${update.to}`.green
|
|
495
|
-
);
|
|
549
|
+
this.log.ok(` - ${this.type} ${e} updated: ${update.from} => ${update.to}`.green);
|
|
496
550
|
}
|
|
551
|
+
|
|
497
552
|
for (const [e, err] of _.toPairs(errors)) {
|
|
498
553
|
if (err instanceof NotUpdatableError) {
|
|
499
|
-
log(
|
|
500
|
-
|
|
501
|
-
`- '${e}' was not installed via npm, so we could not check ` +
|
|
502
|
-
`for updates`.yellow
|
|
554
|
+
this.log.warn(
|
|
555
|
+
` - '${e}' was not installed via npm, so we could not check ` + `for updates`.yellow
|
|
503
556
|
);
|
|
504
557
|
} else if (err instanceof NoUpdatesAvailableError) {
|
|
505
|
-
log(
|
|
558
|
+
this.log.info(` - '${e}' had no updates available`.yellow);
|
|
506
559
|
} else {
|
|
507
560
|
// otherwise, make it pop with red!
|
|
508
|
-
log(
|
|
561
|
+
this.log.error(` - '${e}' failed to update: ${err}`.red);
|
|
509
562
|
}
|
|
510
563
|
}
|
|
511
|
-
|
|
512
564
|
return {updates, errors};
|
|
513
565
|
}
|
|
514
566
|
|
|
@@ -519,15 +571,12 @@ class ExtensionCommand {
|
|
|
519
571
|
* @param {string} ext - name of extension
|
|
520
572
|
* @return {Promise<PossibleUpdates>}
|
|
521
573
|
*/
|
|
522
|
-
async checkForExtensionUpdate
|
|
574
|
+
async checkForExtensionUpdate(ext) {
|
|
523
575
|
// TODO decide how we want to handle beta versions?
|
|
524
576
|
// this is a helper method, 'ext' is assumed to already be installed here, and of the npm
|
|
525
577
|
// install type
|
|
526
578
|
const {version, pkgName} = this.config.installedExtensions[ext];
|
|
527
|
-
let unsafeUpdate = await npm.getLatestVersion(
|
|
528
|
-
this.config.appiumHome,
|
|
529
|
-
pkgName
|
|
530
|
-
);
|
|
579
|
+
let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
|
|
531
580
|
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
|
532
581
|
this.config.appiumHome,
|
|
533
582
|
pkgName,
|
|
@@ -557,9 +606,8 @@ class ExtensionCommand {
|
|
|
557
606
|
* @param {string} version - version string identifier to update extension to
|
|
558
607
|
* @returns {Promise<void>}
|
|
559
608
|
*/
|
|
560
|
-
async updateExtension
|
|
609
|
+
async updateExtension(installSpec, version) {
|
|
561
610
|
const {pkgName} = this.config.installedExtensions[installSpec];
|
|
562
|
-
await fs.rimraf(this.config.getInstallPath(installSpec));
|
|
563
611
|
const extData = await this.installViaNpm({
|
|
564
612
|
installSpec,
|
|
565
613
|
pkgName,
|
|
@@ -580,16 +628,16 @@ class ExtensionCommand {
|
|
|
580
628
|
* @param {RunOptions} opts
|
|
581
629
|
* @return {Promise<RunOutput>}
|
|
582
630
|
*/
|
|
583
|
-
async _run
|
|
584
|
-
if (!
|
|
585
|
-
throw
|
|
631
|
+
async _run({installSpec, scriptName}) {
|
|
632
|
+
if (!this.config.isInstalled(installSpec)) {
|
|
633
|
+
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
|
586
634
|
}
|
|
587
635
|
|
|
588
636
|
const extConfig = this.config.installedExtensions[installSpec];
|
|
589
637
|
|
|
590
638
|
// note: TS cannot understand that _.has() is a type guard
|
|
591
639
|
if (!extConfig.scripts) {
|
|
592
|
-
throw
|
|
640
|
+
throw this._createFatalError(
|
|
593
641
|
`The ${this.type} named '${installSpec}' does not contain the ` +
|
|
594
642
|
`"scripts" field underneath the "appium" field in its package.json`
|
|
595
643
|
);
|
|
@@ -598,13 +646,13 @@ class ExtensionCommand {
|
|
|
598
646
|
const extScripts = extConfig.scripts;
|
|
599
647
|
|
|
600
648
|
if (!_.isPlainObject(extScripts)) {
|
|
601
|
-
throw
|
|
649
|
+
throw this._createFatalError(
|
|
602
650
|
`The ${this.type} named '${installSpec}' "scripts" field must be a plain object`
|
|
603
651
|
);
|
|
604
652
|
}
|
|
605
653
|
|
|
606
654
|
if (!_.has(extScripts, scriptName)) {
|
|
607
|
-
throw
|
|
655
|
+
throw this._createFatalError(
|
|
608
656
|
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
|
609
657
|
);
|
|
610
658
|
}
|
|
@@ -617,20 +665,17 @@ class ExtensionCommand {
|
|
|
617
665
|
|
|
618
666
|
runner.on('stream-line', (line) => {
|
|
619
667
|
output.enqueue(line);
|
|
620
|
-
log(
|
|
668
|
+
this.log.log(line);
|
|
621
669
|
});
|
|
622
670
|
|
|
623
671
|
await runner.start(0);
|
|
624
672
|
|
|
625
673
|
try {
|
|
626
674
|
await runner.join();
|
|
627
|
-
log(
|
|
675
|
+
this.log.ok(`${scriptName} successfully ran`.green);
|
|
628
676
|
return {output: output.getBuff()};
|
|
629
677
|
} catch (err) {
|
|
630
|
-
log(
|
|
631
|
-
this.isJsonOutput,
|
|
632
|
-
`Encountered an error when running '${scriptName}': ${err.message}`.red
|
|
633
|
-
);
|
|
678
|
+
this.log.error(`Encountered an error when running '${scriptName}': ${err.message}`.red);
|
|
634
679
|
return {error: err.message, output: output.getBuff()};
|
|
635
680
|
}
|
|
636
681
|
}
|
|
@@ -658,14 +703,14 @@ export {ExtensionCommand};
|
|
|
658
703
|
*/
|
|
659
704
|
|
|
660
705
|
/**
|
|
661
|
-
* @typedef {import('
|
|
662
|
-
* @typedef {import('
|
|
663
|
-
* @typedef {import('
|
|
706
|
+
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
707
|
+
* @typedef {import('@appium/types').DriverType} DriverType
|
|
708
|
+
* @typedef {import('@appium/types').PluginType} PluginType
|
|
664
709
|
*/
|
|
665
710
|
|
|
666
711
|
/**
|
|
667
712
|
* @template {ExtensionType} ExtType
|
|
668
|
-
* @typedef {import('
|
|
713
|
+
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
|
669
714
|
*/
|
|
670
715
|
|
|
671
716
|
/**
|
|
@@ -675,17 +720,17 @@ export {ExtensionCommand};
|
|
|
675
720
|
|
|
676
721
|
/**
|
|
677
722
|
* @template {ExtensionType} ExtType
|
|
678
|
-
* @typedef {import('
|
|
723
|
+
* @typedef {import('appium/types').ExtMetadata<ExtType>} ExtMetadata
|
|
679
724
|
*/
|
|
680
725
|
|
|
681
726
|
/**
|
|
682
727
|
* @template {ExtensionType} ExtType
|
|
683
|
-
* @typedef {import('
|
|
728
|
+
* @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
|
|
684
729
|
*/
|
|
685
730
|
|
|
686
731
|
/**
|
|
687
732
|
* @template {ExtensionType} ExtType
|
|
688
|
-
* @typedef {import('
|
|
733
|
+
* @typedef {import('appium/types').ExtPackageJson<ExtType>} ExtPackageJson
|
|
689
734
|
*/
|
|
690
735
|
|
|
691
736
|
/**
|
|
@@ -697,7 +742,7 @@ export {ExtensionCommand};
|
|
|
697
742
|
|
|
698
743
|
/**
|
|
699
744
|
* Possible return value for {@linkcode ExtensionCommand.list}
|
|
700
|
-
* @typedef {import('
|
|
745
|
+
* @typedef {import('appium/types').InternalMetadata & ExtensionMetadata} InstalledExtensionListData
|
|
701
746
|
*/
|
|
702
747
|
|
|
703
748
|
/**
|
|
@@ -774,17 +819,23 @@ export {ExtensionCommand};
|
|
|
774
819
|
* Options for {@linkcode ExtensionCommand._install}
|
|
775
820
|
* @typedef InstallArgs
|
|
776
821
|
* @property {string} installSpec - the name or spec of an extension to install
|
|
777
|
-
* @property {import('
|
|
822
|
+
* @property {import('appium/types').InstallType} installType - how to install this extension. One of the INSTALL_TYPES
|
|
778
823
|
* @property {string} [packageName] - for git/github installs, the extension node package name
|
|
779
824
|
*/
|
|
780
825
|
|
|
781
826
|
/**
|
|
782
827
|
* Returned by {@linkcode ExtensionCommand.getExtensionFields}
|
|
783
828
|
* @template {ExtensionType} ExtType
|
|
784
|
-
* @typedef {ExtMetadata<ExtType> & { pkgName: string, version: string } & import('
|
|
829
|
+
* @typedef {ExtMetadata<ExtType> & { pkgName: string, version: string, appiumVersion: string } & import('appium/types').CommonExtMetadata} ExtensionFields
|
|
785
830
|
*/
|
|
786
831
|
|
|
787
832
|
/**
|
|
788
833
|
* @template {ExtensionType} ExtType
|
|
789
834
|
* @typedef {ExtType extends DriverType ? typeof import('../constants').KNOWN_DRIVERS : ExtType extends PluginType ? typeof import('../constants').KNOWN_PLUGINS : never} KnownExtensions
|
|
790
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
|
+
*/
|