appium 2.0.0-beta.46 → 2.0.0-beta.48

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 (141) 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.d.ts +24 -5
  9. package/build/lib/cli/driver-command.d.ts.map +1 -1
  10. package/build/lib/cli/driver-command.js +78 -88
  11. package/build/lib/cli/driver-command.js.map +1 -1
  12. package/build/lib/cli/extension-command.d.ts +33 -24
  13. package/build/lib/cli/extension-command.d.ts.map +1 -1
  14. package/build/lib/cli/extension-command.js +729 -512
  15. package/build/lib/cli/extension-command.js.map +1 -1
  16. package/build/lib/cli/extension.d.ts +7 -6
  17. package/build/lib/cli/extension.d.ts.map +1 -1
  18. package/build/lib/cli/extension.js +68 -65
  19. package/build/lib/cli/extension.js.map +1 -1
  20. package/build/lib/cli/parser.d.ts +3 -3
  21. package/build/lib/cli/parser.d.ts.map +1 -1
  22. package/build/lib/cli/parser.js +234 -192
  23. package/build/lib/cli/parser.js.map +1 -1
  24. package/build/lib/cli/plugin-command.js +58 -87
  25. package/build/lib/cli/plugin-command.js.map +1 -1
  26. package/build/lib/cli/utils.js +66 -69
  27. package/build/lib/cli/utils.js.map +1 -1
  28. package/build/lib/config-file.d.ts.map +1 -1
  29. package/build/lib/config-file.js +189 -120
  30. package/build/lib/config-file.js.map +1 -1
  31. package/build/lib/config.d.ts.map +1 -1
  32. package/build/lib/config.js +254 -213
  33. package/build/lib/config.js.map +1 -1
  34. package/build/lib/constants.d.ts +6 -5
  35. package/build/lib/constants.d.ts.map +1 -1
  36. package/build/lib/constants.js +65 -59
  37. package/build/lib/constants.js.map +1 -1
  38. package/build/lib/extension/driver-config.js +199 -164
  39. package/build/lib/extension/driver-config.js.map +1 -1
  40. package/build/lib/extension/extension-config.d.ts +33 -26
  41. package/build/lib/extension/extension-config.d.ts.map +1 -1
  42. package/build/lib/extension/extension-config.js +541 -396
  43. package/build/lib/extension/extension-config.js.map +1 -1
  44. package/build/lib/extension/index.js +98 -68
  45. package/build/lib/extension/index.js.map +1 -1
  46. package/build/lib/extension/manifest-migrations.d.ts +27 -0
  47. package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
  48. package/build/lib/extension/manifest-migrations.js +118 -0
  49. package/build/lib/extension/manifest-migrations.js.map +1 -0
  50. package/build/lib/extension/manifest.d.ts +35 -63
  51. package/build/lib/extension/manifest.d.ts.map +1 -1
  52. package/build/lib/extension/manifest.js +500 -240
  53. package/build/lib/extension/manifest.js.map +1 -1
  54. package/build/lib/extension/package-changed.js +57 -61
  55. package/build/lib/extension/package-changed.js.map +1 -1
  56. package/build/lib/extension/plugin-config.d.ts +2 -3
  57. package/build/lib/extension/plugin-config.d.ts.map +1 -1
  58. package/build/lib/extension/plugin-config.js +94 -70
  59. package/build/lib/extension/plugin-config.js.map +1 -1
  60. package/build/lib/grid-register.js +119 -137
  61. package/build/lib/grid-register.js.map +1 -1
  62. package/build/lib/logger.d.ts +1 -1
  63. package/build/lib/logger.d.ts.map +1 -1
  64. package/build/lib/logger.js +5 -15
  65. package/build/lib/logger.js.map +1 -1
  66. package/build/lib/logsink.d.ts.map +1 -1
  67. package/build/lib/logsink.js +189 -183
  68. package/build/lib/logsink.js.map +1 -1
  69. package/build/lib/main.d.ts +19 -12
  70. package/build/lib/main.d.ts.map +1 -1
  71. package/build/lib/main.js +330 -304
  72. package/build/lib/main.js.map +1 -1
  73. package/build/lib/schema/arg-spec.js +153 -108
  74. package/build/lib/schema/arg-spec.js.map +1 -1
  75. package/build/lib/schema/cli-args.js +203 -164
  76. package/build/lib/schema/cli-args.js.map +1 -1
  77. package/build/lib/schema/cli-transformers.js +117 -72
  78. package/build/lib/schema/cli-transformers.js.map +1 -1
  79. package/build/lib/schema/index.js +17 -32
  80. package/build/lib/schema/index.js.map +1 -1
  81. package/build/lib/schema/keywords.js +125 -67
  82. package/build/lib/schema/keywords.js.map +1 -1
  83. package/build/lib/schema/schema.d.ts.map +1 -1
  84. package/build/lib/schema/schema.js +582 -417
  85. package/build/lib/schema/schema.js.map +1 -1
  86. package/build/lib/utils.d.ts +41 -255
  87. package/build/lib/utils.d.ts.map +1 -1
  88. package/build/lib/utils.js +342 -193
  89. package/build/lib/utils.js.map +1 -1
  90. package/build/tsconfig.tsbuildinfo +1 -1
  91. package/build/types/cli.d.ts +45 -34
  92. package/build/types/cli.d.ts.map +1 -1
  93. package/build/types/cli.js +3 -0
  94. package/build/types/cli.js.map +1 -0
  95. package/build/types/index.d.ts +1 -2
  96. package/build/types/index.d.ts.map +1 -1
  97. package/build/types/index.js +19 -0
  98. package/build/types/index.js.map +1 -0
  99. package/build/types/manifest/base.d.ts +135 -0
  100. package/build/types/manifest/base.d.ts.map +1 -0
  101. package/build/types/manifest/base.js +3 -0
  102. package/build/types/manifest/base.js.map +1 -0
  103. package/build/types/manifest/index.d.ts +19 -0
  104. package/build/types/manifest/index.d.ts.map +1 -0
  105. package/build/types/manifest/index.js +40 -0
  106. package/build/types/manifest/index.js.map +1 -0
  107. package/build/types/manifest/v3.d.ts +139 -0
  108. package/build/types/manifest/v3.d.ts.map +1 -0
  109. package/build/types/manifest/v3.js +3 -0
  110. package/build/types/manifest/v3.js.map +1 -0
  111. package/lib/appium.js +1 -1
  112. package/lib/cli/args.js +1 -1
  113. package/lib/cli/driver-command.js +17 -0
  114. package/lib/cli/extension-command.js +119 -65
  115. package/lib/cli/extension.js +9 -8
  116. package/lib/cli/parser.js +2 -2
  117. package/lib/config-file.js +2 -3
  118. package/lib/config.js +3 -2
  119. package/lib/constants.js +7 -5
  120. package/lib/extension/extension-config.js +52 -47
  121. package/lib/extension/manifest-migrations.js +120 -0
  122. package/lib/extension/manifest.js +184 -103
  123. package/lib/extension/plugin-config.js +1 -2
  124. package/lib/logsink.js +26 -5
  125. package/lib/main.js +58 -50
  126. package/lib/schema/schema.js +6 -1
  127. package/lib/utils.js +62 -0
  128. package/package.json +24 -25
  129. package/scripts/autoinstall-extensions.js +78 -26
  130. package/types/cli.ts +81 -42
  131. package/types/index.ts +1 -2
  132. package/types/manifest/README.md +30 -0
  133. package/types/manifest/base.ts +158 -0
  134. package/types/manifest/index.ts +27 -0
  135. package/types/manifest/v3.ts +161 -0
  136. package/build/types/appium-manifest.d.ts +0 -59
  137. package/build/types/appium-manifest.d.ts.map +0 -1
  138. package/build/types/extension-manifest.d.ts +0 -55
  139. package/build/types/extension-manifest.d.ts.map +0 -1
  140. package/types/appium-manifest.ts +0 -73
  141. 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,12 +32,14 @@ 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
  /**
43
39
  * Given a `package.json` return `true` if it represents an Appium Extension (either a driver or plugin).
44
40
  *
41
+ * _This is a type guard; not a validator._
42
+ *
45
43
  * The `package.json` must have an `appium` property which is an object.
46
44
  * @param {any} value
47
45
  * @returns {value is ExtPackageJson<ExtensionType>}
@@ -54,32 +52,35 @@ function isExtension(value) {
54
52
  _.isString(value.version)
55
53
  );
56
54
  }
55
+
57
56
  /**
58
57
  * Given a `package.json`, return `true` if it represents an Appium Driver.
59
58
  *
60
- * To be considered a driver, a `package.json` must have a fields
61
- * `appium.driverName`, `appium.automationName` and `appium.platformNames`.
59
+ * _This is a type guard; not a validator._
60
+ *
61
+ * To be considered a driver, a `package.json` must have an `appium.driverName` field.
62
+ *
63
+ * Further validation of the `appium` property happens elsewhere.
62
64
  * @param {any} value - Value to test
63
65
  * @returns {value is ExtPackageJson<DriverType>}
64
66
  */
