appium 2.0.0-beta.46 → 2.0.0-beta.47

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 (138) hide show
  1. package/README.md +145 -44
  2. package/build/lib/appium.d.ts +3 -103
  3. package/build/lib/appium.d.ts.map +1 -1
  4. package/build/lib/appium.js +679 -549
  5. package/build/lib/appium.js.map +1 -1
  6. package/build/lib/cli/args.js +247 -127
  7. package/build/lib/cli/args.js.map +1 -1
  8. package/build/lib/cli/driver-command.js +63 -88
  9. package/build/lib/cli/driver-command.js.map +1 -1
  10. package/build/lib/cli/extension-command.d.ts +32 -23
  11. package/build/lib/cli/extension-command.d.ts.map +1 -1
  12. package/build/lib/cli/extension-command.js +730 -512
  13. package/build/lib/cli/extension-command.js.map +1 -1
  14. package/build/lib/cli/extension.d.ts +7 -6
  15. package/build/lib/cli/extension.d.ts.map +1 -1
  16. package/build/lib/cli/extension.js +68 -65
  17. package/build/lib/cli/extension.js.map +1 -1
  18. package/build/lib/cli/parser.d.ts +3 -3
  19. package/build/lib/cli/parser.d.ts.map +1 -1
  20. package/build/lib/cli/parser.js +234 -192
  21. package/build/lib/cli/parser.js.map +1 -1
  22. package/build/lib/cli/plugin-command.js +58 -87
  23. package/build/lib/cli/plugin-command.js.map +1 -1
  24. package/build/lib/cli/utils.js +66 -69
  25. package/build/lib/cli/utils.js.map +1 -1
  26. package/build/lib/config-file.d.ts.map +1 -1
  27. package/build/lib/config-file.js +189 -120
  28. package/build/lib/config-file.js.map +1 -1
  29. package/build/lib/config.d.ts.map +1 -1
  30. package/build/lib/config.js +254 -213
  31. package/build/lib/config.js.map +1 -1
  32. package/build/lib/constants.d.ts +5 -5
  33. package/build/lib/constants.d.ts.map +1 -1
  34. package/build/lib/constants.js +64 -59
  35. package/build/lib/constants.js.map +1 -1
  36. package/build/lib/extension/driver-config.js +199 -164
  37. package/build/lib/extension/driver-config.js.map +1 -1
  38. package/build/lib/extension/extension-config.d.ts +18 -16
  39. package/build/lib/extension/extension-config.d.ts.map +1 -1
  40. package/build/lib/extension/extension-config.js +523 -396
  41. package/build/lib/extension/extension-config.js.map +1 -1
  42. package/build/lib/extension/index.js +98 -68
  43. package/build/lib/extension/index.js.map +1 -1
  44. package/build/lib/extension/manifest-migrations.d.ts +27 -0
  45. package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
  46. package/build/lib/extension/manifest-migrations.js +99 -0
  47. package/build/lib/extension/manifest-migrations.js.map +1 -0
  48. package/build/lib/extension/manifest.d.ts +7 -56
  49. package/build/lib/extension/manifest.d.ts.map +1 -1
  50. package/build/lib/extension/manifest.js +432 -240
  51. package/build/lib/extension/manifest.js.map +1 -1
  52. package/build/lib/extension/package-changed.js +57 -61
  53. package/build/lib/extension/package-changed.js.map +1 -1
  54. package/build/lib/extension/plugin-config.d.ts +2 -3
  55. package/build/lib/extension/plugin-config.d.ts.map +1 -1
  56. package/build/lib/extension/plugin-config.js +94 -70
  57. package/build/lib/extension/plugin-config.js.map +1 -1
  58. package/build/lib/grid-register.js +119 -137
  59. package/build/lib/grid-register.js.map +1 -1
  60. package/build/lib/logger.d.ts +1 -1
  61. package/build/lib/logger.d.ts.map +1 -1
  62. package/build/lib/logger.js +5 -15
  63. package/build/lib/logger.js.map +1 -1
  64. package/build/lib/logsink.d.ts.map +1 -1
  65. package/build/lib/logsink.js +189 -183
  66. package/build/lib/logsink.js.map +1 -1
  67. package/build/lib/main.d.ts +19 -12
  68. package/build/lib/main.d.ts.map +1 -1
  69. package/build/lib/main.js +330 -304
  70. package/build/lib/main.js.map +1 -1
  71. package/build/lib/schema/arg-spec.js +153 -108
  72. package/build/lib/schema/arg-spec.js.map +1 -1
  73. package/build/lib/schema/cli-args.js +203 -164
  74. package/build/lib/schema/cli-args.js.map +1 -1
  75. package/build/lib/schema/cli-transformers.js +117 -72
  76. package/build/lib/schema/cli-transformers.js.map +1 -1
  77. package/build/lib/schema/index.js +17 -32
  78. package/build/lib/schema/index.js.map +1 -1
  79. package/build/lib/schema/keywords.js +125 -67
  80. package/build/lib/schema/keywords.js.map +1 -1
  81. package/build/lib/schema/schema.d.ts.map +1 -1
  82. package/build/lib/schema/schema.js +582 -417
  83. package/build/lib/schema/schema.js.map +1 -1
  84. package/build/lib/utils.d.ts +41 -255
  85. package/build/lib/utils.d.ts.map +1 -1
  86. package/build/lib/utils.js +342 -193
  87. package/build/lib/utils.js.map +1 -1
  88. package/build/tsconfig.tsbuildinfo +1 -1
  89. package/build/types/cli.d.ts +45 -34
  90. package/build/types/cli.d.ts.map +1 -1
  91. package/build/types/cli.js +3 -0
  92. package/build/types/cli.js.map +1 -0
  93. package/build/types/index.d.ts +1 -2
  94. package/build/types/index.d.ts.map +1 -1
  95. package/build/types/index.js +19 -0
  96. package/build/types/index.js.map +1 -0
  97. package/build/types/manifest/base.d.ts +135 -0
  98. package/build/types/manifest/base.d.ts.map +1 -0
  99. package/build/types/manifest/base.js +3 -0
  100. package/build/types/manifest/base.js.map +1 -0
  101. package/build/types/manifest/index.d.ts +19 -0
  102. package/build/types/manifest/index.d.ts.map +1 -0
  103. package/build/types/manifest/index.js +40 -0
  104. package/build/types/manifest/index.js.map +1 -0
  105. package/build/types/manifest/v3.d.ts +139 -0
  106. package/build/types/manifest/v3.d.ts.map +1 -0
  107. package/build/types/manifest/v3.js +3 -0
  108. package/build/types/manifest/v3.js.map +1 -0
  109. package/lib/appium.js +1 -1
  110. package/lib/cli/args.js +1 -1
  111. package/lib/cli/extension-command.js +116 -61
  112. package/lib/cli/extension.js +9 -8
  113. package/lib/cli/parser.js +2 -2
  114. package/lib/config-file.js +2 -3
  115. package/lib/config.js +3 -2
  116. package/lib/constants.js +6 -5
  117. package/lib/extension/extension-config.js +24 -25
  118. package/lib/extension/manifest-migrations.js +99 -0
  119. package/lib/extension/manifest.js +79 -72
  120. package/lib/extension/plugin-config.js +1 -2
  121. package/lib/logsink.js +26 -5
  122. package/lib/main.js +58 -50
  123. package/lib/schema/schema.js +6 -1
  124. package/lib/utils.js +62 -0
  125. package/package.json +23 -24
  126. package/scripts/autoinstall-extensions.js +78 -26
  127. package/types/cli.ts +81 -42
  128. package/types/index.ts +1 -2
  129. package/types/manifest/README.md +30 -0
  130. package/types/manifest/base.ts +158 -0
  131. package/types/manifest/index.ts +27 -0
  132. package/types/manifest/v3.ts +161 -0
  133. package/build/types/appium-manifest.d.ts +0 -59
  134. package/build/types/appium-manifest.d.ts.map +0 -1
  135. package/build/types/extension-manifest.d.ts +0 -55
  136. package/build/types/extension-manifest.d.ts.map +0 -1
  137. package/types/appium-manifest.ts +0 -73
  138. package/types/extension-manifest.ts +0 -64
