appium 2.0.0-beta.3 → 2.0.0-beta.30

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 (139) hide show
  1. package/README.md +10 -11
  2. package/build/lib/appium.d.ts +215 -0
  3. package/build/lib/appium.d.ts.map +1 -0
  4. package/build/lib/appium.js +238 -132
  5. package/build/lib/cli/args.d.ts +20 -0
  6. package/build/lib/cli/args.d.ts.map +1 -0
  7. package/build/lib/cli/args.js +96 -282
  8. package/build/lib/cli/driver-command.d.ts +36 -0
  9. package/build/lib/cli/driver-command.d.ts.map +1 -0
  10. package/build/lib/cli/driver-command.js +19 -12
  11. package/build/lib/cli/extension-command.d.ts +345 -0
  12. package/build/lib/cli/extension-command.d.ts.map +1 -0
  13. package/build/lib/cli/extension-command.js +171 -96
  14. package/build/lib/cli/extension.d.ts +14 -0
  15. package/build/lib/cli/extension.d.ts.map +1 -0
  16. package/build/lib/cli/extension.js +31 -16
  17. package/build/lib/cli/parser.d.ts +79 -0
  18. package/build/lib/cli/parser.d.ts.map +1 -0
  19. package/build/lib/cli/parser.js +152 -95
  20. package/build/lib/cli/plugin-command.d.ts +39 -0
  21. package/build/lib/cli/plugin-command.d.ts.map +1 -0
  22. package/build/lib/cli/plugin-command.js +18 -13
  23. package/build/lib/cli/utils.d.ts +29 -0
  24. package/build/lib/cli/utils.d.ts.map +1 -0
  25. package/build/lib/cli/utils.js +27 -3
  26. package/build/lib/config-file.d.ts +100 -0
  27. package/build/lib/config-file.d.ts.map +1 -0
  28. package/build/lib/config-file.js +136 -0
  29. package/build/lib/config.d.ts +40 -0
  30. package/build/lib/config.d.ts.map +1 -0
  31. package/build/lib/config.js +92 -67
  32. package/build/lib/constants.d.ts +48 -0
  33. package/build/lib/constants.d.ts.map +1 -0
  34. package/build/lib/constants.js +60 -0
  35. package/build/lib/extension/driver-config.d.ts +84 -0
  36. package/build/lib/extension/driver-config.d.ts.map +1 -0
  37. package/build/lib/extension/driver-config.js +190 -0
  38. package/build/lib/extension/extension-config.d.ts +170 -0
  39. package/build/lib/extension/extension-config.d.ts.map +1 -0
  40. package/build/lib/extension/extension-config.js +297 -0
  41. package/build/lib/extension/index.d.ts +39 -0
  42. package/build/lib/extension/index.d.ts.map +1 -0
  43. package/build/lib/extension/index.js +77 -0
  44. package/build/lib/extension/manifest.d.ts +174 -0
  45. package/build/lib/extension/manifest.d.ts.map +1 -0
  46. package/build/lib/extension/manifest.js +246 -0
  47. package/build/lib/extension/package-changed.d.ts +11 -0
  48. package/build/lib/extension/package-changed.d.ts.map +1 -0
  49. package/build/lib/extension/package-changed.js +68 -0
  50. package/build/lib/extension/plugin-config.d.ts +62 -0
  51. package/build/lib/extension/plugin-config.d.ts.map +1 -0
  52. package/build/lib/extension/plugin-config.js +87 -0
  53. package/build/lib/grid-register.d.ts +10 -0
  54. package/build/lib/grid-register.d.ts.map +1 -0
  55. package/build/lib/grid-register.js +21 -25
  56. package/build/lib/logger.d.ts +3 -0
  57. package/build/lib/logger.d.ts.map +1 -0
  58. package/build/lib/logger.js +4 -6
  59. package/build/lib/logsink.d.ts +4 -0
  60. package/build/lib/logsink.d.ts.map +1 -0
  61. package/build/lib/logsink.js +12 -16
  62. package/build/lib/main.d.ts +51 -0
  63. package/build/lib/main.d.ts.map +1 -0
  64. package/build/lib/main.js +174 -82
  65. package/build/lib/schema/arg-spec.d.ts +143 -0
  66. package/build/lib/schema/arg-spec.d.ts.map +1 -0
  67. package/build/lib/schema/arg-spec.js +119 -0
  68. package/build/lib/schema/cli-args.d.ts +19 -0
  69. package/build/lib/schema/cli-args.d.ts.map +1 -0
  70. package/build/lib/schema/cli-args.js +180 -0
  71. package/build/lib/schema/cli-transformers.d.ts +5 -0
  72. package/build/lib/schema/cli-transformers.d.ts.map +1 -0
  73. package/build/lib/schema/cli-transformers.js +74 -0
  74. package/build/lib/schema/index.d.ts +3 -0
  75. package/build/lib/schema/index.d.ts.map +1 -0
  76. package/build/lib/schema/index.js +34 -0
  77. package/build/lib/schema/keywords.d.ts +24 -0
  78. package/build/lib/schema/keywords.d.ts.map +1 -0
  79. package/build/lib/schema/keywords.js +70 -0
  80. package/build/lib/schema/schema.d.ts +259 -0
  81. package/build/lib/schema/schema.d.ts.map +1 -0
  82. package/build/lib/schema/schema.js +452 -0
  83. package/build/lib/utils.d.ts +66 -0
  84. package/build/lib/utils.d.ts.map +1 -0
  85. package/build/lib/utils.js +35 -139
  86. package/build/tsconfig.tsbuildinfo +1 -0
  87. package/index.js +11 -0
  88. package/lib/appium-config.schema.json +278 -0
  89. package/lib/appium.js +398 -155
  90. package/lib/cli/args.js +174 -377
  91. package/lib/cli/driver-command.js +22 -7
  92. package/lib/cli/extension-command.js +372 -177
  93. package/lib/cli/extension.js +32 -10
  94. package/lib/cli/parser.js +252 -83
  95. package/lib/cli/plugin-command.js +19 -6
  96. package/lib/cli/utils.js +22 -2
  97. package/lib/config-file.js +223 -0
  98. package/lib/config.js +169 -69
  99. package/lib/constants.js +78 -0
  100. package/lib/extension/driver-config.js +249 -0
  101. package/lib/extension/extension-config.js +458 -0
  102. package/lib/extension/index.js +102 -0
  103. package/lib/extension/manifest.js +486 -0
  104. package/lib/extension/package-changed.js +63 -0
  105. package/lib/extension/plugin-config.js +113 -0
  106. package/lib/grid-register.js +25 -22
  107. package/lib/logger.js +1 -1
  108. package/lib/logsink.js +14 -7
  109. package/lib/main.js +233 -83
  110. package/lib/schema/arg-spec.js +232 -0
  111. package/lib/schema/cli-args.js +261 -0
  112. package/lib/schema/cli-transformers.js +122 -0
  113. package/lib/schema/index.js +2 -0
  114. package/lib/schema/keywords.js +134 -0
  115. package/lib/schema/schema.js +734 -0
  116. package/lib/utils.js +85 -129
  117. package/package.json +62 -85
  118. package/scripts/postinstall.js +71 -0
  119. package/types/appium-manifest.d.ts +61 -0
  120. package/types/cli.d.ts +134 -0
  121. package/types/extension.d.ts +56 -0
  122. package/types/external-manifest.d.ts +58 -0
  123. package/types/index.d.ts +7 -0
  124. package/CHANGELOG.md +0 -3515
  125. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  126. package/build/lib/cli/npm.js +0 -206
  127. package/build/lib/cli/parser-helpers.js +0 -82
  128. package/build/lib/driver-config.js +0 -77
  129. package/build/lib/drivers.js +0 -96
  130. package/build/lib/extension-config.js +0 -251
  131. package/build/lib/plugin-config.js +0 -59
  132. package/build/lib/plugins.js +0 -14
  133. package/lib/cli/npm.js +0 -183
  134. package/lib/cli/parser-helpers.js +0 -79
  135. package/lib/driver-config.js +0 -46
  136. package/lib/drivers.js +0 -81
  137. package/lib/extension-config.js +0 -208
  138. package/lib/plugin-config.js +0 -34
  139. package/lib/plugins.js +0 -10