65
67
  function isDriver(value) {
66
- return (
67
- isExtension(value) &&
68
- _.isString(_.get(value, 'appium.driverName')) &&
69
- _.isString(_.get(value, 'appium.automationName')) &&
70
- _.isArray(_.get(value, 'appium.platformNames'))
71
- );
68
+ return isExtension(value) && 'driverName' in value.appium && _.isString(value.appium.driverName);
72
69
  }
73
70
 
74
71
  /**
75
72
  * Given a `package.json`, return `true` if it represents an Appium Plugin.
76
73
  *
74
+ * _This is a type guard; not a validator._
75
+ *
77
76
  * To be considered a plugin, a `package.json` must have an `appium.pluginName` field.
77
+ *
78
+ * Further validation of the `appium` property happens elsewhere.
78
79
  * @param {any} value - Value to test
79
80
  * @returns {value is ExtPackageJson<PluginType>}
80
81
  */
81
82
  function isPlugin(value) {
82
- return isExtension(value) && _.isString(_.get(value, 'appium.pluginName'));
83
+ return isExtension(value) && 'pluginName' in value.appium && _.isString(value.appium.pluginName);
83
84
  }
84
85
 
85
86
  /**
@@ -93,23 +94,21 @@ export class Manifest {
93
94
  *
94
95
  * Contains proxies for automatic persistence on disk
95
96
  * @type {ManifestData}
96
- * @private
97
97
  */
