appium 2.5.4 → 2.6.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.
Files changed (45) hide show
  1. package/build/lib/cli/args.d.ts.map +1 -1
  2. package/build/lib/cli/args.js +10 -0
  3. package/build/lib/cli/args.js.map +1 -1
  4. package/build/lib/cli/parser.d.ts +5 -0
  5. package/build/lib/cli/parser.d.ts.map +1 -1
  6. package/build/lib/cli/parser.js +38 -1
  7. package/build/lib/cli/parser.js.map +1 -1
  8. package/build/lib/cli/setup-command.d.ts +37 -0
  9. package/build/lib/cli/setup-command.d.ts.map +1 -0
  10. package/build/lib/cli/setup-command.js +173 -0
  11. package/build/lib/cli/setup-command.js.map +1 -0
  12. package/build/lib/config.d.ts +38 -18
  13. package/build/lib/config.d.ts.map +1 -1
  14. package/build/lib/config.js +75 -3
  15. package/build/lib/config.js.map +1 -1
  16. package/build/lib/constants.d.ts +21 -2
  17. package/build/lib/constants.d.ts.map +1 -1
  18. package/build/lib/constants.js +24 -7
  19. package/build/lib/constants.js.map +1 -1
  20. package/build/lib/grid-register.js +2 -2
  21. package/build/lib/grid-register.js.map +1 -1
  22. package/build/lib/logsink.d.ts.map +1 -1
  23. package/build/lib/logsink.js +23 -5
  24. package/build/lib/logsink.js.map +1 -1
  25. package/build/lib/main.d.ts +1 -0
  26. package/build/lib/main.d.ts.map +1 -1
  27. package/build/lib/main.js +15 -1
  28. package/build/lib/main.js.map +1 -1
  29. package/build/lib/utils.d.ts +12 -2
  30. package/build/lib/utils.d.ts.map +1 -1
  31. package/build/lib/utils.js +15 -3
  32. package/build/lib/utils.js.map +1 -1
  33. package/build/types/cli.d.ts +23 -6
  34. package/build/types/cli.d.ts.map +1 -1
  35. package/lib/cli/args.js +10 -0
  36. package/lib/cli/parser.js +52 -2
  37. package/lib/cli/setup-command.js +185 -0
  38. package/lib/config.js +81 -20
  39. package/lib/constants.js +31 -6
  40. package/lib/grid-register.js +2 -2
  41. package/lib/logsink.js +24 -5
  42. package/lib/main.js +17 -1
  43. package/lib/utils.js +15 -3
  44. package/package.json +14 -14
  45. package/types/cli.ts +33 -8
package/lib/cli/args.js CHANGED
@@ -290,6 +290,16 @@ const serverArgsDisallowedInConfig = new Map([
290
290
  help: 'Show info about the Appium build and exit',
291
291
  },
292
292
  ],
