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.
- package/README.md +145 -44
- package/build/lib/appium.d.ts +3 -103
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +679 -549
- package/build/lib/appium.js.map +1 -1
- package/build/lib/cli/args.js +247 -127
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/driver-command.js +63 -88
- package/build/lib/cli/driver-command.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +32 -23
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +730 -512
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/extension.d.ts +7 -6
- package/build/lib/cli/extension.d.ts.map +1 -1
- package/build/lib/cli/extension.js +68 -65
- package/build/lib/cli/extension.js.map +1 -1
- package/build/lib/cli/parser.d.ts +3 -3
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +234 -192
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/cli/plugin-command.js +58 -87
- package/build/lib/cli/plugin-command.js.map +1 -1
- package/build/lib/cli/utils.js +66 -69
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/config-file.d.ts.map +1 -1
- package/build/lib/config-file.js +189 -120
- package/build/lib/config-file.js.map +1 -1
- package/build/lib/config.d.ts.map +1 -1
- package/build/lib/config.js +254 -213
- package/build/lib/config.js.map +1 -1
- package/build/lib/constants.d.ts +5 -5
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +64 -59
- package/build/lib/constants.js.map +1 -1
- package/build/lib/extension/driver-config.js +199 -164
- package/build/lib/extension/driver-config.js.map +1 -1
- package/build/lib/extension/extension-config.d.ts +18 -16
- package/build/lib/extension/extension-config.d.ts.map +1 -1
- package/build/lib/extension/extension-config.js +523 -396
- package/build/lib/extension/extension-config.js.map +1 -1
- package/build/lib/extension/index.js +98 -68
- package/build/lib/extension/index.js.map +1 -1
- package/build/lib/extension/manifest-migrations.d.ts +27 -0
- package/build/lib/extension/manifest-migrations.d.ts.map +1 -0
- package/build/lib/extension/manifest-migrations.js +99 -0
- package/build/lib/extension/manifest-migrations.js.map +1 -0
- package/build/lib/extension/manifest.d.ts +7 -56
- package/build/lib/extension/manifest.d.ts.map +1 -1
- package/build/lib/extension/manifest.js +432 -240
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/lib/extension/package-changed.js +57 -61
- package/build/lib/extension/package-changed.js.map +1 -1
- package/build/lib/extension/plugin-config.d.ts +2 -3
- package/build/lib/extension/plugin-config.d.ts.map +1 -1
- package/build/lib/extension/plugin-config.js +94 -70
- package/build/lib/extension/plugin-config.js.map +1 -1
- package/build/lib/grid-register.js +119 -137
- package/build/lib/grid-register.js.map +1 -1
- package/build/lib/logger.d.ts +1 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js +5 -15
- package/build/lib/logger.js.map +1 -1
- package/build/lib/logsink.d.ts.map +1 -1
- package/build/lib/logsink.js +189 -183
- package/build/lib/logsink.js.map +1 -1
- package/build/lib/main.d.ts +19 -12
- package/build/lib/main.d.ts.map +1 -1
- package/build/lib/main.js +330 -304
- package/build/lib/main.js.map +1 -1
- package/build/lib/schema/arg-spec.js +153 -108
- package/build/lib/schema/arg-spec.js.map +1 -1
- package/build/lib/schema/cli-args.js +203 -164
- package/build/lib/schema/cli-args.js.map +1 -1
- package/build/lib/schema/cli-transformers.js +117 -72
- package/build/lib/schema/cli-transformers.js.map +1 -1
- package/build/lib/schema/index.js +17 -32
- package/build/lib/schema/index.js.map +1 -1
- package/build/lib/schema/keywords.js +125 -67
- package/build/lib/schema/keywords.js.map +1 -1
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +582 -417
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.d.ts +41 -255
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +342 -193
- package/build/lib/utils.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/cli.d.ts +45 -34
- package/build/types/cli.d.ts.map +1 -1
- package/build/types/cli.js +3 -0
- package/build/types/cli.js.map +1 -0
- package/build/types/index.d.ts +1 -2
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +19 -0
- package/build/types/index.js.map +1 -0
- package/build/types/manifest/base.d.ts +135 -0
- package/build/types/manifest/base.d.ts.map +1 -0
- package/build/types/manifest/base.js +3 -0
- package/build/types/manifest/base.js.map +1 -0
- package/build/types/manifest/index.d.ts +19 -0
- package/build/types/manifest/index.d.ts.map +1 -0
- package/build/types/manifest/index.js +40 -0
- package/build/types/manifest/index.js.map +1 -0
- package/build/types/manifest/v3.d.ts +139 -0
- package/build/types/manifest/v3.d.ts.map +1 -0
- package/build/types/manifest/v3.js +3 -0
- package/build/types/manifest/v3.js.map +1 -0
- package/lib/appium.js +1 -1
- package/lib/cli/args.js +1 -1
- package/lib/cli/extension-command.js +116 -61
- package/lib/cli/extension.js +9 -8
- package/lib/cli/parser.js +2 -2
- package/lib/config-file.js +2 -3
- package/lib/config.js +3 -2
- package/lib/constants.js +6 -5
- package/lib/extension/extension-config.js +24 -25
- package/lib/extension/manifest-migrations.js +99 -0
- package/lib/extension/manifest.js +79 -72
- package/lib/extension/plugin-config.js +1 -2
- package/lib/logsink.js +26 -5
- package/lib/main.js +58 -50
- package/lib/schema/schema.js +6 -1
- package/lib/utils.js +62 -0
- package/package.json +23 -24
- package/scripts/autoinstall-extensions.js +78 -26
- package/types/cli.ts +81 -42
- package/types/index.ts +1 -2
- package/types/manifest/README.md +30 -0
- package/types/manifest/base.ts +158 -0
- package/types/manifest/index.ts +27 -0
- package/types/manifest/v3.ts +161 -0
- package/build/types/appium-manifest.d.ts +0 -59
- package/build/types/appium-manifest.d.ts.map +0 -1
- package/build/types/extension-manifest.d.ts +0 -55
- package/build/types/extension-manifest.d.ts.map +0 -1
- package/types/appium-manifest.ts +0 -73
- package/types/extension-manifest.ts +0 -64
|
@@ -18,6 +18,16 @@ const UPDATE_ALL = 'installed';
|
|
|
18
18
|
class NotUpdatableError extends Error {}
|
|
19
19
|
class NoUpdatesAvailableError extends Error {}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Omits `driverName`/`pluginName` props from the receipt to make a {@linkcode ExtManifest}
|
|
23
|
+
* @template {ExtensionType} ExtType
|
|
24
|
+
* @param {ExtInstallReceipt<ExtType>} receipt
|
|
25
|
+
* @returns {ExtManifest<ExtType>}
|
|
26
|
+
*/
|
|
27
|
+
function receiptToManifest(receipt) {
|
|
28
|
+
return /** @type {ExtManifest<ExtType>} */ (_.omit(receipt, 'driverName', 'pluginName'));
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
/**
|
|
22
32
|
* @template {ExtensionType} ExtType
|
|
23
33
|
*/
|
|
@@ -128,10 +138,14 @@ class ExtensionCommand {
|
|
|
128
138
|
// also don't need to check for updates on non-npm exts
|
|
129
139
|
continue;
|
|
130
140
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
try {
|
|
142
|
+
const updates = await this.checkForExtensionUpdate(ext);
|
|
143
|
+
data.updateVersion = updates.safeUpdate;
|
|
144
|
+
data.unsafeUpdateVersion = updates.unsafeUpdate;
|
|
145
|
+
data.upToDate = updates.safeUpdate === null && updates.unsafeUpdate === null;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
data.updateError = e.message;
|
|
148
|
+
}
|
|
135
149
|
}
|
|
136
150
|
});
|
|
137
151
|
|
|
@@ -149,8 +163,15 @@ class ExtensionCommand {
|
|
|
149
163
|
let upToDateTxt = '';
|
|
150
164
|
let unsafeUpdateTxt = '';
|
|
151
165
|
if (data.installed) {
|
|
152
|
-
const {
|
|
153
|
-
|
|
166
|
+
const {
|
|
167
|
+
installType,
|
|
168
|
+
installSpec,
|
|
169
|
+
updateVersion,
|
|
170
|
+
unsafeUpdateVersion,
|
|
171
|
+
version,
|
|
172
|
+
upToDate,
|
|
173
|
+
updateError,
|
|
174
|
+
} = data;
|
|
154
175
|
let typeTxt;
|
|
155
176
|
switch (installType) {
|
|
156
177
|
case INSTALL_TYPE_GIT:
|
|
@@ -166,14 +187,18 @@ class ExtensionCommand {
|
|
|
166
187
|
installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
|
|
167
188
|
|
|
168
189
|
if (showUpdates) {
|
|
169
|
-
if (
|
|
170
|
-
updateTxt = ` [${
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
if (updateError) {
|
|
191
|
+
updateTxt = ` [Cannot check for updates: ${updateError}]`.red;
|
|
192
|
+
} else {
|
|
193
|
+
if (updateVersion) {
|
|
194
|
+
updateTxt = ` [${updateVersion} available]`.magenta;
|
|
195
|
+
}
|
|
196
|
+
if (upToDate) {
|
|
197
|
+
upToDateTxt = ` [Up to date]`.green;
|
|
198
|
+
}
|
|
199
|
+
if (unsafeUpdateVersion) {
|
|
200
|
+
unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
|
|
201
|
+
}
|
|
177
202
|
}
|
|
178
203
|
}
|
|
179
204
|
}
|
|
@@ -187,12 +212,12 @@ class ExtensionCommand {
|
|
|
187
212
|
/**
|
|
188
213
|
* Install an extension
|
|
189
214
|
*
|
|
190
|
-
* @param {
|
|
215
|
+
* @param {InstallOpts} opts
|
|
191
216
|
* @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
|
|
192
217
|
*/
|
|
193
218
|
async _install({installSpec, installType, packageName}) {
|
|
194
|
-
/** @type {
|
|
195
|
-
let
|
|
219
|
+
/** @type {ExtInstallReceipt<ExtType>} */
|
|
220
|
+
let receipt;
|
|
196
221
|
|
|
197
222
|
if (packageName && [INSTALL_TYPE_LOCAL, INSTALL_TYPE_NPM].includes(installType)) {
|
|
198
223
|
throw this._createFatalError(`When using --source=${installType}, cannot also use --package`);
|
|
@@ -205,7 +230,7 @@ class ExtensionCommand {
|
|
|
205
230
|
/**
|
|
206
231
|
* @type {InstallViaNpmArgs}
|
|
207
232
|
*/
|
|
208
|
-
let
|
|
233
|
+
let installViaNpmOpts;
|
|
209
234
|
|
|
210
235
|
/**
|
|
211
236
|
* The probable (?) name of the extension derived from the install spec.
|
|
@@ -223,8 +248,9 @@ class ExtensionCommand {
|
|
|
223
248
|
'it should be of the form <org>/<repo>'
|
|
224
249
|
);
|
|
225
250
|
}
|
|
226
|
-
|
|
251
|
+
installViaNpmOpts = {
|
|
227
252
|
installSpec,
|
|
253
|
+
installType,
|
|
228
254
|
pkgName: /** @type {string} */ (packageName),
|
|
229
255
|
};
|
|
230
256
|
probableExtName = installSpec;
|
|
@@ -232,8 +258,9 @@ class ExtensionCommand {
|
|
|
232
258
|
// git urls can have '.git' at the end, but this is not necessary and would complicate the
|
|
233
259
|
// way we download and name directories, so we can just remove it
|
|
234
260
|
installSpec = installSpec.replace(/\.git$/, '');
|
|
235
|
-
|
|
261
|
+
installViaNpmOpts = {
|
|
236
262
|
installSpec,
|
|
263
|
+
installType,
|
|
237
264
|
pkgName: /** @type {string} */ (packageName),
|
|
238
265
|
};
|
|
239
266
|
probableExtName = installSpec;
|
|
@@ -279,7 +306,7 @@ class ExtensionCommand {
|
|
|
279
306
|
installType = INSTALL_TYPE_NPM;
|
|
280
307
|
}
|
|
281
308
|
}
|
|
282
|
-
|
|
309
|
+
installViaNpmOpts = {installSpec, pkgName, pkgVer, installType};
|
|
283
310
|
}
|
|
284
311
|
|
|
285
312
|
// fail fast here if we can
|
|
@@ -291,11 +318,11 @@ class ExtensionCommand {
|
|
|
291
318
|
);
|
|
292
319
|
}
|
|
293
320
|
|
|
294
|
-
|
|
321
|
+
receipt = await this.installViaNpm(installViaNpmOpts);
|
|
295
322
|
|
|
296
323
|
// this _should_ be the same as `probablyExtName` as the one derived above unless
|
|
297
324
|
// install type is local.
|
|
298
|
-
const extName =
|
|
325
|
+
const extName = receipt[/** @type {string} */ (`${this.type}Name`)];
|
|
299
326
|
|
|
300
327
|
// check _a second time_ with the more-accurate extName
|
|
301
328
|
if (this.config.isInstalled(extName)) {
|
|
@@ -308,10 +335,9 @@ class ExtensionCommand {
|
|
|
308
335
|
|
|
309
336
|
// this field does not exist as such in the manifest (it's used as a property name instead)
|
|
310
337
|
// so that's why it's being removed here.
|
|
311
|
-
delete extData[/** @type {string} */ (`${this.type}Name`)];
|
|
312
|
-
|
|
313
338
|
/** @type {ExtManifest<ExtType>} */
|
|
314
|
-
const extManifest =
|
|
339
|
+
const extManifest = receiptToManifest(receipt);
|
|
340
|
+
|
|
315
341
|
const [errors, warnings] = await B.all([
|
|
316
342
|
this.config.getProblems(extName, extManifest),
|
|
317
343
|
this.config.getWarnings(extName, extManifest),
|
|
@@ -340,7 +366,7 @@ class ExtensionCommand {
|
|
|
340
366
|
}
|
|
341
367
|
|
|
342
368
|
// log info for the user
|
|
343
|
-
this.log.info(this.getPostInstallText({extName, extData}));
|
|
369
|
+
this.log.info(this.getPostInstallText({extName, extData: receipt}));
|
|
344
370
|
|
|
345
371
|
return this.config.installedExtensions;
|
|
346
372
|
}
|
|
@@ -349,21 +375,28 @@ class ExtensionCommand {
|
|
|
349
375
|
* Install an extension via NPM
|
|
350
376
|
*
|
|
351
377
|
* @param {InstallViaNpmArgs} args
|
|
378
|
+
* @returns {Promise<ExtInstallReceipt<ExtType>>}
|
|
352
379
|
*/
|
|
353
|
-
async installViaNpm({installSpec, pkgName, pkgVer}) {
|
|
380
|
+
async installViaNpm({installSpec, pkgName, pkgVer, installType}) {
|
|
354
381
|
const npmSpec = `${pkgName}${pkgVer ? '@' + pkgVer : ''}`;
|
|
355
382
|
const specMsg = npmSpec === installSpec ? '' : ` using NPM install spec '${npmSpec}'`;
|
|
356
383
|
const msg = `Installing '${installSpec}'${specMsg}`;
|
|
357
384
|
try {
|
|
358
|
-
const
|
|
359
|
-
const
|
|
385
|
+
const {pkg, path} = await spinWith(this.isJsonOutput, msg, async () => {
|
|
386
|
+
const {pkg, installPath: path} = await npm.installPackage(this.config.appiumHome, pkgName, {
|
|
360
387
|
pkgVer,
|
|
388
|
+
installType,
|
|
361
389
|
});
|
|
362
|
-
this.validatePackageJson(
|
|
363
|
-
return
|
|
390
|
+
this.validatePackageJson(pkg, installSpec);
|
|
391
|
+
return {pkg, path};
|
|
364
392
|
});
|
|
365
393
|
|
|
366
|
-
return this.
|
|
394
|
+
return this.getInstallationReceipt({
|
|
395
|
+
pkg,
|
|
396
|
+
installPath: path,
|
|
397
|
+
installType,
|
|
398
|
+
installSpec,
|
|
399
|
+
});
|
|
367
400
|
} catch (err) {
|
|
368
401
|
throw this._createFatalError(`Encountered an error when installing package: ${err.message}`);
|
|
369
402
|
}
|
|
@@ -382,25 +415,31 @@ class ExtensionCommand {
|
|
|
382
415
|
}
|
|
383
416
|
|
|
384
417
|
/**
|
|
385
|
-
*
|
|
386
|
-
* 'appium' field in the JSON data. We need this information to e.g. determine which class to
|
|
387
|
-
* load as the main driver class, or to be able to detect incompatibilities between driver and
|
|
388
|
-
* appium versions.
|
|
418
|
+
* Once a package is installed on-disk, this gathers some necessary metadata for validation.
|
|
389
419
|
*
|
|
390
|
-
* @param {
|
|
391
|
-
* @returns {
|
|
420
|
+
* @param {GetInstallationReceiptOpts<ExtType>} opts
|
|
421
|
+
* @returns {ExtInstallReceipt<ExtType>}
|
|
392
422
|
*/
|
|
393
|
-
|
|
394
|
-
const {appium, name, version, peerDependencies} =
|
|
423
|
+
getInstallationReceipt({pkg, installPath, installType, installSpec}) {
|
|
424
|
+
const {appium, name, version, peerDependencies} = pkg;
|
|
395
425
|
|
|
396
|
-
/** @type {
|
|
397
|
-
const
|
|
398
|
-
...appium,
|
|
426
|
+
/** @type {import('appium/types').InternalMetadata} */
|
|
427
|
+
const internal = {
|
|
399
428
|
pkgName: name,
|
|
400
429
|
version,
|
|
430
|
+
installType,
|
|
431
|
+
installSpec,
|
|
432
|
+
installPath,
|
|
401
433
|
appiumVersion: peerDependencies?.appium,
|
|
402
434
|
};
|
|
403
|
-
|
|
435
|
+
|
|
436
|
+
/** @type {ExtMetadata<ExtType>} */
|
|
437
|
+
const extMetadata = appium;
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
...internal,
|
|
441
|
+
...extMetadata,
|
|
442
|
+
};
|
|
404
443
|
}
|
|
405
444
|
|
|
406
445
|
/**
|
|
@@ -410,13 +449,13 @@ class ExtensionCommand {
|
|
|
410
449
|
* - `name`
|
|
411
450
|
* - `version`
|
|
412
451
|
* - `appium`
|
|
413
|
-
* @param {import('type-fest').PackageJson}
|
|
452
|
+
* @param {import('type-fest').PackageJson} pkg - `package.json` of extension
|
|
414
453
|
* @param {string} installSpec - Extension name/spec
|
|
415
454
|
* @throws {ReferenceError} If `package.json` has a missing or invalid field
|
|
416
|
-
* @returns {
|
|
455
|
+
* @returns {pkg is ExtPackageJson<ExtType>}
|
|
417
456
|
*/
|
|
418
|
-
validatePackageJson(
|
|
419
|
-
const {appium, name, version} = /** @type {ExtPackageJson<ExtType>} */ (
|
|
457
|
+
validatePackageJson(pkg, installSpec) {
|
|
458
|
+
const {appium, name, version} = /** @type {ExtPackageJson<ExtType>} */ (pkg);
|
|
420
459
|
|
|
421
460
|
/**
|
|
422
461
|
*
|
|
@@ -608,9 +647,10 @@ class ExtensionCommand {
|
|
|
608
647
|
* @returns {Promise<void>}
|
|
609
648
|
*/
|
|
610
649
|
async updateExtension(installSpec, version) {
|
|
611
|
-
const {pkgName} = this.config.installedExtensions[installSpec];
|
|
650
|
+
const {pkgName, installType} = this.config.installedExtensions[installSpec];
|
|
612
651
|
const extData = await this.installViaNpm({
|
|
613
652
|
installSpec,
|
|
653
|
+
installType,
|
|
614
654
|
pkgName,
|
|
615
655
|
pkgVer: version,
|
|
616
656
|
});
|
|
@@ -637,7 +677,7 @@ class ExtensionCommand {
|
|
|
637
677
|
const extConfig = this.config.installedExtensions[installSpec];
|
|
638
678
|
|
|
639
679
|
// note: TS cannot understand that _.has() is a type guard
|
|
640
|
-
if (!extConfig
|
|
680
|
+
if (!('scripts' in extConfig)) {
|
|
641
681
|
throw this._createFatalError(
|
|
642
682
|
`The ${this.type} named '${installSpec}' does not contain the ` +
|
|
643
683
|
`"scripts" field underneath the "appium" field in its package.json`
|
|
@@ -646,13 +686,13 @@ class ExtensionCommand {
|
|
|
646
686
|
|
|
647
687
|
const extScripts = extConfig.scripts;
|
|
648
688
|
|
|
649
|
-
if (!_.isPlainObject(extScripts)) {
|
|
689
|
+
if (!extScripts || !_.isPlainObject(extScripts)) {
|
|
650
690
|
throw this._createFatalError(
|
|
651
691
|
`The ${this.type} named '${installSpec}' "scripts" field must be a plain object`
|
|
652
692
|
);
|
|
653
693
|
}
|
|
654
694
|
|
|
655
|
-
if (!
|
|
695
|
+
if (!(scriptName in extScripts)) {
|
|
656
696
|
throw this._createFatalError(
|
|
657
697
|
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
|
658
698
|
);
|
|
@@ -701,6 +741,7 @@ export {ExtensionCommand};
|
|
|
701
741
|
* @property {string?} updateVersion - If the extension is installed, the version it can be updated to
|
|
702
742
|
* @property {string?} unsafeUpdateVersion - Same as above, but a major version bump
|
|
703
743
|
* @property {boolean} upToDate - If the extension is installed and the latest
|
|
744
|
+
* @property {string?} updateError - Update check error message (if present)
|
|
704
745
|
*/
|
|
705
746
|
|
|
706
747
|
/**
|
|
@@ -734,6 +775,11 @@ export {ExtensionCommand};
|
|
|
734
775
|
* @typedef {import('appium/types').ExtPackageJson<ExtType>} ExtPackageJson
|
|
735
776
|
*/
|
|
736
777
|
|
|
778
|
+
/**
|
|
779
|
+
* @template {ExtensionType} ExtType
|
|
780
|
+
* @typedef {import('appium/types').ExtInstallReceipt<ExtType>} ExtInstallReceipt
|
|
781
|
+
*/
|
|
782
|
+
|
|
737
783
|
/**
|
|
738
784
|
* Possible return value for {@linkcode ExtensionCommand.list}
|
|
739
785
|
* @typedef UninstalledExtensionListData
|
|
@@ -806,6 +852,7 @@ export {ExtensionCommand};
|
|
|
806
852
|
* @typedef InstallViaNpmArgs
|
|
807
853
|
* @property {string} installSpec - the name or spec of an extension to install
|
|
808
854
|
* @property {string} pkgName - the NPM package name of the extension
|
|
855
|
+
* @property {import('appium/types').InstallType} installType - type of install
|
|
809
856
|
* @property {string} [pkgVer] - the specific version of the NPM package
|
|
810
857
|
*/
|
|
811
858
|
|
|
@@ -819,18 +866,12 @@ export {ExtensionCommand};
|
|
|
819
866
|
|
|
820
867
|
/**
|
|
821
868
|
* Options for {@linkcode ExtensionCommand._install}
|
|
822
|
-
* @typedef
|
|
869
|
+
* @typedef InstallOpts
|
|
823
870
|
* @property {string} installSpec - the name or spec of an extension to install
|
|
824
|
-
* @property {
|
|
871
|
+
* @property {InstallType} installType - how to install this extension. One of the INSTALL_TYPES
|
|
825
872
|
* @property {string} [packageName] - for git/github installs, the extension node package name
|
|
826
873
|
*/
|
|
827
874
|
|
|
828
|
-
/**
|
|
829
|
-
* Returned by {@linkcode ExtensionCommand.getExtensionFields}
|
|
830
|
-
* @template {ExtensionType} ExtType
|
|
831
|
-
* @typedef {ExtMetadata<ExtType> & { pkgName: string, version: string, appiumVersion: string } & import('appium/types').CommonExtMetadata} ExtensionFields
|
|
832
|
-
*/
|
|
833
|
-
|
|
834
875
|
/**
|
|
835
876
|
* @template {ExtensionType} ExtType
|
|
836
877
|
* @typedef {ExtType extends DriverType ? typeof import('../constants').KNOWN_DRIVERS : ExtType extends PluginType ? typeof import('../constants').KNOWN_PLUGINS : never} KnownExtensions
|
|
@@ -841,3 +882,17 @@ export {ExtensionCommand};
|
|
|
841
882
|
* @property {boolean} showInstalled - whether should show only installed extensions
|
|
842
883
|
* @property {boolean} showUpdates - whether should show available updates
|
|
843
884
|
*/
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Opts for {@linkcode ExtensionCommand.getInstallationReceipt}
|
|
888
|
+
* @template {ExtensionType} ExtType
|
|
889
|
+
* @typedef GetInstallationReceiptOpts
|
|
890
|
+
* @property {string} installPath
|
|
891
|
+
* @property {string} installSpec
|
|
892
|
+
* @property {ExtPackageJson<ExtType>} pkg
|
|
893
|
+
* @property {InstallType} installType
|
|
894
|
+
*/
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* @typedef {import('appium/types').InstallType} InstallType
|
|
898
|
+
*/
|
package/lib/cli/extension.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
|
3
|
+
import {isExtensionCommandArgs} from '../utils';
|
|
2
4
|
import DriverCommand from './driver-command';
|
|
3
5
|
import PluginCommand from './plugin-command';
|
|
4
|
-
import {DRIVER_TYPE, PLUGIN_TYPE} from '../constants';
|
|
5
6
|
import {errAndQuit, JSON_SPACES} from './utils';
|
|
6
7
|
|
|
7
8
|
export const commandClasses = Object.freeze(
|
|
@@ -15,18 +16,18 @@ export const commandClasses = Object.freeze(
|
|
|
15
16
|
* Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
|
|
16
17
|
* can be represented as a JS object.
|
|
17
18
|
*
|
|
18
|
-
* @
|
|
19
|
+
* @template {import('appium/types').CliExtensionCommand} Cmd
|
|
20
|
+
* @template {import('appium/types').CliExtensionSubcommand} SubCmd
|
|
21
|
+
* @param {import('appium/types').Args<Cmd, SubCmd>} args - JS object where the key is the parameter name (as defined in
|
|
19
22
|
* driver-parser.js)
|
|
20
|
-
* @
|
|
21
|
-
* @param {import('../extension/extension-config').ExtensionConfig<ExtType>} config - Extension config object
|
|
23
|
+
* @param {import('../extension/extension-config').ExtensionConfig<Cmd>} config - Extension config object
|
|
22
24
|
*/
|
|
23
25
|
async function runExtensionCommand(args, config) {
|
|
24
26
|
// TODO driver config file should be locked while any of these commands are
|
|
25
27
|
// running to prevent weird situations
|
|
26
28
|
let jsonResult = null;
|
|
27
|
-
const {extensionType: type} = config;
|
|
28
|
-
|
|
29
|
-
if (!extCmd) {
|
|
29
|
+
const {extensionType: type} = config; // NOTE this is the same as `args.subcommand`
|
|
30
|
+
if (!isExtensionCommandArgs(args)) {
|
|
30
31
|
throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
|
|
31
32
|
}
|
|
32
33
|
let {json, suppressOutput} = args;
|
|
@@ -34,7 +35,7 @@ async function runExtensionCommand(args, config) {
|
|
|
34
35
|
if (suppressOutput) {
|
|
35
36
|
json = true;
|
|
36
37
|
}
|
|
37
|
-
const CommandClass = /** @type {ExtCommand<
|
|
38
|
+
const CommandClass = /** @type {ExtCommand<Cmd>} */ (commandClasses[type]);
|
|
38
39
|
const cmd = new CommandClass({config, json});
|
|
39
40
|
try {
|
|
40
41
|
jsonResult = await cmd.execute(args);
|
package/lib/cli/parser.js
CHANGED
|
@@ -89,9 +89,9 @@ class ArgParser {
|
|
|
89
89
|
* If no subcommand is passed in, this method will inject the `server` subcommand.
|
|
90
90
|
*
|
|
91
91
|
* `ArgParser.prototype.parse_args` is an alias of this method.
|
|
92
|
-
* @template [
|
|
92
|
+
* @template {import('appium/types').CliCommand} [Cmd=import('appium/types').ServerCommand]
|
|
93
93
|
* @param {string[]} [args] - Array of arguments, ostensibly from `process.argv`. Gathers args from `process.argv` if not provided.
|
|
94
|
-
* @returns {import('appium/types').Args<
|
|
94
|
+
* @returns {import('appium/types').Args<Cmd>} - The parsed arguments
|
|
95
95
|
*/
|
|
96
96
|
parseArgs(args = process.argv.slice(2)) {
|
|
97
97
|
if (!NON_SERVER_ARGS.has(args[0])) {
|
package/lib/config-file.js
CHANGED
|
@@ -154,9 +154,8 @@ export function normalizeConfig(config) {
|
|
|
154
154
|
const normalize = (config, section) => {
|
|
155
155
|
const obj = _.isUndefined(section) ? config : _.get(config, section, config);
|
|
156
156
|
|
|
157
|
-
const mappedObj = _.mapKeys(
|
|
158
|
-
|
|
159
|
-
(__, prop) => schema.properties[prop]?.appiumCliDest ?? _.camelCase(prop)
|
|
157
|
+
const mappedObj = _.mapKeys(obj, (__, prop) =>
|
|
158
|
+
_.get(schema, `properties.server.properties[${prop}].appiumCliDest`, _.camelCase(prop))
|
|
160
159
|
);
|
|
161
160
|
|
|
162
161
|
return _.mapValues(mappedObj, (value, property) => {
|
package/lib/config.js
CHANGED
|
@@ -298,11 +298,12 @@ function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parse
|
|
|
298
298
|
} else {
|
|
299
299
|
console.log(`\n(no configuration file loaded)`);
|
|
300
300
|
}
|
|
301
|
-
|
|
301
|
+
const compactedNonDefaultPreConfigArgs = compactConfig(nonDefaultPreConfigParsedArgs);
|
|
302
|
+
if (_.isEmpty(compactedNonDefaultPreConfigArgs)) {
|
|
302
303
|
console.log(`\n(no CLI parameters provided)`);
|
|
303
304
|
} else {
|
|
304
305
|
console.log('\nvia CLI or function call:\n');
|
|
305
|
-
console.dir(
|
|
306
|
+
console.dir(compactedNonDefaultPreConfigArgs);
|
|
306
307
|
}
|
|
307
308
|
console.log('\nfinal configuration:\n');
|
|
308
309
|
console.dir(compactConfig(parsedArgs));
|
package/lib/constants.js
CHANGED
|
@@ -29,6 +29,7 @@ export const KNOWN_PLUGINS = Object.freeze(
|
|
|
29
29
|
images: '@appium/images-plugin',
|
|
30
30
|
'execute-driver': '@appium/execute-driver-plugin',
|
|
31
31
|
'relaxed-caps': '@appium/relaxed-caps-plugin',
|
|
32
|
+
'universal-xml': '@appium/universal-xml-plugin',
|
|
32
33
|
})
|
|
33
34
|
);
|
|
34
35
|
|
|
@@ -40,13 +41,8 @@ export const KNOWN_DRIVERS = Object.freeze(
|
|
|
40
41
|
/** @type {const} */ ({
|
|
41
42
|
uiautomator2: 'appium-uiautomator2-driver',
|
|
42
43
|
xcuitest: 'appium-xcuitest-driver',
|
|
43
|
-
youiengine: 'appium-youiengine-driver',
|
|
44
|
-
windows: 'appium-windows-driver',
|
|
45
|
-
mac: 'appium-mac-driver',
|
|
46
44
|
mac2: 'appium-mac2-driver',
|
|
47
45
|
espresso: 'appium-espresso-driver',
|
|
48
|
-
tizen: 'appium-tizen-driver',
|
|
49
|
-
flutter: 'appium-flutter-driver',
|
|
50
46
|
safari: 'appium-safari-driver',
|
|
51
47
|
gecko: 'appium-geckodriver',
|
|
52
48
|
})
|
|
@@ -67,3 +63,8 @@ export const EXT_SUBCOMMAND_INSTALL = 'install';
|
|
|
67
63
|
export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
|
|
68
64
|
export const EXT_SUBCOMMAND_UPDATE = 'update';
|
|
69
65
|
export const EXT_SUBCOMMAND_RUN = 'run';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Current revision of the manifest (`extensions.yaml`) schema
|
|
69
|
+
*/
|
|
70
|
+
export const CURRENT_SCHEMA_REV = 3;
|
|
@@ -441,7 +441,7 @@ export class ExtensionConfig {
|
|
|
441
441
|
|
|
442
442
|
/**
|
|
443
443
|
* @param {ExtName<ExtType>} extName
|
|
444
|
-
* @param {ExtManifest<ExtType
|
|
444
|
+
* @param {ExtManifest<ExtType>} extManifest
|
|
445
445
|
* @param {ExtensionConfigMutationOpts} [opts]
|
|
446
446
|
* @returns {Promise<void>}
|
|
447
447
|
*/
|
|
@@ -504,11 +504,17 @@ export class ExtensionConfig {
|
|
|
504
504
|
}
|
|
505
505
|
|
|
506
506
|
/**
|
|
507
|
-
*
|
|
507
|
+
* Returns--with reasonable accuracy--the path on disk to the extension.
|
|
508
|
+
*
|
|
509
|
+
* If `installPath` is present in the manifest, then it is used; otherwise we just guess.
|
|
510
|
+
* @param {keyof typeof this.installedExtensions} extName
|
|
508
511
|
* @returns {string}
|
|
509
512
|
*/
|
|
510
513
|
getInstallPath(extName) {
|
|
511
|
-
return
|
|
514
|
+
return (
|
|
515
|
+
this.installedExtensions[extName]?.installPath ??
|
|
516
|
+
path.join(this.appiumHome, 'node_modules', this.installedExtensions[extName].pkgName)
|
|
517
|
+
);
|
|
512
518
|
}
|
|
513
519
|
|
|
514
520
|
/**
|
|
@@ -628,36 +634,39 @@ export {INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GIT
|
|
|
628
634
|
/**
|
|
629
635
|
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
630
636
|
* @typedef {import('./manifest').Manifest} Manifest
|
|
637
|
+
* @typedef {import('../cli/extension-command').ExtensionListData} ExtensionListData
|
|
638
|
+
* @typedef {import('../cli/extension-command').InstalledExtensionListData} InstalledExtensionListData
|
|
639
|
+
* @typedef {import('appium/types').InstallType} InstallType
|
|
631
640
|
*/
|
|
632
641
|
|
|
633
642
|
/**
|
|
634
|
-
* @template
|
|
635
|
-
* @typedef {import('appium/types').ExtManifest<
|
|
643
|
+
* @template {ExtensionType} ExtType
|
|
644
|
+
* @typedef {import('appium/types').ExtManifest<ExtType>} ExtManifest
|
|
636
645
|
*/
|
|
637
646
|
|
|
638
647
|
/**
|
|
639
|
-
* @template
|
|
640
|
-
* @typedef {
|
|
648
|
+
* @template {ExtensionType} ExtType
|
|
649
|
+
* @typedef {ExtManifest<ExtType> & {schema: NonNullable<ExtManifest<ExtType>['schema']>}} ExtManifestWithSchema
|
|
641
650
|
*/
|
|
642
651
|
|
|
643
652
|
/**
|
|
644
|
-
* @template
|
|
645
|
-
* @typedef {import('appium/types').ExtName<
|
|
653
|
+
* @template {ExtensionType} ExtType
|
|
654
|
+
* @typedef {import('appium/types').ExtName<ExtType>} ExtName
|
|
646
655
|
*/
|
|
647
656
|
|
|
648
657
|
/**
|
|
649
|
-
* @template
|
|
650
|
-
* @typedef {import('appium/types').ExtClass<
|
|
658
|
+
* @template {ExtensionType} ExtType
|
|
659
|
+
* @typedef {import('appium/types').ExtClass<ExtType>} ExtClass
|
|
651
660
|
*/
|
|
652
661
|
|
|
653
662
|
/**
|
|
654
|
-
* @template
|
|
655
|
-
* @typedef {import('appium/types').ExtRecord<
|
|
663
|
+
* @template {ExtensionType} ExtType
|
|
664
|
+
* @typedef {import('appium/types').ExtRecord<ExtType>} ExtRecord
|
|
656
665
|
*/
|
|
657
666
|
|
|
658
667
|
/**
|
|
659
|
-
* @template
|
|
660
|
-
* @typedef {import('../cli/extension').ExtCommand<
|
|
668
|
+
* @template {ExtensionType} ExtType
|
|
669
|
+
* @typedef {import('../cli/extension').ExtCommand<ExtType>} ExtCommand
|
|
661
670
|
*/
|
|
662
671
|
|
|
663
672
|
/**
|
|
@@ -665,13 +674,3 @@ export {INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GIT
|
|
|
665
674
|
* @typedef ExtensionConfigMutationOpts
|
|
666
675
|
* @property {boolean} [write=true] Whether or not to write the manifest to disk after a mutation operation
|
|
667
676
|
*/
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* A valid install type
|
|
671
|
-
* @typedef {typeof INSTALL_TYPE_NPM | typeof INSTALL_TYPE_GIT | typeof INSTALL_TYPE_LOCAL | typeof INSTALL_TYPE_GITHUB} InstallType
|
|
672
|
-
*/
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* @typedef {import('../cli/extension-command').ExtensionListData} ExtensionListData
|
|
676
|
-
* @typedef {import('../cli/extension-command').InstalledExtensionListData} InstalledExtensionListData
|
|
677
|
-
*/
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import log from '../logger';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This contains logic to migrate old versions of `extensions.yaml` to new versions.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Constant for v3 of the schema rev.
|
|
11
|
+
*/
|
|
12
|
+
const SCHEMA_REV_3 = 3;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Collection of functions to migrate from one version to another
|
|
16
|
+
* @type {{[P in keyof ManifestDataVersions]?: Migration}}
|
|
17
|
+
*/
|
|
18
|
+
const Migrations = {
|
|
19
|
+
/**
|
|
20
|
+
* If `installPath` is missing from the `ExtManifest` for any extension, return `true`.
|
|
21
|
+
*
|
|
22
|
+
* We cannot easily determine the path of any given extension here, but returning `true`
|
|
23
|
+
* will cause the `Manifest` to sync with the filesystem, which will populate the missing field.
|
|
24
|
+
*
|
|
25
|
+
* @type {Migration}
|
|
26
|
+
*/
|
|
27
|
+
[SCHEMA_REV_3]: (data) => {
|
|
28
|
+
let shouldSync = false;
|
|
29
|
+
const allExtData = [...Object.values(data.drivers), ...Object.values(data.plugins)];
|
|
30
|
+
for (const metadata of allExtData) {
|
|
31
|
+
if (!('installPath' in metadata)) {
|
|
32
|
+
shouldSync = true;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return shouldSync;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Set `schemaRev` to a specific version
|
|
42
|
+
* @param {AnyManifestDataVersion} data
|
|
43
|
+
* @param {keyof ManifestDataVersions} version
|
|
44
|
+
* @returns {boolean} Whether the data was modified
|
|
45
|
+
*/
|
|
46
|
+
function setSchemaRev(data, version) {
|
|
47
|
+
if (data.schemaRev ?? 0 < version) {
|
|
48
|
+
data.schemaRev = version;
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Applies a series of migration functions to a manifest to update its manifest schema version.
|
|
56
|
+
*
|
|
57
|
+
* `data` is modified in-place.
|
|
58
|
+
*
|
|
59
|
+
* @param {Manifest} manifest
|
|
60
|
+
* @param {AnyManifestDataVersion} data
|
|
61
|
+
* @returns {Promise<boolean>} If `true` existing packages should be synced from disk and the manifest should be persisted.
|
|
62
|
+
*/
|
|
63
|
+
export async function migrate(manifest, data) {
|
|
64
|
+
let didChange = false;
|
|
65
|
+
for await (const [v, migration] of Object.entries(Migrations)) {
|
|
66
|
+
const version = /** @type {keyof ManifestDataVersions} */ (Number(v));
|
|
67
|
+
didChange = (await migration(data, manifest)) || didChange;
|
|
68
|
+
didChange = setSchemaRev(data, version) || didChange;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (didChange) {
|
|
72
|
+
// 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}`);
|
|
74
|
+
}
|
|
75
|
+
return didChange;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Migration functions take a {@linkcode Manifest} and its data and **mutates `data` in-place**.
|
|
80
|
+
*
|
|
81
|
+
* A migration function should not modify `schemaRev`, as this is done automatically.
|
|
82
|
+
*
|
|
83
|
+
* A migration function can also call out to, say, {@linkcode Manifest.syncWithInstalledExtensions}, which
|
|
84
|
+
* may apply the mutation itself.
|
|
85
|
+
*
|
|
86
|
+
* 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
|
+
* @callback Migration
|
|
88
|
+
* @param {AnyManifestDataVersion} data - The raw data from `Manifest`
|
|
89
|
+
* @param {Manifest} manifest - The `Manifest` instance
|
|
90
|
+
* @returns {boolean|Promise<boolean>} If `true`, then something changed
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {import('appium/types').ManifestData} ManifestData
|
|
95
|
+
* @typedef {import('appium/types').AnyManifestDataVersion} AnyManifestDataVersion
|
|
96
|
+
* @typedef {import('appium/types').ManifestDataVersions} ManifestDataVersions
|
|
97
|
+
* @typedef {import('appium/types').ManifestV2.ManifestData} ManifestDataV2
|
|
98
|
+
* @typedef {import('./manifest').Manifest} Manifest
|
|
99
|
+
*/
|