appium 2.2.3 → 2.4.0
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 +131 -158
- package/build/lib/appium.d.ts +76 -4
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +327 -1
- package/build/lib/appium.js.map +1 -1
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +24 -2
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/driver-command.d.ts +16 -0
- package/build/lib/cli/driver-command.d.ts.map +1 -1
- package/build/lib/cli/driver-command.js +16 -0
- package/build/lib/cli/driver-command.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +26 -2
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +135 -4
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +11 -7
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/cli/plugin-command.d.ts +16 -0
- package/build/lib/cli/plugin-command.d.ts.map +1 -1
- package/build/lib/cli/plugin-command.js +16 -0
- package/build/lib/cli/plugin-command.js.map +1 -1
- package/build/lib/cli/utils.d.ts.map +1 -1
- package/build/lib/cli/utils.js +3 -4
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/config-file.d.ts +1 -1
- package/build/lib/config.d.ts +8 -7
- package/build/lib/config.d.ts.map +1 -1
- package/build/lib/config.js +54 -64
- package/build/lib/config.js.map +1 -1
- package/build/lib/constants.d.ts +10 -0
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +11 -1
- package/build/lib/constants.js.map +1 -1
- package/build/lib/doctor/doctor.d.ts +55 -0
- package/build/lib/doctor/doctor.d.ts.map +1 -0
- package/build/lib/doctor/doctor.js +201 -0
- package/build/lib/doctor/doctor.js.map +1 -0
- package/build/lib/extension/driver-config.d.ts +2 -2
- package/build/lib/extension/driver-config.d.ts.map +1 -1
- package/build/lib/extension/driver-config.js +4 -4
- package/build/lib/extension/driver-config.js.map +1 -1
- package/build/lib/extension/extension-config.d.ts +10 -3
- 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/index.d.ts +6 -4
- package/build/lib/extension/index.d.ts.map +1 -1
- package/build/lib/extension/index.js +74 -32
- package/build/lib/extension/index.js.map +1 -1
- package/build/lib/extension/manifest.js +1 -1
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/lib/extension/plugin-config.js +1 -1
- package/build/lib/extension/plugin-config.js.map +1 -1
- package/build/lib/grid-register.d.ts.map +1 -1
- package/build/lib/grid-register.js +1 -2
- package/build/lib/grid-register.js.map +1 -1
- package/build/lib/main.d.ts +4 -0
- package/build/lib/main.d.ts.map +1 -1
- package/build/lib/main.js +31 -52
- package/build/lib/main.js.map +1 -1
- package/build/lib/schema/schema.d.ts +5 -0
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +7 -7
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.d.ts +3 -0
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +2 -1
- package/build/lib/utils.js.map +1 -1
- package/build/types/cli.d.ts +1 -1
- package/build/types/cli.d.ts.map +1 -1
- package/lib/appium.js +388 -2
- package/lib/cli/args.js +27 -2
- package/lib/cli/driver-command.js +18 -0
- package/lib/cli/extension-command.js +160 -9
- package/lib/cli/parser.js +22 -8
- package/lib/cli/plugin-command.js +18 -0
- package/lib/cli/utils.js +5 -7
- package/lib/config.js +46 -65
- package/lib/constants.js +12 -0
- package/lib/doctor/doctor.js +209 -0
- package/lib/extension/driver-config.js +3 -3
- package/lib/extension/extension-config.js +33 -15
- package/lib/extension/index.js +80 -43
- package/lib/grid-register.js +1 -2
- package/lib/main.js +42 -61
- package/lib/utils.js +2 -1
- package/package.json +15 -18
- package/types/cli.ts +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import B from 'bluebird';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {npm, util, env, console} from '@appium/support';
|
|
5
|
+
import {npm, util, env, console, fs, system} from '@appium/support';
|
|
6
6
|
import {spinWith, RingBuffer} from './utils';
|
|
7
7
|
import {
|
|
8
8
|
INSTALL_TYPE_NPM,
|
|
@@ -15,6 +15,10 @@ import {SubProcess} from 'teen_process';
|
|
|
15
15
|
import {packageDidChange} from '../extension/package-changed';
|
|
16
16
|
import {spawn} from 'child_process';
|
|
17
17
|
import {inspect} from 'node:util';
|
|
18
|
+
import {pathToFileURL} from 'url';
|
|
19
|
+
import {Doctor} from '../doctor/doctor';
|
|
20
|
+
import {npmPackage} from '../utils';
|
|
21
|
+
import semver from 'semver';
|
|
18
22
|
|
|
19
23
|
const UPDATE_ALL = 'installed';
|
|
20
24
|
|
|
@@ -31,6 +35,19 @@ function receiptToManifest(receipt) {
|
|
|
31
35
|
return /** @type {ExtManifest<ExtType>} */ (_.omit(receipt, 'driverName', 'pluginName'));
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Fetches the remote extension version requirement
|
|
40
|
+
*
|
|
41
|
+
* @param {string} installSpec Extension name (could also contain the version suffix)
|
|
42
|
+
* @returns {Promise<[string, string|null]>}
|
|
43
|
+
*/
|
|
44
|
+
async function getRemoteExtensionVersionReq(installSpec) {
|
|
45
|
+
const allDeps = await npm.getPackageInfo(installSpec, ['peerDependencies', 'dependencies']);
|
|
46
|
+
const requiredVersionPair = _.flatMap(_.values(allDeps).map(_.toPairs))
|
|
47
|
+
.find(([name]) => name === 'appium');
|
|
48
|
+
return [npmPackage.version, requiredVersionPair ? requiredVersionPair[1] : null];
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
/**
|
|
35
52
|
* @template {ExtensionType} ExtType
|
|
36
53
|
*/
|
|
@@ -223,6 +240,29 @@ class ExtensionCliCommand {
|
|
|
223
240
|
return listData;
|
|
224
241
|
}
|
|
225
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Checks whether the given extension is compatible with the currently installed server
|
|
245
|
+
*
|
|
246
|
+
* @param {InstallViaNpmArgs} installViaNpmOpts
|
|
247
|
+
* @returns {Promise<void>}
|
|
248
|
+
*/
|
|
249
|
+
async _checkInstallCompatibility({installSpec, pkgName, installType}) {
|
|
250
|
+
if (INSTALL_TYPE_NPM !== installType) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await spinWith(this.isJsonOutput, `Checking if '${pkgName}' is compatible`, async () => {
|
|
255
|
+
const [serverVersion, extVersionRequirement] = await getRemoteExtensionVersionReq(installSpec);
|
|
256
|
+
if (serverVersion && extVersionRequirement && !semver.satisfies(serverVersion, extVersionRequirement)) {
|
|
257
|
+
throw this._createFatalError(
|
|
258
|
+
`'${installSpec}' cannot be installed because the server version it requires (${extVersionRequirement}) ` +
|
|
259
|
+
`does not meet the currently installed one (${serverVersion}). Please install ` +
|
|
260
|
+
`a compatible server version first.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
226
266
|
/**
|
|
227
267
|
* Install an extension
|
|
228
268
|
*
|
|
@@ -332,6 +372,8 @@ class ExtensionCliCommand {
|
|
|
332
372
|
);
|
|
333
373
|
}
|
|
334
374
|
|
|
375
|
+
await this._checkInstallCompatibility(installViaNpmOpts);
|
|
376
|
+
|
|
335
377
|
receipt = await this.installViaNpm(installViaNpmOpts);
|
|
336
378
|
|
|
337
379
|
// this _should_ be the same as `probablyExtName` as the one derived above unless
|
|
@@ -695,6 +737,85 @@ class ExtensionCliCommand {
|
|
|
695
737
|
});
|
|
696
738
|
}
|
|
697
739
|
|
|
740
|
+
/**
|
|
741
|
+
* Runs doctor checks for the given extension
|
|
742
|
+
*
|
|
743
|
+
* @param {DoctorOptions} opts
|
|
744
|
+
* @returns {Promise<import('@appium/types').IDoctorCheck[]>}
|
|
745
|
+
*/
|
|
746
|
+
async _doctor({installSpec}) {
|
|
747
|
+
if (!this.config.isInstalled(installSpec)) {
|
|
748
|
+
throw this._createFatalError(`The ${this.type} "${installSpec}" is not installed`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const moduleRoot = this.config.getInstallPath(installSpec);
|
|
752
|
+
const packageJsonPath = path.join(moduleRoot, 'package.json');
|
|
753
|
+
if (!await fs.exists(packageJsonPath)) {
|
|
754
|
+
throw this._createFatalError(
|
|
755
|
+
`No package.json could be found for "${installSpec}" ${this.type}`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
let doctorSpec;
|
|
759
|
+
try {
|
|
760
|
+
doctorSpec = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')).appium?.doctor;
|
|
761
|
+
} catch (e) {
|
|
762
|
+
throw this._createFatalError(
|
|
763
|
+
`The manifest at '${packageJsonPath}' cannot be parsed: ${e.message}`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (!doctorSpec) {
|
|
767
|
+
this.log.info(`The ${this.type} "${installSpec}" does not export any doctor checks`);
|
|
768
|
+
return [];
|
|
769
|
+
}
|
|
770
|
+
if (!_.isPlainObject(doctorSpec) || !_.isArray(doctorSpec.checks)) {
|
|
771
|
+
throw this._createFatalError(
|
|
772
|
+
`The 'doctor' entry in the package manifest '${packageJsonPath}' must be a proper object ` +
|
|
773
|
+
`containing the 'checks' key with the array of script paths`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
const paths = doctorSpec.checks.map((/** @type {string} */ p) => {
|
|
777
|
+
const scriptPath = path.resolve(moduleRoot, p);
|
|
778
|
+
if (!path.normalize(scriptPath).startsWith(path.normalize(moduleRoot))) {
|
|
779
|
+
this.log.error(
|
|
780
|
+
`The doctor check script '${p}' from the package manifest '${packageJsonPath}' must be located ` +
|
|
781
|
+
`in the '${moduleRoot}' root folder. It will be skipped`
|
|
782
|
+
);
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
return scriptPath;
|
|
786
|
+
}).filter(Boolean);
|
|
787
|
+
/** @type {Promise[]} */
|
|
788
|
+
const loadChecksPromises = [];
|
|
789
|
+
for (const p of paths) {
|
|
790
|
+
const promise = (async () => {
|
|
791
|
+
// https://github.com/nodejs/node/issues/31710
|
|
792
|
+
const scriptPath = system.isWindows() ? pathToFileURL(p).href : p;
|
|
793
|
+
try {
|
|
794
|
+
return await import(scriptPath);
|
|
795
|
+
} catch (e) {
|
|
796
|
+
this.log.warn(`Unable to load doctor checks from '${p}': ${e.message}`);
|
|
797
|
+
}
|
|
798
|
+
})();
|
|
799
|
+
loadChecksPromises.push(promise);
|
|
800
|
+
}
|
|
801
|
+
const isDoctorCheck = (/** @type {any} */ x) =>
|
|
802
|
+
['diagnose', 'fix', 'hasAutofix', 'isOptional'].every((method) => _.isFunction(x?.[method]));
|
|
803
|
+
/** @type {import('@appium/types').IDoctorCheck[]} */
|
|
804
|
+
const checks = _.flatMap((await B.all(loadChecksPromises)).filter(Boolean).map(_.toPairs))
|
|
805
|
+
.map(([, value]) => value)
|
|
806
|
+
.filter(isDoctorCheck);
|
|
807
|
+
if (_.isEmpty(checks)) {
|
|
808
|
+
this.log.info(`The ${this.type} "${installSpec}" exports no valid doctor checks`);
|
|
809
|
+
return [];
|
|
810
|
+
}
|
|
811
|
+
this.log.debug(
|
|
812
|
+
`Running ${util.pluralize('doctor check', checks.length, true)} ` +
|
|
813
|
+
`for the "${installSpec}" ${this.type}`
|
|
814
|
+
);
|
|
815
|
+
await new Doctor(checks).run();
|
|
816
|
+
return checks;
|
|
817
|
+
}
|
|
818
|
+
|
|
698
819
|
/**
|
|
699
820
|
* Runs a script cached inside the `scripts` field under `appium`
|
|
700
821
|
* inside of the extension's `package.json` file. Will throw
|
|
@@ -729,15 +850,42 @@ class ExtensionCliCommand {
|
|
|
729
850
|
);
|
|
730
851
|
}
|
|
731
852
|
|
|
853
|
+
if (!scriptName) {
|
|
854
|
+
const allScripts = _.toPairs(extScripts);
|
|
855
|
+
const root = this.config.getInstallPath(installSpec);
|
|
856
|
+
const existingScripts = await B.filter(
|
|
857
|
+
allScripts,
|
|
858
|
+
async ([, p]) => await fs.exists(path.join(root, p))
|
|
859
|
+
);
|
|
860
|
+
if (_.isEmpty(existingScripts)) {
|
|
861
|
+
this.log.info(`The ${this.type} named '${installSpec}' does not contain any scripts`);
|
|
862
|
+
} else {
|
|
863
|
+
this.log.info(`The ${this.type} named '${installSpec}' contains ` +
|
|
864
|
+
`${util.pluralize('script', existingScripts.length, true)}:`);
|
|
865
|
+
existingScripts.forEach(([name]) => this.log.info(` - ${name}`));
|
|
866
|
+
}
|
|
867
|
+
this.log.ok(`Successfully retrieved the list of scripts`.green);
|
|
868
|
+
return {};
|
|
869
|
+
}
|
|
870
|
+
|
|
732
871
|
if (!(scriptName in /** @type {Record<string,string>} */ (extScripts))) {
|
|
733
872
|
throw this._createFatalError(
|
|
734
873
|
`The ${this.type} named '${installSpec}' does not support the script: '${scriptName}'`
|
|
735
874
|
);
|
|
736
875
|
}
|
|
737
876
|
|
|
877
|
+
const scriptPath = extScripts[scriptName];
|
|
878
|
+
const moduleRoot = this.config.getInstallPath(installSpec);
|
|
879
|
+
const normalizedScriptPath = path.normalize(path.resolve(moduleRoot, scriptPath));
|
|
880
|
+
if (!normalizedScriptPath.startsWith(path.normalize(moduleRoot))) {
|
|
881
|
+
throw this._createFatalError(
|
|
882
|
+
`The '${scriptPath}' script must be located in the '${moduleRoot}' folder`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
738
886
|
if (bufferOutput) {
|
|
739
|
-
const runner = new SubProcess(process.execPath, [
|
|
740
|
-
cwd:
|
|
887
|
+
const runner = new SubProcess(process.execPath, [scriptPath, ...extraArgs], {
|
|
888
|
+
cwd: moduleRoot,
|
|
741
889
|
});
|
|
742
890
|
|
|
743
891
|
const output = new RingBuffer(50);
|
|
@@ -761,11 +909,7 @@ class ExtensionCliCommand {
|
|
|
761
909
|
|
|
762
910
|
try {
|
|
763
911
|
await new B((resolve, reject) => {
|
|
764
|
-
this._runUnbuffered(
|
|
765
|
-
this.config.getInstallPath(installSpec),
|
|
766
|
-
extScripts[scriptName],
|
|
767
|
-
extraArgs
|
|
768
|
-
)
|
|
912
|
+
this._runUnbuffered(moduleRoot, scriptPath, extraArgs)
|
|
769
913
|
.on('error', (err) => {
|
|
770
914
|
// generally this is of the "I can't find the script" variety.
|
|
771
915
|
// this is a developer bug: the extension is pointing to a script that is not where the
|
|
@@ -869,11 +1013,18 @@ export {ExtensionCliCommand as ExtensionCommand};
|
|
|
869
1013
|
* Options for {@linkcode ExtensionCliCommand._run}.
|
|
870
1014
|
* @typedef RunOptions
|
|
871
1015
|
* @property {string} installSpec - name of the extension to run a script from
|
|
872
|
-
* @property {string} scriptName - name of the script to run
|
|
1016
|
+
* @property {string} [scriptName] - name of the script to run. If not provided
|
|
1017
|
+
* then all available script names will be printed
|
|
873
1018
|
* @property {string[]} [extraArgs] - arguments to pass to the script
|
|
874
1019
|
* @property {boolean} [bufferOutput] - if true, will buffer the output of the script and return it
|
|
875
1020
|
*/
|
|
876
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* Options for {@linkcode ExtensionCliCommand.doctor}.
|
|
1024
|
+
* @typedef DoctorOptions
|
|
1025
|
+
* @property {string} installSpec - name of the extension to run doctor checks for
|
|
1026
|
+
*/
|
|
1027
|
+
|
|
877
1028
|
/**
|
|
878
1029
|
* Return value of {@linkcode ExtensionCliCommand._run}
|
|
879
1030
|
*
|
package/lib/cli/parser.js
CHANGED
|
@@ -2,7 +2,17 @@ import {fs} from '@appium/support';
|
|
|
2
2
|
import {ArgumentParser} from 'argparse';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DRIVER_TYPE,
|
|
7
|
+
EXT_SUBCOMMAND_DOCTOR,
|
|
8
|
+
EXT_SUBCOMMAND_INSTALL,
|
|
9
|
+
EXT_SUBCOMMAND_LIST,
|
|
10
|
+
EXT_SUBCOMMAND_RUN,
|
|
11
|
+
EXT_SUBCOMMAND_UNINSTALL,
|
|
12
|
+
EXT_SUBCOMMAND_UPDATE,
|
|
13
|
+
PLUGIN_TYPE,
|
|
14
|
+
SERVER_SUBCOMMAND
|
|
15
|
+
} from '../constants';
|
|
6
16
|
import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
|
|
7
17
|
import {rootDir} from '../config';
|
|
8
18
|
import {getExtensionArgs, getServerArgs} from './args';
|
|
@@ -191,8 +201,7 @@ class ArgParser {
|
|
|
191
201
|
|
|
192
202
|
const serverArgs = getServerArgs();
|
|
193
203
|
for (const [flagsOrNames, opts] of serverArgs) {
|
|
194
|
-
// TS doesn't like the spread operator here.
|
|
195
|
-
// @ts-ignore
|
|
204
|
+
// @ts-ignore TS doesn't like the spread operator here.
|
|
196
205
|
serverParser.add_argument(...flagsOrNames, {...opts});
|
|
197
206
|
}
|
|
198
207
|
|
|
@@ -221,33 +230,38 @@ class ArgParser {
|
|
|
221
230
|
*/
|
|
222
231
|
const parserSpecs = [
|
|
223
232
|
{
|
|
224
|
-
command:
|
|
233
|
+
command: EXT_SUBCOMMAND_LIST,
|
|
225
234
|
args: extensionArgs[type].list,
|
|
226
235
|
help: `List available and installed ${type}s`,
|
|
227
236
|
aliases: ['ls'],
|
|
228
237
|
},
|
|
229
238
|
{
|
|
230
|
-
command:
|
|
239
|
+
command: EXT_SUBCOMMAND_INSTALL,
|
|
231
240
|
args: extensionArgs[type].install,
|
|
232
241
|
help: `Install a ${type}`,
|
|
233
242
|
},
|
|
234
243
|
{
|
|
235
|
-
command:
|
|
244
|
+
command: EXT_SUBCOMMAND_UNINSTALL,
|
|
236
245
|
args: extensionArgs[type].uninstall,
|
|
237
246
|
help: `Uninstall a ${type}`,
|
|
238
247
|
},
|
|
239
248
|
{
|
|
240
|
-
command:
|
|
249
|
+
command: EXT_SUBCOMMAND_UPDATE,
|
|
241
250
|
args: extensionArgs[type].update,
|
|
242
251
|
help: `Update installed ${type}s to the latest version`,
|
|
243
252
|
},
|
|
244
253
|
{
|
|
245
|
-
command:
|
|
254
|
+
command: EXT_SUBCOMMAND_RUN,
|
|
246
255
|
args: extensionArgs[type].run,
|
|
247
256
|
help:
|
|
248
257
|
`Run a script (defined inside the ${type}'s package.json under the ` +
|
|
249
258
|
`“scripts” field inside the “appium” field) from an installed ${type}`,
|
|
250
259
|
},
|
|
260
|
+
{
|
|
261
|
+
command: EXT_SUBCOMMAND_DOCTOR,
|
|
262
|
+
args: extensionArgs[type].doctor,
|
|
263
|
+
help: `Run doctor checks (if any defined) for the given ${type}`,
|
|
264
|
+
},
|
|
251
265
|
];
|
|
252
266
|
|
|
253
267
|
for (const {command, args, help, aliases} of parserSpecs) {
|
|
@@ -65,6 +65,18 @@ export default class PluginCliCommand extends ExtensionCliCommand {
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Runs doctor checks for the given plugin
|
|
70
|
+
*
|
|
71
|
+
* @param {PluginDoctorOptions} opts
|
|
72
|
+
* @returns {Promise<import('@appium/types').IDoctorCheck[]>}
|
|
73
|
+
*/
|
|
74
|
+
async doctor({plugin}) {
|
|
75
|
+
return await super._doctor({
|
|
76
|
+
installSpec: plugin,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
/**
|
|
69
81
|
*
|
|
70
82
|
* @param {import('./extension-command').ExtensionArgs} opts
|
|
@@ -141,3 +153,9 @@ export default class PluginCliCommand extends ExtensionCliCommand {
|
|
|
141
153
|
* @property {string} scriptName - name of the script to run
|
|
142
154
|
* @property {string[]} [extraArgs] - arguments to pass to the script
|
|
143
155
|
*/
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Options for {@linkcode PluginCliCommand.doctor}.
|
|
159
|
+
* @typedef PluginDoctorOptions
|
|
160
|
+
* @property {string} plugin - name of the plugin to run doctor checks for
|
|
161
|
+
*/
|
package/lib/cli/utils.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
|
|
5
|
-
const JSON_SPACES = 4;
|
|
5
|
+
export const JSON_SPACES = 4;
|
|
6
6
|
|
|
7
7
|
/***
|
|
8
8
|
* Log an error to the console and exit the process.
|
|
9
9
|
* @param {boolean} json - whether we should log json or text
|
|
10
10
|
* @param {any} msg - error message, object, Error instance, etc.
|
|
11
11
|
*/
|
|
12
|
-
function errAndQuit(json, msg) {
|
|
12
|
+
export function errAndQuit(json, msg) {
|
|
13
13
|
if (json) {
|
|
14
14
|
console.log(JSON.stringify({error: `${msg}`}, null, JSON_SPACES));
|
|
15
15
|
} else {
|
|
@@ -26,7 +26,7 @@ function errAndQuit(json, msg) {
|
|
|
26
26
|
* @param {boolean} json - whether we are in json mode (and should therefore not log)
|
|
27
27
|
* @param {string} msg - string to log
|
|
28
28
|
*/
|
|
29
|
-
function log(json, msg) {
|
|
29
|
+
export function log(json, msg) {
|
|
30
30
|
!json && console.log(msg);
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -36,7 +36,7 @@ function log(json, msg) {
|
|
|
36
36
|
* @param {string} msg - string to log
|
|
37
37
|
* @param {function} fn - function to wrap with spinning
|
|
38
38
|
*/
|
|
39
|
-
async function spinWith(json, msg, fn) {
|
|
39
|
+
export async function spinWith(json, msg, fn) {
|
|
40
40
|
if (json) {
|
|
41
41
|
return await fn();
|
|
42
42
|
}
|
|
@@ -52,7 +52,7 @@ async function spinWith(json, msg, fn) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
class RingBuffer {
|
|
55
|
+
export class RingBuffer {
|
|
56
56
|
constructor(size = 50) {
|
|
57
57
|
this.size = size;
|
|
58
58
|
this.buffer = [];
|
|
@@ -70,5 +70,3 @@ class RingBuffer {
|
|
|
70
70
|
this.buffer.push(item);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
export {errAndQuit, log, spinWith, JSON_SPACES, RingBuffer};
|
package/lib/config.js
CHANGED
|
@@ -4,17 +4,14 @@ import {system, fs} from '@appium/support';
|
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import {exec} from 'teen_process';
|
|
6
6
|
import semver from 'semver';
|
|
7
|
-
import
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import {npmPackage} from './utils';
|
|
8
9
|
import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const APPIUM_VER = npmPackage.version;
|
|
11
|
+
export const APPIUM_VER = npmPackage.version;
|
|
13
12
|
const ENGINES = /** @type {Record<string,string>} */ (npmPackage.engines);
|
|
14
13
|
const MIN_NODE_VERSION = ENGINES.node;
|
|
15
|
-
const MIN_NPM_VERSION = ENGINES.npm;
|
|
16
14
|
|
|
17
|
-
const GIT_META_ROOT = '.git';
|
|
18
15
|
const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
|
|
19
16
|
const GITHUB_API = 'https://api.github.com/repos/appium/appium';
|
|
20
17
|
|
|
@@ -29,15 +26,6 @@ function getNodeVersion() {
|
|
|
29
26
|
return /** @type {import('semver').SemVer} */ (semver.coerce(process.version));
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
/**
|
|
33
|
-
* Returns version of `npm`
|
|
34
|
-
* @returns {Promise<string>}
|
|
35
|
-
*/
|
|
36
|
-
async function getNpmVersion() {
|
|
37
|
-
const {stdout} = await exec(system.isWindows() ? 'npm.cmd' : 'npm', ['--version']);
|
|
38
|
-
return stdout.trim();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
29
|
/**
|
|
42
30
|
* @param {boolean} [useGithubApiFallback]
|
|
43
31
|
*/
|
|
@@ -53,27 +41,25 @@ async function updateBuildInfo(useGithubApiFallback = false) {
|
|
|
53
41
|
}
|
|
54
42
|
}
|
|
55
43
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return await findUp(GIT_META_ROOT, {cwd: rootDir, type: 'directory'});
|
|
65
|
-
}
|
|
44
|
+
/** @type {() => Promise<string?>} */
|
|
45
|
+
const getFullGitPath = _.memoize(async function getFullGitPath() {
|
|
46
|
+
try {
|
|
47
|
+
return await fs.which(GIT_BINARY);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
66
52
|
|
|
67
53
|
/**
|
|
68
54
|
* @param {boolean} [useGithubApiFallback]
|
|
69
55
|
* @returns {Promise<string?>}
|
|
70
56
|
*/
|
|
71
57
|
async function getGitRev(useGithubApiFallback = false) {
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
58
|
+
const fullGitPath = await getFullGitPath();
|
|
59
|
+
if (fullGitPath) {
|
|
74
60
|
try {
|
|
75
|
-
const {stdout} = await exec(
|
|
76
|
-
cwd:
|
|
61
|
+
const {stdout} = await exec(fullGitPath, ['rev-parse', 'HEAD'], {
|
|
62
|
+
cwd: __dirname,
|
|
77
63
|
});
|
|
78
64
|
return stdout.trim();
|
|
79
65
|
} catch (ign) {}
|
|
@@ -103,11 +89,11 @@ async function getGitRev(useGithubApiFallback = false) {
|
|
|
103
89
|
* @returns {Promise<string?>}
|
|
104
90
|
*/
|
|
105
91
|
async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
92
|
+
const fullGitPath = await getFullGitPath();
|
|
93
|
+
if (fullGitPath) {
|
|
108
94
|
try {
|
|
109
|
-
const {stdout} = await exec(
|
|
110
|
-
cwd:
|
|
95
|
+
const {stdout} = await exec(fullGitPath, ['show', '-s', '--format=%ci', commitSha], {
|
|
96
|
+
cwd: __dirname,
|
|
111
97
|
});
|
|
112
98
|
return stdout.trim();
|
|
113
99
|
} catch (ign) {}
|
|
@@ -149,28 +135,6 @@ function checkNodeOk() {
|
|
|
149
135
|
}
|
|
150
136
|
}
|
|
151
137
|
|
|
152
|
-
export async function checkNpmOk() {
|
|
153
|
-
const npmVersion = await getNpmVersion();
|
|
154
|
-
if (!semver.satisfies(npmVersion, MIN_NPM_VERSION)) {
|
|
155
|
-
throw new Error(
|
|
156
|
-
`npm version must be at least ${MIN_NPM_VERSION}; current is ${npmVersion}. Run "npm install -g npm" to upgrade.`
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function warnNodeDeprecations() {
|
|
162
|
-
/**
|
|
163
|
-
* Uncomment this section to get node version deprecation warnings
|
|
164
|
-
* Also add test cases to config-specs.js to cover the cases added
|
|
165
|
-
**/
|
|
166
|
-
// const version = getNodeVersion();
|
|
167
|
-
// if (version.major < 8) {
|
|
168
|
-
// logger.warn(`Appium support for versions of node < ${version.major} has been ` +
|
|
169
|
-
// 'deprecated and will be removed in a future version. Please ' +
|
|
170
|
-
// 'upgrade!');
|
|
171
|
-
// }
|
|
172
|
-
}
|
|
173
|
-
|
|
174
138
|
async function showBuildInfo() {
|
|
175
139
|
await updateBuildInfo(true);
|
|
176
140
|
console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
|
|
@@ -311,16 +275,36 @@ function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parse
|
|
|
311
275
|
}
|
|
312
276
|
|
|
313
277
|
/**
|
|
314
|
-
* @param {string}
|
|
278
|
+
* @param {string} root
|
|
279
|
+
* @param {boolean} [requireWriteable=true]
|
|
280
|
+
* @param {string} [displayName='folder path']
|
|
281
|
+
* @throws {Error}
|
|
315
282
|
*/
|
|
316
|
-
async function
|
|
283
|
+
export async function requireDir(root, requireWriteable = true, displayName = 'folder path') {
|
|
284
|
+
let stat;
|
|
317
285
|
try {
|
|
318
|
-
await fs.
|
|
286
|
+
stat = await fs.stat(root);
|
|
319
287
|
} catch (e) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
288
|
+
if (e.code === 'ENOENT') {
|
|
289
|
+
try {
|
|
290
|
+
await fs.mkdir(root, {recursive: true});
|
|
291
|
+
return;
|
|
292
|
+
} catch {}
|
|
293
|
+
}
|
|
294
|
+
throw new Error(`The ${displayName} '${root}' must exist and be a valid directory`);
|
|
295
|
+
}
|
|
296
|
+
if (stat && !stat.isDirectory()) {
|
|
297
|
+
throw new Error(`The ${displayName} '${root}' must be a valid directory`);
|
|
298
|
+
}
|
|
299
|
+
if (requireWriteable) {
|
|
300
|
+
try {
|
|
301
|
+
await fs.access(root, fs.constants.W_OK);
|
|
302
|
+
} catch (e) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`The ${displayName} '${root}' must be ` +
|
|
305
|
+
`writeable for the current user account '${os.userInfo().username}'`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
324
308
|
}
|
|
325
309
|
}
|
|
326
310
|
|
|
@@ -330,11 +314,8 @@ export {
|
|
|
330
314
|
getBuildInfo,
|
|
331
315
|
checkNodeOk,
|
|
332
316
|
showBuildInfo,
|
|
333
|
-
warnNodeDeprecations,
|
|
334
|
-
validateTmpDir,
|
|
335
317
|
getNonDefaultServerArgs,
|
|
336
318
|
getGitRev,
|
|
337
|
-
APPIUM_VER,
|
|
338
319
|
updateBuildInfo,
|
|
339
320
|
showConfig,
|
|
340
321
|
rootDir,
|
package/lib/constants.js
CHANGED
|
@@ -64,6 +64,7 @@ export const EXT_SUBCOMMAND_INSTALL = 'install';
|
|
|
64
64
|
export const EXT_SUBCOMMAND_UNINSTALL = 'uninstall';
|
|
65
65
|
export const EXT_SUBCOMMAND_UPDATE = 'update';
|
|
66
66
|
export const EXT_SUBCOMMAND_RUN = 'run';
|
|
67
|
+
export const EXT_SUBCOMMAND_DOCTOR = 'doctor';
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Current revision of the manifest (`extensions.yaml`) schema
|
|
@@ -77,3 +78,14 @@ export const CURRENT_SCHEMA_REV = 4;
|
|
|
77
78
|
* memory usage, perf, and/or log output, and higher limits may be difficult to scan.
|
|
78
79
|
*/
|
|
79
80
|
export const LONG_STACKTRACE_LIMIT = 100;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Where should the bidi websocket handler live on the server?
|
|
84
|
+
*/
|
|
85
|
+
export const BIDI_BASE_PATH = '/bidi';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The name of the event for drivers to emit when they want to send bidi events to a client over
|
|
89
|
+
* a bidi socket
|
|
90
|
+
*/
|
|
91
|
+
export const BIDI_EVENT_NAME = 'bidiEvent';
|