appium 2.0.0-beta.3 → 2.0.0-beta.34

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 (149) 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 +241 -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 +80 -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 +41 -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 +54 -0
  63. package/build/lib/main.d.ts.map +1 -0
  64. package/build/lib/main.js +189 -90
  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/build/types/appium-manifest.d.ts +40 -0
  88. package/build/types/appium-manifest.d.ts.map +1 -0
  89. package/build/types/cli.d.ts +112 -0
  90. package/build/types/cli.d.ts.map +1 -0
  91. package/build/types/extension.d.ts +43 -0
  92. package/build/types/extension.d.ts.map +1 -0
  93. package/build/types/external-manifest.d.ts +47 -0
  94. package/build/types/external-manifest.d.ts.map +1 -0
  95. package/build/types/index.d.ts +15 -0
  96. package/build/types/index.d.ts.map +1 -0
  97. package/index.js +11 -0
  98. package/lib/appium-config.schema.json +278 -0
  99. package/lib/appium.js +402 -155
  100. package/lib/cli/args.js +174 -377
  101. package/lib/cli/driver-command.js +22 -7
  102. package/lib/cli/extension-command.js +372 -177
  103. package/lib/cli/extension.js +32 -10
  104. package/lib/cli/parser.js +253 -83
  105. package/lib/cli/plugin-command.js +19 -6
  106. package/lib/cli/utils.js +22 -2
  107. package/lib/config-file.js +223 -0
  108. package/lib/config.js +170 -69
  109. package/lib/constants.js +78 -0
  110. package/lib/extension/driver-config.js +249 -0
  111. package/lib/extension/extension-config.js +458 -0
  112. package/lib/extension/index.js +102 -0
  113. package/lib/extension/manifest.js +484 -0
  114. package/lib/extension/package-changed.js +63 -0
  115. package/lib/extension/plugin-config.js +113 -0
  116. package/lib/grid-register.js +25 -22
  117. package/lib/logger.js +1 -1
  118. package/lib/logsink.js +14 -7
  119. package/lib/main.js +255 -90
  120. package/lib/schema/arg-spec.js +232 -0
  121. package/lib/schema/cli-args.js +261 -0
  122. package/lib/schema/cli-transformers.js +122 -0
  123. package/lib/schema/index.js +2 -0
  124. package/lib/schema/keywords.js +134 -0
  125. package/lib/schema/schema.js +734 -0
  126. package/lib/utils.js +85 -129
  127. package/package.json +68 -85
  128. package/scripts/postinstall.js +71 -0
  129. package/types/appium-manifest.ts +61 -0
  130. package/types/cli.ts +153 -0
  131. package/types/extension.ts +56 -0
  132. package/types/external-manifest.ts +58 -0
  133. package/types/index.ts +14 -0
  134. package/CHANGELOG.md +0 -3515
  135. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  136. package/build/lib/cli/npm.js +0 -206
  137. package/build/lib/cli/parser-helpers.js +0 -82
  138. package/build/lib/driver-config.js +0 -77
  139. package/build/lib/drivers.js +0 -96
  140. package/build/lib/extension-config.js +0 -251
  141. package/build/lib/plugin-config.js +0 -59
  142. package/build/lib/plugins.js +0 -14
  143. package/lib/cli/npm.js +0 -183
  144. package/lib/cli/parser-helpers.js +0 -79
  145. package/lib/driver-config.js +0 -46
  146. package/lib/drivers.js +0 -81
  147. package/lib/extension-config.js +0 -208
  148. package/lib/plugin-config.js +0 -34
  149. package/lib/plugins.js +0 -10
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,27 @@ async function preflightChecks ({parser, args, driverConfig, pluginConfig, throw
50
53
  }
51
54
  }
52
55
 
56
+ /**
57
+ * @param {Args} 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
 
64
+ /**
65
+ * @param {Args['defaultCapabilities']} caps
66
+ */
58
67
  function logDefaultCapabilitiesWarning (caps) {
59
68
  logger.info('Default capabilities, which will be added to each request ' +
60
69
  'unless overridden by desired capabilities:');
61
- inspectObject(caps);
70
+ inspect(caps);
62
71
  }
63
72
 