293
+ [
294
+ ['--show-debug-info'],
295
+ {
296
+ dest: 'showDebugInfo',
297
+ action: 'store_const',
298
+ const: true,
299
+ required: false,
300
+ help: 'Show debug info about the current Appium deployment and exit',
301
+ },
302
+ ],
293
303
  [
294
304
  ['--show-config'],
295
305
  {
package/lib/cli/parser.js CHANGED
@@ -11,11 +11,19 @@ import {
11
11
  EXT_SUBCOMMAND_UNINSTALL,
12
12
  EXT_SUBCOMMAND_UPDATE,
13
13
  PLUGIN_TYPE,
14
- SERVER_SUBCOMMAND
14
+ SERVER_SUBCOMMAND,
15
+ SETUP_SUBCOMMAND
15
16
  } from '../constants';
16
17
  import {finalizeSchema, getArgSpec, hasArgSpec} from '../schema';
17
18
  import {rootDir} from '../config';
18
19
  import {getExtensionArgs, getServerArgs} from './args';
20
+ import {
21
+ SUBCOMMAND_MOBILE,
22
+ SUBCOMMAND_DESKTOP,
23
+ SUBCOMMAND_BROWSER,
24
+ getPresetDrivers,
25
+ determinePlatformName
26
+ } from './setup-command';
19
27
 
20
28
  export const EXTRA_ARGS = 'extraArgs';
21
29
 
@@ -24,7 +32,7 @@ export const EXTRA_ARGS = 'extraArgs';
24
32
  * will automatially inject the `server` subcommand.
25
33
  */
26
34
  const NON_SERVER_ARGS = Object.freeze(
27
- new Set([DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND, '-h', '--help', '-v', '--version'])
35
+ new Set([SETUP_SUBCOMMAND, DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND, '-h', '--help', '-v', '--version'])
28
36
  );
29
37
 
30
38
  const version = fs.readPackageJsonFrom(rootDir).version;
@@ -76,6 +84,9 @@ class ArgParser {
76
84
 
77
85
  const subParsers = parser.add_subparsers({dest: 'subcommand'});
78
86
 
87
+ // add the 'setup' command
88
+ ArgParser._addSetupToParser(subParsers);
89
+
79
90
  // add the 'server' subcommand, and store the raw arguments on the parser
80
91
  // object as a way for other parts of the code to work with the arguments
81
92
  // conceptually rather than just through argparse
@@ -280,6 +291,45 @@ class ArgParser {
280
291
  }
281
292
  }
282
293
  }
294
+
295
+ /**
296
+ * Add subcommand and sub-sub commands for 'setup' subcommand.
297
+ * @param {import('argparse').SubParser} subParser
298
+ */
299
+ static _addSetupToParser(subParser) {
300
+ const setupParser = subParser.add_parser('setup', {
301
+ add_help: true,
302
+ help: `Select a preset of official drivers/plugins to install ` +
303
+ `compatible with '${determinePlatformName()}' host platform. ` +
304
+ `Existing drivers/plugins will remain. The default preset is 'mobile'.`,
305
+ });
306
+
307
+
308
+ ArgParser._patchExit(setupParser);
309
+ const extSubParsers = setupParser.add_subparsers({
310
+ dest: `setupCommand`,
311
+ });
312
+
313
+ const parserSpecs = [
314
+ {
315
+ command: SUBCOMMAND_MOBILE,
316
+ help: `The preset for mobile devices: ${_.join(getPresetDrivers(SUBCOMMAND_MOBILE), ',')}`
317
+ },
318
+ {
319
+ command: SUBCOMMAND_BROWSER,
320
+ help: `The preset for desktop browser drivers: ${_.join(getPresetDrivers(SUBCOMMAND_BROWSER), ',')}`
321
+ },
322
+ {
323
+ command: SUBCOMMAND_DESKTOP,
324
+ help: `The preset for desktop application drivers: ${_.join(getPresetDrivers(SUBCOMMAND_DESKTOP), ',')}`
325
+ },
326
+ ];
327
+
328
+ for (const {command, help} of parserSpecs) {
329
+ const parser = extSubParsers.add_parser(command, {help});
330
+ ArgParser._patchExit(parser);
331
+ }
332
+ }
283
333
  }
284
334
 
