appium 2.0.0-beta.47 → 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 (32) hide show
  1. package/build/lib/cli/driver-command.d.ts +24 -5
  2. package/build/lib/cli/driver-command.d.ts.map +1 -1
  3. package/build/lib/cli/driver-command.js +15 -0
  4. package/build/lib/cli/driver-command.js.map +1 -1
  5. package/build/lib/cli/extension-command.d.ts +1 -1
  6. package/build/lib/cli/extension-command.d.ts.map +1 -1
  7. package/build/lib/cli/extension-command.js +3 -4
  8. package/build/lib/cli/extension-command.js.map +1 -1
  9. package/build/lib/constants.d.ts +1 -0
  10. package/build/lib/constants.d.ts.map +1 -1
  11. package/build/lib/constants.js +1 -0
  12. package/build/lib/constants.js.map +1 -1
  13. package/build/lib/extension/extension-config.d.ts +15 -10
  14. package/build/lib/extension/extension-config.d.ts.map +1 -1
  15. package/build/lib/extension/extension-config.js +32 -14
  16. package/build/lib/extension/extension-config.js.map +1 -1
  17. package/build/lib/extension/manifest-migrations.d.ts +8 -8
  18. package/build/lib/extension/manifest-migrations.d.ts.map +1 -1
  19. package/build/lib/extension/manifest-migrations.js +39 -20
  20. package/build/lib/extension/manifest-migrations.js.map +1 -1
  21. package/build/lib/extension/manifest.d.ts +29 -8
  22. package/build/lib/extension/manifest.d.ts.map +1 -1
  23. package/build/lib/extension/manifest.js +104 -36
  24. package/build/lib/extension/manifest.js.map +1 -1
  25. package/build/tsconfig.tsbuildinfo +1 -1
  26. package/lib/cli/driver-command.js +17 -0
  27. package/lib/cli/extension-command.js +3 -4
  28. package/lib/constants.js +1 -0
  29. package/lib/extension/extension-config.js +28 -22
  30. package/lib/extension/manifest-migrations.js +41 -20
  31. package/lib/extension/manifest.js +113 -39
  32. package/package.json +9 -9
package/lib/constants.js CHANGED
@@ -45,6 +45,7 @@ export const KNOWN_DRIVERS = Object.freeze(
45
45
  espresso: 'appium-espresso-driver',
46
46
  safari: 'appium-safari-driver',
47
47
  gecko: 'appium-geckodriver',
48
+ chromium: 'appium-chromium-driver',
48
49
  })
49
50
  );
50
51
 