64
- async function logStartupInfo (parser, args) {
73
+ /**
74
+ * @param {ParsedArgs} args
75
+ */
76
+ async function logStartupInfo (args) {
65
77
  let welcome = `Welcome to Appium v${APPIUM_VER}`;
66
78
  let appiumRev = await getGitRev();
67
79
  if (appiumRev) {
@@ -69,7 +81,7 @@ async function logStartupInfo (parser, args) {
69
81
  }
70
82
  logger.info(welcome);
71
83
 
72
- let showArgs = getNonDefaultArgs(parser, args);
84
+ let showArgs = getNonDefaultServerArgs(args);
73
85
  if (_.size(showArgs)) {
74
86
  logNonDefaultArgsWarning(showArgs);
75
87
  }
@@ -83,20 +95,77 @@ async function logStartupInfo (parser, args) {
83
95
  // }
84
96
  }
85
97
 
98
+ /**
99
+ * Logs the address and port the server is listening on
100
+ * @param {string} address - Address
101
+ * @param {number} port - Port
102
+ * @returns {void}
103
+ */
86
104
  function logServerPort (address, port) {
87
105
  let logMessage = `Appium REST http interface listener started on ` +
88
106
  `${address}:${port}`;
89
107
  logger.info(logMessage);
90
108
  }
91
109
 
92
- async function main (args = null) {
93
- let parser = getParser();
110
+ /**
111
+ * Gets a list of `updateServer` functions from all extensions
112
+ * @param {DriverClass[]} driverClasses
113
+ * @param {PluginClass[]} pluginClasses
114
+ * @returns {import('@appium/base-driver/lib/basedriver/driver').UpdateServerCallback[]}
115
+ */
116
+ function getServerUpdaters (driverClasses, pluginClasses) {
117
+ return _.compact(_.map([...driverClasses, ...pluginClasses], 'updateServer'));
118
+ }
119
+
120
+ /**
121
+ * Makes a big `MethodMap` from all the little `MethodMap`s in the extensions
122
+ * @param {DriverClass[]} driverClasses
123
+ * @param {PluginClass[]} pluginClasses
124
+ * @returns {import('@appium/types').MethodMap}
125
+ */
126
+ function getExtraMethodMap (driverClasses, pluginClasses) {
127
+ return [...driverClasses, ...pluginClasses].reduce(
128
+ (map, klass) => ({...map, .../** @type {DriverClass} */(klass).newMethodMap ?? {}}),
129
+ {}
130
+ );
131
+ }
132
+
133
+ /**
134
+ * @template [T=WithServerSubcommand]
135
+ * @param {Args<T>} args
136
+ * @returns {args is Args<WithServerSubcommand>}
137
+ */
138
+ function areServerCommandArgs (args) {
139
+ return args.subcommand === SERVER_SUBCOMMAND;
140
+ }
141
+
142
+ /**
143
+ * Initializes Appium, but does not start the server.
144
+ *
145
+ * Use this to get at the configuration schema.
146
+ *
147
+ * If `args` contains a non-empty `subcommand` which is not `server`, this function will return an empty object.
148
+ *
149
+ * @template [T=WithServerSubcommand]
150
+ * @param {Args<T>} [args] - Partial args (progammatic usage only)
151
+ * @returns {Promise<ServerInitResult | ExtCommandInitResult>}
152
+ * @example
153
+ * import {init, getSchema} from 'appium';
154
+ * const options = {}; // config object
155
+ * await init(options);
156
+ * const schema = getSchema(); // entire config schema including plugins and drivers
157
+ */
158
+ async function init (args) {
159
+ const appiumHome = args?.appiumHome ?? await resolveAppiumHome();
160
+
161
+ const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
162
+
163
+ const parser = getParser();
94
164
  let throwInsteadOfExit = false;
95
- 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);
165
+ /** @type {Args<T>} */
166
+ let preConfigArgs;
99
167
 
168
+ if (args) {
100
169
  // if we have a containing package instead of running as a CLI process,
101
170
  // that package might not appreciate us calling 'process.exit' willy-
102
171
  // nilly, so give it the option to have us throw instead of exit
@@ -105,80 +174,136 @@ async function main (args = null) {
105
174
  // but remove it since it's not a real server arg per se
106
175
  delete args.throwInsteadOfExit;
107
176
  }
177
+ preConfigArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
108
178
  } else {
109
179
  // otherwise parse from CLI
110
- args = parser.parseArgs();
180
+ preConfigArgs = /** @type {Args<T>} */(parser.parseArgs());
111
181
  }
112
- await logsinkInit(args);
113
182
 
114
- // if the user has requested the 'driver' CLI, don't run the normal server,
115
- // 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();
183
+ const configResult = await readConfigFile(preConfigArgs.configFile);
184
+
185
+ if (!_.isEmpty(configResult.errors)) {
186
+ throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
119
187
  }
120
188
 
121
- if (args.logFilters) {
122
- const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(args.logFilters);
123
- if (!_.isEmpty(issues)) {
124
- throw new Error(`The log filtering rules config '${args.logFilters}' has issues: ` +
125
- JSON.stringify(issues, null, 2));
189
+ // merge config and apply defaults.
190
+ // the order of precendece is:
191
+ // 1. command line args
192
+ // 2. config file
193
+ // 3. defaults from config file.
194
+ if (!areServerCommandArgs(preConfigArgs)) {
195
+
196
+ // if the user has requested the 'driver' CLI, don't run the normal server,
197
+ // but instead pass control to the driver CLI
198
+ if (preConfigArgs.subcommand === DRIVER_TYPE) {
199
+ await runExtensionCommand(preConfigArgs, driverConfig);
200
+ return {};
126
201
  }
127
- if (_.isEmpty(rules)) {
128
- logger.warn(`Found no log filtering rules in '${args.logFilters}'. Is that expected?`);
129
- } else {
130
- logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${args.logFilters}'`);
202
+ if (preConfigArgs.subcommand === PLUGIN_TYPE) {
203
+ await runExtensionCommand(preConfigArgs, pluginConfig);
204
+ return {};
131
205
  }
206
+ /* istanbul ignore next */
207
+ return {}; // should never happen
208
+ } else {
209
+ const defaults = getDefaultsForSchema(false);
210
+
211
+ /** @type {ParsedArgs} */
212
+ const serverArgs = _.defaultsDeep(
213
+ preConfigArgs,
214
+ configResult.config?.server,
215
+ defaults
216
+ );
217
+
218
+ if (preConfigArgs.showConfig) {
219
+ showConfig(getNonDefaultServerArgs(preConfigArgs), configResult, defaults, serverArgs);
220
+ return {};
221
+ }
222
+
223
+ await logsinkInit(serverArgs);
224
+
225
+ if (serverArgs.logFilters) {
226
+ const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(serverArgs.logFilters);
227
+ if (!_.isEmpty(issues)) {
228
+ throw new Error(`The log filtering rules config '${serverArgs.logFilters}' has issues: ` +
229
+ JSON.stringify(issues, null, 2));
230
+ }
231
+ if (_.isEmpty(rules)) {
232
+ logger.warn(`Found no log filtering rules in '${serverArgs.logFilters}'. Is that expected?`);
233
+ } else {
234
+ logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${serverArgs.logFilters}'`);
235
+ }
236
+ }
237
+
238
+ const appiumDriver = new AppiumDriver(serverArgs);
239
+ // set the config on the umbrella driver so it can match drivers to caps
240
+ appiumDriver.driverConfig = driverConfig;
241
+ await preflightChecks(serverArgs, throwInsteadOfExit);
242
+
243
+ return /** @type {ServerInitResult} */({appiumDriver, parsedArgs: serverArgs, driverConfig, pluginConfig});
132
244
  }
245
+ }
133
246
 
134
- let appiumDriver = new AppiumDriver(args);
135
- const driverConfig = new DriverConfig(args.appiumHome);
136
- const pluginConfig = new PluginConfig(args.appiumHome);
137
- appiumDriver.driverConfig = driverConfig;
138
- await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
139
- await logStartupInfo(parser, args);
247
+ /**
248
+ * Initializes Appium's config. Starts server if appropriate and resolves the
249
+ * server instance if so; otherwise resolves w/ `undefined`.
250
+ * @template [T=WithServerSubcommand]
251
+ * @param {Args<T>} [args] - Arguments from CLI or otherwise
252
+ * @returns {Promise<import('@appium/types').AppiumServer|undefined>}
253
+ */
254
+ async function main (args) {
255
+ const {appiumDriver, parsedArgs, pluginConfig, driverConfig} = /** @type {ServerInitResult} */(await init(args));
256
+
257
+ if (!appiumDriver || !parsedArgs || !pluginConfig || !driverConfig) {
258
+ // if this branch is taken, we've run a different subcommand, so there's nothing
259
+ // left to do here.
260
+ return;
261
+ }
262
+
263
+ const pluginClasses = getActivePlugins(pluginConfig, parsedArgs.usePlugins);
264
+ // set the active plugins on the umbrella driver so it can use them for commands
265
+ appiumDriver.pluginClasses = pluginClasses;
266
+
267
+ await logStartupInfo(parsedArgs);
140
268
  let routeConfiguringFunction = makeRouter(appiumDriver);
141
269
 
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;
270
+ const driverClasses = getActiveDrivers(driverConfig, parsedArgs.useDrivers);
271
+ const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
272
+ const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
160
273
 
161
- let server = await baseServer({
274
+ const serverOpts = {
162
275
  routeConfiguringFunction,
163
- port: args.port,
164
- hostname: args.address,
165
- allowCors: args.allowCors,
166
- basePath: args.basePath,
167
- plugins,
168
- });
169
- if (args.allowCors) {
276
+ port: parsedArgs.port,
277
+ hostname: parsedArgs.address,
278
+ allowCors: parsedArgs.allowCors,
279
+ basePath: parsedArgs.basePath,
280
+ serverUpdaters,
281
+ extraMethodMap,
282
+ };
283
+ if (parsedArgs.keepAliveTimeout) {
284
+ serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
285
+ }
286
+ let server;
287
+ try {
288
+ server = await baseServer(serverOpts);
289
+ } catch (err) {
290
+ logger.error(`Could not configure Appium server. It's possible that a driver or plugin tried ` +
291
+ `to update the server and failed. Original error: ${err.message}`);
292
+ logger.debug(err.stack);
293
+ return process.exit(1);
294
+ }
295
+
296
+ if (parsedArgs.allowCors) {
170
297
  logger.warn('You have enabled CORS requests from any host. Be careful not ' +
171
298
  'to visit sites which could maliciously try to start Appium ' +
172
299
  'sessions on your machine');
173
300
  }
174
301
  appiumDriver.server = server;
175
302
  try {
176
- // TODO prelaunch if args.launch is set
177
- // TODO: startAlertSocket(server, appiumServer);
178
-
179
303
  // configure as node on grid, if necessary
180
- if (args.nodeconfig !== null) {
181
- await registerNode(args.nodeconfig, args.address, args.port, args.basePath);
304
+ // falsy values should not cause this to run
305
+ if (parsedArgs.nodeconfig) {
306
+ await registerNode(parsedArgs.nodeconfig, parsedArgs.address, parsedArgs.port, parsedArgs.basePath);
182
307
  }
183
308
  } catch (err) {
184
309
  await server.close();
@@ -202,15 +327,55 @@ async function main (args = null) {
202
327
  });
203
328
  }
204
329
 
205
- logServerPort(args.address, args.port);
330
+ logServerPort(parsedArgs.address, parsedArgs.port);
206
331
  driverConfig.print();
207
- pluginConfig.print(plugins);
332
+ pluginConfig.print(pluginClasses.map((p) => p.pluginName));
208
333
 
209
334
  return server;
210
335
  }
211
336
 
337
+ // NOTE: this is here for backwards compat for any scripts referencing `main.js` directly
338
+ // (more specifically, `build/lib/main.js`)
339
+ // the executable is now `../index.js`, so that module will typically be `require.main`.
212
340
  if (require.main === module) {
213
341
  asyncify(main);
214
342
  }
215
343
 
216
- export { main };
344
+ // everything below here is intended to be a public API.
345
+ export { readConfigFile } from './config-file';
346
+ export { finalizeSchema, getSchema, validate } from './schema/schema';
347
+ export { main, init, resolveAppiumHome };
348
+
349
+ /**
350
+ * @typedef {import('../types').DriverType} DriverType
351
+ * @typedef {import('../types').PluginType} PluginType
352
+ * @typedef {import('../types').DriverClass} DriverClass
353
+ * @typedef {import('../types').PluginClass} PluginClass
354
+ * @typedef {import('../types').WithServerSubcommand} WithServerSubcommand
355
+ */
356
+
357
+ /**
358
+ * Literally an empty object
359
+ * @typedef { {} } ExtCommandInitResult
360
+ */
361
+
362
+ /**
363
+ * @typedef ServerInitData
364
+ * @property {AppiumDriver} appiumDriver - The Appium driver
365
+ * @property {import('../types').ParsedArgs} parsedArgs - The parsed arguments
366
+ */
367
+
368
+ /**
369
+ * @typedef {ServerInitData & import('./extension').ExtensionConfigs} ServerInitResult
370
+ */
371
+
372
+ /**
373
+ * @template [T=WithServerSubcommand]
374
+ * @typedef {import('../types').Args<T>} Args
375
+ */
376
+
377
+ /**
378
+ * @template [T=WithServerSubcommand]
379
+ * @typedef {import('../types').ParsedArgs<T>} ParsedArgs
380
+ */
381
+
@@ -0,0 +1,232 @@
1
+ import _ from 'lodash';
2
+
3
+ /**
4
+ * The original ID of the Appium config schema.
5
+ * We use this in the CLI to convert it to `argparse` options.
6
+ */
7
+ export const APPIUM_CONFIG_SCHEMA_ID = 'appium.json';
8
+
9
+ /**
10
+ * The schema prop containing server-related options. Everything in here
11
+ * is "native" to Appium.
12
+ * Used by {@link flattenSchema} for transforming the schema into CLI args.
13
+ */
14
+ export const SERVER_PROP_NAME = 'server';
15
+
16
+ /**
17
+ * Used to parse extension info from a schema ID.
18
+ */
19
+ const SCHEMA_ID_REGEXP = /^(?<extType>.+?)-(?<normalizedExtName>.+)\.json$/;
20
+
21
+ /**
22
+ * Avoid typos by using constants!
23
+ */
24
+ const PROPERTIES = 'properties';
25
+
26
+ /**
27
+ * An `ArgSpec` is a class representing metadata about an argument (or config
28
+ * option) used for cross-referencing.
29
+ *
30
+ * This class has no instance methods, and is basically just a read-only "struct".
31
+ * @template D
32
+ */
33
+ export class ArgSpec {
34
+ /**
35
+ * The canonical name of the argument. Corresponds to key in schema's `properties` prop.
36
+ * @type {string}
37
+ */
38
+ name;
39
+
40
+ /**
41
+ * The `ExtensionType` of the argument. This will be set if the arg came from an extension;
42
+ * otherwise it will be `undefined`.
43
+ * @type {ExtensionType|undefined}
44
+ */
45
+ extType;
46
+
47
+ /**
48
+ * The name of the extension, if this argument came from an extension.
49
+ *
50
+ * Otherwise `undefined`.
51
+ * @type {string|undefined}
52
+ */
53
+ extName;
54
+
55
+ /**
56
+ * The schema ID (`$id`) for the argument. This is automatically determined, and any user-provided `$id`s will be overwritten.
57
+ *
58
+ * @type {string}
59
+ */
60
+ ref;
61
+
62
+ /**
63
+ * The CLI argument, sans leading dashes.
64
+ * @type {string}
65
+ */
66
+ arg;
67
+
68
+ /**
69
+ * The desired keypath for the argument after arguments have been parsed.
70
+ *
71
+ * Typically this is camelCased. If the arg came from an extension, it will be prefixed with
72
+ * `<extType>.<extName>.`
73
+ * @type {string}
74
+ */
75
+ dest;
76
+
77
+ /**
78
+ * The same as {@link ArgSpec.dest} but without the leading `<extType>.<extName>.` prefix.
79
+ */
80
+ rawDest;
81
+
82
+ /**
83
+ * Whatever the default value of this argument is, as specified by the
84
+ * `default` property of the schema.
85
+ * @type {D|undefined}
86
+ */
87
+ defaultValue;
88
+
89
+ /**
90
+ * Builds some computed fields and assigns them to the instance.
91
+ *
92
+ * Undefined properties are not assigned.
93
+ *
94
+ * The _constructor_ is private. Use {@link ArgSpec.create} instead.
95
+ * @private
96
+ * @param {string} name
97
+ * @param {ArgSpecOptions<D>} [opts]
98
+ */
99
+ constructor (name, {extType, extName, dest, defaultValue} = {}) {
100
+ // we must normalize the extension name to fit into our convention for CLI
101
+ // args.
102
+ const arg = ArgSpec.toArg(name, extType, extName);
103
+
104
+ const ref = ArgSpec.toSchemaRef(name, extType, extName);
105
+
106
+ // if no explicit `dest` provided, just camelCase the name to avoid needing
107
+ // to use bracket syntax when accessing props on the parsed args object.
108
+ const rawDest = _.camelCase(dest ?? name);
109
+
110
+ const destKeypath =
111
+ extType && extName ? [extType, extName, rawDest].join('.') : rawDest;
112
+
113
+ this.defaultValue = defaultValue;
114
+ this.name = name;
115
+ this.extType = extType;
116
+ this.extName = extName;
117
+ this.arg = arg;
118
+ this.dest = destKeypath;
119
+ this.ref = ref;
120
+ this.rawDest = rawDest;
121
+ }
122
+
123
+ /**
124
+ * Return the schema ID (`$id`) for the **argument** given the parameters.
125
+ *
126
+ * If you need the "root" or "base" schema ID, use {@link ArgSpec.toSchemaBaseRef} instead.
127
+ * @param {string} name - Argument name
128
+ * @param {ExtensionType} [extType] - Extension type
129
+ * @param {string} [extName] - Extension name
130
+ * @returns {string} Schema ID
131
+ */
132
+ static toSchemaRef (name, extType, extName) {
133
+ const baseRef = ArgSpec.toSchemaBaseRef(extType, extName);
134
+ if (extType && extName) {
135
+ return [`${baseRef}#`, PROPERTIES, name].join('/');
136
+ }
137
+ return [`${baseRef}#`, PROPERTIES, SERVER_PROP_NAME, PROPERTIES, name].join('/');
138
+ }
139
+
140
+ /**
141
+ * Return the schema ID for an extension or the base schema ID.
142
+ * @param {ExtensionType} [extType] - Extension type
143
+ * @param {string} [extName] - Extension name
144
+ */
145
+ static toSchemaBaseRef (extType, extName) {
146
+ if (extType && extName) {
147
+ return `${extType}-${ArgSpec.toNormalizedExtName(extName)}.json`;
148
+ }
149
+ return APPIUM_CONFIG_SCHEMA_ID;
150
+ }
151
+
152
+ /**
153
+ * Return the unique ID for the argument given the parameters.
154
+ * @param {string} name - Argument name
155
+ * @param {ExtensionType} [extType] - Extension type
156
+ * @param {string} [extName] - Extension name
157
+ * @returns {string} Unique ID
158
+ */
159
+ static toArg (name, extType, extName) {
160
+ const properName = _.kebabCase(name.replace(/^--?/, ''));
161
+ if (extType && extName) {
162
+ return [extType, _.kebabCase(extName), properName].join('-');
163
+ }
164
+ return properName;
165
+ }
166
+
167
+ /**
168
+ * Normalizes a raw extension name (not including the type).
169
+ * @param {string} extName - Extension name
170
+ * @returns {string} Normalized extension name
171
+ */
172
+ static toNormalizedExtName (extName) {
173
+ return _.kebabCase(extName);
174
+ }
175
+
176
+ /**
177
+ * When given the root ID of a schema for an extension (`<extType>-<normalizedExtName>.json`) Returns an object containing the extension type and the _normalized_ extension name.
178
+ * @param {string} schemaId - Root schema ID
179
+ * @returns { {extType?: ExtensionType, normalizedExtName?: string} }
180
+ */
181
+ static extensionInfoFromRootSchemaId (schemaId) {
182
+ const matches = schemaId.match(SCHEMA_ID_REGEXP);
183
+ if (matches?.groups) {
184
+ const {extType, normalizedExtName} =
185
+ /** @type { {extType: ExtensionType, normalizedExtName: string} } */ (
186
+ matches.groups
187
+ );
188
+ return {extType, normalizedExtName};
189
+ }
190
+ return {};
191
+ }
192
+
193
+ /**
194
+ * Creates an `ArgSpec`
195
+ *
196
+ * @param {string} name - The canonical name of the argument. Corresponds to a key in a schema's
197
+ * `properties` property.
198
+ * @template D
199
+ * @param {ArgSpecOptions<D>} [opts] - Options
200
+ * @returns {Readonly<ArgSpec>}
201
+ */
202
+ static create (name, opts) {
203
+ return Object.freeze(new ArgSpec(name, opts));
204
+ }
205
+
206
+ /**
207
+ * String representation, useful for debugging
208
+ * @returns {string}
209
+ */
210
+ /* istanbul ignore next */
211
+ toString () {
212
+ let str = `[ArgSpec] ${this.name} (${this.ref})`;
213
+ if (this.extType && this.extName) {
214
+ str += ` (ext: ${this.extType}/${this.extName})`;
215
+ }
216
+ return str;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Options for {@link ArgSpec.create}
222
+ * @template D
223
+ * @typedef ArgSpecOptions
224
+ * @property {string} [extName]
225
+ * @property {ExtensionType} [extType]
226
+ * @property {string} [dest]
227
+ * @property {D} [defaultValue]
228
+ */
229
+
230
+ /**
231
+ * @typedef {import('../extension/manifest').ExtensionType} ExtensionType
232
+ */