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.
- package/build/lib/cli/driver-command.d.ts +24 -5
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +15 -0
- package/build/lib/cli/driver-command.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +1 -1
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +3 -4
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/constants.d.ts +1 -0
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +1 -0
- package/build/lib/constants.js.map +1 -1
- package/build/lib/extension/extension-config.d.ts +15 -10
- package/build/lib/extension/extension-config.d.ts.map +1 -1
- package/build/lib/extension/extension-config.js +32 -14
- package/build/lib/extension/extension-config.js.map +1 -1
- package/build/lib/extension/manifest-migrations.d.ts +8 -8
- package/build/lib/extension/manifest-migrations.d.ts.map +1 -1
- package/build/lib/extension/manifest-migrations.js +39 -20
- package/build/lib/extension/manifest-migrations.js.map +1 -1
- package/build/lib/extension/manifest.d.ts +29 -8
- package/build/lib/extension/manifest.d.ts.map +1 -1
- package/build/lib/extension/manifest.js +104 -36
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/lib/cli/driver-command.js +17 -0
- package/lib/cli/extension-command.js +3 -4
- package/lib/constants.js +1 -0
- package/lib/extension/extension-config.js +28 -22
- package/lib/extension/manifest-migrations.js +41 -20
- package/lib/extension/manifest.js +113 -39
- package/package.json +9 -9
package/lib/constants.js
CHANGED
|
@@ -33,13 +33,18 @@ const INSTALL_TYPES = new Set([
|
|
|
33
33
|
* @template {ExtensionType} ExtType
|
|
34
34
|
*/
|
|
35
35
|
export class ExtensionConfig {
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The type of extension this class is responsible for.
|
|
38
|
+
* @type {ExtType}
|
|
39
|
+
*/
|
|
37
40
|
extensionType;
|
|
38
41
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
240
|
-
return this
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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]: (
|
|
34
|
+
[SCHEMA_REV_3]: (manifest) => {
|
|
28
35
|
let shouldSync = false;
|
|
29
|
-
|
|
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
|
-
*
|
|
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(
|
|
47
|
-
if (
|
|
48
|
-
|
|
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
|
|
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(
|
|
68
|
-
didChange = setSchemaRev(
|
|
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${
|
|
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
|
-
*
|
|
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 {
|
|
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
|
-
*
|
|
57
|
-
*
|
|
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(
|
|
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 (
|
|
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`
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
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
|
-
|
|
289
|
-
const data = _.
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
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
|
-
|
|
453
|
+
log.debug('Discovering newly installed extensions...');
|
|
454
|
+
shouldWrite = (await this.syncWithInstalledExtensions()) || shouldWrite;
|
|
382
455
|
}
|
|
383
456
|
|
|
384
|
-
if (
|
|
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.
|
|
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.
|
|
61
|
-
"@appium/base-plugin": "^2.0.
|
|
62
|
-
"@appium/docutils": "^0.1.
|
|
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.
|
|
65
|
-
"@appium/types": "^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.
|
|
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": "
|
|
106
|
+
"gitHead": "2e76ba9607729f59ca967e47c2cba738e90a57b8",
|
|
107
107
|
"typedoc": {
|
|
108
108
|
"entryPoint": "./build/lib/main.js"
|
|
109
109
|
}
|