appium 3.4.2 → 3.5.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/build/lib/appium.d.ts +3 -0
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +73 -51
- package/build/lib/appium.js.map +1 -1
- package/build/lib/bidi-commands.d.ts.map +1 -1
- package/build/lib/bidi-commands.js +36 -31
- package/build/lib/bidi-commands.js.map +1 -1
- package/build/lib/bootstrap/appium-initializer.d.ts.map +1 -1
- package/build/lib/bootstrap/appium-initializer.js +13 -13
- package/build/lib/bootstrap/appium-initializer.js.map +1 -1
- package/build/lib/bootstrap/appium-main-runner.d.ts.map +1 -1
- package/build/lib/bootstrap/appium-main-runner.js +2 -2
- package/build/lib/bootstrap/appium-main-runner.js.map +1 -1
- package/build/lib/bootstrap/config-file.d.ts.map +1 -1
- package/build/lib/bootstrap/config-file.js +11 -11
- package/build/lib/bootstrap/config-file.js.map +1 -1
- package/build/lib/bootstrap/grid-v3-register.d.ts.map +1 -1
- package/build/lib/bootstrap/grid-v3-register.js +9 -6
- package/build/lib/bootstrap/grid-v3-register.js.map +1 -1
- package/build/lib/bootstrap/main-helpers.d.ts +1 -1
- package/build/lib/bootstrap/main-helpers.d.ts.map +1 -1
- package/build/lib/bootstrap/main-helpers.js +19 -20
- package/build/lib/bootstrap/main-helpers.js.map +1 -1
- package/build/lib/bootstrap/node-helpers.d.ts +1 -1
- package/build/lib/bootstrap/node-helpers.d.ts.map +1 -1
- package/build/lib/bootstrap/node-helpers.js +8 -16
- package/build/lib/bootstrap/node-helpers.js.map +1 -1
- package/build/lib/bootstrap/startup-config.d.ts.map +1 -1
- package/build/lib/bootstrap/startup-config.js +32 -30
- package/build/lib/bootstrap/startup-config.js.map +1 -1
- package/build/lib/cli/args.d.ts +3 -2
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +2 -5
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/driver-command.d.ts +1 -2
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +7 -8
- package/build/lib/cli/driver-command.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +2 -2
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +105 -71
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/extension.d.ts.map +1 -1
- package/build/lib/cli/extension.js.map +1 -1
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +21 -14
- package/build/lib/cli/parser.js.map +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 +5 -5
- package/build/lib/cli/plugin-command.js.map +1 -1
- package/build/lib/cli/setup-command.d.ts +1 -1
- package/build/lib/cli/setup-command.d.ts.map +1 -1
- package/build/lib/cli/setup-command.js +14 -19
- package/build/lib/cli/setup-command.js.map +1 -1
- package/build/lib/cli/utils.d.ts.map +1 -1
- package/build/lib/cli/utils.js +3 -2
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/doctor/doctor.d.ts +0 -1
- package/build/lib/doctor/doctor.d.ts.map +1 -1
- package/build/lib/doctor/doctor.js +12 -17
- package/build/lib/doctor/doctor.js.map +1 -1
- package/build/lib/extension/driver-config.d.ts.map +1 -1
- package/build/lib/extension/driver-config.js +11 -12
- package/build/lib/extension/driver-config.js.map +1 -1
- package/build/lib/extension/extension-config.d.ts.map +1 -1
- package/build/lib/extension/extension-config.js +30 -30
- package/build/lib/extension/extension-config.js.map +1 -1
- package/build/lib/extension/index.d.ts.map +1 -1
- package/build/lib/extension/index.js +28 -41
- package/build/lib/extension/index.js.map +1 -1
- package/build/lib/extension/manifest.d.ts +3 -2
- package/build/lib/extension/manifest.d.ts.map +1 -1
- package/build/lib/extension/manifest.js +20 -24
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/lib/extension/plugin-config.d.ts.map +1 -1
- package/build/lib/extension/plugin-config.js +7 -9
- package/build/lib/extension/plugin-config.js.map +1 -1
- package/build/lib/helpers/build.d.ts +0 -1
- package/build/lib/helpers/build.d.ts.map +1 -1
- package/build/lib/helpers/build.js +2 -4
- package/build/lib/helpers/build.js.map +1 -1
- package/build/lib/helpers/capability.d.ts.map +1 -1
- package/build/lib/helpers/capability.js +27 -22
- package/build/lib/helpers/capability.js.map +1 -1
- package/build/lib/helpers/network.d.ts.map +1 -1
- package/build/lib/helpers/network.js +4 -3
- package/build/lib/helpers/network.js.map +1 -1
- package/build/lib/insecure-features.d.ts.map +1 -1
- package/build/lib/insecure-features.js +9 -13
- package/build/lib/insecure-features.js.map +1 -1
- package/build/lib/inspector-commands.d.ts.map +1 -1
- package/build/lib/inspector-commands.js +29 -28
- package/build/lib/inspector-commands.js.map +1 -1
- package/build/lib/logsink.d.ts.map +1 -1
- package/build/lib/logsink.js +8 -7
- package/build/lib/logsink.js.map +1 -1
- package/build/lib/schema/arg-spec.d.ts.map +1 -1
- package/build/lib/schema/arg-spec.js +5 -8
- package/build/lib/schema/arg-spec.js.map +1 -1
- package/build/lib/schema/cli-args.d.ts.map +1 -1
- package/build/lib/schema/cli-args.js +20 -19
- package/build/lib/schema/cli-args.js.map +1 -1
- package/build/lib/schema/cli-transformers.d.ts.map +1 -1
- package/build/lib/schema/cli-transformers.js.map +1 -1
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +28 -21
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils/hash.d.ts +5 -0
- package/build/lib/utils/hash.d.ts.map +1 -0
- package/build/lib/{utils.js → utils/hash.js} +3 -21
- package/build/lib/utils/hash.js.map +1 -0
- package/build/lib/utils/index.d.ts +8 -0
- package/build/lib/utils/index.d.ts.map +1 -0
- package/build/lib/utils/index.js +31 -0
- package/build/lib/utils/index.js.map +1 -0
- package/build/lib/utils/is-package-changed.d.ts +16 -0
- package/build/lib/utils/is-package-changed.d.ts.map +1 -0
- package/build/lib/utils/is-package-changed.js +62 -0
- package/build/lib/utils/is-package-changed.js.map +1 -0
- package/build/lib/utils/object.d.ts +120 -0
- package/build/lib/utils/object.d.ts.map +1 -0
- package/build/lib/utils/object.js +288 -0
- package/build/lib/utils/object.js.map +1 -0
- package/build/lib/{extension → utils}/package-changed.d.ts +0 -1
- package/build/lib/utils/package-changed.d.ts.map +1 -0
- package/build/lib/{extension → utils}/package-changed.js +3 -4
- package/build/lib/utils/package-changed.js.map +1 -0
- package/build/lib/utils/package-json.d.ts +9 -0
- package/build/lib/utils/package-json.d.ts.map +1 -0
- package/build/lib/utils/package-json.js +32 -0
- package/build/lib/utils/package-json.js.map +1 -0
- package/build/lib/utils/resolve-from.d.ts +11 -0
- package/build/lib/utils/resolve-from.d.ts.map +1 -0
- package/build/lib/utils/resolve-from.js +41 -0
- package/build/lib/utils/resolve-from.js.map +1 -0
- package/build/types/cli.d.ts.map +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/manifest/base.d.ts.map +1 -1
- package/build/types/manifest/v3.d.ts.map +1 -1
- package/build/types/manifest/v4.d.ts.map +1 -1
- package/lib/appium.ts +96 -61
- package/lib/bidi-commands.ts +107 -61
- package/lib/bootstrap/appium-initializer.ts +27 -19
- package/lib/bootstrap/appium-main-runner.ts +8 -4
- package/lib/bootstrap/config-file.ts +33 -16
- package/lib/bootstrap/grid-v3-register.ts +26 -19
- package/lib/bootstrap/main-helpers.ts +25 -19
- package/lib/bootstrap/node-helpers.ts +20 -24
- package/lib/bootstrap/startup-config.ts +42 -39
- package/lib/cli/args.ts +3 -4
- package/lib/cli/driver-command.ts +19 -13
- package/lib/cli/extension-command.ts +214 -149
- package/lib/cli/extension.ts +4 -6
- package/lib/cli/parser.ts +32 -30
- package/lib/cli/plugin-command.ts +20 -9
- package/lib/cli/setup-command.ts +54 -39
- package/lib/cli/utils.ts +4 -3
- package/lib/doctor/doctor.ts +15 -17
- package/lib/extension/driver-config.ts +22 -17
- package/lib/extension/extension-config.ts +76 -69
- package/lib/extension/index.ts +60 -51
- package/lib/extension/manifest.ts +35 -33
- package/lib/extension/plugin-config.ts +15 -10
- package/lib/helpers/build.ts +6 -5
- package/lib/helpers/capability.ts +40 -35
- package/lib/helpers/network.ts +6 -5
- package/lib/insecure-features.ts +16 -14
- package/lib/inspector-commands.ts +72 -40
- package/lib/logsink.ts +22 -26
- package/lib/schema/arg-spec.ts +9 -8
- package/lib/schema/cli-args-guards.ts +4 -4
- package/lib/schema/cli-args.ts +25 -21
- package/lib/schema/cli-transformers.ts +6 -2
- package/lib/schema/format-errors.ts +1 -1
- package/lib/schema/schema.ts +52 -38
- package/lib/{utils.ts → utils/hash.ts} +2 -19
- package/lib/utils/index.ts +23 -0
- package/lib/utils/is-package-changed.ts +78 -0
- package/lib/utils/object.ts +312 -0
- package/lib/{extension → utils}/package-changed.ts +6 -7
- package/lib/utils/package-json.ts +36 -0
- package/lib/utils/resolve-from.ts +40 -0
- package/package.json +16 -19
- package/scripts/autoinstall-extensions.js +8 -13
- package/tsconfig.json +8 -2
- package/types/cli.ts +18 -18
- package/types/index.ts +2 -2
- package/types/manifest/base.ts +2 -2
- package/types/manifest/v3.ts +2 -2
- package/types/manifest/v4.ts +2 -2
- package/build/lib/extension/package-changed.d.ts.map +0 -1
- package/build/lib/extension/package-changed.js.map +0 -1
- package/build/lib/utils.d.ts +0 -13
- package/build/lib/utils.d.ts.map +0 -1
- package/build/lib/utils.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import _ from 'lodash';
|
|
1
|
+
import {asyncfilter, asyncmap} from 'asyncbox';
|
|
3
2
|
import path from 'node:path';
|
|
3
|
+
import {console, env, fs, npm, system, util} from '@appium/support';
|
|
4
4
|
import type {AppiumLogger, ExtensionType, IDoctorCheck} from '@appium/types';
|
|
5
5
|
import type {
|
|
6
6
|
ExtInstallReceipt as AppiumExtInstallReceipt,
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
} from 'appium/types';
|
|
13
13
|
import type {PackageJson} from 'type-fest';
|
|
14
14
|
import type {ExtensionConfig as BaseExtensionConfig} from '../extension/extension-config';
|
|
15
|
-
import {npm, util, env, console, fs, system} from '@appium/support';
|
|
16
15
|
import {spinWith, RingBuffer} from './utils';
|
|
17
16
|
import {
|
|
18
17
|
INSTALL_TYPE_NPM,
|
|
@@ -22,12 +21,11 @@ import {
|
|
|
22
21
|
INSTALL_TYPE_DEV,
|
|
23
22
|
} from '../extension/extension-config';
|
|
24
23
|
import {SubProcess} from 'teen_process';
|
|
25
|
-
import {packageDidChange} from '../extension/package-changed';
|
|
26
24
|
import {spawn} from 'node:child_process';
|
|
27
25
|
import {inspect} from 'node:util';
|
|
28
26
|
import {pathToFileURL} from 'node:url';
|
|
29
27
|
import {Doctor, EXIT_CODE as DOCTOR_EXIT_CODE} from '../doctor/doctor';
|
|
30
|
-
import {
|
|
28
|
+
import {appiumPackageRoot, compact, npmPackage, packageDidChange} from '../utils';
|
|
31
29
|
import * as semver from 'semver';
|
|
32
30
|
|
|
33
31
|
const UPDATE_ALL = 'installed';
|
|
@@ -40,13 +38,15 @@ export type ExtensionCommandOptions<ExtType extends ExtensionType = ExtensionTyp
|
|
|
40
38
|
config: ExtensionConfig<ExtType>;
|
|
41
39
|
json: boolean;
|
|
42
40
|
};
|
|
43
|
-
export type ExtensionConfig<ExtType extends ExtensionType = ExtensionType> =
|
|
41
|
+
export type ExtensionConfig<ExtType extends ExtensionType = ExtensionType> =
|
|
42
|
+
BaseExtensionConfig<ExtType>;
|
|
44
43
|
|
|
45
44
|
export type ExtRecord<ExtType extends ExtensionType = ExtensionType> = AppiumExtRecord<ExtType>;
|
|
46
45
|
|
|
47
46
|
export type ExtMetadata<ExtType extends ExtensionType = ExtensionType> = AppiumExtMetadata<ExtType>;
|
|
48
47
|
export type ExtManifest<ExtType extends ExtensionType = ExtensionType> = AppiumExtManifest<ExtType>;
|
|
49
|
-
export type ExtPackageJson<ExtType extends ExtensionType = ExtensionType> =
|
|
48
|
+
export type ExtPackageJson<ExtType extends ExtensionType = ExtensionType> =
|
|
49
|
+
AppiumExtPackageJson<ExtType>;
|
|
50
50
|
export type ExtInstallReceipt<ExtType extends ExtensionType = ExtensionType> =
|
|
51
51
|
AppiumExtInstallReceipt<ExtType>;
|
|
52
52
|
/**
|
|
@@ -69,8 +69,8 @@ export type ExtensionListData<ExtType extends ExtensionType = ExtensionType> = P
|
|
|
69
69
|
> &
|
|
70
70
|
Partial<ExtensionListMetadata>;
|
|
71
71
|
|
|
72
|
-
export type InstalledExtensionListData<ExtType extends ExtensionType = ExtensionType> =
|
|
73
|
-
ExtensionListMetadata;
|
|
72
|
+
export type InstalledExtensionListData<ExtType extends ExtensionType = ExtensionType> =
|
|
73
|
+
ExtManifest<ExtType> & ExtensionListMetadata;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Return value of {@linkcode ExtensionCliCommand.list}.
|
|
@@ -93,7 +93,10 @@ export type PostInstallText = string;
|
|
|
93
93
|
/**
|
|
94
94
|
* Return value of {@linkcode ExtensionCliCommand._update}.
|
|
95
95
|
*/
|
|
96
|
-
export type ExtensionUpdateResult = {
|
|
96
|
+
export type ExtensionUpdateResult = {
|
|
97
|
+
errors: Record<string, Error>;
|
|
98
|
+
updates: Record<string, UpdateReport>;
|
|
99
|
+
};
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* Used by {@linkcode ExtensionCliCommand.getPostInstallText}
|
|
@@ -177,7 +180,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
177
180
|
/**
|
|
178
181
|
* {@linkcode Record} of official plugins or drivers.
|
|
179
182
|
*/
|
|
180
|
-
protected knownExtensions
|
|
183
|
+
protected knownExtensions!: Record<string, string>;
|
|
181
184
|
|
|
182
185
|
/**
|
|
183
186
|
* If `true`, command output has been requested as JSON.
|
|
@@ -210,12 +213,12 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
210
213
|
* @returns result of the executed extension subcommand
|
|
211
214
|
*/
|
|
212
215
|
async execute(args: Record<string, any>): Promise<unknown> {
|
|
213
|
-
const cmd = args[`${this.type}Command`];
|
|
214
|
-
|
|
216
|
+
const cmd = args[`${this.type}Command`] as string;
|
|
217
|
+
const handler = (this as Record<string, unknown>)[cmd];
|
|
218
|
+
if (typeof handler !== 'function') {
|
|
215
219
|
throw this._createFatalError(`Cannot handle ${this.type} command ${cmd}`);
|
|
216
220
|
}
|
|
217
|
-
|
|
218
|
-
return await executeCmd(args);
|
|
221
|
+
return await handler.call(this, args);
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
/**
|
|
@@ -263,7 +266,11 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
263
266
|
* Build the initial list data structure from installed and known extensions
|
|
264
267
|
*
|
|
265
268
|
*/
|
|
266
|
-
protected async _install({
|
|
269
|
+
protected async _install({
|
|
270
|
+
installSpec,
|
|
271
|
+
installType,
|
|
272
|
+
packageName,
|
|
273
|
+
}: InstallOpts): Promise<Record<string, any>> {
|
|
267
274
|
if (packageName && [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)) {
|
|
268
275
|
throw this._createFatalError(`When using --source=${installType}, cannot also use --package`);
|
|
269
276
|
}
|
|
@@ -286,7 +293,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
286
293
|
if (installSpec.split('/').length !== 2) {
|
|
287
294
|
throw this._createFatalError(
|
|
288
295
|
`Github ${this.type} spec ${installSpec} appeared to be invalid; ` +
|
|
289
|
-
'it should be of the form <org>/<repo>'
|
|
296
|
+
'it should be of the form <org>/<repo>',
|
|
290
297
|
);
|
|
291
298
|
}
|
|
292
299
|
installViaNpmOpts = {
|
|
@@ -335,7 +342,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
335
342
|
// dereference the actual npm package ('appiupm-xcuitest-driver'), so
|
|
336
343
|
// check it exists and get the correct package
|
|
337
344
|
const knownNames = Object.keys(this.knownExtensions);
|
|
338
|
-
if (!
|
|
345
|
+
if (!knownNames.includes(name)) {
|
|
339
346
|
const msg =
|
|
340
347
|
`Could not resolve ${this.type}; are you sure it's in the list ` +
|
|
341
348
|
`of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
|
|
@@ -356,7 +363,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
356
363
|
throw this._createFatalError(
|
|
357
364
|
`A ${this.type} named "${probableExtName}" is already installed. ` +
|
|
358
365
|
`Did you mean to update? Run "appium ${this.type} update". See ` +
|
|
359
|
-
`installed ${this.type}s with "appium ${this.type} list --installed"
|
|
366
|
+
`installed ${this.type}s with "appium ${this.type} list --installed".`,
|
|
360
367
|
);
|
|
361
368
|
}
|
|
362
369
|
|
|
@@ -366,14 +373,17 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
366
373
|
|
|
367
374
|
// this _should_ be the same as `probablyExtName` as the one derived above unless
|
|
368
375
|
// install type is local.
|
|
369
|
-
const extName =
|
|
376
|
+
const extName =
|
|
377
|
+
this.type === 'driver'
|
|
378
|
+
? (receipt as ExtInstallReceipt<'driver'>).driverName
|
|
379
|
+
: (receipt as ExtInstallReceipt<'plugin'>).pluginName;
|
|
370
380
|
|
|
371
381
|
// check _a second time_ with the more-accurate extName
|
|
372
382
|
if (this.config.isInstalled(extName)) {
|
|
373
383
|
throw this._createFatalError(
|
|
374
384
|
`A ${this.type} named "${extName}" is already installed. ` +
|
|
375
385
|
`Did you mean to update? Run "appium ${this.type} update". See ` +
|
|
376
|
-
`installed ${this.type}s with "appium ${this.type} list --installed"
|
|
386
|
+
`installed ${this.type}s with "appium ${this.type} list --installed".`,
|
|
377
387
|
);
|
|
378
388
|
}
|
|
379
389
|
|
|
@@ -381,7 +391,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
381
391
|
// so that's why it's being removed here.
|
|
382
392
|
const extManifest = receiptToManifest(receipt);
|
|
383
393
|
|
|
384
|
-
const [errors, warnings] = await
|
|
394
|
+
const [errors, warnings] = await Promise.all([
|
|
385
395
|
this.config.getProblems(extName, extManifest as any),
|
|
386
396
|
this.config.getWarnings(extName, extManifest as any),
|
|
387
397
|
]);
|
|
@@ -389,15 +399,15 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
389
399
|
const warningMap = new Map([[extName, warnings]]);
|
|
390
400
|
const {errorSummaries, warningSummaries} = this.config.getValidationResultSummaries(
|
|
391
401
|
errorMap,
|
|
392
|
-
warningMap
|
|
402
|
+
warningMap,
|
|
393
403
|
);
|
|
394
404
|
|
|
395
|
-
if (!
|
|
405
|
+
if (!util.isEmpty(errorSummaries)) {
|
|
396
406
|
throw this._createFatalError(errorSummaries.join('\n'));
|
|
397
407
|
}
|
|
398
408
|
|
|
399
409
|
// note that we won't show any warnings if there were errors.
|
|
400
|
-
if (!
|
|
410
|
+
if (!util.isEmpty(warningSummaries)) {
|
|
401
411
|
this.log.warn(warningSummaries.join('\n'));
|
|
402
412
|
}
|
|
403
413
|
|
|
@@ -410,7 +420,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
410
420
|
|
|
411
421
|
// log info for the user
|
|
412
422
|
this.log.info(
|
|
413
|
-
this.getPostInstallText({extName, extData: receipt as unknown as ExtInstallReceipt<ExtType>})
|
|
423
|
+
this.getPostInstallText({extName, extData: receipt as unknown as ExtInstallReceipt<ExtType>}),
|
|
414
424
|
);
|
|
415
425
|
|
|
416
426
|
return this.config.installedExtensions;
|
|
@@ -428,7 +438,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
428
438
|
protected async _uninstall({installSpec}: UninstallOpts): Promise<Record<string, any>> {
|
|
429
439
|
if (!this.config.isInstalled(installSpec)) {
|
|
430
440
|
throw this._createFatalError(
|
|
431
|
-
`Can't uninstall ${this.type} '${installSpec}'; it is not installed
|
|
441
|
+
`Can't uninstall ${this.type} '${installSpec}'; it is not installed`,
|
|
432
442
|
);
|
|
433
443
|
}
|
|
434
444
|
const extRecord = this.config.installedExtensions[installSpec];
|
|
@@ -441,7 +451,9 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
441
451
|
await npm.uninstallPackage(this.config.appiumHome, pkgName);
|
|
442
452
|
});
|
|
443
453
|
await this.config.removeExtension(installSpec);
|
|
444
|
-
this.log.ok(
|
|
454
|
+
this.log.ok(
|
|
455
|
+
console.styleText('green', `Successfully uninstalled ${this.type} '${installSpec}'`),
|
|
456
|
+
);
|
|
445
457
|
return this.config.installedExtensions;
|
|
446
458
|
}
|
|
447
459
|
|
|
@@ -449,12 +461,15 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
449
461
|
* Attempt to update one or more drivers using NPM
|
|
450
462
|
*
|
|
451
463
|
*/
|
|
452
|
-
protected async _update({
|
|
464
|
+
protected async _update({
|
|
465
|
+
installSpec,
|
|
466
|
+
unsafe,
|
|
467
|
+
}: ExtensionUpdateOpts): Promise<ExtensionUpdateResult> {
|
|
453
468
|
const shouldUpdateAll = installSpec === UPDATE_ALL;
|
|
454
469
|
// if we're specifically requesting an update for an extension, make sure it's installed
|
|
455
470
|
if (!shouldUpdateAll && !this.config.isInstalled(installSpec)) {
|
|
456
471
|
throw this._createFatalError(
|
|
457
|
-
`The ${this.type} "${installSpec}" was not installed, so can't be updated
|
|
472
|
+
`The ${this.type} "${installSpec}" was not installed, so can't be updated`,
|
|
458
473
|
);
|
|
459
474
|
}
|
|
460
475
|
const extsToUpdate = shouldUpdateAll
|
|
@@ -484,13 +499,13 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
484
499
|
throw new NoUpdatesAvailableError();
|
|
485
500
|
}
|
|
486
501
|
return update;
|
|
487
|
-
}
|
|
502
|
+
},
|
|
488
503
|
);
|
|
489
504
|
if (!unsafe && !update.safeUpdate) {
|
|
490
505
|
throw this._createFatalError(
|
|
491
506
|
`The ${this.type} '${e}' has a major revision update ` +
|
|
492
507
|
`(${update.current} => ${update.unsafeUpdate}), which could include ` +
|
|
493
|
-
`breaking changes. If you want to apply this update, re-run with --unsafe
|
|
508
|
+
`breaking changes. If you want to apply this update, re-run with --unsafe`,
|
|
494
509
|
);
|
|
495
510
|
}
|
|
496
511
|
const updateVer = unsafe && update.unsafeUpdate ? update.unsafeUpdate : update.safeUpdate;
|
|
@@ -500,37 +515,43 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
500
515
|
await spinWith(
|
|
501
516
|
this.isJsonOutput,
|
|
502
517
|
`Updating ${this.type} '${e}' from ${update.current} to ${updateVer}`,
|
|
503
|
-
async () => await this.updateExtension(e, updateVer)
|
|
518
|
+
async () => await this.updateExtension(e, updateVer),
|
|
504
519
|
);
|
|
505
520
|
// if we're doing a safe update, but an unsafe update is also available, let the user know
|
|
506
521
|
if (!unsafe && update.unsafeUpdate) {
|
|
507
|
-
const newMajorUpdateMsg =
|
|
522
|
+
const newMajorUpdateMsg =
|
|
523
|
+
`A newer major version ${update.unsafeUpdate} ` +
|
|
508
524
|
`is available for ${this.type} '${e}', which could include breaking changes. ` +
|
|
509
525
|
`If you want to apply this update, re-run with --unsafe`;
|
|
510
|
-
this.log.info(
|
|
526
|
+
this.log.info(console.styleText('yellow', newMajorUpdateMsg));
|
|
511
527
|
}
|
|
512
528
|
updates[e] = {from: update.current, to: updateVer};
|
|
513
529
|
} catch (err) {
|
|
514
|
-
errors[e] = err;
|
|
530
|
+
errors[e] = err instanceof Error ? err : new Error(String(err));
|
|
515
531
|
}
|
|
516
532
|
}
|
|
517
533
|
|
|
518
534
|
this.log.info('Update report:');
|
|
519
535
|
|
|
520
|
-
for (const [e, update] of
|
|
521
|
-
this.log.ok(
|
|
536
|
+
for (const [e, update] of Object.entries(updates)) {
|
|
537
|
+
this.log.ok(
|
|
538
|
+
console.styleText('green', ` - ${this.type} ${e} updated: ${update.from} => ${update.to}`),
|
|
539
|
+
);
|
|
522
540
|
}
|
|
523
541
|
|
|
524
|
-
for (const [e, err] of
|
|
542
|
+
for (const [e, err] of Object.entries(errors)) {
|
|
525
543
|
if (err instanceof NotUpdatableError) {
|
|
526
544
|
this.log.warn(
|
|
527
|
-
|
|
545
|
+
console.styleText(
|
|
546
|
+
'yellow',
|
|
547
|
+
` - '${e}' was not installed via npm, so we could not check for updates`,
|
|
548
|
+
),
|
|
528
549
|
);
|
|
529
550
|
} else if (err instanceof NoUpdatesAvailableError) {
|
|
530
|
-
this.log.info(` - '${e}' had no updates available
|
|
551
|
+
this.log.info(console.styleText('yellow', ` - '${e}' had no updates available`));
|
|
531
552
|
} else {
|
|
532
553
|
// otherwise, make it pop with red!
|
|
533
|
-
this.log.error(` - '${e}' failed to update: ${err}
|
|
554
|
+
this.log.error(console.styleText('red', ` - '${e}' failed to update: ${err}`));
|
|
534
555
|
}
|
|
535
556
|
}
|
|
536
557
|
return {updates, errors};
|
|
@@ -551,7 +572,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
551
572
|
let safeUpdate = await npm.getLatestSafeUpgradeVersion(
|
|
552
573
|
this.config.appiumHome,
|
|
553
574
|
pkgName,
|
|
554
|
-
version
|
|
575
|
+
version,
|
|
555
576
|
);
|
|
556
577
|
if (unsafeUpdate !== null && !util.compareVersions(unsafeUpdate, '>', version)) {
|
|
557
578
|
// the latest version is not greater than the current version, so there's no possible update
|
|
@@ -583,41 +604,42 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
583
604
|
|
|
584
605
|
const moduleRoot = this.config.getInstallPath(installSpec);
|
|
585
606
|
const packageJsonPath = path.join(moduleRoot, 'package.json');
|
|
586
|
-
if (!await fs.exists(packageJsonPath)) {
|
|
607
|
+
if (!(await fs.exists(packageJsonPath))) {
|
|
587
608
|
throw this._createFatalError(
|
|
588
|
-
`No package.json could be found for "${installSpec}" ${this.type}
|
|
609
|
+
`No package.json could be found for "${installSpec}" ${this.type}`,
|
|
589
610
|
);
|
|
590
611
|
}
|
|
591
612
|
let doctorSpec: {checks: string[]} | undefined;
|
|
592
613
|
try {
|
|
593
614
|
doctorSpec = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')).appium?.doctor;
|
|
594
615
|
} catch (e) {
|
|
616
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
595
617
|
throw this._createFatalError(
|
|
596
|
-
`The manifest at '${packageJsonPath}' cannot be parsed: ${
|
|
618
|
+
`The manifest at '${packageJsonPath}' cannot be parsed: ${message}`,
|
|
597
619
|
);
|
|
598
620
|
}
|
|
599
621
|
if (!doctorSpec) {
|
|
600
622
|
this.log.info(`The ${this.type} "${installSpec}" does not export any doctor checks`);
|
|
601
623
|
return 0;
|
|
602
624
|
}
|
|
603
|
-
if (!
|
|
625
|
+
if (!util.isPlainObject(doctorSpec) || !Array.isArray(doctorSpec.checks)) {
|
|
604
626
|
throw this._createFatalError(
|
|
605
627
|
`The 'doctor' entry in the package manifest '${packageJsonPath}' must be a proper object ` +
|
|
606
|
-
|
|
628
|
+
`containing the 'checks' key with the array of script paths`,
|
|
607
629
|
);
|
|
608
630
|
}
|
|
609
631
|
const paths: string[] = doctorSpec.checks
|
|
610
632
|
.map((p) => {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
633
|
+
const scriptPath = path.resolve(moduleRoot, p);
|
|
634
|
+
if (!path.normalize(scriptPath).startsWith(path.normalize(moduleRoot))) {
|
|
635
|
+
this.log.error(
|
|
636
|
+
`The doctor check script '${p}' from the package manifest '${packageJsonPath}' must be located ` +
|
|
637
|
+
`in the '${moduleRoot}' root folder. It will be skipped`,
|
|
638
|
+
);
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
return scriptPath;
|
|
642
|
+
})
|
|
621
643
|
.filter((p): p is string => Boolean(p));
|
|
622
644
|
const loadChecksPromises: Promise<unknown>[] = [];
|
|
623
645
|
for (const p of paths) {
|
|
@@ -627,23 +649,27 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
627
649
|
try {
|
|
628
650
|
return await import(scriptPath);
|
|
629
651
|
} catch (e) {
|
|
630
|
-
|
|
652
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
653
|
+
this.log.warn(`Unable to load doctor checks from '${p}': ${message}`);
|
|
631
654
|
}
|
|
632
655
|
})();
|
|
633
656
|
loadChecksPromises.push(promise);
|
|
634
657
|
}
|
|
635
|
-
const isDoctorCheck = (x) =>
|
|
636
|
-
['diagnose', 'fix', 'hasAutofix', 'isOptional'].every(
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
658
|
+
const isDoctorCheck = (x: unknown): x is IDoctorCheck =>
|
|
659
|
+
['diagnose', 'fix', 'hasAutofix', 'isOptional'].every(
|
|
660
|
+
(method) => typeof (x as IDoctorCheck)?.[method as keyof IDoctorCheck] === 'function',
|
|
661
|
+
);
|
|
662
|
+
const checks: IDoctorCheck[] = (await Promise.all(loadChecksPromises))
|
|
663
|
+
.filter((mod): mod is object => Boolean(mod))
|
|
664
|
+
.flatMap((mod) => Object.values(mod))
|
|
665
|
+
.filter(isDoctorCheck);
|
|
666
|
+
if (util.isEmpty(checks)) {
|
|
641
667
|
this.log.info(`The ${this.type} "${installSpec}" exports no valid doctor checks`);
|
|
642
668
|
return 0;
|
|
643
669
|
}
|
|
644
670
|
this.log.debug(
|
|
645
671
|
`Running ${util.pluralize('doctor check', checks.length, true)} ` +
|
|
646
|
-
|
|
672
|
+
`for the "${installSpec}" ${this.type}`,
|
|
647
673
|
);
|
|
648
674
|
const exitCode = await new Doctor(checks).run();
|
|
649
675
|
if (exitCode !== DOCTOR_EXIT_CODE.SUCCESS) {
|
|
@@ -677,39 +703,41 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
677
703
|
if (!('scripts' in extConfig)) {
|
|
678
704
|
throw this._createFatalError(
|
|
679
705
|
`The ${this.type} named '${installSpec}' does not contain the ` +
|
|
680
|
-
`"scripts" field underneath the "appium" field in its package.json
|
|
706
|
+
`"scripts" field underneath the "appium" field in its package.json`,
|
|
681
707
|
);
|
|
682
708
|
}
|
|
683
709
|
|
|
684
710
|
const extScripts = extConfig.scripts;
|
|
685
711
|
|
|
686
|
-
if (!extScripts || !
|
|
712
|
+
if (!extScripts || !util.isPlainObject(extScripts)) {
|
|
687
713
|
throw this._createFatalError(
|
|
688
|
-
`The ${this.type} named '${installSpec}' "scripts" field must be a plain object
|
|
714
|
+
`The ${this.type} named '${installSpec}' "scripts" field must be a plain object`,
|
|
689
715
|
);
|
|
690
716
|
}
|
|
691
717
|
|
|
692
718
|
if (!scriptName) {
|
|
693
|
-
const allScripts =
|
|
719
|
+
const allScripts = Object.entries(extScripts as Record<string, string>);
|
|
694
720
|
const root = this.config.getInstallPath(installSpec);
|
|
695
|
-
const existingScripts = await
|
|
721
|
+
const existingScripts = await asyncfilter(
|
|
696
722
|
allScripts,
|
|
697
|
-
async ([, p]) => await fs.exists(path.join(root, p))
|
|
723
|
+
async ([, p]) => await fs.exists(path.join(root, p)),
|
|
698
724
|
);
|
|
699
|
-
if (
|
|
725
|
+
if (util.isEmpty(existingScripts)) {
|
|
700
726
|
this.log.info(`The ${this.type} named '${installSpec}' does not contain any scripts`);
|
|
701
727
|
} else {
|
|
702
|
-
this.log.info(
|
|
703
|
-
|
|
728
|
+
this.log.info(
|
|
729
|
+
`The ${this.type} named '${installSpec}' contains ` +
|
|
730
|
+
`${util.pluralize('script', existingScripts.length, true)}:`,
|
|
731
|
+
);
|
|
704
732
|
existingScripts.forEach(([name]) => this.log.info(` - ${name}`));
|
|
705
733
|
}
|
|
706
|
-
this.log.ok(
|
|
734
|
+
this.log.ok(console.styleText('green', 'Successfully retrieved the list of scripts'));
|
|
707
735
|
return {};
|
|
708
736
|
}
|
|
709
737
|
|
|
710
738
|
if (!(scriptName in extScripts)) {
|
|
711
739
|
throw this._createFatalError(
|
|
712
|
-
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'
|
|
740
|
+
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`,
|
|
713
741
|
);
|
|
714
742
|
}
|
|
715
743
|
|
|
@@ -718,7 +746,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
718
746
|
const normalizedScriptPath = path.normalize(path.resolve(moduleRoot, scriptPath));
|
|
719
747
|
if (!normalizedScriptPath.startsWith(path.normalize(moduleRoot))) {
|
|
720
748
|
throw this._createFatalError(
|
|
721
|
-
`The '${scriptPath}' script must be located in the '${moduleRoot}' folder
|
|
749
|
+
`The '${scriptPath}' script must be located in the '${moduleRoot}' folder`,
|
|
722
750
|
);
|
|
723
751
|
}
|
|
724
752
|
|
|
@@ -738,16 +766,17 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
738
766
|
|
|
739
767
|
try {
|
|
740
768
|
await runner.join();
|
|
741
|
-
this.log.ok(`${scriptName} successfully ran
|
|
769
|
+
this.log.ok(console.styleText('green', `${scriptName} successfully ran`));
|
|
742
770
|
return {output: output.getBuff()};
|
|
743
771
|
} catch (err) {
|
|
744
|
-
const
|
|
772
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
773
|
+
const message = `Encountered an error when running '${scriptName}': ${errMessage}`;
|
|
745
774
|
throw this._createFatalError(message);
|
|
746
775
|
}
|
|
747
776
|
}
|
|
748
777
|
|
|
749
778
|
try {
|
|
750
|
-
await new
|
|
779
|
+
await new Promise<void>((resolve, reject) => {
|
|
751
780
|
this._runUnbuffered(moduleRoot, scriptPath, extraArgs)
|
|
752
781
|
.once('error', (err) => {
|
|
753
782
|
// generally this is of the "I can't find the script" variety.
|
|
@@ -763,10 +792,11 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
763
792
|
}
|
|
764
793
|
});
|
|
765
794
|
});
|
|
766
|
-
this.log.ok(`${scriptName} successfully ran
|
|
795
|
+
this.log.ok(console.styleText('green', `${scriptName} successfully ran`));
|
|
767
796
|
return {};
|
|
768
797
|
} catch (err) {
|
|
769
|
-
const
|
|
798
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
799
|
+
const message = `Encountered an error when running '${scriptName}': ${errMessage}`;
|
|
770
800
|
throw this._createFatalError(message);
|
|
771
801
|
}
|
|
772
802
|
}
|
|
@@ -774,7 +804,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
774
804
|
private _buildListData(showInstalled: boolean): ExtensionList {
|
|
775
805
|
const installedNames = Object.keys(this.config.installedExtensions);
|
|
776
806
|
const knownNames = Object.keys(this.knownExtensions);
|
|
777
|
-
return [...installedNames, ...knownNames].reduce((acc, name) => {
|
|
807
|
+
return [...installedNames, ...knownNames].reduce<ExtensionList>((acc, name) => {
|
|
778
808
|
if (!acc[name]) {
|
|
779
809
|
if (installedNames.includes(name)) {
|
|
780
810
|
acc[name] = {
|
|
@@ -807,17 +837,18 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
807
837
|
|
|
808
838
|
// the string used for installation is either <name>@<ver> in the case of a standard NPM
|
|
809
839
|
// package, or whatever the user sent in otherwise.
|
|
810
|
-
const installStr =
|
|
840
|
+
const installStr =
|
|
841
|
+
installType === INSTALL_TYPE_NPM ? `${pkgName}${pkgVer ? `@${pkgVer}` : ''}` : installSpec;
|
|
811
842
|
const appiumHome = this.config.appiumHome;
|
|
812
843
|
try {
|
|
813
844
|
const {pkg, installPath} = await spinWith(
|
|
814
845
|
this.isJsonOutput,
|
|
815
846
|
installMsg,
|
|
816
|
-
async () => await npm.installPackage(appiumHome, installStr, {pkgName, installType})
|
|
847
|
+
async () => await npm.installPackage(appiumHome, installStr, {pkgName, installType}),
|
|
817
848
|
);
|
|
818
849
|
|
|
819
850
|
const validatedPkg = await spinWith(this.isJsonOutput, validateMsg, async () =>
|
|
820
|
-
this.validatePackageJson(pkg, installSpec)
|
|
851
|
+
this.validatePackageJson(pkg, installSpec),
|
|
821
852
|
);
|
|
822
853
|
|
|
823
854
|
return this.getInstallationReceipt({
|
|
@@ -827,11 +858,11 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
827
858
|
installSpec,
|
|
828
859
|
});
|
|
829
860
|
} catch (err) {
|
|
830
|
-
|
|
861
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
862
|
+
throw this._createFatalError(`Encountered an error when installing package: ${message}`);
|
|
831
863
|
}
|
|
832
864
|
}
|
|
833
865
|
|
|
834
|
-
|
|
835
866
|
/**
|
|
836
867
|
* Actually update an extension installed by NPM, using the NPM cli. And update the installation
|
|
837
868
|
* manifest.
|
|
@@ -848,8 +879,17 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
848
879
|
pkgVer: version,
|
|
849
880
|
});
|
|
850
881
|
|
|
851
|
-
|
|
852
|
-
|
|
882
|
+
const extManifest =
|
|
883
|
+
this.type === 'driver'
|
|
884
|
+
? (({driverName, ...rest}) => {
|
|
885
|
+
void driverName;
|
|
886
|
+
return rest;
|
|
887
|
+
})(extData as ExtInstallReceipt<'driver'>)
|
|
888
|
+
: (({pluginName, ...rest}) => {
|
|
889
|
+
void pluginName;
|
|
890
|
+
return rest;
|
|
891
|
+
})(extData as ExtInstallReceipt<'plugin'>);
|
|
892
|
+
await this.config.updateExtension(installSpec, extManifest as any);
|
|
853
893
|
}
|
|
854
894
|
|
|
855
895
|
/**
|
|
@@ -864,7 +904,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
864
904
|
cwd: string,
|
|
865
905
|
script: string,
|
|
866
906
|
args: string[] = [],
|
|
867
|
-
opts: Record<string, any> = {}
|
|
907
|
+
opts: Record<string, any> = {},
|
|
868
908
|
) {
|
|
869
909
|
return spawn(process.execPath, [script, ...args], {
|
|
870
910
|
cwd,
|
|
@@ -919,7 +959,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
919
959
|
|
|
920
960
|
const createMissingFieldError = (field: string): ReferenceError =>
|
|
921
961
|
new ReferenceError(
|
|
922
|
-
`${this.type} "${installSpec}" invalid; missing a \`${field}\` field of its \`package.json
|
|
962
|
+
`${this.type} "${installSpec}" invalid; missing a \`${field}\` field of its \`package.json\``,
|
|
923
963
|
);
|
|
924
964
|
|
|
925
965
|
if (!name) {
|
|
@@ -944,7 +984,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
944
984
|
private async _checkForUpdates(
|
|
945
985
|
listData: ExtensionList,
|
|
946
986
|
showUpdates: boolean,
|
|
947
|
-
lsMsg: string
|
|
987
|
+
lsMsg: string,
|
|
948
988
|
): Promise<void> {
|
|
949
989
|
await spinWith(this.isJsonOutput, lsMsg, async () => {
|
|
950
990
|
// We'd like to still show lsMsg even if showUpdates is false
|
|
@@ -953,11 +993,11 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
953
993
|
}
|
|
954
994
|
|
|
955
995
|
// Filter to only extensions that need update checks (installed npm packages)
|
|
956
|
-
const extensionsToCheck =
|
|
957
|
-
([, data]) => data.installed && data.installType === INSTALL_TYPE_NPM
|
|
996
|
+
const extensionsToCheck = Object.entries(listData as Record<string, any>).filter(
|
|
997
|
+
([, data]) => data.installed && data.installType === INSTALL_TYPE_NPM,
|
|
958
998
|
);
|
|
959
999
|
|
|
960
|
-
await
|
|
1000
|
+
await asyncmap(
|
|
961
1001
|
extensionsToCheck,
|
|
962
1002
|
async ([ext, data]) => {
|
|
963
1003
|
try {
|
|
@@ -969,7 +1009,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
969
1009
|
data.updateError = (e as Error).message;
|
|
970
1010
|
}
|
|
971
1011
|
},
|
|
972
|
-
{concurrency: MAX_CONCURRENT_REPO_FETCHES}
|
|
1012
|
+
{concurrency: MAX_CONCURRENT_REPO_FETCHES},
|
|
973
1013
|
);
|
|
974
1014
|
});
|
|
975
1015
|
}
|
|
@@ -980,15 +1020,15 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
980
1020
|
*/
|
|
981
1021
|
private async _addRepositoryUrlsToListData(listData: ExtensionList): Promise<void> {
|
|
982
1022
|
await spinWith(this.isJsonOutput, 'Fetching repository information', async () => {
|
|
983
|
-
await
|
|
984
|
-
|
|
1023
|
+
await asyncmap(
|
|
1024
|
+
Object.values(listData),
|
|
985
1025
|
async (data) => {
|
|
986
1026
|
const repoUrl = await this._getRepositoryUrl(data);
|
|
987
1027
|
if (repoUrl) {
|
|
988
1028
|
data.repositoryUrl = repoUrl;
|
|
989
1029
|
}
|
|
990
1030
|
},
|
|
991
|
-
{concurrency: MAX_CONCURRENT_REPO_FETCHES}
|
|
1031
|
+
{concurrency: MAX_CONCURRENT_REPO_FETCHES},
|
|
992
1032
|
);
|
|
993
1033
|
});
|
|
994
1034
|
}
|
|
@@ -999,9 +1039,9 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
999
1039
|
*/
|
|
1000
1040
|
private async _displayNormalListOutput(
|
|
1001
1041
|
listData: ExtensionList,
|
|
1002
|
-
showUpdates: boolean
|
|
1042
|
+
showUpdates: boolean,
|
|
1003
1043
|
): Promise<ExtensionList> {
|
|
1004
|
-
for (const [name, data] of
|
|
1044
|
+
for (const [name, data] of Object.entries(listData)) {
|
|
1005
1045
|
const line = await this._formatExtensionLine(name, data, showUpdates);
|
|
1006
1046
|
this.log.log(line);
|
|
1007
1047
|
}
|
|
@@ -1016,15 +1056,15 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1016
1056
|
private async _formatExtensionLine(
|
|
1017
1057
|
name: string,
|
|
1018
1058
|
data: ExtensionListData,
|
|
1019
|
-
showUpdates: boolean
|
|
1059
|
+
showUpdates: boolean,
|
|
1020
1060
|
): Promise<string> {
|
|
1021
1061
|
if (data.installed) {
|
|
1022
1062
|
const installTxt = this._formatInstallText(data);
|
|
1023
1063
|
const updateTxt = showUpdates ? this._formatUpdateText(data) : '';
|
|
1024
|
-
return `- ${
|
|
1064
|
+
return `- ${console.styleText('yellow', name)}${installTxt}${updateTxt}`;
|
|
1025
1065
|
}
|
|
1026
|
-
const installTxt = ' [not installed]'
|
|
1027
|
-
return `- ${
|
|
1066
|
+
const installTxt = console.styleText('grey', ' [not installed]');
|
|
1067
|
+
return `- ${console.styleText('yellow', name)}${installTxt}`;
|
|
1028
1068
|
}
|
|
1029
1069
|
|
|
1030
1070
|
/**
|
|
@@ -1033,14 +1073,17 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1033
1073
|
*/
|
|
1034
1074
|
private _formatInstallText(data: ExtensionListData): string {
|
|
1035
1075
|
const {installType, installSpec, version} = data;
|
|
1036
|
-
let typeTxt;
|
|
1076
|
+
let typeTxt: string;
|
|
1077
|
+
let isTypeTextStyled = false;
|
|
1037
1078
|
switch (installType) {
|
|
1038
1079
|
case INSTALL_TYPE_GIT:
|
|
1039
1080
|
case INSTALL_TYPE_GITHUB:
|
|
1040
|
-
typeTxt = `(cloned from ${installSpec})
|
|
1081
|
+
typeTxt = console.styleText('yellow', `(cloned from ${installSpec})`);
|
|
1082
|
+
isTypeTextStyled = true;
|
|
1041
1083
|
break;
|
|
1042
1084
|
case INSTALL_TYPE_LOCAL:
|
|
1043
|
-
typeTxt = `(linked from ${installSpec})
|
|
1085
|
+
typeTxt = console.styleText('magenta', `(linked from ${installSpec})`);
|
|
1086
|
+
isTypeTextStyled = true;
|
|
1044
1087
|
break;
|
|
1045
1088
|
case INSTALL_TYPE_DEV:
|
|
1046
1089
|
typeTxt = '(dev mode)';
|
|
@@ -1048,7 +1091,10 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1048
1091
|
default:
|
|
1049
1092
|
typeTxt = '(npm)';
|
|
1050
1093
|
}
|
|
1051
|
-
|
|
1094
|
+
const installLabelStart = console.styleText('green', '[installed ');
|
|
1095
|
+
const installLabelEnd = console.styleText('green', ']');
|
|
1096
|
+
const installTypeText = isTypeTextStyled ? typeTxt : console.styleText('green', typeTxt);
|
|
1097
|
+
return `${console.styleText('yellow', `@${String(version)}`)} ${installLabelStart}${installTypeText}${installLabelEnd}`;
|
|
1052
1098
|
}
|
|
1053
1099
|
|
|
1054
1100
|
/**
|
|
@@ -1058,17 +1104,17 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1058
1104
|
private _formatUpdateText(data: ExtensionListData): string {
|
|
1059
1105
|
const {updateVersion, unsafeUpdateVersion, upToDate, updateError} = data;
|
|
1060
1106
|
if (updateError) {
|
|
1061
|
-
return ` [Cannot check for updates: ${updateError}]
|
|
1107
|
+
return console.styleText('red', ` [Cannot check for updates: ${updateError}]`);
|
|
1062
1108
|
}
|
|
1063
1109
|
let txt = '';
|
|
1064
1110
|
if (updateVersion) {
|
|
1065
|
-
txt += ` [${updateVersion} available]
|
|
1111
|
+
txt += console.styleText('magenta', ` [${updateVersion} available]`);
|
|
1066
1112
|
}
|
|
1067
1113
|
if (upToDate) {
|
|
1068
|
-
txt +=
|
|
1114
|
+
txt += console.styleText('green', ' [Up to date]');
|
|
1069
1115
|
}
|
|
1070
1116
|
if (unsafeUpdateVersion) {
|
|
1071
|
-
txt += ` [${unsafeUpdateVersion} available (potentially unsafe)]
|
|
1117
|
+
txt += console.styleText('cyan', ` [${unsafeUpdateVersion} available (potentially unsafe)]`);
|
|
1072
1118
|
}
|
|
1073
1119
|
return txt;
|
|
1074
1120
|
}
|
|
@@ -1079,9 +1125,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1079
1125
|
*/
|
|
1080
1126
|
private async _getRepositoryUrl(data: ExtensionListData): Promise<string | null> {
|
|
1081
1127
|
if (data.installed && data.installPath) {
|
|
1082
|
-
return await this._getRepositoryUrlFromInstalled(
|
|
1083
|
-
data
|
|
1084
|
-
);
|
|
1128
|
+
return await this._getRepositoryUrlFromInstalled(data);
|
|
1085
1129
|
}
|
|
1086
1130
|
if (data.pkgName && !data.installed) {
|
|
1087
1131
|
return await this._getRepositoryUrlFromNpm(data.pkgName);
|
|
@@ -1150,12 +1194,19 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1150
1194
|
}
|
|
1151
1195
|
|
|
1152
1196
|
await spinWith(this.isJsonOutput, `Checking if '${pkgName}' is compatible`, async () => {
|
|
1153
|
-
const [serverVersion, extVersionRequirement] = await getRemoteExtensionVersionReq(
|
|
1154
|
-
|
|
1197
|
+
const [serverVersion, extVersionRequirement] = await getRemoteExtensionVersionReq(
|
|
1198
|
+
pkgName,
|
|
1199
|
+
pkgVer,
|
|
1200
|
+
);
|
|
1201
|
+
if (
|
|
1202
|
+
serverVersion &&
|
|
1203
|
+
extVersionRequirement &&
|
|
1204
|
+
!semver.satisfies(serverVersion, extVersionRequirement)
|
|
1205
|
+
) {
|
|
1155
1206
|
throw this._createFatalError(
|
|
1156
1207
|
`'${installSpec}' cannot be installed because the server version it requires (${extVersionRequirement}) ` +
|
|
1157
|
-
|
|
1158
|
-
|
|
1208
|
+
`does not meet the currently installed one (${serverVersion}). Please install ` +
|
|
1209
|
+
`a compatible server version first.`,
|
|
1159
1210
|
);
|
|
1160
1211
|
}
|
|
1161
1212
|
});
|
|
@@ -1171,7 +1222,7 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1171
1222
|
*/
|
|
1172
1223
|
protected abstract validateExtensionFields(
|
|
1173
1224
|
extMetadata: ExtMetadata<ExtType>,
|
|
1174
|
-
installSpec: string
|
|
1225
|
+
installSpec: string,
|
|
1175
1226
|
): void;
|
|
1176
1227
|
|
|
1177
1228
|
/**
|
|
@@ -1194,29 +1245,35 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
|
|
|
1194
1245
|
export async function injectAppiumSymlinks(
|
|
1195
1246
|
driverConfig: ExtensionConfig<any>,
|
|
1196
1247
|
pluginConfig: ExtensionConfig<any>,
|
|
1197
|
-
logger: AppiumLogger
|
|
1248
|
+
logger: AppiumLogger,
|
|
1198
1249
|
): Promise<void> {
|
|
1199
1250
|
const isNpmInstalledExtension = (
|
|
1200
|
-
details: InstalledExtensionLike
|
|
1201
|
-
): details is InstalledExtensionLike & {
|
|
1202
|
-
|
|
1251
|
+
details: InstalledExtensionLike,
|
|
1252
|
+
): details is InstalledExtensionLike & {
|
|
1253
|
+
installType: typeof INSTALL_TYPE_NPM;
|
|
1254
|
+
installPath: string;
|
|
1255
|
+
} => details.installType === INSTALL_TYPE_NPM && Boolean(details.installPath);
|
|
1203
1256
|
|
|
1204
1257
|
const installedExtensions = [
|
|
1205
1258
|
...Object.values(driverConfig.installedExtensions || {}),
|
|
1206
1259
|
...Object.values(pluginConfig.installedExtensions || {}),
|
|
1207
1260
|
] as InstalledExtensionLike[];
|
|
1208
1261
|
|
|
1209
|
-
const installPaths =
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1262
|
+
const installPaths = compact(
|
|
1263
|
+
installedExtensions
|
|
1264
|
+
.filter((details): details is InstalledExtensionLike => Boolean(details))
|
|
1265
|
+
.filter(isNpmInstalledExtension)
|
|
1266
|
+
.map((details) => details.installPath),
|
|
1267
|
+
);
|
|
1213
1268
|
// After the extension is installed, we try to inject the appium module symlink
|
|
1214
1269
|
// into the extension's node_modules folder if it is not there yet.
|
|
1215
1270
|
// We also inject the symlink into other installed extensions' node_modules folders
|
|
1216
1271
|
// as these might be cleaned up unexpectedly by npm
|
|
1217
1272
|
// (see https://github.com/appium/python-client/pull/1177#issuecomment-3419826643).
|
|
1218
1273
|
await Promise.all(
|
|
1219
|
-
installPaths.map((installPath) =>
|
|
1274
|
+
installPaths.map((installPath) =>
|
|
1275
|
+
injectAppiumSymlink(path.join(installPath, 'node_modules'), logger),
|
|
1276
|
+
),
|
|
1220
1277
|
);
|
|
1221
1278
|
}
|
|
1222
1279
|
|
|
@@ -1224,9 +1281,16 @@ export async function injectAppiumSymlinks(
|
|
|
1224
1281
|
* Omits `driverName`/`pluginName` props from the receipt to make a {@linkcode ExtManifest}
|
|
1225
1282
|
*/
|
|
1226
1283
|
function receiptToManifest<ExtType extends ExtensionType>(
|
|
1227
|
-
receipt: ExtInstallReceipt<ExtType
|
|
1284
|
+
receipt: ExtInstallReceipt<ExtType>,
|
|
1228
1285
|
): ExtManifest<ExtType> {
|
|
1229
|
-
|
|
1286
|
+
if ('driverName' in receipt) {
|
|
1287
|
+
const {driverName, ...manifest} = receipt as ExtInstallReceipt<'driver'>;
|
|
1288
|
+
void driverName;
|
|
1289
|
+
return manifest as unknown as ExtManifest<ExtType>;
|
|
1290
|
+
}
|
|
1291
|
+
const {pluginName, ...manifest} = receipt as ExtInstallReceipt<'plugin'>;
|
|
1292
|
+
void pluginName;
|
|
1293
|
+
return manifest as unknown as ExtManifest<ExtType>;
|
|
1230
1294
|
}
|
|
1231
1295
|
|
|
1232
1296
|
/**
|
|
@@ -1237,15 +1301,19 @@ function receiptToManifest<ExtType extends ExtensionType>(
|
|
|
1237
1301
|
*/
|
|
1238
1302
|
async function getRemoteExtensionVersionReq(
|
|
1239
1303
|
pkgName: string,
|
|
1240
|
-
pkgVer?: string
|
|
1304
|
+
pkgVer?: string,
|
|
1241
1305
|
): Promise<[string, string | null]> {
|
|
1242
|
-
const allDeps = await npm.getPackageInfo(
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
);
|
|
1246
|
-
const requiredVersionPair =
|
|
1306
|
+
const allDeps = await npm.getPackageInfo(`${pkgName}${pkgVer ? `@${pkgVer}` : ``}`, [
|
|
1307
|
+
'peerDependencies',
|
|
1308
|
+
'dependencies',
|
|
1309
|
+
]);
|
|
1310
|
+
const requiredVersionPair = Object.values(allDeps)
|
|
1311
|
+
.flatMap((dep) => Object.entries(dep ?? {}))
|
|
1247
1312
|
.find(([name]) => name === 'appium');
|
|
1248
|
-
return [
|
|
1313
|
+
return [
|
|
1314
|
+
npmPackage.version,
|
|
1315
|
+
requiredVersionPair ? (requiredVersionPair[1] as string | null) : null,
|
|
1316
|
+
];
|
|
1249
1317
|
}
|
|
1250
1318
|
|
|
1251
1319
|
/**
|
|
@@ -1255,22 +1323,19 @@ async function getRemoteExtensionVersionReq(
|
|
|
1255
1323
|
* @param dstFolder The destination folder where the symlink should be created
|
|
1256
1324
|
*/
|
|
1257
1325
|
async function injectAppiumSymlink(dstFolder: string, logger: AppiumLogger): Promise<void> {
|
|
1258
|
-
let appiumModuleRoot = '';
|
|
1259
1326
|
try {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
await fs.symlink(appiumModuleRoot, symlinkPath, system.isWindows() ? 'junction' : 'dir');
|
|
1327
|
+
const symlinkPath = path.join(dstFolder, path.basename(appiumPackageRoot));
|
|
1328
|
+
if ((await fs.exists(dstFolder)) && !(await fs.exists(symlinkPath))) {
|
|
1329
|
+
await fs.symlink(appiumPackageRoot, symlinkPath, system.isWindows() ? 'junction' : 'dir');
|
|
1264
1330
|
}
|
|
1265
1331
|
} catch (error) {
|
|
1266
1332
|
// This error is not fatal, we may still doing just fine if the module being loaded is a CJS one
|
|
1267
1333
|
logger.info(
|
|
1268
|
-
`Cannot create a symlink to the appium module '${
|
|
1269
|
-
|
|
1334
|
+
`Cannot create a symlink to the appium module '${appiumPackageRoot}' in '${dstFolder}'. ` +
|
|
1335
|
+
`Original error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1270
1336
|
);
|
|
1271
1337
|
}
|
|
1272
1338
|
}
|
|
1273
1339
|
|
|
1274
1340
|
export default ExtensionCliCommand;
|
|
1275
1341
|
export {ExtensionCliCommand as ExtensionCommand};
|
|
1276
|
-
|