appium 3.5.0 → 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.
Files changed (195) hide show
  1. package/build/lib/appium.d.ts.map +1 -1
  2. package/build/lib/appium.js +43 -45
  3. package/build/lib/appium.js.map +1 -1
  4. package/build/lib/bidi-commands.d.ts.map +1 -1
  5. package/build/lib/bidi-commands.js +34 -29
  6. package/build/lib/bidi-commands.js.map +1 -1
  7. package/build/lib/bootstrap/appium-initializer.d.ts.map +1 -1
  8. package/build/lib/bootstrap/appium-initializer.js +13 -13
  9. package/build/lib/bootstrap/appium-initializer.js.map +1 -1
  10. package/build/lib/bootstrap/appium-main-runner.d.ts.map +1 -1
  11. package/build/lib/bootstrap/appium-main-runner.js +2 -2
  12. package/build/lib/bootstrap/appium-main-runner.js.map +1 -1
  13. package/build/lib/bootstrap/config-file.d.ts.map +1 -1
  14. package/build/lib/bootstrap/config-file.js +11 -11
  15. package/build/lib/bootstrap/config-file.js.map +1 -1
  16. package/build/lib/bootstrap/grid-v3-register.d.ts.map +1 -1
  17. package/build/lib/bootstrap/grid-v3-register.js +9 -6
  18. package/build/lib/bootstrap/grid-v3-register.js.map +1 -1
  19. package/build/lib/bootstrap/main-helpers.d.ts +1 -1
  20. package/build/lib/bootstrap/main-helpers.d.ts.map +1 -1
  21. package/build/lib/bootstrap/main-helpers.js +19 -20
  22. package/build/lib/bootstrap/main-helpers.js.map +1 -1
  23. package/build/lib/bootstrap/node-helpers.d.ts +1 -1
  24. package/build/lib/bootstrap/node-helpers.d.ts.map +1 -1
  25. package/build/lib/bootstrap/node-helpers.js +7 -14
  26. package/build/lib/bootstrap/node-helpers.js.map +1 -1
  27. package/build/lib/bootstrap/startup-config.d.ts.map +1 -1
  28. package/build/lib/bootstrap/startup-config.js +32 -30
  29. package/build/lib/bootstrap/startup-config.js.map +1 -1
  30. package/build/lib/cli/args.d.ts +3 -2
  31. package/build/lib/cli/args.d.ts.map +1 -1
  32. package/build/lib/cli/args.js +2 -5
  33. package/build/lib/cli/args.js.map +1 -1
  34. package/build/lib/cli/driver-command.d.ts +1 -2
  35. package/build/lib/cli/driver-command.d.ts.map +1 -1
  36. package/build/lib/cli/driver-command.js +7 -8
  37. package/build/lib/cli/driver-command.js.map +1 -1
  38. package/build/lib/cli/extension-command.d.ts +2 -2
  39. package/build/lib/cli/extension-command.d.ts.map +1 -1
  40. package/build/lib/cli/extension-command.js +100 -66
  41. package/build/lib/cli/extension-command.js.map +1 -1
  42. package/build/lib/cli/extension.d.ts.map +1 -1
  43. package/build/lib/cli/extension.js.map +1 -1
  44. package/build/lib/cli/parser.d.ts.map +1 -1
  45. package/build/lib/cli/parser.js +21 -14
  46. package/build/lib/cli/parser.js.map +1 -1
  47. package/build/lib/cli/plugin-command.d.ts +1 -1
  48. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  49. package/build/lib/cli/plugin-command.js +5 -5
  50. package/build/lib/cli/plugin-command.js.map +1 -1
  51. package/build/lib/cli/setup-command.d.ts +1 -1
  52. package/build/lib/cli/setup-command.d.ts.map +1 -1
  53. package/build/lib/cli/setup-command.js +14 -19
  54. package/build/lib/cli/setup-command.js.map +1 -1
  55. package/build/lib/cli/utils.d.ts.map +1 -1
  56. package/build/lib/cli/utils.js +3 -2
  57. package/build/lib/cli/utils.js.map +1 -1
  58. package/build/lib/doctor/doctor.d.ts +0 -1
  59. package/build/lib/doctor/doctor.d.ts.map +1 -1
  60. package/build/lib/doctor/doctor.js +12 -17
  61. package/build/lib/doctor/doctor.js.map +1 -1
  62. package/build/lib/extension/driver-config.d.ts.map +1 -1
  63. package/build/lib/extension/driver-config.js +11 -12
  64. package/build/lib/extension/driver-config.js.map +1 -1
  65. package/build/lib/extension/extension-config.d.ts.map +1 -1
  66. package/build/lib/extension/extension-config.js +28 -27
  67. package/build/lib/extension/extension-config.js.map +1 -1
  68. package/build/lib/extension/index.d.ts.map +1 -1
  69. package/build/lib/extension/index.js +13 -16
  70. package/build/lib/extension/index.js.map +1 -1
  71. package/build/lib/extension/manifest.d.ts +3 -2
  72. package/build/lib/extension/manifest.d.ts.map +1 -1
  73. package/build/lib/extension/manifest.js +19 -22
  74. package/build/lib/extension/manifest.js.map +1 -1
  75. package/build/lib/extension/plugin-config.d.ts.map +1 -1
  76. package/build/lib/extension/plugin-config.js +7 -9
  77. package/build/lib/extension/plugin-config.js.map +1 -1
  78. package/build/lib/helpers/build.d.ts +0 -1
  79. package/build/lib/helpers/build.d.ts.map +1 -1
  80. package/build/lib/helpers/build.js +2 -4
  81. package/build/lib/helpers/build.js.map +1 -1
  82. package/build/lib/helpers/capability.d.ts.map +1 -1
  83. package/build/lib/helpers/capability.js +27 -22
  84. package/build/lib/helpers/capability.js.map +1 -1
  85. package/build/lib/helpers/network.d.ts.map +1 -1
  86. package/build/lib/helpers/network.js +4 -3
  87. package/build/lib/helpers/network.js.map +1 -1
  88. package/build/lib/insecure-features.d.ts.map +1 -1
  89. package/build/lib/insecure-features.js +9 -13
  90. package/build/lib/insecure-features.js.map +1 -1
  91. package/build/lib/inspector-commands.d.ts.map +1 -1
  92. package/build/lib/inspector-commands.js +29 -28
  93. package/build/lib/inspector-commands.js.map +1 -1
  94. package/build/lib/logsink.d.ts.map +1 -1
  95. package/build/lib/logsink.js +8 -7
  96. package/build/lib/logsink.js.map +1 -1
  97. package/build/lib/schema/arg-spec.d.ts.map +1 -1
  98. package/build/lib/schema/arg-spec.js +5 -8
  99. package/build/lib/schema/arg-spec.js.map +1 -1
  100. package/build/lib/schema/cli-args.d.ts.map +1 -1
  101. package/build/lib/schema/cli-args.js +20 -19
  102. package/build/lib/schema/cli-args.js.map +1 -1
  103. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  104. package/build/lib/schema/cli-transformers.js.map +1 -1
  105. package/build/lib/schema/schema.d.ts.map +1 -1
  106. package/build/lib/schema/schema.js +28 -21
  107. package/build/lib/schema/schema.js.map +1 -1
  108. package/build/lib/utils/hash.d.ts +5 -0
  109. package/build/lib/utils/hash.d.ts.map +1 -0
  110. package/build/lib/{utils.js → utils/hash.js} +3 -21
  111. package/build/lib/utils/hash.js.map +1 -0
  112. package/build/lib/utils/index.d.ts +8 -0
  113. package/build/lib/utils/index.d.ts.map +1 -0
  114. package/build/lib/utils/index.js +31 -0
  115. package/build/lib/utils/index.js.map +1 -0
  116. package/build/lib/utils/is-package-changed.d.ts +16 -0
  117. package/build/lib/utils/is-package-changed.d.ts.map +1 -0
  118. package/build/lib/utils/is-package-changed.js +62 -0
  119. package/build/lib/utils/is-package-changed.js.map +1 -0
  120. package/build/lib/utils/object.d.ts +120 -0
  121. package/build/lib/utils/object.d.ts.map +1 -0
  122. package/build/lib/utils/object.js +288 -0
  123. package/build/lib/utils/object.js.map +1 -0
  124. package/build/lib/{extension → utils}/package-changed.d.ts +0 -1
  125. package/build/lib/utils/package-changed.d.ts.map +1 -0
  126. package/build/lib/{extension → utils}/package-changed.js +3 -4
  127. package/build/lib/utils/package-changed.js.map +1 -0
  128. package/build/lib/utils/package-json.d.ts +9 -0
  129. package/build/lib/utils/package-json.d.ts.map +1 -0
  130. package/build/lib/utils/package-json.js +32 -0
  131. package/build/lib/utils/package-json.js.map +1 -0
  132. package/build/lib/utils/resolve-from.d.ts +11 -0
  133. package/build/lib/utils/resolve-from.d.ts.map +1 -0
  134. package/build/lib/utils/resolve-from.js +41 -0
  135. package/build/lib/utils/resolve-from.js.map +1 -0
  136. package/build/types/cli.d.ts.map +1 -1
  137. package/build/types/index.d.ts.map +1 -1
  138. package/build/types/manifest/base.d.ts.map +1 -1
  139. package/build/types/manifest/v3.d.ts.map +1 -1
  140. package/build/types/manifest/v4.d.ts.map +1 -1
  141. package/lib/appium.ts +59 -55
  142. package/lib/bidi-commands.ts +106 -60
  143. package/lib/bootstrap/appium-initializer.ts +27 -19
  144. package/lib/bootstrap/appium-main-runner.ts +8 -4
  145. package/lib/bootstrap/config-file.ts +33 -16
  146. package/lib/bootstrap/grid-v3-register.ts +26 -19
  147. package/lib/bootstrap/main-helpers.ts +25 -19
  148. package/lib/bootstrap/node-helpers.ts +19 -22
  149. package/lib/bootstrap/startup-config.ts +42 -39
  150. package/lib/cli/args.ts +3 -4
  151. package/lib/cli/driver-command.ts +19 -13
  152. package/lib/cli/extension-command.ts +208 -143
  153. package/lib/cli/extension.ts +4 -6
  154. package/lib/cli/parser.ts +32 -30
  155. package/lib/cli/plugin-command.ts +20 -9
  156. package/lib/cli/setup-command.ts +54 -39
  157. package/lib/cli/utils.ts +4 -3
  158. package/lib/doctor/doctor.ts +15 -17
  159. package/lib/extension/driver-config.ts +22 -17
  160. package/lib/extension/extension-config.ts +74 -66
  161. package/lib/extension/index.ts +42 -25
  162. package/lib/extension/manifest.ts +34 -31
  163. package/lib/extension/plugin-config.ts +15 -10
  164. package/lib/helpers/build.ts +6 -5
  165. package/lib/helpers/capability.ts +40 -35
  166. package/lib/helpers/network.ts +6 -5
  167. package/lib/insecure-features.ts +16 -14
  168. package/lib/inspector-commands.ts +72 -40
  169. package/lib/logsink.ts +22 -26
  170. package/lib/schema/arg-spec.ts +9 -8
  171. package/lib/schema/cli-args-guards.ts +4 -4
  172. package/lib/schema/cli-args.ts +25 -21
  173. package/lib/schema/cli-transformers.ts +6 -2
  174. package/lib/schema/format-errors.ts +1 -1
  175. package/lib/schema/schema.ts +52 -38
  176. package/lib/{utils.ts → utils/hash.ts} +2 -19
  177. package/lib/utils/index.ts +23 -0
  178. package/lib/utils/is-package-changed.ts +78 -0
  179. package/lib/utils/object.ts +312 -0
  180. package/lib/{extension → utils}/package-changed.ts +6 -7
  181. package/lib/utils/package-json.ts +36 -0
  182. package/lib/utils/resolve-from.ts +40 -0
  183. package/package.json +13 -16
  184. package/scripts/autoinstall-extensions.js +8 -8
  185. package/tsconfig.json +8 -2
  186. package/types/cli.ts +18 -18
  187. package/types/index.ts +2 -2
  188. package/types/manifest/base.ts +2 -2
  189. package/types/manifest/v3.ts +2 -2
  190. package/types/manifest/v4.ts +2 -2
  191. package/build/lib/extension/package-changed.d.ts.map +0 -1
  192. package/build/lib/extension/package-changed.js.map +0 -1
  193. package/build/lib/utils.d.ts +0 -13
  194. package/build/lib/utils.d.ts.map +0 -1
  195. package/build/lib/utils.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import {asyncfilter, asyncmap} from 'asyncbox';