@@ -33,13 +33,18 @@ const INSTALL_TYPES = new Set([
33
33
  * @template {ExtensionType} ExtType
34
34
  */
35
35
  export class ExtensionConfig {
36
- /** @type {ExtType} */
36
+ /**
37
+ * The type of extension this class is responsible for.
38
+ * @type {ExtType}
39
+ */
37
40
  extensionType;
38
41
 
39
- /** @type {`${ExtType}s`} */
40
- configKey;
41
-
42
- /** @type {ExtRecord<ExtType>} */
42
+ /**
43
+ * Manifest data for the extensions of this type.
44
+ *
45
+ * This data should _not_ be written to by anything but {@linkcode Manifest}.
46
+ * @type {Readonly<ExtRecord<ExtType>>}
47
+ */
43
48
  installedExtensions;
44
49
 
45
50
  /** @type {import('@appium/types').AppiumLogger} */
@@ -49,9 +54,9 @@ export class ExtensionConfig {
49
54
  manifest;
50
55
 
51
56
  /**
52
- * @type {ExtensionListData}
57
+ * @type {ExtensionListData|undefined}
53
58
  */
54
- _listDataCache;
59
+ #listDataCache;
55
60
 
56
61
  /**
57
62
  * @protected
@@ -60,7 +65,6 @@ export class ExtensionConfig {
60
65
  */
61
66
  constructor(extensionType, manifest) {
62
67
  this.extensionType = extensionType;
63
- this.configKey = `${extensionType}s`;
64
68
  this.installedExtensions = manifest.getExtensionData(extensionType);
65
69
  this.manifest = manifest;
66
70
  }
@@ -204,8 +208,8 @@ export class ExtensionConfig {
204
208
  if (!_.isEmpty(errorSummaries)) {
205
209
  log.error(
206
210
  `Appium encountered ${util.pluralize('error', errorMap.size, true)} while validating ${
207
- this.configKey
208
- } found in manifest ${this.manifestPath}`
211
+ this.extensionType
212
+ }s found in manifest ${this.manifestPath}`
209
213
  );
210
214
  for (const summary of errorSummaries) {
211
215
  log.error(summary);
@@ -219,7 +223,7 @@ export class ExtensionConfig {
219
223
  'warning',
220
224
  warningMap.size,
221
225
  true
222
- )} while validating ${this.configKey} found in manifest ${this.manifestPath}`
226
+ )} while validating ${this.extensionType}s found in manifest ${this.manifestPath}`
223
227
  );
224
228
  for (const summary of warningSummaries) {
225
229
  log.warn(summary);
@@ -231,18 +235,20 @@ export class ExtensionConfig {
231
235
 
232
236
  /**
233
237
  * Retrieves listing data for extensions via command class.
234
- * Caches the result in {@linkcode ExtensionConfig._listDataCache}
238
+ *
239
+ * This is an expensive operation, so the result is cached. Currently, there is no
240
+ * use case for invalidating the cache.
235
241
  * @protected
236
242
  * @returns {Promise<ExtensionListData>}
237
243
  */
238
244
  async getListData() {
239
- if (this._listDataCache) {
240
- return this._listDataCache;
245
+ if (this.#listDataCache) {
246
+ return this.#listDataCache;
241
247
  }
242
248
  const CommandClass = /** @type {ExtCommand<ExtType>} */ (commandClasses[this.extensionType]);
243
249
  const cmd = new CommandClass({config: this, json: true});
244
250
  const listData = await cmd.list({showInstalled: true, showUpdates: true});
245
- this._listDataCache = listData;
251
+ this.#listDataCache = listData;
246
252
  return listData;
247
253
  }
248
254
 
@@ -433,7 +439,7 @@ export class ExtensionConfig {
433
439
  * @returns {Promise<void>}
434
440
  */
435
441
  async addExtension(extName, extManifest, {write = true} = {}) {
436
- this.manifest.addExtension(this.extensionType, extName, extManifest);
442
+ this.manifest.setExtension(this.extensionType, extName, extManifest);
437
443
  if (write) {
438
444
  await this.manifest.write();
439
445
  }
@@ -446,10 +452,10 @@ export class ExtensionConfig {
446
452
  * @returns {Promise<void>}
447
453
  */
448
454
  async updateExtension(extName, extManifest, {write = true} = {}) {
449
- this.installedExtensions[extName] = {
455
+ this.manifest.setExtension(this.extensionType, extName, {
450
456
  ...this.installedExtensions[extName],
451
457
  ...extManifest,
452
- };
458
+ });
453
459
  if (write) {
454
460
  await this.manifest.write();
455
461
  }
@@ -463,7 +469,7 @@ export class ExtensionConfig {
463
469
  * @returns {Promise<void>}
464
470
  */
465
471
  async removeExtension(extName, {write = true} = {}) {
466
- delete this.installedExtensions[extName];
472
+ this.manifest.deleteExtension(this.extensionType, extName);
467
473
  if (write) {
468
474
  await this.manifest.write();
469
475
  }
@@ -477,13 +483,13 @@ export class ExtensionConfig {
477
483
  print(activeNames) {
478
484
  if (_.isEmpty(this.installedExtensions)) {
479
485
  log.info(
480
- `No ${this.configKey} have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
486
+ `No ${this.extensionType}s have been installed in ${this.appiumHome}. Use the "appium ${this.extensionType}" ` +
481
487
  'command to install the one(s) you want to use.'
482
488
  );
483
489
  return;
484
490
  }
485
491
 
486
- log.info(`Available ${this.configKey}:`);
492
+ log.info(`Available ${this.extensionType}s:`);
487
493
  for (const [extName, extManifest] of /** @type {[string, ExtManifest<ExtType>][]} */ (
488
494
  _.toPairs(this.installedExtensions)
489
495
  )) {
@@ -552,7 +558,7 @@ export class ExtensionConfig {
552
558
  * @returns {boolean}
553
559
  */
554
560
  isInstalled(extName) {
555
- return _.includes(Object.keys(this.installedExtensions), extName);
561
+ return extName in this.installedExtensions;
556
562
  }
557
563
 
558
564
  /**
@@ -1,3 +1,4 @@
1
+ import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
1
2
  import log from '../logger';
2
3
 
3
4
  /**
@@ -12,7 +13,13 @@ import log from '../logger';
12
13
  const SCHEMA_REV_3 = 3;
13
14
 
14
15
  /**
15
- * Collection of functions to migrate from one version to another
16
+ * Collection of functions to migrate from one version to another.
17
+ *
18
+ * These functions _should not actually perform the migration_; rather, they
19
+ * should return `true` if the migration should be performed. The migration
20
+ * itself will happen within {@linkcode Manifest.syncWithInstalledExtensions}; the extensions
21
+ * will be checked and the manifest file updated per the state of the filesystem.
22
+ *
16
23
  * @type {{[P in keyof ManifestDataVersions]?: Migration}}
17
24
  */
18
25
  const Migrations = {
@@ -24,9 +31,13 @@ const Migrations = {
24
31
  *
25
32
  * @type {Migration}
26
33
  */
27
- [SCHEMA_REV_3]: (data) => {
34
+ [SCHEMA_REV_3]: (manifest) => {
28
35
  let shouldSync = false;
29
- const allExtData = [...Object.values(data.drivers), ...Object.values(data.plugins)];
36
+ /** @type {Array<ExtManifest<PluginType>|ExtManifest<DriverType>>} */
37
+ const allExtData = [
38
+ ...Object.values(manifest.getExtensionData(DRIVER_TYPE)),
39
+ ...Object.values(manifest.getExtensionData(PLUGIN_TYPE)),
40
+ ];
30
41
  for (const metadata of allExtData) {
31
42
  if (!('installPath' in metadata)) {
32
43
  shouldSync = true;
@@ -38,14 +49,20 @@ const Migrations = {
38
49
  };
39
50
 
40
51
  /**
41
- * Set `schemaRev` to a specific version
42
- * @param {AnyManifestDataVersion} data
52
+ * Set `schemaRev` to a specific version.
53
+ *
54
+ * This _does_ mutate `data` in-place, unlike functions in
55
+ * {@linkcode Migrations}.
56
+ *
57
+ * Again, returning `true` means that the manifest should be--at
58
+ * minimum--persisted to disk, since we changed it.
59
+ * @param {Readonly<Manifest>} manifest
43
60
  * @param {keyof ManifestDataVersions} version
44
61
  * @returns {boolean} Whether the data was modified
45
62
  */
46
- function setSchemaRev(data, version) {
47
- if (data.schemaRev ?? 0 < version) {
48
- data.schemaRev = version;
63
+ function setSchemaRev(manifest, version) {
64
+ if (manifest.schemaRev ?? 0 < version) {
65
+ manifest.setSchemaRev(version);
49
66
  return true;
50
67
  }
51
68
  return false;
@@ -56,44 +73,48 @@ function setSchemaRev(data, version) {
56
73
  *
57
74
  * `data` is modified in-place.
58
75
  *
59
- * @param {Manifest} manifest
60
- * @param {AnyManifestDataVersion} data
76
+ * @param {Readonly<Manifest>} manifest
61
77
  * @returns {Promise<boolean>} If `true` existing packages should be synced from disk and the manifest should be persisted.
62
78
  */
63
- export async function migrate(manifest, data) {
79
+ export async function migrate(manifest) {
64
80
  let didChange = false;
65
81
  for await (const [v, migration] of Object.entries(Migrations)) {
66
82
  const version = /** @type {keyof ManifestDataVersions} */ (Number(v));
67
- didChange = (await migration(data, manifest)) || didChange;
68
- didChange = setSchemaRev(data, version) || didChange;
83
+ didChange = (await migration(manifest)) || didChange;
84
+ didChange = setSchemaRev(manifest, version) || didChange;
69
85
  }
70
86
 
71
87
  if (didChange) {
72
88
  // this is not _technically_ true, since we don't actually write the file here.
73
- log.info(`Upgraded extension manifest to schema v${data.schemaRev}`);
89
+ log.info(`Upgraded extension manifest to schema v${manifest.schemaRev}`);
74
90
  }
91
+
75
92
  return didChange;
76
93
  }
77
94
 
78
95
  /**
79
- * Migration functions take a {@linkcode Manifest} and its data and **mutates `data` in-place**.
96
+ * A migration function. It will return `true` if a change _should be made_.
80
97
  *
81
98
  * A migration function should not modify `schemaRev`, as this is done automatically.
82
99
  *
83
- * A migration function can also call out to, say, {@linkcode Manifest.syncWithInstalledExtensions}, which
84
- * may apply the mutation itself.
85
- *
86
100
  * Note that at the time of writing, we're not able to determine the version of the _current_ manifest file if there is no `schemaRev` prop present (and we may not want to trust it anyway).
87
101
  * @callback Migration
88
- * @param {AnyManifestDataVersion} data - The raw data from `Manifest`
89
- * @param {Manifest} manifest - The `Manifest` instance
102
+ * @param {Readonly<Manifest>} manifest - The `Manifest` instance
90
103
  * @returns {boolean|Promise<boolean>} If `true`, then something changed
91
104
  */
92
105
 
93
106
  /**
94
107
  * @typedef {import('appium/types').ManifestData} ManifestData
108
+ * @typedef {import('@appium/types').DriverType} DriverType
109
+ * @typedef {import('@appium/types').PluginType} PluginType
95
110
  * @typedef {import('appium/types').AnyManifestDataVersion} AnyManifestDataVersion
96
111
  * @typedef {import('appium/types').ManifestDataVersions} ManifestDataVersions
112
+ * @typedef {import('@appium/types').ExtensionType} ExtensionType
97
113
  * @typedef {import('appium/types').ManifestV2.ManifestData} ManifestDataV2
98
114
  * @typedef {import('./manifest').Manifest} Manifest
99
115
  */
116
+
117
+ /**
118
+ * @template {ExtensionType} ExtType
119
+ * @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
120
+ */
@@ -38,6 +38,8 @@ const INITIAL_MANIFEST_DATA = Object.freeze({
38
38
  /**
39
39
  * Given a `package.json` return `true` if it represents an Appium Extension (either a driver or plugin).
40
40
  *
41
+ * _This is a type guard; not a validator._
42
+ *
41
43
  * The `package.json` must have an `appium` property which is an object.
42
44
  * @param {any} value
43
45
  * @returns {value is ExtPackageJson<ExtensionType>}
@@ -50,32 +52,35 @@ function isExtension(value) {
50
52
  _.isString(value.version)
51
53
  );
52
54
  }
55
+
53
56
  /**
54
57
  * Given a `package.json`, return `true` if it represents an Appium Driver.
55
58
  *
56
- * To be considered a driver, a `package.json` must have a fields
57
- * `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.
58
64
  * @param {any} value - Value to test
59
65
  * @returns {value is ExtPackageJson<DriverType>}
60
66
  */
61
67
  function isDriver(value) {
62
- return (
63
- isExtension(value) &&
64
- _.isString(_.get(value, 'appium.driverName')) &&
65
- _.isString(_.get(value, 'appium.automationName')) &&
66
- _.isArray(_.get(value, 'appium.platformNames'))
67
- );
68
+ return isExtension(value) && 'driverName' in value.appium && _.isString(value.appium.driverName);
68
69
  }
69
70
 
70
71
  /**
71
72
  * Given a `package.json`, return `true` if it represents an Appium Plugin.
72
73
  *
74
+ * _This is a type guard; not a validator._
75
+ *
73
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.
74
79
  * @param {any} value - Value to test
75
80
  * @returns {value is ExtPackageJson<PluginType>}
76
81
  */
77
82
  function isPlugin(value) {
78
- return isExtension(value) && _.isString(_.get(value, 'appium.pluginName'));
83
+ return isExtension(value) && 'pluginName' in value.appium && _.isString(value.appium.pluginName);
79
84
  }
80
85
 
81
86
  /**
@@ -166,7 +171,23 @@ export class Manifest {
166
171
  const onMatch = async (filepath) => {
167
172
  try {
168
173
  const pkg = JSON.parse(await fs.readFile(filepath, 'utf8'));
169
- 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
+ }
170
191
  const changed = this.addExtensionFromPackage(pkg, filepath);
171
192
  didChange = didChange || changed;
172
193
  }
@@ -228,11 +249,10 @@ export class Manifest {
228
249
  /**
229
250
  * Given a path to a `package.json`, add it as either a driver or plugin to the manifest.
230
251
  *
231
- * Will _not_ overwrite existing entries.
232
252
  * @template {ExtensionType} ExtType
233
253
  * @param {ExtPackageJson<ExtType>} pkgJson
234
254
  * @param {string} pkgPath
235
- * @returns {boolean} - `true` upon success, `false` if the extension is already registered.
255
+ * @returns {boolean} - `true` if this method did anything.
236
256
  */
237
257
  addExtensionFromPackage(pkgJson, pkgPath) {
238
258
  const extensionPath = path.dirname(pkgPath);
@@ -250,26 +270,28 @@ export class Manifest {
250
270
  };
251
271
 
252
272
  if (isDriver(pkgJson)) {
253
- if (!this.hasDriver(pkgJson.appium.driverName)) {
254
- this.addExtension(DRIVER_TYPE, pkgJson.appium.driverName, {
255
- ..._.omit(pkgJson.appium, 'driverName'),
256
- ...internal,
257
- });
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);
258
279
  return true;
259
280
  }
260
281
  return false;
261
282
  } else if (isPlugin(pkgJson)) {
262
- if (!this.hasPlugin(pkgJson.appium.pluginName)) {
263
- this.addExtension(PLUGIN_TYPE, pkgJson.appium.pluginName, {
264
- ..._.omit(pkgJson.appium, 'pluginName'),
265
- ...internal,
266
- });
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);
267
289
  return true;
268
290
  }
269
291
  return false;
270
292
  } else {
271
293
  throw new TypeError(
272
- `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}.`
273
295
  );
274
296
  }
275
297
  }
@@ -285,12 +307,29 @@ export class Manifest {
285
307
  * @param {ExtManifest<ExtType>} extData - Extension metadata
286
308
  * @returns {ExtManifest<ExtType>} A clone of `extData`, potentially with a mutated `appiumVersion` field
287
309
  */
288
- addExtension(extType, extName, extData) {
289
- const data = _.clone(extData);
310
+ setExtension(extType, extName, extData) {
311
+ const data = _.cloneDeep(extData);
290
312
  this.#data[`${extType}s`][extName] = data;
291
313
  return data;
292
314
  }
293
315
 
316
+ /**
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
+
294
333
  /**
295
334
  * Returns the `APPIUM_HOME` path
296
335
  */
@@ -305,12 +344,19 @@ export class Manifest {
305
344
  return this.#manifestPath;
306
345
  }
307
346
 
347
+ /**
348
+ * Returns the schema rev of this manifest
349
+ */
350
+ get schemaRev() {
351
+ return this.#data.schemaRev;
352
+ }
353
+
308
354
  /**
309
355
  * Returns extension data for a particular type.
310
356
  *
311
357
  * @template {ExtensionType} ExtType
312
358
  * @param {ExtType} extType
313
- * @returns {ExtRecord<ExtType>}
359
+ * @returns {Readonly<ExtRecord<ExtType>>}
314
360
  */
315
361
  getExtensionData(extType) {
316
362
  return this.#data[/** @type {string} */ (`${extType}s`)];
@@ -319,11 +365,18 @@ export class Manifest {
319
365
  /**
320
366
  * Reads manifest from disk and _overwrites_ the internal data.
321
367
  *
322
- * 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.
323
371
  *
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.
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.
377
+ *
378
+ * Only one read operation can happen at a time.
325
379
  *
326
- * Only one read operation should happen at a time.
327
380
  * @returns {Promise<ManifestData>} The data
328
381
  */
329
382
  async read() {
@@ -335,17 +388,23 @@ export class Manifest {
335
388
  this.#reading = (async () => {
336
389
  /** @type {ManifestData} */
337
390
  let data;
338
- let isNewFile = false;
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;
339
396
  await this.#setManifestPath();
340
397
  try {
341
- log.debug(`Reading ${this.#manifestPath}...`);
342
398
  const yaml = await fs.readFile(this.#manifestPath, 'utf8');
343
399
  data = YAML.parse(yaml);
344
- 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
+ );
345
403
  } catch (err) {
346
404
  if (err.code === 'ENOENT') {
405
+ log.debug(`No manifest file found at ${this.#manifestPath}; creating`);
347
406
  data = _.cloneDeep(INITIAL_MANIFEST_DATA);
348
- isNewFile = true;
407
+ shouldWrite = true;
349
408
  } else {
350
409
  if (this.#manifestPath) {
351
410
  throw new Error(
@@ -364,27 +423,42 @@ export class Manifest {
364
423
 
365
424
  this.#data = data;
366
425
 
367
- let shouldWrite = false;
368
-
369
- if ((data.schemaRev ?? 0) < CURRENT_SCHEMA_REV) {
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) {
370
431
  log.debug(
371
432
  `Updating manifest schema from rev ${data.schemaRev ?? '(none)'} to ${CURRENT_SCHEMA_REV}`
372
433
  );
373
- shouldWrite = await migrate(this, this.#data);
434
+ shouldWrite = await migrate(this);
374
435
  }
375
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
+ */
376
448
  if (
377
449
  shouldWrite ||
378
450
  ((await env.hasAppiumDependency(this.appiumHome)) &&
379
451
  (await packageDidChange(this.appiumHome)))
380
452
  ) {
381
- shouldWrite = await this.syncWithInstalledExtensions();
453
+ log.debug('Discovering newly installed extensions...');
454
+ shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
382
455
  }
383
456
 
384
- if (isNewFile || shouldWrite) {
457
+ if (shouldWrite) {
385
458
  await this.write();
386
459
  }
387
460
  })();
461
+
388
462
  try {
389
463
  await this.#reading;
390
464
  return this.#data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium",
3
- "version": "2.0.0-beta.47",
3
+ "version": "2.0.0-beta.48",
4
4
  "description": "Automation for Apps.",
5
5
  "keywords": [
6
6
  "automation",
@@ -52,17 +52,17 @@
52
52
  "postinstall": "node ./scripts/autoinstall-extensions.js",
53
53
  "publish:docs": "cross-env APPIUM_DOCS_PUBLISH=1 npm run build:docs",
54
54
  "test": "npm run test:unit",
55
- "test:e2e": "mocha --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
55
+ "test:e2e": "mocha -p --timeout 1m --slow 30s \"./test/e2e/**/*.spec.js\"",
56
56
  "test:smoke": "cross-env APPIUM_HOME=./local_appium_home node ./index.js driver install uiautomator2 && cross-env APPIUM_HOME=./local_appium_home node ./index.js driver list",
57
57
  "test:unit": "mocha \"./test/unit/**/*.spec.js\""
58
58
  },
59
59
  "dependencies": {
60
- "@appium/base-driver": "^9.0.0",
61
- "@appium/base-plugin": "^2.0.0",
62
- "@appium/docutils": "^0.1.0",
60
+ "@appium/base-driver": "^9.1.0",
61
+ "@appium/base-plugin": "^2.0.1",
62
+ "@appium/docutils": "^0.1.1",
63
63
  "@appium/schema": "^0.1.0",
64
- "@appium/support": "^3.0.0",
65
- "@appium/types": "^0.6.0",
64
+ "@appium/support": "^3.0.1",
65
+ "@appium/types": "^0.7.0",
66
66
  "@sidvind/better-ajv-errors": "2.1.0",
67
67
  "@types/argparse": "2.0.10",
68
68
  "@types/bluebird": "3.5.38",
@@ -90,7 +90,7 @@
90
90
  "semver": "7.3.8",
91
91
  "source-map-support": "0.5.21",
92
92
  "teen_process": "2.0.2",
93
- "type-fest": "3.3.0",
93
+ "type-fest": "3.4.0",
94
94
  "winston": "3.8.2",
95
95
  "wrap-ansi": "7.0.0",
96
96
  "yaml": "2.1.3"
@@ -103,7 +103,7 @@
103
103
  "access": "public",
104
104
  "tag": "next"
105
105
  },
106
- "gitHead": "0823f0b60e40395cd1dc3b72cfa3c0092bc81302",
106
+ "gitHead": "2e76ba9607729f59ca967e47c2cba738e90a57b8",
107
107
  "typedoc": {
108
108
  "entryPoint": "./build/lib/main.js"
109
109
  }