98
- _data;
98
+ #data;
99
99
 
100
100
  /**
101
101
  * Path to `APPIUM_HOME`.
102
- * @private
103
102
  * @type {Readonly<string>}
104
103
  */
105
- _appiumHome;
104
+ #appiumHome;
106
105
 
107
106
  /**
108
107
  * Path to `extensions.yaml`
109
108
  * @type {string}
110
109
  * Not set until {@link Manifest.read} is called.
111
110
  */
112
- _manifestPath;
111
+ #manifestPath;
113
112
 
114
113
  /**
115
114
  * Helps avoid writing multiple times.
@@ -118,10 +117,9 @@ export class Manifest {
118
117
  * set to a `Promise`. When the call to `write()` is complete, the `Promise`
119
118
  * will resolve and then this value will be set to `undefined`. Concurrent calls
120
119
  * made while this value is a `Promise` will return the `Promise` itself.
121
- * @private
122
120
  * @type {Promise<boolean>|undefined}
123
121
  */
124
- _writing;
122
+ #writing;
125
123
 
126
124
  /**
127
125
  * Helps avoid reading multiple times.
@@ -130,10 +128,9 @@ export class Manifest {
130
128
  * set to a `Promise`. When the call to `read()` is complete, the `Promise`
131
129
  * will resolve and then this value will be set to `undefined`. Concurrent calls
132
130
  * made while this value is a `Promise` will return the `Promise` itself.
133
- * @private
134
131
  * @type {Promise<void>|undefined}
135
132
  */
136
- _reading;
133
+ #reading;
137
134
 
138
135
  /**
139
136
  * Sets internal data to a fresh clone of {@link INITIAL_MANIFEST_DATA}
@@ -143,8 +140,8 @@ export class Manifest {
143
140
  * @private
144
141
  */
145
142
  constructor(appiumHome) {
146
- this._appiumHome = appiumHome;
147
- this._data = _.cloneDeep(INITIAL_MANIFEST_DATA);
143
+ this.#appiumHome = appiumHome;
144
+ this.#data = _.cloneDeep(INITIAL_MANIFEST_DATA);
148
145
  }