2
- import _ from 'lodash';
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 {getAppiumModuleRoot, npmPackage} from '../utils';
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> = BaseExtensionConfig<ExtType>;
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> = AppiumExtPackageJson<ExtType>;
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> = ExtManifest<ExtType> &
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 = {errors: Record<string, Error>; updates: Record<string, UpdateReport>};
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: Record<string, string>;
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
- if (!_.isFunction(this[cmd])) {
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
- const executeCmd = this[cmd].bind(this);
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({installSpec, installType, packageName}: InstallOpts): Promise<Record<string, any>> {
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 (!_.includes(knownNames, name)) {
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 = receipt[`${this.type}Name`];
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
 
@@ -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 (!_.isEmpty(errorSummaries)) {
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 (!_.isEmpty(warningSummaries)) {
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(`Successfully uninstalled ${this.type} '${installSpec}'`.green);
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({installSpec, unsafe}: ExtensionUpdateOpts): Promise<ExtensionUpdateResult> {
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 = `A newer major version ${update.unsafeUpdate} ` +
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(newMajorUpdateMsg.yellow);
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 _.toPairs(updates)) {
521
- this.log.ok(` - ${this.type} ${e} updated: ${update.from} => ${update.to}`.green);
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 _.toPairs(errors)) {
542
+ for (const [e, err] of Object.entries(errors)) {
525
543
  if (err instanceof NotUpdatableError) {
526
544
  this.log.warn(
527
- ` - '${e}' was not installed via npm, so we could not check ` + `for updates`.yellow
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`.yellow);
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}`.red);
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: ${e.message}`
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 (!_.isPlainObject(doctorSpec) || !_.isArray(doctorSpec.checks)) {
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
- `containing the 'checks' key with the array of script paths`
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
- const scriptPath = path.resolve(moduleRoot, p);
612
- if (!path.normalize(scriptPath).startsWith(path.normalize(moduleRoot))) {
613
- this.log.error(
614
- `The doctor check script '${p}' from the package manifest '${packageJsonPath}' must be located ` +
615
- `in the '${moduleRoot}' root folder. It will be skipped`
616
- );
617
- return null;
618
- }
619
- return scriptPath;
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
- this.log.warn(`Unable to load doctor checks from '${p}': ${e.message}`);
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((method) => _.isFunction(x?.[method]));
637
- const checks: IDoctorCheck[] = _.flatMap((await Promise.all(loadChecksPromises)).filter(Boolean).map(_.toPairs))
638
- .map(([, value]) => value)
639
- .filter(isDoctorCheck) as IDoctorCheck[];
640
- if (_.isEmpty(checks)) {
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
- `for the "${installSpec}" ${this.type}`
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 || !_.isPlainObject(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 = _.toPairs(extScripts as Record<string, string>);
719
+ const allScripts = Object.entries(extScripts as Record<string, string>);
694
720
  const root = this.config.getInstallPath(installSpec);
695
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 (_.isEmpty(existingScripts)) {
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(`The ${this.type} named '${installSpec}' contains ` +
703
- `${util.pluralize('script', existingScripts.length, true)}:`);
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(`Successfully retrieved the list of scripts`.green);
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,10 +766,11 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
738
766
 
739
767
  try {
740
768
  await runner.join();
741
- this.log.ok(`${scriptName} successfully ran`.green);
769
+ this.log.ok(console.styleText('green', `${scriptName} successfully ran`));
742
770
  return {output: output.getBuff()};
743
771
  } catch (err) {
744
- const message = `Encountered an error when running '${scriptName}': ${err.message}`;
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
  }
@@ -763,10 +792,11 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
763
792
  }
764
793
  });
765
794
  });
766
- this.log.ok(`${scriptName} successfully ran`.green);
795
+ this.log.ok(console.styleText('green', `${scriptName} successfully ran`));
767
796
  return {};
768
797
  } catch (err) {
769
- const message = `Encountered an error when running '${scriptName}': ${err.message}`;
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 = installType === INSTALL_TYPE_NPM ? `${pkgName}${pkgVer ? `@${pkgVer}` : ''}` : installSpec;
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
- throw this._createFatalError(`Encountered an error when installing package: ${err.message}`);
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
- delete extData[`${this.type}Name`];
852
- await this.config.updateExtension(installSpec, extData as any);
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,8 +993,8 @@ 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 = _.toPairs(listData as Record<string, any>).filter(
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
1000
  await asyncmap(
@@ -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
  }
@@ -981,14 +1021,14 @@ abstract class ExtensionCliCommand<ExtType extends ExtensionType = ExtensionType
981
1021
  private async _addRepositoryUrlsToListData(listData: ExtensionList): Promise<void> {
982
1022
  await spinWith(this.isJsonOutput, 'Fetching repository information', async () => {
983
1023
  await asyncmap(
984
- _.values(listData),
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 _.toPairs(listData)) {
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 `- ${name.yellow}${installTxt}${updateTxt}`;
1064
+ return `- ${console.styleText('yellow', name)}${installTxt}${updateTxt}`;
1025
1065
  }
1026
- const installTxt = ' [not installed]'.grey;
1027
- return `- ${name.yellow}${installTxt}`;
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})`.yellow;
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})`.magenta;
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
- return `@${String(version).yellow} ${('[installed ' + typeTxt + ']').green}`;
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}]`.red;
1107
+ return console.styleText('red', ` [Cannot check for updates: ${updateError}]`);
1062
1108
  }
1063
1109
  let txt = '';
1064
1110
  if (updateVersion) {
1065
- txt += ` [${updateVersion} available]`.magenta;
1111
+ txt += console.styleText('magenta', ` [${updateVersion} available]`);
1066
1112
  }
1067
1113
  if (upToDate) {
1068
- txt += ` [Up to date]`.green;
1114
+ txt += console.styleText('green', ' [Up to date]');
1069
1115
  }
1070
1116
  if (unsafeUpdateVersion) {
1071
- txt += ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
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(pkgName, pkgVer);
1154
- if (serverVersion && extVersionRequirement && !semver.satisfies(serverVersion, extVersionRequirement)) {
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
- `does not meet the currently installed one (${serverVersion}). Please install ` +
1158
- `a compatible server version first.`
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 & {installType: typeof INSTALL_TYPE_NPM; installPath: string} =>
1202
- details.installType === INSTALL_TYPE_NPM && Boolean(details.installPath);
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 = _.compact(installedExtensions
1210
- .filter((details): details is InstalledExtensionLike => Boolean(details))
1211
- .filter(isNpmInstalledExtension)
1212
- .map((details) => details.installPath));
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) => injectAppiumSymlink(path.join(installPath, 'node_modules'), logger))
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
- return _.omit(receipt, 'driverName', 'pluginName') as ExtManifest<ExtType>;
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
- `${pkgName}${pkgVer ? `@${pkgVer}` : ``}`,
1244
- ['peerDependencies', 'dependencies']
1245
- );
1246
- const requiredVersionPair = _.flatMap(_.values(allDeps).map(_.toPairs))
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 [npmPackage.version, requiredVersionPair ? requiredVersionPair[1] : null];
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
- appiumModuleRoot = getAppiumModuleRoot();
1261
- const symlinkPath = path.join(dstFolder, path.basename(appiumModuleRoot));
1262
- if (await fs.exists(dstFolder) && !(await fs.exists(symlinkPath))) {
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 '${appiumModuleRoot}' in '${dstFolder}'. ` +
1269
- `Original error: ${error.message}`
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
-