285
335
  /**
@@ -0,0 +1,185 @@
1
+ import _ from 'lodash';
2
+ import {
3
+ DESKTOP_BROWSERS,
4
+ DESKTOP_DRIVERS,
5
+ MOBILE_DRIVERS
6
+ } from '../constants';
7
+ import {runExtensionCommand} from './extension';
8
+ import { system } from '@appium/support';
9
+ import log from '../logger';
10
+
11
+ /**
12
+ * Subcommands of preset for setup
13
+ */
14
+ export const SUBCOMMAND_MOBILE = 'mobile';
15
+ export const SUBCOMMAND_DESKTOP = 'desktop';
16
+ export const SUBCOMMAND_BROWSER = 'browser';
17
+
18
+ /**
19
+ * Pairs of preset subcommand and driver candidates.
20
+ * Driver names listed in KNOWN_DRIVERS to install by default
21
+ */
22
+ const PRESET_PAIRS = Object.freeze(
23
+ /** @type {const} */ ({
24
+ mobile: _.keys(MOBILE_DRIVERS),
25
+ desktop: _.keys(DESKTOP_DRIVERS),
26
+ browser: _.keys(DESKTOP_BROWSERS)
27
+ }),
28
+ );
29
+ const DRIVERS_ONLY_MACOS = ['xcuitest', 'safari', 'mac2'];
30
+
31
+ const DRIVERS_ONLY_WINDOWS = ['windows'];
32
+
33
+ /**
34
+ * Plugin names listed in KNOWN_PLUGINS to install by default.
35
+ */
36
+ export const DEFAULT_PLUGINS = ['images'];
37
+
38
+ /**
39
+ * Return a list of drivers available for current host platform.
40
+ * @param {import('appium/types').CliCommandSetupSubcommand} presetName
41
+ * @returns {Array<string>}
42
+ */
43
+ export function getPresetDrivers(presetName) {
44
+ return _.filter(PRESET_PAIRS[presetName], (driver) => {
45
+ if (_.includes(DRIVERS_ONLY_MACOS, driver)) {
46
+ return system.isMac();
47
+ }
48
+
49
+ if (_.includes(DRIVERS_ONLY_WINDOWS, driver)) {
50
+ return system.isWindows();
51
+ }
52
+
53
+ return true;
54
+ });
55
+
56
+ }
57
+
58
+ /**
59
+ * Return desktop platform name for setup command description.
60
+ * @returns {string}
61
+ */
62
+ export function determinePlatformName() {
63
+ if (system.isMac()) {
64
+ return 'macOS';
65
+ } else if (system.isWindows()) {
66
+ return 'Windows';
67
+ }
68
+ return 'Linux';
69
+ }
70
+
71
+ /**
72
+ * Run 'setup' command to install drivers/plugins into the given appium home.
73
+ * @template {import('appium/types').CliCommandSetup} SetupCmd
74
+ * @param {import('appium/types').Args<SetupCmd>} preConfigArgs
75
+ * @param {string} appiumHome
76
+ * @param {DriverConfig} driverConfig
77
+ * @param {PluginConfig} pluginConfig
78
+ * @returns {Promise<void>}
79
+ */
80
+ export async function runSetupCommand(appiumHome, preConfigArgs, driverConfig, pluginConfig) {
81
+ switch (preConfigArgs.setupCommand) {
82
+ case SUBCOMMAND_DESKTOP:
83
+ await setupDesktopAppDrivers(driverConfig);
84
+ await setupDefaultPlugins(pluginConfig);
85
+ break;
86
+ case SUBCOMMAND_BROWSER:
87
+ await setupBrowserDrivers(driverConfig);
88
+ await setupDefaultPlugins(pluginConfig);
89
+ break;
90
+ default:
91
+ await setupMobileDrivers(driverConfig);
92
+ await setupDefaultPlugins(pluginConfig);
93
+ break;
94
+ }
95
+ };
96
+
97
+ /**
98
+ * Install drivers listed in DEFAULT_DRIVERS.
99
+ * @param {DriverConfig} driverConfig
100
+ * @returns {Promise<void>}
101
+ */
102
+ async function setupMobileDrivers(driverConfig) {
103
+ await installDrivers(SUBCOMMAND_MOBILE, driverConfig);
104
+ }
105
+
106
+ /**
107
+ * Install all of known drivers listed in BROWSER_DRIVERS.
108
+ * @param {DriverConfig} driverConfig
109
+ * @returns {Promise<void>}
110
+ */
111
+ async function setupBrowserDrivers(driverConfig) {
112
+ await installDrivers(SUBCOMMAND_BROWSER, driverConfig);
113
+ }
114
+
115
+ /**
116
+ * Install all of known drivers listed in DESKTOP_APP_DRIVERS.
117
+ * @param {DriverConfig} driverConfig
118
+ * @returns {Promise<void>}
119
+ */
120
+ async function setupDesktopAppDrivers(driverConfig) {
121
+ await installDrivers(SUBCOMMAND_DESKTOP, driverConfig);
122
+ }
123
+
124
+ /**
125
+ * Install the given driver name. It skips the installation if the given driver name was already installed.
126
+ * @param {import('appium/types').CliCommandSetupSubcommand} subcommand
127
+ * @param {DriverConfig} driverConfig
128
+ * @returns {Promise<void>}
129
+ */
130
+ async function installDrivers(subcommand, driverConfig) {
131
+ for (const driverName of getPresetDrivers(subcommand)) {
132
+ await installExtension(driverName, extensionCommandArgs('driver', driverName, 'install'), driverConfig);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Install plugins listed in DEFAULT_PLUGINS.
138
+ * @param {PluginConfig} pluginConfig
139
+ * @returns {Promise<void>}
140
+ */
141
+ async function setupDefaultPlugins(pluginConfig) {
142
+ for (const pluginName of DEFAULT_PLUGINS) {
143
+ await installExtension(pluginName, extensionCommandArgs('plugin', pluginName, 'install'), pluginConfig);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Run the given extensionConfigArgs command after checking if the given extensionName was already installed.
149
+ * @param {string} extensionName
150
+ * @param {Args} extensionConfigArgs
151
+ * @param {DriverConfig|PluginConfig} extensionConfig
152
+ * @returns
153
+ */
154
+ async function installExtension(extensionName, extensionConfigArgs, extensionConfig) {
155
+ if (_.keys(extensionConfig.installedExtensions).includes(extensionName)) {
156
+ log.info(`${extensionName} (${extensionConfig.installedExtensions[extensionName].version}) is already installed. ` +
157
+ `Skipping the installation.`);
158
+ return;
159
+ }
160
+ await runExtensionCommand(extensionConfigArgs, extensionConfig);
161
+ }
162
+
163
+ /**
164
+ * Return the command config for driver or plugin.
165
+ * @param {CliExtensionCommand} extensionCommand
166
+ * @param {string} extensionName
167
+ * @param {CliExtensionSubcommand} command
168
+ * @returns {Args}
169
+ */
170
+ function extensionCommandArgs(extensionCommand, extensionName, command) {
171
+ return (extensionCommand === 'plugin')
172
+ ? {'subcommand': 'plugin', 'pluginCommand': command, 'plugin': extensionName}
173
+ : {'subcommand': 'driver', 'driverCommand': command, 'driver': extensionName};
174
+ }
175
+
176
+ /**
177
+ * @typedef {import('appium/types').CliExtensionCommand} CliExtensionCommand
178
+ * @typedef {import('appium/types').CliExtensionSubcommand} CliExtensionSubcommand
179
+ * @typedef {import('../extension/extension-config').ExtensionConfig<CliExtensionCommand>} PluginConfig
180
+ * @typedef {import('../extension/extension-config').ExtensionConfig<CliExtensionCommand>} DriverConfig
181
+ */
182
+
183
+ /**
184
+ * @typedef {import('appium/types').Args<CliExtensionCommand, CliExtensionSubcommand>} Args
185
+ */
package/lib/config.js CHANGED
@@ -1,16 +1,18 @@
1
1
  /* eslint-disable no-console */
2
2
  import _ from 'lodash';
3
- import {system, fs} from '@appium/support';
3
+ import {system, fs, npm} from '@appium/support';
4
4
  import axios from 'axios';
5
5
  import {exec} from 'teen_process';
6
6
  import semver from 'semver';
7
7
  import os from 'node:os';
8
8
  import {npmPackage} from './utils';
9
+ import B from 'bluebird';
9
10
  import {getDefaultsForSchema, getAllArgSpecs} from './schema/schema';
10
11
 
11
12
  export const APPIUM_VER = npmPackage.version;
12
13
  const ENGINES = /** @type {Record<string,string>} */ (npmPackage.engines);
13
14
  const MIN_NODE_VERSION = ENGINES.node;
15
+ export const rootDir = fs.findRoot(__dirname);
14
16
 
15
17
  const GIT_BINARY = `git${system.isWindows() ? '.exe' : ''}`;
16
18
  const GITHUB_API = 'https://api.github.com/repos/appium/appium';
@@ -28,8 +30,9 @@ function getNodeVersion() {
28
30
 
29
31
  /**
30
32
  * @param {boolean} [useGithubApiFallback]
33
+ * @returns {Promise<void>}
31
34
  */
32
- async function updateBuildInfo(useGithubApiFallback = false) {
35
+ export async function updateBuildInfo(useGithubApiFallback = false) {
33
36
  const sha = await getGitRev(useGithubApiFallback);
34
37
  if (!sha) {
35
38
  return;
@@ -50,11 +53,61 @@ const getFullGitPath = _.memoize(async function getFullGitPath() {
50
53
  }
51
54
  });
52
55
 
56
+ /**
57
+ * Prints server debug into into the console.
58
+ *
59
+ * @param {{
60
+ * driverConfig: import('./extension/driver-config').DriverConfig,
61
+ * pluginConfig: import('./extension/plugin-config').PluginConfig,
62
+ * appiumHome: string
63
+ * }} info
64
+ * @returns {Promise<void>}
65
+ */
66
+ export async function showDebugInfo({driverConfig, pluginConfig, appiumHome}) {
67
+ const getNpmVersion = async () => {
68
+ const {stdout} = await npm.exec('--version', [], {cwd: process.cwd()});
69
+ return _.trim(stdout);
70
+ };
71
+ const findNpmLocation = async () => await fs.which(system.isWindows() ? 'npm.cmd' : 'npm');
72
+ const [npmVersion, npmLocation] = await B.all([
73
+ ...([getNpmVersion, findNpmLocation].map((f) => getSafeResult(f, 'unknown'))),
74
+ /** @type {any} */ (updateBuildInfo()),
75
+ ]);
76
+ const debugInfo = {
77
+ os: {
78
+ platform: os.platform(),
79
+ release: os.release(),
80
+ arch: os.arch(),
81
+ homedir: os.homedir(),
82
+ username: os.userInfo().username,
83
+ },
84
+ node: {
85
+ version: process.version,
86
+ arch: process.arch,
87
+ cwd: process.cwd(),
88
+ argv: process.argv,
89
+ env: process.env,
90
+ npm: {
91
+ location: npmLocation,
92
+ version: npmVersion,
93
+ },
94
+ },
95
+ appium: {
96
+ location: rootDir,
97
+ homedir: appiumHome,
98
+ build: getBuildInfo(),
99
+ drivers: driverConfig.installedExtensions,
100
+ plugins: pluginConfig.installedExtensions,
101
+ },
102
+ };
103
+ console.log(JSON.stringify(debugInfo, null, 2));
104
+ }
105
+
53
106
  /**
54
107
  * @param {boolean} [useGithubApiFallback]
55
108
  * @returns {Promise<string?>}
56
109
  */
57
- async function getGitRev(useGithubApiFallback = false) {
110
+ export async function getGitRev(useGithubApiFallback = false) {
58
111
  const fullGitPath = await getFullGitPath();
59
112
  if (fullGitPath) {
60
113
  try {
@@ -122,11 +175,15 @@ async function getGitTimestamp(commitSha, useGithubApiFallback = false) {
122
175
  * and succeeds.
123
176
  * @returns {import('appium/types').BuildInfo}
124
177
  */
125
- function getBuildInfo() {
178
+ export function getBuildInfo() {
126
179
  return BUILD_INFO;
127
180
  }
128
181
 
129
- function checkNodeOk() {
182
+ /**
183
+ * @returns {void}
184
+ * @throws {Error} If Node version is outside of the supported range
185
+ */
186
+ export function checkNodeOk() {
130
187
  const version = getNodeVersion();
131
188
  if (!semver.satisfies(version, MIN_NODE_VERSION)) {
132
189
  throw new Error(
@@ -135,7 +192,7 @@ function checkNodeOk() {
135
192
  }
136
193
  }
137
194
 
138
- async function showBuildInfo() {
195
+ export async function showBuildInfo() {
139
196
  await updateBuildInfo(true);
140
197
  console.log(JSON.stringify(getBuildInfo())); // eslint-disable-line no-console
141
198
  }
@@ -145,7 +202,7 @@ async function showBuildInfo() {
145
202
  * @param {Args} parsedArgs
146
203
  * @returns {Args}
147
204
  */
148
- function getNonDefaultServerArgs(parsedArgs) {
205
+ export function getNonDefaultServerArgs(parsedArgs) {
149
206
  /**
150
207
  * Flattens parsed args into a single level object for comparison with
151
208
  * flattened defaults across server args and extension args.
@@ -253,7 +310,7 @@ const compactConfig = _.partial(
253
310
  * @param {Partial<ParsedArgs>} defaults - Configuration defaults from schemas
254
311
  * @param {ParsedArgs} parsedArgs - Entire parsed args object
255
312
  */
256
- function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
313
+ export function showConfig(nonDefaultPreConfigParsedArgs, configResult, defaults, parsedArgs) {
257
314
  console.log('Appium Configuration\n');
258
315
  console.log('from defaults:\n');
259
316
  console.dir(compactConfig(defaults));
@@ -308,18 +365,22 @@ export async function requireDir(root, requireWriteable = true, displayName = 'f
308
365
  }
309
366
  }
310
367
 
311
- const rootDir = fs.findRoot(__dirname);
312
-
313
- export {
314
- getBuildInfo,
315
- checkNodeOk,
316
- showBuildInfo,
317
- getNonDefaultServerArgs,
318
- getGitRev,
319
- updateBuildInfo,
320
- showConfig,
321
- rootDir,
322
- };
368
+ /**
369
+ * Calculates the result of the given function and return its value
370
+ * or the default one if there was an exception.
371
+ *
372
+ * @template T
373
+ * @param {() => Promise<T>} f
374
+ * @param {T} defaultValue
375
+ * @returns {Promise<T>}
376
+ */
377
+ async function getSafeResult(f, defaultValue) {
378
+ try {
379
+ return await f();
380
+ } catch {
381
+ return defaultValue;
382
+ }
383
+ }
323
384
 
324
385
  /**
325
386
  * @typedef {import('appium/types').ParsedArgs} ParsedArgs
package/lib/constants.js CHANGED
@@ -15,6 +15,12 @@ export const PLUGIN_TYPE = 'plugin';
15
15
  */
16
16
  export const SERVER_SUBCOMMAND = 'server';
17
17
 
18
+ /**
19
+ * The `setup` command of the `appium` CLI
20
+ */
21
+ export const SETUP_SUBCOMMAND = 'setup';
22
+
23
+
18
24
  /**
19
25
  * The value of `--use-plugins` if _all_ plugins should be loaded
20
26
  */
@@ -33,22 +39,41 @@ export const KNOWN_PLUGINS = Object.freeze(
33
39
  }),
34
40
  );
35
41
 
36
- // This is a map of driver names to npm packages representing those drivers.
37
- // The drivers in this list will be available to the CLI so users can just
38
- // type 'appium driver install 'name'', rather than having to specify the full
39
- // npm package. I.e., these are the officially recognized drivers.
40
- export const KNOWN_DRIVERS = Object.freeze(
42
+ export const MOBILE_DRIVERS = Object.freeze(
41
43
  /** @type {const} */ ({
42
44
  uiautomator2: 'appium-uiautomator2-driver',
43
45
  xcuitest: 'appium-xcuitest-driver',
44
- mac2: 'appium-mac2-driver',
45
46
  espresso: 'appium-espresso-driver',
47
+ }),
48
+ );
49
+
50
+ export const DESKTOP_DRIVERS = Object.freeze(
51
+ /** @type {const} */ ({
52
+ mac2: 'appium-mac2-driver',
53
+ windows: 'appium-windows-driver',
54
+ }),
55
+ );
56
+
57
+ export const DESKTOP_BROWSERS = Object.freeze(
58
+ /** @type {const} */ ({
46
59
  safari: 'appium-safari-driver',
47
60
  gecko: 'appium-geckodriver',
48
61
  chromium: 'appium-chromium-driver',
49
62
  }),
50
63
  );
51
64
 
65
+ // This is a map of driver names to npm packages representing those drivers.
66
+ // The drivers in this list will be available to the CLI so users can just
67
+ // type 'appium driver install 'name'', rather than having to specify the full
68
+ // npm package. I.e., these are the officially recognized drivers.
69
+ export const KNOWN_DRIVERS = Object.freeze(
70
+ /** @type {const} */ ({
71
+ ...MOBILE_DRIVERS,
72
+ ...DESKTOP_DRIVERS,
73
+ ...DESKTOP_BROWSERS,
74
+ }),
75
+ );
76
+
52
77
  /**
53
78
  * Relative path to directory containing any Appium internal files
54
79
  */
@@ -57,13 +57,13 @@ function postRequest(configHolder, addr, port, basePath) {
57
57
  // Move Selenium 3 configuration properties to configuration object
58
58
  if (!_.has(configHolder, 'configuration')) {
59
59
  let configuration = {};
60
- for (const property in configHolder) {
60
+ for (const property in /** @type {import('@appium/types').StringRecord} */ (configHolder)) {
61
61
  if (_.has(configHolder, property) && property !== 'capabilities') {
62
62
  configuration[property] = configHolder[property];
63
63
  delete configHolder[property];
64
64
  }
65
65
  }
66
- configHolder.configuration = configuration;
66
+ /** @type {import('@appium/types').StringRecord} */ (configHolder).configuration = configuration;
67
67
  }
68
68
 
69
69
  // if the node config does not have the appium/webdriver url, host, and port,
package/lib/logsink.js CHANGED
@@ -131,8 +131,13 @@ function createHttpTransport(args, logLvl) {
131
131
  });
132
132
  }
133
133
 
134
+ /**
135
+ *
136
+ * @param {import('@appium/types').StringRecord} args
137
+ * @returns {Promise<import('winston-transport')[]>}
138
+ */
134
139
  async function createTransports(args) {
135
- let transports = [];
140
+ const transports = [];
136
141
  let consoleLogLevel = null;
137
142
  let fileLogLevel = null;
138
143
 
@@ -201,11 +206,14 @@ async function init(args) {
201
206
  // clean up in case we have initiated before since npmlog is a global object
202
207
  clear();
203
208
 
209
+ const transports = await createTransports(args);
210
+ const transportNames = new Set(transports.map((tr) => tr.constructor.name));
204
211
  log = createLogger({
205
- transports: await createTransports(args),
212
+ transports,
206
213
  levels,
207
214
  });
208
215
 
216
+ const reportedLoggerErrors = new Set();
209
217
  // Capture logs emitted via npmlog and pass them through winston
210
218
  npmlog.on('log', ({level, message, prefix}) => {
211
219
  const winstonLevel = npmToWinstonLevels[level] || 'info';
@@ -217,9 +225,20 @@ async function init(args) {
217
225
  : getColorizedPrefix(decoratedPrefix);
218
226
  msg = `${args.logNoColors ? decoratedPrefix : toColorizedDecoratedPrefix()} ${msg}`;
219
227
  }
220
- log[winstonLevel](msg);
221
- if (args.logHandler && _.isFunction(args.logHandler)) {
222
- args.logHandler(level, msg);
228
+ try {
229
+ log[winstonLevel](msg);
230
+ if (_.isFunction(args.logHandler)) {
231
+ args.logHandler(level, msg);
232
+ }
233
+ } catch (e) {
234
+ if (!reportedLoggerErrors.has(e.message) && process.stderr.writable) {
235
+ // eslint-disable-next-line no-console
236
+ console.error(
237
+ `The log message '${_.truncate(msg, {length: 30})}' cannot be written into ` +
238
+ `one or more requested destinations: ${transportNames}. Original error: ${e.message}`
239
+ );
240
+ reportedLoggerErrors.add(e.message);
241
+ }
223
242
  }
224
243
  });
225
244
  }
package/lib/main.js CHANGED
@@ -13,6 +13,7 @@ import {asyncify} from 'asyncbox';
13
13
  import _ from 'lodash';
14
14
  import {AppiumDriver} from './appium';
15
15
  import {runExtensionCommand} from './cli/extension';
16
+ import { runSetupCommand } from './cli/setup-command';
16
17
  import {getParser} from './cli/parser';
17
18
  import {
18
19
  APPIUM_VER,
@@ -21,6 +22,7 @@ import {
21
22
  getNonDefaultServerArgs,
22
23
  showConfig,
23
24
  showBuildInfo,
25
+ showDebugInfo,
24
26
  requireDir,
25
27
  } from './config';
26
28
  import {readConfigFile} from './config-file';
@@ -38,6 +40,7 @@ import {
38
40
  fetchInterfaces,
39
41
  V4_BROADCAST_IP,
40
42
  V6_BROADCAST_IP,
43
+ isSetupCommandArgs,
41
44
  } from './utils';
42
45
  import net from 'node:net';
43
46
 
@@ -216,7 +219,7 @@ async function init(args) {
216
219
  }
217
220
 
218
221
  // merge config and apply defaults.
219
- // the order of precendece is:
222
+ // the order of precedence is:
220
223
  // 1. command line args
221
224
  // 2. config file
222
225
  // 3. defaults from config file.
@@ -231,6 +234,15 @@ async function init(args) {
231
234
  return /** @type {InitResult<Cmd>} */ ({});
232
235
  }
233
236
 
237
+ if (preConfigArgs.showDebugInfo) {
238
+ await showDebugInfo({
239
+ driverConfig,
240
+ pluginConfig,
241
+ appiumHome,
242
+ });
243
+ return /** @type {InitResult<Cmd>} */ ({});
244
+ }
245
+
234
246
  await logsinkInit(serverArgs);
235
247
 
236
248
  if (serverArgs.logFilters) {
@@ -276,6 +288,9 @@ async function init(args) {
276
288
  pluginConfig,
277
289
  appiumHome,
278
290
  });
291
+ } else if (isSetupCommandArgs(preConfigArgs)) {
292
+ await runSetupCommand(appiumHome, preConfigArgs, driverConfig, pluginConfig);
293
+ return /** @type {InitResult<Cmd>} */ ({});
279
294
  } else {
280
295
  await requireDir(appiumHome, true, appiumHomeSourceName);
281
296
  if (isExtensionCommandArgs(preConfigArgs)) {
@@ -465,6 +480,7 @@ export {main, init, resolveAppiumHome};
465
480
  * @typedef {import('appium/types').CliCommandServer} ServerCommand
466
481
  * @typedef {import('appium/types').CliCommandDriver} DriverCommand
467
482
  * @typedef {import('appium/types').CliCommandPlugin} PluginCommand
483
+ * @typedef {import('appium/types').CliCommandSetup} SetupCommand
468
484
  * @typedef {import('./extension').DriverNameMap} DriverNameMap
469
485
  * @typedef {import('./extension').PluginNameMap} PluginNameMap
470
486
  */