@@ -0,0 +1,113 @@
1
+
2
+ import _ from 'lodash';
3
+ import {ExtensionConfig} from './extension-config';
4
+ import log from '../logger';
5
+ import {PLUGIN_TYPE} from '../constants';
6
+
7
+ /**
8
+ * @extends {ExtensionConfig<PluginType>}
9
+ */
10
+ export class PluginConfig extends ExtensionConfig {
11
+
12
+ /**
13
+ * A mapping of {@link Manifest} instances to {@link PluginConfig} instances.
14
+ *
15
+ * `Manifest` and {@link ExtensionConfig} have a one-to-many relationship; each `Manifest` should be associated with a `DriverConfig` and a `PluginConfig`; no more, no less.
16
+ *
17
+ * This variable tracks the `Manifest`-to-`PluginConfig` portion.
18
+ *
19
+ * @type {WeakMap<Manifest,PluginConfig>}
20
+ * @private
21
+ */
22
+ static _instances = new WeakMap();
23
+
24
+ /**
25
+ * Call {@link PluginConfig.create} instead.
26
+ *
27
+ * Just calls the superclass' constructor with the correct extension type
28
+ * @private
29
+ * @param {Manifest} manifest - IO object
30
+ * @param {PluginConfigOptions} [opts]
31
+ */
32
+ constructor (manifest, {extData, logFn} = {}) {
33
+ super(PLUGIN_TYPE, manifest, logFn);
34
+
35
+ if (extData) {
36
+ this.validate(extData);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Creates a new {@link PluginConfig} instance for a {@link Manifest} instance.
42
+ *
43
+ * @param {Manifest} manifest
44
+ * @param {PluginConfigOptions} [opts]
45
+ * @throws If `manifest` already associated with a `PluginConfig`
46
+ * @returns {PluginConfig}
47
+ */
48
+ static create (manifest, {extData, logFn} = {}) {
49
+ const instance = new PluginConfig(manifest, {logFn, extData});
50
+ if (PluginConfig.getInstance(manifest)) {
51
+ throw new Error(`Manifest with APPIUM_HOME ${manifest.appiumHome} already has a PluginConfig; use PluginConfig.getInstance() to retrieve it.`);
52
+ }
53
+ PluginConfig._instances.set(manifest, instance);
54
+ return instance;
55
+ }
56
+
57
+ /**
58
+ * Returns a PluginConfig associated with a Manifest
59
+ * @param {Manifest} manifest
60
+ * @returns {PluginConfig|undefined}
61
+ */
62
+ static getInstance (manifest) {
63
+ return PluginConfig._instances.get(manifest);
64
+ }
65
+
66
+ /**
67
+ * @param {string} pluginName
68
+ * @param {import('../../types/appium-manifest').ExtManifest<PluginType>} pluginData
69
+ * @returns {string}
70
+ */
71
+ extensionDesc (pluginName, {version}) {
72
+ return `${pluginName}@${version}`;
73
+ }
74
+
75
+ /**
76
+ *
77
+ * @param {(keyof PluginRecord)[]} activeNames
78
+ * @returns {void}
79
+ */
80
+ print (activeNames) {
81
+ const pluginNames = Object.keys(this.installedExtensions);
82
+
83
+ if (_.isEmpty(pluginNames)) {
84
+ log.info(`No plugins have been installed. Use the "appium plugin" ` +
85
+ 'command to install the one(s) you want to use.');
86
+ return;
87
+ }
88
+
89
+ log.info(`Available plugins:`);
90
+ for (const [pluginName, pluginData] of _.toPairs(this.installedExtensions)) {
91
+ const activeTxt = _.includes(activeNames, pluginName) ? ' (ACTIVE)' : '';
92
+ log.info(` - ${this.extensionDesc(pluginName, pluginData)}${activeTxt}`);
93
+ }
94
+
95
+ if (_.isEmpty(activeNames)) {
96
+ log.info('No plugins activated. Use the --use-plugins flag with names of plugins to activate');
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @typedef PluginConfigOptions
103
+ * @property {import('./extension-config').ExtensionLogFn} [logFn] - Optional logging function
104
+ * @property {import('../../types/appium-manifest').PluginRecord} [extData] - Extension data
105
+ */
106
+
107
+
108
+ /**
109
+ * @typedef {import('../../types/appium-manifest').PluginRecord} PluginRecord
110
+ * @typedef {import('../../types').PluginType} PluginType
111
+ * @typedef {import('../../types/external-manifest').ExtMetadata<PluginType>} PluginMetadata
112
+ * @typedef {import('./manifest').Manifest} Manifest
113
+ */
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import { fs } from 'appium-support';
2
+ import { fs } from '@appium/support';
3
3
  import logger from './logger';
4
4
  import _ from 'lodash';
5
5
 
@@ -9,20 +9,31 @@ const hubUri = (config) => {
9
9
  return `${protocol}://${config.hubHost}:${config.hubPort}`;
10
10
  };
11
11
 
12
- async function registerNode (configFile, addr, port, basePath) {
13
- let data;
14
- try {
15
- data = await fs.readFile(configFile, 'utf-8');
16
- } catch (err) {
17
- logger.error(`Unable to load node configuration file to register with grid: ${err.message}`);
18
- return;
12
+ /**
13
+ * Registers a new node with a selenium grid
14
+ * @param {string|object} data - Path or object representing selenium grid node config file. If a `string`, all subsequent arguments are required!
15
+ * @param {string} [addr] - Bind to this address
16
+ * @param {number} [port] - Bind to this port
17
+ * @param {string} [basePath] - Base path for the grid
18
+ */
19
+ async function registerNode (data, addr, port, basePath) {
20
+ let configFilePath;
21
+ if (_.isString(data)) {
22
+ configFilePath = data;
23
+ try {
24
+ data = await fs.readFile(data, 'utf-8');
25
+ } catch (err) {
26
+ logger.error(`Unable to load node configuration file ${configFilePath} to register with grid: ${err.message}`);
27
+ return;
28
+ }
29
+ try {
30
+ data = JSON.parse(data);
31
+ } catch (err) {
32
+ logger.errorAndThrow(`Syntax error in node configuration file ${configFilePath}: ${err.message}`);
33
+ return;
34
+ }
19
35
  }
20
36
 
21
- // Check presence of data before posting it to the selenium grid
22
- if (!data) {
23
- logger.error('No data found in the node configuration file to send to the grid');
24
- return;
25
- }
26
37
  postRequest(data, addr, port, basePath);
27
38
  }
28
39
 
@@ -39,15 +50,7 @@ async function registerToGrid (postOptions, configHolder) {
39
50
  }
40
51
  }
41
52
 
42
- function postRequest (data, addr, port, basePath) {
43
- // parse json to get hub host and port
44
- let configHolder;
45
- try {
46
- configHolder = JSON.parse(data);
47
- } catch (err) {
48
- logger.errorAndThrow(`Syntax error in node configuration file: ${err.message}`);
49
- }
50
-
53
+ function postRequest (configHolder, addr, port, basePath) {
51
54
  // Move Selenium 3 configuration properties to configuration object
52
55
  if (!_.has(configHolder, 'configuration')) {
53
56
  let configuration = {};
package/lib/logger.js CHANGED
@@ -1,4 +1,4 @@
1
- import { logger } from 'appium-support';
1
+ import { logger } from '@appium/support';
2
2
 
3
3
 
4
4
  let log = logger.getLogger('Appium');
package/lib/logsink.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import npmlog from 'npmlog';
2
2
  import { createLogger, format, transports } from 'winston';
3
- import { fs, logger } from 'appium-support';
4
- import dateformat from 'dateformat';
3
+ import { fs, logger } from '@appium/support';
5
4
  import _ from 'lodash';
6
5
 
7
6
 
@@ -36,16 +35,20 @@ const npmToWinstonLevels = {
36
35
  };
37
36
 
38
37
  let log = null;
39
- let timeZone = null;
38
+ let useLocalTimeZone = false;
40
39
 
41
40
  // add the timestamp in the correct format to the log info object
42
41
  const timestampFormat = format.timestamp({
43
42
  format () {
44
43
  let date = new Date();
45
- if (!timeZone) {
46
- date = new Date(date.valueOf() + date.getTimezoneOffset() * 60000);
44
+ if (useLocalTimeZone) {
45
+ date = new Date(date.valueOf() - date.getTimezoneOffset() * 60000);
47
46
  }
48
- return dateformat(date, 'yyyy-mm-dd HH:MM:ss:l');
47
+ // '2012-11-04T14:51:06.157Z' -> '2012-11-04 14:51:06:157'
48
+ return date.toISOString()
49
+ .replace(/[TZ]/g, ' ')
50
+ .replace(/\./g, ':')
51
+ .trim();
49
52
  },
50
53
  });
51
54
 
@@ -63,6 +66,8 @@ const stripColorFormat = format(function stripColor (info) {
63
66
 
64
67
  function createConsoleTransport (args, logLvl) {
65
68
  return new (transports.Console)({
69
+ // `name` is unsupported per winston's type declarations
70
+ // @ts-expect-error
66
71
  name: 'console',
67
72
  handleExceptions: true,
68
73
  exitOnError: false,
@@ -89,6 +94,7 @@ function createConsoleTransport (args, logLvl) {
89
94
 
90
95
  function createFileTransport (args, logLvl) {
91
96
  return new (transports.File)({
97
+ // @ts-expect-error
92
98
  name: 'file',
93
99
  filename: args.logFile,
94
100
  maxFiles: 1,
@@ -117,6 +123,7 @@ function createHttpTransport (args, logLvl) {
117
123
  }
118
124
 
119
125
  return new (transports.Http)({
126
+ // @ts-expect-error
120
127
  name: 'http',
121
128
  host,
122
129
  port,
@@ -182,7 +189,7 @@ async function createTransports (args) {
182
189
 
183
190
  async function init (args) {
184
191
  // set de facto param passed to timestamp function
185
- timeZone = args.localTimezone;
192
+ useLocalTimeZone = args.localTimezone;
186
193
 
187
194
  // clean up in case we have initiated before since npmlog is a global object
188
195
  clear();
package/lib/main.js CHANGED
@@ -1,42 +1,45 @@
1
1
  #!/usr/bin/env node
2
- // transpile:main
3
2
 
4
- import { init as logsinkInit } from './logsink';
5
- import logger from './logger'; // logger needs to remain first of imports
6
- import _ from 'lodash';
7
- import { server as baseServer, routeConfiguringFunction as makeRouter } from 'appium-base-driver';
3
+
4
+ import { init as logsinkInit } from './logsink'; // this import needs to come first since it sets up global npmlog
5
+ import logger from './logger'; // logger needs to remain second
6
+ // @ts-ignore
7
+ import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
8
+ import { logger as logFactory, util, env } from '@appium/support';
8
9
  import { asyncify } from 'asyncbox';
9
- import { default as getParser, getDefaultServerArgs } from './cli/parser';
10
- import { USE_ALL_PLUGINS } from './cli/args';
11
- import { logger as logFactory, util } from 'appium-support';
12
- import {
13
- showConfig, checkNodeOk, validateServerArgs,
14
- warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
15
- getGitRev, APPIUM_VER
16
- } from './config';
17
- import DriverConfig from './driver-config';
18
- import PluginConfig from './plugin-config';
19
- import { DRIVER_TYPE, PLUGIN_TYPE } from './extension-config';
20
- import { runExtensionCommand } from './cli/extension';
10
+ import _ from 'lodash';
21
11
  import { AppiumDriver } from './appium';
12
+ import { runExtensionCommand } from './cli/extension';
13
+ import { getParser } from './cli/parser';
14
+ import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showConfig, showBuildInfo, validateTmpDir, warnNodeDeprecations } from './config';
15
+ import { readConfigFile } from './config-file';
16
+ import { loadExtensions, getActivePlugins, getActiveDrivers } from './extension';
17
+ import { DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND } from './constants';
22
18
  import registerNode from './grid-register';
23
- import { inspectObject } from './utils';
19
+ import { getDefaultsForSchema, validate } from './schema/schema';
20
+ import { inspect } from './utils';
24
21
 
22
+ const {resolveAppiumHome} = env;
25
23
 
26
- async function preflightChecks ({parser, args, driverConfig, pluginConfig, throwInsteadOfExit = false}) {
24
+ /**
25
+ *
26
+ * @param {ParsedArgs} args
27
+ * @param {boolean} [throwInsteadOfExit]
28
+ */
29
+ async function preflightChecks (args, throwInsteadOfExit = false) {
27
30
  try {
28
31
  checkNodeOk();
29
32
  if (args.longStacktrace) {
30
33
  require('longjohn').async_trace_limit = -1;
31
34
  }
32
- if (args.showConfig) {
33
- await showConfig();
35
+ if (args.showBuildInfo) {
36
+ await showBuildInfo();
34
37
  process.exit(0);
35
38
  }
36
39
  warnNodeDeprecations();
37
- validateServerArgs(parser, args);
38
- await driverConfig.read();
39
- await pluginConfig.read();
40
+
41
+ validate(args);
42
+
40
43
  if (args.tmpDir) {
41
44
  await validateTmpDir(args.tmpDir);
42
45
  }
@@ -50,18 +53,24 @@ async function preflightChecks ({parser, args, driverConfig, pluginConfig, throw
50
53
  }
51
54
  }
52
55
 
56
+ /**
57
+ * @param {Partial<ParsedArgs>} args
58
+ */
53
59
  function logNonDefaultArgsWarning (args) {
54
60
  logger.info('Non-default server args:');
55
- inspectObject(args);
61
+ inspect(args);
56
62
  }
57
63
 
58
64
  function logDefaultCapabilitiesWarning (caps) {
59
65
  logger.info('Default capabilities, which will be added to each request ' +
60
66
  'unless overridden by desired capabilities:');
61
- inspectObject(caps);
67
+ inspect(caps);
62
68
  }
63
69
 
64
- async function logStartupInfo (parser, args) {
70
+ /**
71
+ * @param {ParsedArgs} args
72
+ */
73
+ async function logStartupInfo (args) {
65
74
  let welcome = `Welcome to Appium v${APPIUM_VER}`;
66
75
  let appiumRev = await getGitRev();
67
76
  if (appiumRev) {
@@ -69,7 +78,7 @@ async function logStartupInfo (parser, args) {
69
78
  }
70
79
  logger.info(welcome);
71
80
 
72
- let showArgs = getNonDefaultArgs(parser, args);
81
+ let showArgs = getNonDefaultServerArgs(args);
73
82
  if (_.size(showArgs)) {
74
83
  logNonDefaultArgsWarning(showArgs);
75
84
  }
@@ -83,20 +92,74 @@ async function logStartupInfo (parser, args) {
83
92
  // }
84
93
  }
85
94
 
95
+ /**
96
+ * Logs the address and port the server is listening on
97
+ * @param {string} address - Address
98
+ * @param {number} port - Port
99
+ * @returns {void}
100
+ */
86
101
  function logServerPort (address, port) {
87
102
  let logMessage = `Appium REST http interface listener started on ` +
88
103
  `${address}:${port}`;
89
104
  logger.info(logMessage);
90
105
  }
91
106
 
92
- async function main (args = null) {
93
- let parser = getParser();
107
+ /**
108
+ * Gets a list of `updateServer` functions from all extensions
109
+ * @param {DriverClass[]} driverClasses
110
+ * @param {PluginClass[]} pluginClasses
111
+ * @returns {import('@appium/base-driver/lib/basedriver/driver').UpdateServerCallback[]}
112
+ */
113
+ function getServerUpdaters (driverClasses, pluginClasses) {
114
+ return _.compact(_.map([...driverClasses, ...pluginClasses], 'updateServer'));
115
+ }
116
+
117
+ /**
118
+ * Makes a big `MethodMap` from all the little `MethodMap`s in the extensions
119
+ * @param {DriverClass[]} driverClasses
120
+ * @param {PluginClass[]} pluginClasses
121
+ * @returns {import('@appium/types').MethodMap}
122
+ */
123
+ function getExtraMethodMap (driverClasses, pluginClasses) {
124
+ return [...driverClasses, ...pluginClasses].reduce(
125
+ (map, klass) => ({...map, ...klass.newMethodMap}),
126
+ {}
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Initializes Appium, but does not start the server.
132
+ *
133
+ * Use this to get at the configuration schema.
134
+ *
135
+ * If `args` contains a non-empty `subcommand` which is not `server`, this function will return an empty object.
136
+ *
137
+ * @param {PartialArgs} [args] - Partial args (progammatic usage only)
138
+ * @returns {Promise<ServerInitResult | ExtCommandInitResult>}
139
+ * @example
140
+ * import {init, getSchema} from 'appium';
141
+ * const options = {}; // config object
142
+ * await init(options);
143
+ * const schema = getSchema(); // entire config schema including plugins and drivers
144
+ */
145
+ async function init (args) {
146
+ const appiumHome = args?.appiumHome ?? await resolveAppiumHome();
147
+
148
+ const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
149
+
150
+ const parser = getParser();
94
151
  let throwInsteadOfExit = false;
152
+ /** @type {ParsedArgs} */
153
+ let preConfigParsedArgs;
154
+ /** @type {ParsedArgs} */
155
+ let parsedArgs;
156
+ /**
157
+ * This is a definition (instead of declaration) because TS can't figure out
158
+ * the value will be defined when it's used.
159
+ * @type {ReturnType<getDefaultsForSchema>}
160
+ */
161
+ let defaults = {};
95
162
  if (args) {
96
- // a containing package passed in their own args, let's fill them out
97
- // with defaults
98
- args = Object.assign({}, getDefaultServerArgs(), args);
99
-
100
163
  // if we have a containing package instead of running as a CLI process,
101
164
  // that package might not appreciate us calling 'process.exit' willy-
102
165
  // nilly, so give it the option to have us throw instead of exit
@@ -105,80 +168,134 @@ async function main (args = null) {
105
168
  // but remove it since it's not a real server arg per se
106
169
  delete args.throwInsteadOfExit;
107
170
  }
171
+ preConfigParsedArgs = /** @type {ParsedArgs} */({...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND});
108
172
  } else {
109
173
  // otherwise parse from CLI
110
- args = parser.parseArgs();
174
+ preConfigParsedArgs = parser.parseArgs();
175
+ }
176
+
177
+ const configResult = await readConfigFile(preConfigParsedArgs.configFile);
178
+
179
+ if (!_.isEmpty(configResult.errors)) {
180
+ throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
111
181
  }
112
- await logsinkInit(args);
182
+
183
+ // merge config and apply defaults.
184
+ // the order of precendece is:
185
+ // 1. command line args
186
+ // 2. config file
187
+ // 3. defaults from config file.
188
+ if (preConfigParsedArgs.subcommand === SERVER_SUBCOMMAND) {
189
+ defaults = getDefaultsForSchema(false);
190
+
191
+ parsedArgs = _.defaultsDeep(
192
+ preConfigParsedArgs,
193
+ configResult.config?.server,
194
+ defaults
195
+ );
196
+
197
+ if (preConfigParsedArgs.showConfig) {
198
+ showConfig(getNonDefaultServerArgs(preConfigParsedArgs), configResult, defaults, parsedArgs);
199
+ return {};
200
+ }
201
+
202
+ } else {
203
+ parsedArgs = preConfigParsedArgs;
204
+ }
205
+
206
+ await logsinkInit(parsedArgs);
113
207
 
114
208
  // if the user has requested the 'driver' CLI, don't run the normal server,
115
209
  // but instead pass control to the driver CLI
116
- if (args.subcommand === DRIVER_TYPE || args.subcommand === PLUGIN_TYPE) {
117
- await runExtensionCommand(args, args.subcommand);
118
- process.exit();
210
+ if (parsedArgs.subcommand === DRIVER_TYPE) {
211
+ await runExtensionCommand(parsedArgs, driverConfig);
212
+ return {};
213
+ }
214
+ if (parsedArgs.subcommand === PLUGIN_TYPE) {
215
+ await runExtensionCommand(parsedArgs, pluginConfig);
216
+ return {};
119
217
  }
120
218
 
121
- if (args.logFilters) {
122
- const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(args.logFilters);
219
+ if (parsedArgs.logFilters) {
220
+ const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(parsedArgs.logFilters);
123
221
  if (!_.isEmpty(issues)) {
124
- throw new Error(`The log filtering rules config '${args.logFilters}' has issues: ` +
222
+ throw new Error(`The log filtering rules config '${parsedArgs.logFilters}' has issues: ` +
125
223
  JSON.stringify(issues, null, 2));
126
224
  }
127
225
  if (_.isEmpty(rules)) {
128
- logger.warn(`Found no log filtering rules in '${args.logFilters}'. Is that expected?`);
226
+ logger.warn(`Found no log filtering rules in '${parsedArgs.logFilters}'. Is that expected?`);
129
227
  } else {
130
- logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${args.logFilters}'`);
228
+ logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${parsedArgs.logFilters}'`);
131
229
  }
132
230
  }
133
231
 
134
- let appiumDriver = new AppiumDriver(args);
135
- const driverConfig = new DriverConfig(args.appiumHome);
136
- const pluginConfig = new PluginConfig(args.appiumHome);
232
+ const appiumDriver = new AppiumDriver(parsedArgs);
233
+ // set the config on the umbrella driver so it can match drivers to caps
137
234
  appiumDriver.driverConfig = driverConfig;
138
- await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
139
- await logStartupInfo(parser, args);
235
+ await preflightChecks(parsedArgs, throwInsteadOfExit);
236
+
237
+ return /** @type {ServerInitResult} */({appiumDriver, parsedArgs, driverConfig, pluginConfig});
238
+ }
239
+
240
+ /**
241
+ * Initializes Appium's config. Starts server if appropriate and resolves the
242
+ * server instance if so; otherwise resolves w/ `undefined`.
243
+ * @param {PartialArgs} [args] - Arguments from CLI or otherwise
244
+ * @returns {Promise<import('http').Server|undefined>}
245
+ */
246
+ async function main (args) {
247
+ const {appiumDriver, parsedArgs, pluginConfig, driverConfig} = /** @type {ServerInitResult} */(await init(args));
248
+
249
+ if (!appiumDriver || !parsedArgs || !pluginConfig || !driverConfig) {
250
+ // if this branch is taken, we've run a different subcommand, so there's nothing
251
+ // left to do here.
252
+ return;
253
+ }
254
+
255
+ const pluginClasses = getActivePlugins(pluginConfig, parsedArgs.usePlugins);
256
+ // set the active plugins on the umbrella driver so it can use them for commands
257
+ appiumDriver.pluginClasses = pluginClasses;
258
+
259
+ await logStartupInfo(parsedArgs);
140
260
  let routeConfiguringFunction = makeRouter(appiumDriver);
141
261
 
142
- // find any plugin name which has been installed, and which has been requested for activation by
143
- // using the --plugins flag, and turn each one into an instantiated plugin object, so we can send
144
- // them as objects to the server init. we also want to send/assign them to the umbrella driver so
145
- // it can use them to wrap command execution
146
- const plugins = Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
147
- _.includes(args.plugins, pluginName) ||
148
- (args.plugins.length === 1 && args.plugins[0] === USE_ALL_PLUGINS)
149
- ).map((pluginName) => {
150
- try {
151
- const PluginClass = pluginConfig.require(pluginName);
152
- return new PluginClass(pluginName);
153
- } catch (err) {
154
- logger.error(`Could not load plugin '${pluginName}', so it will not be available. Error ` +
155
- `in loading the plugin was: ${err}`);
156
- return false;
157
- }
158
- }).filter(Boolean);
159
- appiumDriver.plugins = plugins;
262
+ const driverClasses = getActiveDrivers(driverConfig, parsedArgs.useDrivers);
263
+ const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
264
+ const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
160
265
 
161
- let server = await baseServer({
266
+ const serverOpts = {
162
267
  routeConfiguringFunction,
163
- port: args.port,
164
- hostname: args.address,
165
- allowCors: args.allowCors,
166
- basePath: args.basePath,
167
- plugins,
168
- });
169
- if (args.allowCors) {
268
+ port: parsedArgs.port,
269
+ hostname: parsedArgs.address,
270
+ allowCors: parsedArgs.allowCors,
271
+ basePath: parsedArgs.basePath,
272
+ serverUpdaters,
273
+ extraMethodMap,
274
+ };
275
+ if (parsedArgs.keepAliveTimeout) {
276
+ serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
277
+ }
278
+ let server;
279
+ try {
280
+ server = await baseServer(serverOpts);
281
+ } catch (err) {
282
+ logger.error(`Could not configure Appium server. It's possible that a driver or plugin tried ` +
283
+ `to update the server and failed. Original error: ${err.message}`);
284
+ logger.debug(err.stack);
285
+ return process.exit(1);
286
+ }
287
+
288
+ if (parsedArgs.allowCors) {
170
289
  logger.warn('You have enabled CORS requests from any host. Be careful not ' +
171
290
  'to visit sites which could maliciously try to start Appium ' +
172
291
  'sessions on your machine');
173
292
  }
174
293
  appiumDriver.server = server;
175
294
  try {
176
- // TODO prelaunch if args.launch is set
177
- // TODO: startAlertSocket(server, appiumServer);
178
-
179
295
  // configure as node on grid, if necessary
180
- if (args.nodeconfig !== null) {
181
- await registerNode(args.nodeconfig, args.address, args.port, args.basePath);
296
+ // falsy values should not cause this to run
297
+ if (parsedArgs.nodeconfig) {
298
+ await registerNode(parsedArgs.nodeconfig, parsedArgs.address, parsedArgs.port, parsedArgs.basePath);
182
299
  }
183
300
  } catch (err) {
184
301
  await server.close();
@@ -202,15 +319,48 @@ async function main (args = null) {
202
319
  });
203
320
  }
204
321
 
205
- logServerPort(args.address, args.port);
322
+ logServerPort(parsedArgs.address, parsedArgs.port);
206
323
  driverConfig.print();
207
- pluginConfig.print(plugins);
324
+ pluginConfig.print(pluginClasses.map((p) => p.pluginName));
208
325
 
209
326
  return server;
210
327
  }
211
328
 
329
+ // NOTE: this is here for backwards compat for any scripts referencing `main.js` directly
330
+ // (more specifically, `build/lib/main.js`)
331
+ // the executable is now `../index.js`, so that module will typically be `require.main`.
212
332
  if (require.main === module) {
213
333
  asyncify(main);
214
334
  }
215
335
 
216
- export { main };
336
+ // everything below here is intended to be a public API.
337
+ export { readConfigFile } from './config-file';
338
+ export { finalizeSchema, getSchema, validate } from './schema/schema';
339
+ export { main, init, resolveAppiumHome };
340
+
341
+ /**
342
+ * @typedef {import('../types/cli').ParsedArgs} ParsedArgs
343
+ */
344
+
345
+ /**
346
+ * @typedef {import('../types/cli').PartialArgs} PartialArgs
347
+ * @typedef {import('../types').DriverType} DriverType
348
+ * @typedef {import('../types').PluginType} PluginType
349
+ * @typedef {import('../types/extension').DriverClass} DriverClass
350
+ * @typedef {import('../types/extension').PluginClass} PluginClass
351
+ */
352
+
353
+ /**
354
+ * Literally an empty object
355
+ * @typedef { {} } ExtCommandInitResult
356
+ */
357
+
358
+ /**
359
+ * @typedef ServerInitData
360
+ * @property {AppiumDriver} appiumDriver - The Appium driver
361
+ * @property {ParsedArgs} parsedArgs - The parsed arguments
362
+ */
363
+
364
+ /**
365
+ * @typedef {ServerInitData & import('./extension').ExtensionConfigs} ServerInitResult
366
+ */