@@ -8,15 +8,11 @@ import {env, fs} from '@appium/support';
8
8
  import _ from 'lodash';
9
9
  import path from 'path';
10
10
  import YAML from 'yaml';
11
- import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
11
+ import {CURRENT_SCHEMA_REV, DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
12
12
  import log from '../logger';
13
13
  import {INSTALL_TYPE_NPM} from './extension-config';
14
14
  import {packageDidChange} from './package-changed';
15
-
16
- /**
17
- * Current configuration schema revision!
18
- */
19
- const CONFIG_SCHEMA_REV = 2;
15
+ import {migrate} from './manifest-migrations';
20
16
 
21
17
  /**
22
18
  * The name of the prop (`drivers`) used in `extensions.yaml` for drivers.
@@ -36,7 +32,7 @@ const CONFIG_DATA_PLUGIN_KEY = `${PLUGIN_TYPE}s`;
36
32
  const INITIAL_MANIFEST_DATA = Object.freeze({
37
33
  [CONFIG_DATA_DRIVER_KEY]: Object.freeze({}),
38
34
  [CONFIG_DATA_PLUGIN_KEY]: Object.freeze({}),
39
- schemaRev: CONFIG_SCHEMA_REV,
35
+ schemaRev: CURRENT_SCHEMA_REV,
40
36
  });
41
37
 
42
38
  /**
@@ -93,23 +89,21 @@ export class Manifest {
93
89
  *
94
90
  * Contains proxies for automatic persistence on disk
95
91
  * @type {ManifestData}
96
- * @private
97
92
  */
98
- _data;
93
+ #data;
99
94
 
100
95
  /**
101
96
  * Path to `APPIUM_HOME`.
102
- * @private
103
97
  * @type {Readonly<string>}
104
98
  */
105
- _appiumHome;
99
+ #appiumHome;
106
100
 
107
101
  /**
108
102
  * Path to `extensions.yaml`
109
103
  * @type {string}
110
104
  * Not set until {@link Manifest.read} is called.
111
105
  */
112
- _manifestPath;
106
+ #manifestPath;
113
107
 
114
108
  /**
115
109
  * Helps avoid writing multiple times.
@@ -118,10 +112,9 @@ export class Manifest {
118
112
  * set to a `Promise`. When the call to `write()` is complete, the `Promise`
119
113
  * will resolve and then this value will be set to `undefined`. Concurrent calls
120
114
  * made while this value is a `Promise` will return the `Promise` itself.
121
- * @private
122
115
  * @type {Promise<boolean>|undefined}
123
116
  */
124
- _writing;
117
+ #writing;
125
118
 
126
119
  /**
127
120
  * Helps avoid reading multiple times.
@@ -130,10 +123,9 @@ export class Manifest {
130
123
  * set to a `Promise`. When the call to `read()` is complete, the `Promise`
131
124
  * will resolve and then this value will be set to `undefined`. Concurrent calls
132
125
  * made while this value is a `Promise` will return the `Promise` itself.
133
- * @private
134
126
  * @type {Promise<void>|undefined}
135
127
  */
136
- _reading;
128
+ #reading;
137
129
 
138
130
  /**
139
131
  * Sets internal data to a fresh clone of {@link INITIAL_MANIFEST_DATA}
@@ -143,8 +135,8 @@ export class Manifest {
143
135
  * @private
144
136
  */
145
137
  constructor(appiumHome) {
146
- this._appiumHome = appiumHome;
147
- this._data = _.cloneDeep(INITIAL_MANIFEST_DATA);
138
+ this.#appiumHome = appiumHome;
139
+ this.#data = _.cloneDeep(INITIAL_MANIFEST_DATA);
148
140
  }
149
141
 
150
142
  /**
@@ -187,14 +179,14 @@ export class Manifest {
187
179
  */
188
180
  const queue = [
189
181
  // look at `package.json` in `APPIUM_HOME` only
190
- onMatch(path.join(this._appiumHome, 'package.json')),
182
+ onMatch(path.join(this.#appiumHome, 'package.json')),
191
183
  ];
192
184
 
193
185
  // add dependencies to the queue
194
186
  await new B((resolve, reject) => {
195
187
  glob(
196
188
  'node_modules/{*,@*/*}/package.json',
197
- {cwd: this._appiumHome, silent: true, absolute: true},
189
+ {cwd: this.#appiumHome, silent: true, absolute: true},
198
190
  // eslint-disable-next-line promise/prefer-await-to-callbacks
199
191
  (err) => {
200
192
  if (err) {
@@ -221,7 +213,7 @@ export class Manifest {
221
213
  * @returns {boolean}
222
214
  */
223
215
  hasDriver(name) {
224
- return Boolean(this._data.drivers[name]);
216
+ return Boolean(this.#data.drivers[name]);
225
217
  }
226
218
 
227
219
  /**
@@ -230,7 +222,7 @@ export class Manifest {
230
222
  * @returns {boolean}
231
223
  */
232
224
  hasPlugin(name) {
233
- return Boolean(this._data.plugins[name]);
225
+ return Boolean(this.#data.plugins[name]);
234
226
  }
235
227
 
236
228
  /**
@@ -254,6 +246,7 @@ export class Manifest {
254
246
  appiumVersion: pkgJson.peerDependencies?.appium,
255
247
  installType: INSTALL_TYPE_NPM,
256
248
  installSpec: `${pkgJson.name}@${pkgJson.version}`,
249
+ installPath: extensionPath,
257
250
  };
258
251
 
259
252
  if (isDriver(pkgJson)) {
@@ -294,22 +287,22 @@ export class Manifest {
294
287
  */
295
288
  addExtension(extType, extName, extData) {
296
289
  const data = _.clone(extData);
297
- this._data[`${extType}s`][extName] = data;
290
+ this.#data[`${extType}s`][extName] = data;
298
291
  return data;
299
292
  }
300
293
 
301
294
  /**
302
- * Returns the APPIUM_HOME path
295
+ * Returns the `APPIUM_HOME` path
303
296
  */
304
297
  get appiumHome() {
305
- return this._appiumHome;
298
+ return this.#appiumHome;
306
299
  }
307
300
 
308
301
  /**
309
- * Returns the path to the manifest file
302
+ * Returns the path to the manifest file (`extensions.yaml`)
310
303
  */
311
304
  get manifestPath() {
312
- return this._manifestPath;
305
+ return this.#manifestPath;
313
306
  }
314
307
 
315
308
  /**
@@ -320,7 +313,7 @@ export class Manifest {
320
313
  * @returns {ExtRecord<ExtType>}
321
314
  */
322
315
  getExtensionData(extType) {
323
- return this._data[/** @type {string} */ (`${extType}s`)];
316
+ return this.#data[/** @type {string} */ (`${extType}s`)];
324
317
  }
325
318
 
326
319
  /**
@@ -330,23 +323,23 @@ export class Manifest {
330
323
  *
331
324
  * If `APPIUM_HOME` contains a `package.json` with an `appium` dependency, then a hash of the `package.json` will be taken. If this hash differs from the last hash, the contents of `APPIUM_HOME/node_modules` will be scanned for extensions that may have been installed outside of the `appium` CLI. Any found extensions will be added to the manifest file, and if so, the manifest file will be written to disk.
332
325
  *
333
- * Only one read operation should happen at a time. This is controlled via the {@link Manifest._reading} property.
326
+ * Only one read operation should happen at a time.
334
327
  * @returns {Promise<ManifestData>} The data
335
328
  */
336
329
  async read() {
337
- if (this._reading) {
338
- await this._reading;
339
- return this._data;
330
+ if (this.#reading) {
331
+ await this.#reading;
332
+ return this.#data;
340
333
  }
341
334
 
342
- this._reading = (async () => {
335
+ this.#reading = (async () => {
343
336
  /** @type {ManifestData} */
344
337
  let data;
345
338
  let isNewFile = false;
346
- await this._setManifestPath();
339
+ await this.#setManifestPath();
347
340
  try {
348
- log.debug(`Reading ${this._manifestPath}...`);
349
- const yaml = await fs.readFile(this._manifestPath, 'utf8');
341
+ log.debug(`Reading ${this.#manifestPath}...`);
342
+ const yaml = await fs.readFile(this.#manifestPath, 'utf8');
350
343
  data = YAML.parse(yaml);
351
344
  log.debug(`Parsed manifest file: ${JSON.stringify(data, null, 2)}`);
352
345
  } catch (err) {
@@ -354,10 +347,12 @@ export class Manifest {
354
347
  data = _.cloneDeep(INITIAL_MANIFEST_DATA);
355
348
  isNewFile = true;
356
349
  } else {
357
- if (this._manifestPath) {
350
+ if (this.#manifestPath) {
358
351
  throw new Error(
359
352
  `Appium had trouble loading the extension installation ` +
360
- `cache file (${this._manifestPath}). It may be invalid YAML. Specific error: ${err.message}`
353
+ `cache file (${this.#manifestPath}). It may be invalid YAML. Specific error: ${
354
+ err.message
355
+ }`
361
356
  );
362
357
  } else {
363
358
  throw new Error(
@@ -367,47 +362,58 @@ export class Manifest {
367
362
  }
368
363
  }
369
364
 
370
- this._data = data;
371
- let installedExtensionsChanged = false;
365
+ this.#data = data;
366
+
367
+ let shouldWrite = false;
368
+
369
+ if ((data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
370
+ log.debug(
371
+ `Updating manifest schema from rev ${data.schemaRev ?? '(none)'} to ${CURRENT_SCHEMA_REV}`
372
+ );
373
+ shouldWrite = await migrate(this, this.#data);
374
+ }
375
+
372
376
  if (
373
- (await env.hasAppiumDependency(this.appiumHome)) &&
374
- (await packageDidChange(this.appiumHome))
377
+ shouldWrite ||
378
+ ((await env.hasAppiumDependency(this.appiumHome)) &&
379
+ (await packageDidChange(this.appiumHome)))
375
380
  ) {
376
- installedExtensionsChanged = await this.syncWithInstalledExtensions();
381
+ shouldWrite = await this.syncWithInstalledExtensions();
377
382
  }
378
383
 
379
- if (isNewFile || installedExtensionsChanged) {
384
+ if (isNewFile || shouldWrite) {
380
385
  await this.write();
381
386
  }
382
387
  })();
383
388
  try {
384
- await this._reading;
385
- return this._data;
389
+ await this.#reading;
390
+ return this.#data;
386
391
  } finally {
387
- this._reading = undefined;
392
+ this.#reading = undefined;
388
393
  }
389
394
  }
390
395
 
391
396
  /**
392
- * Ensures {@link Manifest._manifestPath} is set.
397
+ * Ensures the internal manifest path is set.
393
398
  *
394
399
  * Creates the directory if necessary.
395
- * @private
396
400
  * @returns {Promise<string>}
397
401
  */
398
- async _setManifestPath() {
399
- if (!this._manifestPath) {
400
- this._manifestPath = await env.resolveManifestPath(this._appiumHome);
402
+ async #setManifestPath() {
403
+ if (!this.#manifestPath) {
404
+ this.#manifestPath = await env.resolveManifestPath(this.#appiumHome);
401
405
 
402
406
  /* istanbul ignore if */
403
- if (path.relative(this._appiumHome, this._manifestPath).startsWith('.')) {
407
+ if (path.relative(this.#appiumHome, this.#manifestPath).startsWith('.')) {
404
408
  throw new Error(
405
- `Mismatch between location of APPIUM_HOME and manifest file. APPIUM_HOME: ${this.appiumHome}, manifest file: ${this._manifestPath}`
409
+ `Mismatch between location of APPIUM_HOME and manifest file. APPIUM_HOME: ${
410
+ this.appiumHome
411
+ }, manifest file: ${this.#manifestPath}`
406
412
  );
407
413
  }
408
414
  }
409
415
 
410
- return this._manifestPath;
416
+ return this.#manifestPath;
411
417
  }
412
418
 
413
419
  /**
@@ -419,34 +425,35 @@ export class Manifest {
419
425
  * @returns {Promise<boolean>} Whether the data was written
420
426
  */
421
427
  async write() {
422
- if (this._writing) {
423
- return this._writing;
428
+ if (this.#writing) {
429
+ return this.#writing;
424
430
  }
425
- this._writing = (async () => {
426
- await this._setManifestPath();
431
+ this.#writing = (async () => {
432
+ await this.#setManifestPath();
427
433
  try {
428
- await fs.mkdirp(path.dirname(this._manifestPath));
434
+ await fs.mkdirp(path.dirname(this.#manifestPath));
429
435
  } catch (err) {
430
436
  throw new Error(
431
437
  `Appium could not create the directory for the manifest file: ${path.dirname(
432
- this._manifestPath
438
+ this.#manifestPath
433
439
  )}. Original error: ${err.message}`
434
440
  );
435
441
  }
436
442
  try {
437
- await fs.writeFile(this._manifestPath, YAML.stringify(this._data), 'utf8');
443
+ await fs.writeFile(this.#manifestPath, YAML.stringify(this.#data), 'utf8');
438
444
  return true;
439
445
  } catch (err) {
440
446
  throw new Error(
441
- `Appium could not write to manifest at ${this._manifestPath} using APPIUM_HOME ${this._appiumHome}. ` +
442
- `Please ensure it is writable. Original error: ${err.message}`
447
+ `Appium could not write to manifest at ${this.#manifestPath} using APPIUM_HOME ${
448
+ this.#appiumHome
449
+ }. Please ensure it is writable. Original error: ${err.message}`
443
450
  );
444
451
  }
445
452
  })();
446
453
  try {
447
- return await this._writing;
454
+ return await this.#writing;
448
455
  } finally {
449
- this._writing = undefined;
456
+ this.#writing = undefined;
450
457
  }
451
458
  }
452
459
  }
@@ -472,18 +479,18 @@ export class Manifest {
472
479
  */
473
480
 
474
481
  /**
475
- * @template T
476
- * @typedef {import('appium/types').ExtPackageJson<T>} ExtPackageJson
482
+ * @template {ExtensionType} ExtType
483
+ * @typedef {import('appium/types').ExtPackageJson<ExtType>} ExtPackageJson
477
484
  */
478
485
 
479
486
  /**
480
- * @template T
481
- * @typedef {import('appium/types').ExtManifest<T>} ExtManifest
487
+ * @template {ExtensionType} ExtType
488
+ * @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
482
489
  */
483
490
 
484
491
  /**
485
- * @template T
486
- * @typedef {import('appium/types').ExtRecord<T>} ExtRecord
492
+ * @template {ExtensionType} ExtType
493
+ * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
487
494
  */
488
495
 
489
496
  /**
@@ -72,7 +72,7 @@ export class PluginConfig extends ExtensionConfig {
72
72
 
73
73
  /**
74
74
  *
75
- * @param {(keyof PluginRecord)[]} activeNames
75
+ * @param {(keyof import('appium/types').ExtRecord<PluginType>)[]} activeNames
76
76
  * @returns {void}
77
77
  */
78
78
  print(activeNames) {
@@ -106,7 +106,6 @@ export class PluginConfig extends ExtensionConfig {
106
106
  */
107
107
 
108
108
  /**
109
- * @typedef {import('appium/types').PluginRecord} PluginRecord
110
109
  * @typedef {import('@appium/types').PluginType} PluginType
111
110
  * @typedef {import('appium/types').ExtMetadata<PluginType>} PluginMetadata
112
111
  * @typedef {import('./manifest').Manifest} Manifest
package/lib/logsink.js CHANGED
@@ -33,6 +33,8 @@ const npmToWinstonLevels = {
33
33
  error: 'error',
34
34
  };
35
35
 
36
+ const encounteredPrefixes = [];
37
+
36
38
  let log = null;
37
39
  let useLocalTimeZone = false;
38
40
 
@@ -186,8 +188,21 @@ async function createTransports(args) {
186
188
  return transports;
187
189
  }
188
190
 
191
+ function getColorizedPrefix(prefix) {
192
+ let prefixId = prefix.split('@')[0].trim();
193
+ prefixId = prefixId.split(' (')[0].trim();
194
+ if (encounteredPrefixes.indexOf(prefixId) < 0) {
195
+ encounteredPrefixes.push(prefixId);
196
+ }
197
+ // using a multiple of 16 should cause 16 colors to be created
198
+ const colorNumber = encounteredPrefixes.indexOf(prefixId) * 16;
199
+ // use the modulus to cycle around color wheel
200
+ return `\x1b[38;5;${colorNumber % 256}m${prefix}\x1b[0m`;
201
+ }
202
+
189
203
  async function init(args) {
190
204
  npmlog.level = 'silent';
205
+
191
206
  // set de facto param passed to timestamp function
192
207
  useLocalTimeZone = args.localTimezone;
193
208
 
@@ -205,11 +220,17 @@ async function init(args) {
205
220
  let msg = logObj.message;
206
221
  if (logObj.prefix) {
207
222
  const prefix = `[${logObj.prefix}]`;
208
- msg = `${args.logNoColors ? prefix : prefix.magenta} ${msg}`;
209
- }
210
- log[winstonLevel](msg);
211
- if (args.logHandler && _.isFunction(args.logHandler)) {
212
- args.logHandler(logObj.level, msg);
223
+ if (args.logNoColors) {
224
+ msg = `${prefix} ${msg}`;
225
+ } if (prefix === '[Appium]') {
226
+ msg = `${prefix.magenta} ${msg}`;
227
+ } else {
228
+ msg = `${getColorizedPrefix(prefix)} ${msg}`;
229
+ }
230
+ log[winstonLevel](msg);
231
+ if (args.logHandler && _.isFunction(args.logHandler)) {
232
+ args.logHandler(logObj.level, msg);
233
+ }
213
234
  }
214
235
  });
215
236
  }
package/lib/main.js CHANGED
@@ -23,10 +23,17 @@ import {
23
23
  } from './config';
24
24
  import {readConfigFile} from './config-file';
25
25
  import {loadExtensions, getActivePlugins, getActiveDrivers} from './extension';
26
- import {DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND} from './constants';
26
+ import {SERVER_SUBCOMMAND} from './constants';
27
27
  import registerNode from './grid-register';
28
28
  import {getDefaultsForSchema, validate} from './schema/schema';
29
- import {inspect, adjustNodePath} from './utils';
29
+ import {
30
+ inspect,
31
+ adjustNodePath,
32
+ isDriverCommandArgs,
33
+ isExtensionCommandArgs,
34
+ isPluginCommandArgs,
35
+ isServerCommandArgs,
36
+ } from './utils';
30
37
 
31
38
  const {resolveAppiumHome} = env;
32
39
 
@@ -143,16 +150,6 @@ function getExtraMethodMap(driverClasses, pluginClasses) {
143
150
  {}
144
151
  );
145
152
  }
146
-
147
- /**
148
- * @template [T=WithServerSubcommand]
149
- * @param {Args<T>} args
150
- * @returns {args is Args<WithServerSubcommand>}
151
- */
152
- function areServerCommandArgs(args) {
153
- return args.subcommand === SERVER_SUBCOMMAND;
154
- }
155
-
156
153
  /**
157
154
  * Initializes Appium, but does not start the server.
158
155
  *
@@ -160,9 +157,10 @@ function areServerCommandArgs(args) {
160
157
  *
161
158
  * If `args` contains a non-empty `subcommand` which is not `server`, this function will return an empty object.
162
159
  *
163
- * @template [T=WithServerSubcommand]
164
- * @param {Args<T>} [args] - Partial args (progammatic usage only)
165
- * @returns {Promise<ServerInitResult | ExtCommandInitResult>}
160
+ * @template {CliCommand} [Cmd=ServerCommand]
161
+ * @template {CliExtensionSubcommand|void} [SubCmd=void]
162
+ * @param {Args<Cmd, SubCmd>} [args] - Partial args (progammatic usage only)
163
+ * @returns {Promise<InitResult<Cmd>>}
166
164
  * @example
167
165
  * import {init, getSchema} from 'appium';
168
166
  * const options = {}; // config object
@@ -178,7 +176,7 @@ async function init(args) {
178
176
 
179
177
  const parser = getParser();
180
178
  let throwInsteadOfExit = false;
181
- /** @type {Args<T>} */
179
+ /** @type {Args<Cmd, SubCmd>} */
182
180
  let preConfigArgs;
183
181
 
184
182
  if (args) {
@@ -193,7 +191,7 @@ async function init(args) {
193
191
  preConfigArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
194
192
  } else {
195
193
  // otherwise parse from CLI
196
- preConfigArgs = /** @type {Args<T>} */ (parser.parseArgs());
194
+ preConfigArgs = /** @type {Args<Cmd, SubCmd>} */ (parser.parseArgs());
197
195
  }
198
196
 
199
197
  const configResult = await readConfigFile(preConfigArgs.configFile);
@@ -211,15 +209,15 @@ async function init(args) {
211
209
  // 1. command line args
212
210
  // 2. config file
213
211
  // 3. defaults from config file.
214
- if (areServerCommandArgs(preConfigArgs)) {
212
+ if (isServerCommandArgs(preConfigArgs)) {
215
213
  const defaults = getDefaultsForSchema(false);
216
214
 
217
215
  /** @type {ParsedArgs} */
218
- const serverArgs = _.defaultsDeep(preConfigArgs, configResult.config?.server, defaults);
216
+ const serverArgs = _.defaultsDeep({}, preConfigArgs, configResult.config?.server, defaults);
219
217
 
220
218
  if (preConfigArgs.showConfig) {
221
219
  showConfig(getNonDefaultServerArgs(preConfigArgs), configResult, defaults, serverArgs);
222
- return {};
220
+ return /** @type {InitResult<Cmd>} */ ({});
223
221
  }
224
222
 
225
223
  await logsinkInit(serverArgs);
@@ -252,49 +250,49 @@ async function init(args) {
252
250
  appiumDriver.driverConfig = driverConfig;
253
251
  await preflightChecks(serverArgs, throwInsteadOfExit);
254
252
 
255
- return /** @type {ServerInitResult} */ ({
253
+ return /** @type {InitResult<Cmd>} */ ({
256
254
  appiumDriver,
257
255
  parsedArgs: serverArgs,
258
256
  driverConfig,
259
257
  pluginConfig,
260
258
  });
261
259
  } else {
262
- const extensionCommandArgs = /** @type {Args<import('appium/types').WithExtSubcommand>} */ (
263
- preConfigArgs
264
- );
265
- // if the user has requested the 'driver' CLI, don't run the normal server,
266
- // but instead pass control to the driver CLI
267
- if (preConfigArgs.subcommand === DRIVER_TYPE) {
268
- await runExtensionCommand(extensionCommandArgs, driverConfig);
269
- return {};
270
- }
271
- if (preConfigArgs.subcommand === PLUGIN_TYPE) {
272
- await runExtensionCommand(extensionCommandArgs, pluginConfig);
273
- return {};
260
+ if (isExtensionCommandArgs(preConfigArgs)) {
261
+ // if the user has requested the 'driver' CLI, don't run the normal server,
262
+ // but instead pass control to the driver CLI
263
+ if (isDriverCommandArgs(preConfigArgs)) {
264
+ await runExtensionCommand(preConfigArgs, driverConfig);
265
+ }
266
+ if (isPluginCommandArgs(preConfigArgs)) {
267
+ await runExtensionCommand(preConfigArgs, pluginConfig);
268
+ }
274
269
  }
275
- /* istanbul ignore next */
276
- return {}; // should never happen
270
+ return /** @type {InitResult<Cmd>} */ ({});
277
271
  }
278
272
  }
279
273
 
280
274
  /**
281
275
  * Initializes Appium's config. Starts server if appropriate and resolves the
282
276
  * server instance if so; otherwise resolves w/ `undefined`.
283
- * @template [T=WithServerSubcommand]
284
- * @param {Args<T>} [args] - Arguments from CLI or otherwise
285
- * @returns {Promise<import('@appium/types').AppiumServer|undefined>}
277
+ * @template {CliCommand} [Cmd=ServerCommand]
278
+ * @template {CliExtensionSubcommand|void} [SubCmd=void]
279
+ * @param {Args<Cmd, SubCmd>} [args] - Arguments from CLI or otherwise
280
+ * @returns {Promise<Cmd extends ServerCommand ? import('@appium/types').AppiumServer : void>}
286
281
  */
287
282
  async function main(args) {
288
- const {appiumDriver, parsedArgs, pluginConfig, driverConfig} = /** @type {ServerInitResult} */ (
289
- await init(args)
290
- );
283
+ const initResult = await init(args);
291
284
 
292
- if (!appiumDriver || !parsedArgs || !pluginConfig || !driverConfig) {
285
+ if (_.isEmpty(initResult)) {
293
286
  // if this branch is taken, we've run a different subcommand, so there's nothing
294
287
  // left to do here.
295
- return;
288
+ return /** @type {Cmd extends ServerCommand ? import('@appium/types').AppiumServer : void} */ (
289
+ undefined
290
+ );
296
291
  }
297
292
 
293
+ const {appiumDriver, pluginConfig, driverConfig, parsedArgs} =
294
+ /** @type {InitResult<ServerCommand>} */ (initResult);
295
+
298
296
  const pluginClasses = getActivePlugins(pluginConfig, parsedArgs.usePlugins);
299
297
  // set the active plugins on the umbrella driver so it can use them for commands
300
298
  appiumDriver.pluginClasses = pluginClasses;
@@ -377,7 +375,9 @@ async function main(args) {
377
375
  driverConfig.print();
378
376
  pluginConfig.print([...pluginClasses.values()]);
379
377
 
380
- return server;
378
+ return /** @type {Cmd extends ServerCommand ? import('@appium/types').AppiumServer : void} */ (
379
+ server
380
+ );
381
381
  }
382
382
 
383
383
  // NOTE: this is here for backwards compat for any scripts referencing `main.js` directly
@@ -397,7 +397,12 @@ export {main, init, resolveAppiumHome};
397
397
  * @typedef {import('@appium/types').PluginType} PluginType
398
398
  * @typedef {import('@appium/types').DriverClass} DriverClass
399
399
  * @typedef {import('@appium/types').PluginClass} PluginClass
400
- * @typedef {import('appium/types').WithServerSubcommand} WithServerSubcommand
400
+ * @typedef {import('appium/types').CliCommand} CliCommand
401
+ * @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
402
+ * @typedef {import('appium/types').CliExtensionCommand} CliExtensionCommand
403
+ * @typedef {import('appium/types').ServerCommand} ServerCommand
404
+ * @typedef {import('appium/types').DriverCommand} DriverCommand
405
+ * @typedef {import('appium/types').PluginCommand} PluginCommand
401
406
  * @typedef {import('./extension').DriverNameMap} DriverNameMap
402
407
  * @typedef {import('./extension').PluginNameMap} PluginNameMap
403
408
  */
@@ -414,15 +419,18 @@ export {main, init, resolveAppiumHome};
414
419
  */
415
420
 
416
421
  /**
417
- * @typedef {ServerInitData & import('./extension').ExtensionConfigs} ServerInitResult
422
+ * @template {CliCommand} Cmd
423
+ * @typedef {Cmd extends ServerCommand ? ServerInitData & import('./extension').ExtensionConfigs : ExtCommandInitResult} InitResult
418
424
  */
419
425
 
420
426
  /**
421
- * @template [T=WithServerSubcommand]
422
- * @typedef {import('appium/types').Args<T>} Args
427
+ * @template {CliCommand} [Cmd=ServerCommand]
428
+ * @template {CliExtensionSubcommand|void} [SubCmd=void]
429
+ * @typedef {import('appium/types').Args<Cmd, SubCmd>} Args
423
430
  */
424
431
 
425
432
  /**
426
- * @template [T=WithServerSubcommand]
427
- * @typedef {import('appium/types').ParsedArgs<T>} ParsedArgs
433
+ * @template {CliCommand} [Cmd=ServerCommand]
434
+ * @template {CliExtensionSubcommand|void} [SubCmd=void]
435
+ * @typedef {import('appium/types').ParsedArgs<Cmd, SubCmd>} ParsedArgs
428
436
  */
@@ -43,6 +43,8 @@ export class RoachHotelMap extends Map {
43
43
  */
44
44
  export const ALLOWED_SCHEMA_EXTENSIONS = new Set(['.json', '.js', '.cjs']);
45
45
 
46
+ const SCHEMA_KEY = '$schema';
47
+
46
48
  /**
47
49
  * A wrapper around Ajv and schema-related functions.
48
50
  *
@@ -316,7 +318,7 @@ class AppiumSchema {
316
318
  }
317
319
  const normalizedExtName = _.kebabCase(extName);
318
320
  if (this.hasRegisteredSchema(extType, normalizedExtName)) {
319
- if (this._registeredSchemas[extType].get(normalizedExtName) === schema) {
321
+ if (_.isEqual(this._registeredSchemas[extType].get(normalizedExtName), schema)) {
320
322
  return;
321
323
  }
322
324
  throw new SchemaNameConflictError(extType, extName);
@@ -441,6 +443,9 @@ class AppiumSchema {
441
443
  for (const {properties, prefix} of stack) {
442
444
  const pairs = _.toPairs(properties);
443
445
  for (const [key, value] of pairs) {
446
+ if (key === SCHEMA_KEY) {
447
+ continue;
448
+ }
444
449
  const {properties, $ref} = value;
445
450
  if (properties) {
446
451
  stack.push({