149
146
 
150
147
  /**
@@ -174,7 +171,23 @@ export class Manifest {
174
171
  const onMatch = async (filepath) => {
175
172
  try {
176
173
  const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
177
- if (isDriver(pkg) || isPlugin(pkg)) {
174
+ if (isExtension(pkg)) {
175
+ const extType = isDriver(pkg) ? DRIVER_TYPE : PLUGIN_TYPE;
176
+ /**
177
+ * this should only be 'unknown' if the extension's `package.json` is invalid
178
+ * @type {string}
179
+ */
180
+ const name = isDriver(pkg)
181
+ ? pkg.appium.driverName
182
+ : isPlugin(pkg)
183
+ ? pkg.appium.pluginName
184
+ : '(unknown)';
185
+ if (
186
+ (isDriver(pkg) && !this.hasDriver(name)) ||
187
+ (isPlugin(pkg) && !this.hasPlugin(name))
188
+ ) {
189
+ log.info(`Discovered installed ${extType} "${name}"`);
190
+ }
178
191
  const changed = this.addExtensionFromPackage(pkg, filepath);
179
192
  didChange = didChange || changed;
180
193
  }
@@ -187,14 +200,14 @@ export class Manifest {
187
200
  */
188
201
  const queue = [
189
202
  // look at `package.json` in `APPIUM_HOME` only
190
- onMatch(path.join(this._appiumHome, 'package.json')),
203
+ onMatch(path.join(this.#appiumHome, 'package.json')),
191
204
  ];
192
205
 
193
206
  // add dependencies to the queue
194
207
  await new B((resolve, reject) => {
195
208
  glob(
196
209
  'node_modules/{*,@*/*}/package.json',
197
- {cwd: this._appiumHome, silent: true, absolute: true},
210
+ {cwd: this.#appiumHome, silent: true, absolute: true},
198
211
  // eslint-disable-next-line promise/prefer-await-to-callbacks
199
212
  (err) => {
200
213
  if (err) {
@@ -221,7 +234,7 @@ export class Manifest {
221
234
  * @returns {boolean}
222
235
  */
223
236
  hasDriver(name) {
224
- return Boolean(this._data.drivers[name]);
237
+ return Boolean(this.#data.drivers[name]);
225
238
  }
226
239
 
227
240
  /**
@@ -230,17 +243,16 @@ export class Manifest {
230
243
  * @returns {boolean}
231
244
  */
232
245
  hasPlugin(name) {
233
- return Boolean(this._data.plugins[name]);
246
+ return Boolean(this.#data.plugins[name]);
234
247
  }
235
248
 
236
249
  /**
237
250
  * Given a path to a `package.json`, add it as either a driver or plugin to the manifest.
238
251
  *
239
- * Will _not_ overwrite existing entries.
240
252
  * @template {ExtensionType} ExtType
241
253
  * @param {ExtPackageJson<ExtType>} pkgJson
242
254
  * @param {string} pkgPath
243
- * @returns {boolean} - `true` upon success, `false` if the extension is already registered.
255
+ * @returns {boolean} - `true` if this method did anything.
244
256
  */
245
257
  addExtensionFromPackage(pkgJson, pkgPath) {
246
258
  const extensionPath = path.dirname(pkgPath);
@@ -254,29 +266,32 @@ export class Manifest {
254
266
  appiumVersion: pkgJson.peerDependencies?.appium,
255
267
  installType: INSTALL_TYPE_NPM,
256
268
  installSpec: `${pkgJson.name}@${pkgJson.version}`,
269
+ installPath: extensionPath,
257
270
  };
258
271
 
259
272
  if (isDriver(pkgJson)) {
260
- if (!this.hasDriver(pkgJson.appium.driverName)) {
261
- this.addExtension(DRIVER_TYPE, pkgJson.appium.driverName, {
262
- ..._.omit(pkgJson.appium, 'driverName'),
263
- ...internal,
264
- });
273
+ const value = {
274
+ ..._.omit(pkgJson.appium, 'driverName'),
275
+ ...internal,
276
+ };
277
+ if (!_.isEqual(value, this.#data.drivers[pkgJson.appium.driverName])) {
278
+ this.setExtension(DRIVER_TYPE, pkgJson.appium.driverName, value);
265
279
  return true;
266
280
  }
267
281
  return false;
268
282
  } else if (isPlugin(pkgJson)) {
269
- if (!this.hasPlugin(pkgJson.appium.pluginName)) {
270
- this.addExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, {
271
- ..._.omit(pkgJson.appium, 'pluginName'),
272
- ...internal,
273
- });
283
+ const value = {
284
+ ..._.omit(pkgJson.appium, 'pluginName'),
285
+ ...internal,
286
+ };
287
+ if (!_.isEqual(value, this.#data.plugins[pkgJson.appium.pluginName])) {
288
+ this.setExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, value);
274
289
  return true;
275
290
  }
276
291
  return false;
277
292
  } else {
278
293
  throw new TypeError(
279
- `The extension in ${extensionPath} is neither a valid driver nor a valid plugin.`
294
+ `The extension in ${extensionPath} is neither a valid ${DRIVER_TYPE} nor a valid ${PLUGIN_TYPE}.`
280
295
  );
281
296
  }
282
297
  }
@@ -292,24 +307,48 @@ export class Manifest {
292
307
  * @param {ExtManifest<ExtType>} extData - Extension metadata
293
308
  * @returns {ExtManifest<ExtType>} A clone of `extData`, potentially with a mutated `appiumVersion` field
294
309
  */
295
- addExtension(extType, extName, extData) {
296
- const data = _.clone(extData);
297
- this._data[`${extType}s`][extName] = data;
310
+ setExtension(extType, extName, extData) {
311
+ const data = _.cloneDeep(extData);
312
+ this.#data[`${extType}s`][extName] = data;
298
313
  return data;
299
314
  }
300
315
 
301
316
  /**
302
- * Returns the APPIUM_HOME path
317
+ * Sets the schema revision
318
+ * @param {keyof import('./manifest-migrations').ManifestDataVersions} rev
319
+ */
320
+ setSchemaRev(rev) {
321
+ this.#data.schemaRev = rev;
322
+ }
323
+
324
+ /**
325
+ * Remove an extension from the manifest.
326
+ * @param {ExtensionType} extType
327
+ * @param {string} extName
328
+ */
329
+ deleteExtension(extType, extName) {
330
+ delete this.#data[`${extType}s`][extName];
331
+ }
332
+
333
+ /**
334
+ * Returns the `APPIUM_HOME` path
303
335
  */
304
336
  get appiumHome() {
305
- return this._appiumHome;
337
+ return this.#appiumHome;
306
338
  }
307
339
 
308
340
  /**
309
- * Returns the path to the manifest file
341
+ * Returns the path to the manifest file (`extensions.yaml`)
310
342
  */
311
343
  get manifestPath() {
312
- return this._manifestPath;
344
+ return this.#manifestPath;
345
+ }
346
+
347
+ /**
348
+ * Returns the schema rev of this manifest
349
+ */
350
+ get schemaRev() {
351
+ return this.#data.schemaRev;
313
352
  }
314
353
 
315
354
  /**
@@ -317,47 +356,62 @@ export class Manifest {
317
356
  *
318
357
  * @template {ExtensionType} ExtType
319
358
  * @param {ExtType} extType
320
- * @returns {ExtRecord<ExtType>}
359
+ * @returns {Readonly<ExtRecord<ExtType>>}
321
360
  */
322
361
  getExtensionData(extType) {
323
- return this._data[/** @type {string} */ (`${extType}s`)];
362
+ return this.#data[/** @type {string} */ (`${extType}s`)];
324
363
  }
325
364
 
326
365
  /**
327
366
  * Reads manifest from disk and _overwrites_ the internal data.
328
367
  *
329
- * If the manifest does not exist on disk, an {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created.
368
+ * If the manifest does not exist on disk, an
369
+ * {@link INITIAL_MANIFEST_DATA "empty"} manifest file will be created, as
370
+ * well as its directory if needed.
371
+ *
372
+ * This will also, if necessary:
373
+ * 1. perform a migration of the manifest data
374
+ * 2. sync the manifest with extensions on-disk (kind of like "auto
375
+ * discovery")
376
+ * 3. write the manifest to disk.
330
377
  *
331
- * 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.
378
+ * Only one read operation can happen at a time.
332
379
  *
333
- * Only one read operation should happen at a time. This is controlled via the {@link Manifest._reading} property.
334
380
  * @returns {Promise<ManifestData>} The data
335
381
  */
336
382
  async read() {
337
- if (this._reading) {
338
- await this._reading;
339
- return this._data;
383
+ if (this.#reading) {
384
+ await this.#reading;
385
+ return this.#data;
340
386
  }
341
387
 
342
- this._reading = (async () => {
388
+ this.#reading = (async () => {
343
389
  /** @type {ManifestData} */
344
390
  let data;
345
- let isNewFile = false;
346
- await this._setManifestPath();
391
+ /**
392
+ * This will be `true` if, after reading, we need to update the manifest data
393
+ * and write it again to disk.
394
+ */
395
+ let shouldWrite = false;
396
+ await this.#setManifestPath();
347
397
  try {
348
- log.debug(`Reading ${this._manifestPath}...`);
349
- const yaml = await fs.readFile(this._manifestPath, 'utf8');
398
+ const yaml = await fs.readFile(this.#manifestPath, 'utf8');
350
399
  data = YAML.parse(yaml);
351
- log.debug(`Parsed manifest file: ${JSON.stringify(data, null, 2)}`);
400
+ log.debug(
401
+ `Parsed manifest file at ${this.#manifestPath}: ${JSON.stringify(data, null, 2)}`
402
+ );
352
403
  } catch (err) {
353
404
  if (err.code === 'ENOENT') {
405
+ log.debug(`No manifest file found at ${this.#manifestPath}; creating`);
354
406
  data = _.cloneDeep(INITIAL_MANIFEST_DATA);
355
- isNewFile = true;
407
+ shouldWrite = true;
356
408
  } else {
357
- if (this._manifestPath) {
409
+ if (this.#manifestPath) {
358
410
  throw new Error(
359
411
  `Appium had trouble loading the extension installation ` +
360
- `cache file (${this._manifestPath}). It may be invalid YAML. Specific error: ${err.message}`
412
+ `cache file (${this.#manifestPath}). It may be invalid YAML. Specific error: ${
413
+ err.message
414
+ }`
361
415
  );
362
416
  } else {
363
417
  throw new Error(
@@ -367,47 +421,73 @@ export class Manifest {
367
421
  }
368
422
  }
369
423
 
370
- this._data = data;
371
- let installedExtensionsChanged = false;
424
+ this.#data = data;
425
+
426
+ /**
427
+ * the only way `shouldWrite` is `true` is if we have a new file. a new
428
+ * file will get the latest schema revision, so we can skip the migration.
429
+ */
430
+ if (!shouldWrite && (data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
431
+ log.debug(
432
+ `Updating manifest schema from rev ${data.schemaRev ?? '(none)'} to ${CURRENT_SCHEMA_REV}`
433
+ );
434
+ shouldWrite = await migrate(this);
435
+ }
436
+
437
+ /**
438
+ * we still may want to sync with installed extensions even if we have a
439
+ * new file. right now this is limited to the following cases:
440
+ * 1. we have a brand new manifest file
441
+ * 2. we have performed a migration on a manifest file
442
+ * 3. `appium` is a dependency within `package.json`, and `package.json`
443
+ * has changed since last time we checked.
444
+ *
445
+ * It may also make sense to sync with the extensions in an arbitrary
446
+ * `APPIUM_HOME`, but we don't do that here.
447
+ */
372
448
  if (
373
- (await env.hasAppiumDependency(this.appiumHome)) &&
374
- (await packageDidChange(this.appiumHome))
449
+ shouldWrite ||
450
+ ((await env.hasAppiumDependency(this.appiumHome)) &&
451
+ (await packageDidChange(this.appiumHome)))
375
452
  ) {
376
- installedExtensionsChanged = await this.syncWithInstalledExtensions();
453
+ log.debug('Discovering newly installed extensions...');
454
+ shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
377
455
  }
378
456
 
379
- if (isNewFile || installedExtensionsChanged) {
457
+ if (shouldWrite) {
380
458
  await this.write();
381
459
  }
382
460
  })();
461
+
383
462
  try {
384
- await this._reading;
385
- return this._data;
463
+ await this.#reading;
464
+ return this.#data;
386
465
  } finally {
387
- this._reading = undefined;
466
+ this.#reading = undefined;
388
467
  }
389
468
  }
390
469
 
391
470
  /**
392
- * Ensures {@link Manifest._manifestPath} is set.
471
+ * Ensures the internal manifest path is set.
393
472
  *
394
473
  * Creates the directory if necessary.
395
- * @private
396
474
  * @returns {Promise<string>}
397
475
  */
398
- async _setManifestPath() {
399
- if (!this._manifestPath) {
400
- this._manifestPath = await env.resolveManifestPath(this._appiumHome);
476
+ async #setManifestPath() {
477
+ if (!this.#manifestPath) {
478
+ this.#manifestPath = await env.resolveManifestPath(this.#appiumHome);
401
479
 
402
480
  /* istanbul ignore if */
403
- if (path.relative(this._appiumHome, this._manifestPath).startsWith('.')) {
481
+ if (path.relative(this.#appiumHome, this.#manifestPath).startsWith('.')) {
404
482
  throw new Error(
405
- `Mismatch between location of APPIUM_HOME and manifest file. APPIUM_HOME: ${this.appiumHome}, manifest file: ${this._manifestPath}`
483
+ `Mismatch between location of APPIUM_HOME and manifest file. APPIUM_HOME: ${
484
+ this.appiumHome
485
+ }, manifest file: ${this.#manifestPath}`
406
486
  );
407
487
  }
408
488
  }
409
489
 
410
- return this._manifestPath;
490
+ return this.#manifestPath;
411
491
  }
412
492
 
413
493
  /**
@@ -419,34 +499,35 @@ export class Manifest {
419
499
  * @returns {Promise<boolean>} Whether the data was written
420
500
  */
421
501
  async write() {
422
- if (this._writing) {
423
- return this._writing;
502
+ if (this.#writing) {
503
+ return this.#writing;
424
504
  }
425
- this._writing = (async () => {
426
- await this._setManifestPath();
505
+ this.#writing = (async () => {
506
+ await this.#setManifestPath();
427
507
  try {
428
- await fs.mkdirp(path.dirname(this._manifestPath));
508
+ await fs.mkdirp(path.dirname(this.#manifestPath));
429
509
  } catch (err) {
430
510
  throw new Error(
431
511
  `Appium could not create the directory for the manifest file: ${path.dirname(
432
- this._manifestPath
512
+ this.#manifestPath
433
513
  )}. Original error: ${err.message}`
434
514
  );
435
515
  }
436
516
  try {
437
- await fs.writeFile(this._manifestPath, YAML.stringify(this._data), 'utf8');
517
+ await fs.writeFile(this.#manifestPath, YAML.stringify(this.#data), 'utf8');
438
518
  return true;
439
519
  } catch (err) {
440
520
  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}`
521
+ `Appium could not write to manifest at ${this.#manifestPath} using APPIUM_HOME ${
522
+ this.#appiumHome
523
+ }. Please ensure it is writable. Original error: ${err.message}`
443
524
  );
444
525
  }
445
526
  })();
446
527
  try {
447
- return await this._writing;
528
+ return await this.#writing;
448
529
  } finally {
449
- this._writing = undefined;
530
+ this.#writing = undefined;
450
531
  }
451
532
  }
452
533
  }
@@ -472,18 +553,18 @@ export class Manifest {
472
553
  */
473
554
 
474
555
  /**
475
- * @template T
476
- * @typedef {import('appium/types').ExtPackageJson<T>} ExtPackageJson
556
+ * @template {ExtensionType} ExtType
557
+ * @typedef {import('appium/types').ExtPackageJson<ExtType>} ExtPackageJson
477
558
  */
478
559
 
479
560
  /**
480
- * @template T
481
- * @typedef {import('appium/types').ExtManifest<T>} ExtManifest
561
+ * @template {ExtensionType} ExtType
562
+ * @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
482
563
  */
483
564
 
484
565
  /**
485
- * @template T
486
- * @typedef {import('appium/types').ExtRecord<T>} ExtRecord
566
+ * @template {ExtensionType} ExtType
567
+ * @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
487
568
  */
488
569
 
489
570
  /**
@@ -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
  }