appium 3.2.2 → 3.3.1

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 (250) hide show
  1. package/build/lib/appium.d.ts +147 -205
  2. package/build/lib/appium.d.ts.map +1 -1
  3. package/build/lib/appium.js +169 -282
  4. package/build/lib/appium.js.map +1 -1
  5. package/build/lib/bidi-commands.d.ts.map +1 -1
  6. package/build/lib/bidi-commands.js +11 -11
  7. package/build/lib/bidi-commands.js.map +1 -1
  8. package/build/lib/bootstrap/appium-initializer.d.ts +21 -0
  9. package/build/lib/bootstrap/appium-initializer.d.ts.map +1 -0
  10. package/build/lib/bootstrap/appium-initializer.js +146 -0
  11. package/build/lib/bootstrap/appium-initializer.js.map +1 -0
  12. package/build/lib/bootstrap/appium-main-runner.d.ts +22 -0
  13. package/build/lib/bootstrap/appium-main-runner.d.ts.map +1 -0
  14. package/build/lib/bootstrap/appium-main-runner.js +109 -0
  15. package/build/lib/bootstrap/appium-main-runner.js.map +1 -0
  16. package/build/lib/bootstrap/config-file.d.ts +37 -0
  17. package/build/lib/bootstrap/config-file.d.ts.map +1 -0
  18. package/build/lib/{config-file.js → bootstrap/config-file.js} +62 -138
  19. package/build/lib/bootstrap/config-file.js.map +1 -0
  20. package/build/lib/bootstrap/grid-v3-register.d.ts +20 -0
  21. package/build/lib/bootstrap/grid-v3-register.d.ts.map +1 -0
  22. package/build/lib/bootstrap/grid-v3-register.js +185 -0
  23. package/build/lib/bootstrap/grid-v3-register.js.map +1 -0
  24. package/build/lib/bootstrap/init-types.d.ts +16 -0
  25. package/build/lib/bootstrap/init-types.d.ts.map +1 -0
  26. package/build/lib/bootstrap/init-types.js +3 -0
  27. package/build/lib/bootstrap/init-types.js.map +1 -0
  28. package/build/lib/bootstrap/main-helpers.d.ts +55 -0
  29. package/build/lib/bootstrap/main-helpers.d.ts.map +1 -0
  30. package/build/lib/bootstrap/main-helpers.js +187 -0
  31. package/build/lib/bootstrap/main-helpers.js.map +1 -0
  32. package/build/lib/bootstrap/node-helpers.d.ts +32 -0
  33. package/build/lib/bootstrap/node-helpers.d.ts.map +1 -0
  34. package/build/lib/bootstrap/node-helpers.js +201 -0
  35. package/build/lib/bootstrap/node-helpers.js.map +1 -0
  36. package/build/lib/bootstrap/startup-config.d.ts +22 -0
  37. package/build/lib/bootstrap/startup-config.d.ts.map +1 -0
  38. package/build/lib/bootstrap/startup-config.js +111 -0
  39. package/build/lib/bootstrap/startup-config.js.map +1 -0
  40. package/build/lib/cli/args.d.ts +16 -12
  41. package/build/lib/cli/args.d.ts.map +1 -1
  42. package/build/lib/cli/args.js +20 -40
  43. package/build/lib/cli/args.js.map +1 -1
  44. package/build/lib/cli/driver-command.d.ts +51 -93
  45. package/build/lib/cli/driver-command.d.ts.map +1 -1
  46. package/build/lib/cli/driver-command.js +11 -66
  47. package/build/lib/cli/driver-command.js.map +1 -1
  48. package/build/lib/cli/extension-command.d.ts +173 -377
  49. package/build/lib/cli/extension-command.d.ts.map +1 -1
  50. package/build/lib/cli/extension-command.js +387 -656
  51. package/build/lib/cli/extension-command.js.map +1 -1
  52. package/build/lib/cli/extension.d.ts +10 -15
  53. package/build/lib/cli/extension.d.ts.map +1 -1
  54. package/build/lib/cli/extension.js +15 -33
  55. package/build/lib/cli/extension.js.map +1 -1
  56. package/build/lib/cli/parser.d.ts +37 -66
  57. package/build/lib/cli/parser.d.ts.map +1 -1
  58. package/build/lib/cli/parser.js +69 -104
  59. package/build/lib/cli/parser.js.map +1 -1
  60. package/build/lib/cli/plugin-command.d.ts +50 -90
  61. package/build/lib/cli/plugin-command.d.ts.map +1 -1
  62. package/build/lib/cli/plugin-command.js +11 -63
  63. package/build/lib/cli/plugin-command.js.map +1 -1
  64. package/build/lib/cli/setup-command.d.ts +21 -26
  65. package/build/lib/cli/setup-command.d.ts.map +1 -1
  66. package/build/lib/cli/setup-command.js +19 -61
  67. package/build/lib/cli/setup-command.js.map +1 -1
  68. package/build/lib/cli/utils.d.ts +33 -35
  69. package/build/lib/cli/utils.d.ts.map +1 -1
  70. package/build/lib/cli/utils.js +48 -50
  71. package/build/lib/cli/utils.js.map +1 -1
  72. package/build/lib/constants.d.ts +23 -23
  73. package/build/lib/constants.d.ts.map +1 -1
  74. package/build/lib/constants.js +10 -15
  75. package/build/lib/constants.js.map +1 -1
  76. package/build/lib/doctor/doctor.d.ts +40 -57
  77. package/build/lib/doctor/doctor.d.ts.map +1 -1
  78. package/build/lib/doctor/doctor.js +31 -62
  79. package/build/lib/doctor/doctor.js.map +1 -1
  80. package/build/lib/extension/driver-config.d.ts +18 -77
  81. package/build/lib/extension/driver-config.d.ts.map +1 -1
  82. package/build/lib/extension/driver-config.js +37 -125
  83. package/build/lib/extension/driver-config.js.map +1 -1
  84. package/build/lib/extension/extension-config.d.ts +103 -210
  85. package/build/lib/extension/extension-config.d.ts.map +1 -1
  86. package/build/lib/extension/extension-config.js +180 -342
  87. package/build/lib/extension/extension-config.js.map +1 -1
  88. package/build/lib/extension/index.d.ts +12 -29
  89. package/build/lib/extension/index.d.ts.map +1 -1
  90. package/build/lib/extension/index.js +33 -75
  91. package/build/lib/extension/index.js.map +1 -1
  92. package/build/lib/extension/manifest-migrations.d.ts +3 -20
  93. package/build/lib/extension/manifest-migrations.d.ts.map +1 -1
  94. package/build/lib/extension/manifest-migrations.js +20 -101
  95. package/build/lib/extension/manifest-migrations.js.map +1 -1
  96. package/build/lib/extension/manifest.d.ts +61 -107
  97. package/build/lib/extension/manifest.d.ts.map +1 -1
  98. package/build/lib/extension/manifest.js +181 -356
  99. package/build/lib/extension/manifest.js.map +1 -1
  100. package/build/lib/extension/package-changed.d.ts +1 -3
  101. package/build/lib/extension/package-changed.d.ts.map +1 -1
  102. package/build/lib/extension/package-changed.js +8 -15
  103. package/build/lib/extension/package-changed.js.map +1 -1
  104. package/build/lib/extension/plugin-config.d.ts +10 -52
  105. package/build/lib/extension/plugin-config.d.ts.map +1 -1
  106. package/build/lib/extension/plugin-config.js +11 -63
  107. package/build/lib/extension/plugin-config.js.map +1 -1
  108. package/build/lib/helpers/build.d.ts +22 -0
  109. package/build/lib/helpers/build.d.ts.map +1 -0
  110. package/build/lib/helpers/build.js +109 -0
  111. package/build/lib/helpers/build.js.map +1 -0
  112. package/build/lib/helpers/capability.d.ts +38 -0
  113. package/build/lib/helpers/capability.d.ts.map +1 -0
  114. package/build/lib/helpers/capability.js +128 -0
  115. package/build/lib/helpers/capability.js.map +1 -0
  116. package/build/lib/helpers/network.d.ts +14 -0
  117. package/build/lib/helpers/network.d.ts.map +1 -0
  118. package/build/lib/helpers/network.js +35 -0
  119. package/build/lib/helpers/network.js.map +1 -0
  120. package/build/lib/insecure-features.js +6 -6
  121. package/build/lib/insecure-features.js.map +1 -1
  122. package/build/lib/inspector-commands.d.ts +6 -0
  123. package/build/lib/inspector-commands.d.ts.map +1 -1
  124. package/build/lib/inspector-commands.js +6 -0
  125. package/build/lib/inspector-commands.js.map +1 -1
  126. package/build/lib/logger.d.ts +2 -3
  127. package/build/lib/logger.d.ts.map +1 -1
  128. package/build/lib/logger.js +2 -3
  129. package/build/lib/logger.js.map +1 -1
  130. package/build/lib/logsink.d.ts +13 -22
  131. package/build/lib/logsink.d.ts.map +1 -1
  132. package/build/lib/logsink.js +48 -103
  133. package/build/lib/logsink.js.map +1 -1
  134. package/build/lib/main.d.ts +15 -58
  135. package/build/lib/main.d.ts.map +1 -1
  136. package/build/lib/main.js +25 -425
  137. package/build/lib/main.js.map +1 -1
  138. package/build/lib/schema/arg-spec.d.ts +32 -107
  139. package/build/lib/schema/arg-spec.d.ts.map +1 -1
  140. package/build/lib/schema/arg-spec.js +11 -107
  141. package/build/lib/schema/arg-spec.js.map +1 -1
  142. package/build/lib/schema/cli-args-guards.d.ts +34 -0
  143. package/build/lib/schema/cli-args-guards.d.ts.map +1 -0
  144. package/build/lib/schema/cli-args-guards.js +49 -0
  145. package/build/lib/schema/cli-args-guards.js.map +1 -0
  146. package/build/lib/schema/cli-args.d.ts +3 -15
  147. package/build/lib/schema/cli-args.d.ts.map +1 -1
  148. package/build/lib/schema/cli-args.js +17 -107
  149. package/build/lib/schema/cli-args.js.map +1 -1
  150. package/build/lib/schema/cli-transformers.d.ts +15 -12
  151. package/build/lib/schema/cli-transformers.d.ts.map +1 -1
  152. package/build/lib/schema/cli-transformers.js +15 -45
  153. package/build/lib/schema/cli-transformers.js.map +1 -1
  154. package/build/lib/schema/format-errors.d.ts +28 -0
  155. package/build/lib/schema/format-errors.d.ts.map +1 -0
  156. package/build/lib/schema/format-errors.js +29 -0
  157. package/build/lib/schema/format-errors.js.map +1 -0
  158. package/build/lib/schema/index.d.ts +4 -2
  159. package/build/lib/schema/index.d.ts.map +1 -1
  160. package/build/lib/schema/index.js +2 -0
  161. package/build/lib/schema/index.js.map +1 -1
  162. package/build/lib/schema/keywords.d.ts +12 -20
  163. package/build/lib/schema/keywords.d.ts.map +1 -1
  164. package/build/lib/schema/keywords.js +6 -51
  165. package/build/lib/schema/keywords.js.map +1 -1
  166. package/build/lib/schema/schema.d.ts +106 -231
  167. package/build/lib/schema/schema.d.ts.map +1 -1
  168. package/build/lib/schema/schema.js +88 -358
  169. package/build/lib/schema/schema.js.map +1 -1
  170. package/build/lib/utils.d.ts +7 -267
  171. package/build/lib/utils.d.ts.map +1 -1
  172. package/build/lib/utils.js +10 -409
  173. package/build/lib/utils.js.map +1 -1
  174. package/lib/{appium.js → appium.ts} +297 -341
  175. package/lib/bidi-commands.ts +10 -14
  176. package/lib/bootstrap/appium-initializer.ts +212 -0
  177. package/lib/bootstrap/appium-main-runner.ts +172 -0
  178. package/lib/bootstrap/config-file.ts +178 -0
  179. package/lib/bootstrap/grid-v3-register.ts +250 -0
  180. package/lib/bootstrap/init-types.ts +31 -0
  181. package/lib/bootstrap/main-helpers.ts +223 -0
  182. package/lib/bootstrap/node-helpers.ts +180 -0
  183. package/lib/bootstrap/startup-config.ts +143 -0
  184. package/lib/cli/{args.js → args.ts} +45 -56
  185. package/lib/cli/driver-command.ts +122 -0
  186. package/lib/cli/{extension-command.js → extension-command.ts} +827 -906
  187. package/lib/cli/extension.ts +65 -0
  188. package/lib/cli/{parser.js → parser.ts} +93 -116
  189. package/lib/cli/plugin-command.ts +117 -0
  190. package/lib/cli/{setup-command.js → setup-command.ts} +59 -74
  191. package/lib/cli/utils.ts +97 -0
  192. package/lib/{constants.js → constants.ts} +30 -41
  193. package/lib/doctor/{doctor.js → doctor.ts} +82 -92
  194. package/lib/extension/driver-config.ts +165 -0
  195. package/lib/extension/{extension-config.js → extension-config.ts} +291 -405
  196. package/lib/extension/index.ts +143 -0
  197. package/lib/extension/manifest-migrations.ts +57 -0
  198. package/lib/extension/manifest.ts +369 -0
  199. package/lib/extension/{package-changed.js → package-changed.ts} +9 -18
  200. package/lib/extension/plugin-config.ts +62 -0
  201. package/lib/helpers/build.ts +111 -0
  202. package/lib/helpers/capability.ts +171 -0
  203. package/lib/helpers/network.ts +30 -0
  204. package/lib/insecure-features.ts +1 -1
  205. package/lib/inspector-commands.ts +6 -1
  206. package/lib/{logger.js → logger.ts} +1 -2
  207. package/lib/{logsink.js → logsink.ts} +91 -137
  208. package/lib/main.ts +60 -0
  209. package/lib/schema/arg-spec.ts +131 -0
  210. package/lib/schema/cli-args-guards.ts +67 -0
  211. package/lib/schema/cli-args.ts +171 -0
  212. package/lib/schema/cli-transformers.ts +83 -0
  213. package/lib/schema/format-errors.ts +43 -0
  214. package/lib/schema/index.ts +4 -0
  215. package/lib/schema/keywords.ts +96 -0
  216. package/lib/schema/schema.ts +448 -0
  217. package/lib/utils.ts +73 -0
  218. package/package.json +17 -18
  219. package/scripts/autoinstall-extensions.js +3 -0
  220. package/build/lib/config-file.d.ts +0 -100
  221. package/build/lib/config-file.d.ts.map +0 -1
  222. package/build/lib/config-file.js.map +0 -1
  223. package/build/lib/config.d.ts +0 -70
  224. package/build/lib/config.d.ts.map +0 -1
  225. package/build/lib/config.js +0 -390
  226. package/build/lib/config.js.map +0 -1
  227. package/build/lib/grid-register.d.ts +0 -10
  228. package/build/lib/grid-register.d.ts.map +0 -1
  229. package/build/lib/grid-register.js +0 -134
  230. package/build/lib/grid-register.js.map +0 -1
  231. package/lib/cli/driver-command.js +0 -174
  232. package/lib/cli/extension.js +0 -74
  233. package/lib/cli/plugin-command.js +0 -164
  234. package/lib/cli/utils.js +0 -91
  235. package/lib/config-file.js +0 -228
  236. package/lib/config.js +0 -389
  237. package/lib/extension/driver-config.js +0 -245
  238. package/lib/extension/index.js +0 -169
  239. package/lib/extension/manifest-migrations.js +0 -136
  240. package/lib/extension/manifest.js +0 -550
  241. package/lib/extension/plugin-config.js +0 -112
  242. package/lib/grid-register.js +0 -146
  243. package/lib/main.js +0 -545
  244. package/lib/schema/arg-spec.js +0 -229
  245. package/lib/schema/cli-args.js +0 -254
  246. package/lib/schema/cli-transformers.js +0 -113
  247. package/lib/schema/index.js +0 -2
  248. package/lib/schema/keywords.js +0 -136
  249. package/lib/schema/schema.js +0 -725
  250. package/lib/utils.js +0 -512
@@ -5,11 +5,7 @@ import {errors} from '@appium/base-driver';
5
5
  import {BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
6
6
  import WebSocket from 'ws';
7
7
  import os from 'node:os';
8
- import {
9
- isBroadcastIp,
10
- fetchInterfaces,
11
- V4_BROADCAST_IP,
12
- } from './utils';
8
+ import {isBroadcastIp, fetchInterfaces, V4_BROADCAST_IP} from './helpers/network';
13
9
  import type {IncomingMessage} from 'node:http';
14
10
  import type {AppiumDriver} from './appium';
15
11
  import type {
@@ -99,15 +95,6 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
99
95
  }
100
96
  }
101
97
 
102
- function wrapCommandWithPlugins(driver: ExtensionCore, plugins: ExtensionCore[], method: string, params: StringRecord): () => Promise<BiDiResultData> {
103
- const [moduleName, methodName] = method.split('.');
104
- let next = async () => await driver.executeBidiCommand(method, params);
105
- for (const plugin of plugins.filter((p) => p.doesBidiCommandExist(moduleName, methodName))) {
106
- next = ((_next) => async () => await plugin.executeBidiCommand(method, params, _next, driver))(next);
107
- }
108
- return next;
109
- }
110
-
111
98
  /**
112
99
  * @param data
113
100
  * @param driver
@@ -204,6 +191,15 @@ export function cleanupBidiSockets(this: AppiumDriver, sessionId: string): void
204
191
  delete this.bidiProxyClients[sessionId];
205
192
  }
206
193
 
194
+ function wrapCommandWithPlugins(driver: ExtensionCore, plugins: ExtensionCore[], method: string, params: StringRecord): () => Promise<BiDiResultData> {
195
+ const [moduleName, methodName] = method.split('.');
196
+ let next = async () => await driver.executeBidiCommand(method, params);
197
+ for (const plugin of plugins.filter((p) => p.doesBidiCommandExist(moduleName, methodName))) {
198
+ next = ((_next) => async () => await plugin.executeBidiCommand(method, params, _next, driver))(next);
199
+ }
200
+ return next;
201
+ }
202
+
207
203
  // #region Private functions
208
204
 
209
205
  /**
@@ -0,0 +1,212 @@
1
+ import {init as logsinkInit} from '../logsink';
2
+ import {log as logger} from '../logger';
3
+ import {util, env} from '@appium/support';
4
+ import _ from 'lodash';
5
+ import type {DriverOpts} from '@appium/types';
6
+ import {AppiumDriver, type AppiumDriverConstraints} from '../appium';
7
+ import {runExtensionCommand} from '../cli/extension';
8
+ import {runSetupCommand} from '../cli/setup-command';
9
+ import {getParser, ArgParser} from '../cli/parser';
10
+ import {readConfigFile} from './config-file';
11
+ import {getNonDefaultServerArgs, showConfig} from './startup-config';
12
+ import {adjustNodePath, requireDir, showDebugInfo} from './node-helpers';
13
+ import {loadExtensions, type ExtensionConfigs} from '../extension';
14
+ import {SERVER_SUBCOMMAND} from '../constants';
15
+ import {injectAppiumSymlinks} from '../cli/extension-command';
16
+ import {getDefaultsForSchema} from '../schema/schema';
17
+ import {
18
+ isDriverCommandArgs,
19
+ isExtensionCommandArgs,
20
+ isPluginCommandArgs,
21
+ isServerCommandArgs,
22
+ isSetupCommandArgs,
23
+ } from '../schema/cli-args-guards';
24
+ import type {
25
+ Args,
26
+ CliCommand,
27
+ CliCommandServer,
28
+ CliCommandSetupSubcommand,
29
+ CliExtensionSubcommand,
30
+ ParsedArgs,
31
+ } from 'appium/types';
32
+ import {determineAppiumHomeSource, preflightChecks} from './main-helpers';
33
+ import type {InitResult, PreConfigArgs} from './init-types';
34
+
35
+ /**
36
+ * Parses CLI/programmatic args, loads config and extensions, and returns server-ready state or runs extension/setup flows.
37
+ */
38
+ export class AppiumInitializer {
39
+ private throwInsteadOfExit = false;
40
+
41
+ /**
42
+ * Resolves Appium home, loads config, and either returns server-ready state for the server subcommand
43
+ * or performs setup/extension CLI work and returns an empty result.
44
+ *
45
+ * @param args - Optional programmatic args; when omitted, parses `process.argv`
46
+ */
47
+ async init<
48
+ Cmd extends CliCommand = CliCommandServer,
49
+ SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand | void = void,
50
+ >(args?: Args<Cmd, SubCmd>): Promise<InitResult<Cmd>> {
51
+ this.throwInsteadOfExit = false;
52
+
53
+ const appiumHome = args?.appiumHome ?? (await env.resolveAppiumHome());
54
+ const appiumHomeSourceName = determineAppiumHomeSource(args?.appiumHome);
55
+ await requireDir(appiumHome, false, appiumHomeSourceName);
56
+
57
+ adjustNodePath();
58
+
59
+ const {driverConfig, pluginConfig} = await loadExtensions(appiumHome);
60
+
61
+ const {preConfigArgs, throwInsteadOfExit} = await this.parsePreConfigArgs(args);
62
+ this.throwInsteadOfExit = throwInsteadOfExit;
63
+
64
+ const configResult = await readConfigFile(preConfigArgs.configFile);
65
+ this.assertConfigFileOk(configResult);
66
+
67
+ if (isServerCommandArgs(preConfigArgs)) {
68
+ return this.finishServerInit(
69
+ preConfigArgs,
70
+ configResult,
71
+ driverConfig,
72
+ pluginConfig,
73
+ appiumHome,
74
+ appiumHomeSourceName,
75
+ );
76
+ }
77
+
78
+ if (isSetupCommandArgs(preConfigArgs)) {
79
+ await runSetupCommand(preConfigArgs, driverConfig, pluginConfig);
80
+ return {} as InitResult<Cmd>;
81
+ }
82
+
83
+ await requireDir(appiumHome, true, appiumHomeSourceName);
84
+ await this.runExtensionCliIfNeeded(preConfigArgs, driverConfig, pluginConfig);
85
+ return {} as InitResult<Cmd>;
86
+ }
87
+
88
+ private async parsePreConfigArgs<
89
+ Cmd extends CliCommand,
90
+ SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand | void,
91
+ >(args?: Args<Cmd, SubCmd>): Promise<{preConfigArgs: PreConfigArgs; throwInsteadOfExit: boolean}> {
92
+ const parser = await getParser();
93
+ let throwInsteadOfExit = false;
94
+
95
+ if (args) {
96
+ if (args.throwInsteadOfExit) {
97
+ throwInsteadOfExit = true;
98
+ delete args.throwInsteadOfExit;
99
+ }
100
+ const preConfigArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND} as PreConfigArgs;
101
+ ArgParser.normalizeServerArgs(preConfigArgs);
102
+ return {preConfigArgs, throwInsteadOfExit};
103
+ }
104
+
105
+ return {preConfigArgs: parser.parseArgs() as PreConfigArgs, throwInsteadOfExit};
106
+ }
107
+
108
+ private assertConfigFileOk(configResult: Awaited<ReturnType<typeof readConfigFile>>): void {
109
+ if (!_.isEmpty(configResult.errors)) {
110
+ throw new Error(
111
+ `Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`,
112
+ );
113
+ }
114
+ }
115
+
116
+ private async finishServerInit<Cmd extends CliCommand>(
117
+ preConfigArgs: PreConfigArgs,
118
+ configResult: Awaited<ReturnType<typeof readConfigFile>>,
119
+ driverConfig: ExtensionConfigs['driverConfig'],
120
+ pluginConfig: ExtensionConfigs['pluginConfig'],
121
+ appiumHome: string,
122
+ appiumHomeSourceName: string,
123
+ ): Promise<InitResult<Cmd>> {
124
+ const defaults = getDefaultsForSchema(false);
125
+ const serverArgs = _.defaultsDeep(
126
+ {},
127
+ preConfigArgs,
128
+ configResult.config?.server,
129
+ defaults,
130
+ ) as ParsedArgs<CliCommandServer>;
131
+
132
+ if (preConfigArgs.showConfig) {
133
+ showConfig(getNonDefaultServerArgs(preConfigArgs as Args), configResult, defaults, serverArgs);
134
+ return {} as InitResult<Cmd>;
135
+ }
136
+
137
+ if (preConfigArgs.showDebugInfo) {
138
+ await showDebugInfo({
139
+ driverConfig,
140
+ pluginConfig,
141
+ appiumHome,
142
+ });
143
+ return {} as InitResult<Cmd>;
144
+ }
145
+
146
+ await logsinkInit(serverArgs);
147
+ await this.applyLogFilters(serverArgs);
148
+
149
+ if (!serverArgs.noPermsCheck) {
150
+ await requireDir(appiumHome, true, appiumHomeSourceName);
151
+ }
152
+
153
+ const appiumDriver = new AppiumDriver(serverArgs as DriverOpts<AppiumDriverConstraints>);
154
+ appiumDriver.driverConfig = driverConfig;
155
+ await preflightChecks(serverArgs, this.throwInsteadOfExit);
156
+
157
+ return {
158
+ appiumDriver,
159
+ parsedArgs: serverArgs,
160
+ driverConfig,
161
+ pluginConfig,
162
+ appiumHome,
163
+ } as InitResult<Cmd>;
164
+ }
165
+
166
+ private async applyLogFilters(serverArgs: ParsedArgs<CliCommandServer>): Promise<void> {
167
+ if (!serverArgs.logFilters) {
168
+ return;
169
+ }
170
+ const {issues, rules} = await logger.unwrap().loadSecureValuesPreprocessingRules(serverArgs.logFilters);
171
+ const argToLog = _.truncate(JSON.stringify(serverArgs.logFilters), {
172
+ length: 150,
173
+ });
174
+ if (!_.isEmpty(issues)) {
175
+ throw new Error(
176
+ `The log filtering rules config ${argToLog} has issues: ` + JSON.stringify(issues, null, 2),
177
+ );
178
+ }
179
+ if (_.isEmpty(rules)) {
180
+ logger.warn(
181
+ `Found no log filtering rules in the ${argToLog} config. ` + `Is that expected?`,
182
+ );
183
+ } else {
184
+ logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)}`);
185
+ }
186
+ }
187
+
188
+ private async runExtensionCliIfNeeded(
189
+ preConfigArgs: PreConfigArgs,
190
+ driverConfig: ExtensionConfigs['driverConfig'],
191
+ pluginConfig: ExtensionConfigs['pluginConfig'],
192
+ ): Promise<void> {
193
+ if (!isExtensionCommandArgs(preConfigArgs)) {
194
+ return;
195
+ }
196
+ if (isDriverCommandArgs(preConfigArgs)) {
197
+ await runExtensionCommand(preConfigArgs, driverConfig);
198
+ }
199
+ if (isPluginCommandArgs(preConfigArgs)) {
200
+ await runExtensionCommand(preConfigArgs, pluginConfig);
201
+ }
202
+
203
+ if (isDriverCommandArgs(preConfigArgs) || isPluginCommandArgs(preConfigArgs)) {
204
+ const cmd = isDriverCommandArgs(preConfigArgs)
205
+ ? preConfigArgs.driverCommand
206
+ : preConfigArgs.pluginCommand;
207
+ if (cmd === 'install') {
208
+ await injectAppiumSymlinks(driverConfig, pluginConfig, logger);
209
+ }
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,172 @@
1
+ import {log as logger} from '../logger';
2
+ import _ from 'lodash';
3
+ import type {AppiumServer} from '@appium/types';
4
+ import {getActivePlugins, getActiveDrivers} from '../extension';
5
+ import registerNode from './grid-v3-register';
6
+ import {
7
+ determineAppiumHomeSource,
8
+ logStartupInfo,
9
+ buildServerOpts,
10
+ createAppiumServer,
11
+ logServerAddress,
12
+ } from './main-helpers';
13
+ import type {InitResult, ServerInitData} from './init-types';
14
+ import type {
15
+ Args,
16
+ CliCommand,
17
+ CliCommandServer,
18
+ CliCommandSetupSubcommand,
19
+ CliExtensionSubcommand,
20
+ } from 'appium/types';
21
+ import type {ServerOpts} from '@appium/base-driver';
22
+ import net from 'node:net';
23
+
24
+ const MAX_SERVER_PROCESS_LISTENERS = 100;
25
+
26
+ /**
27
+ * Starts the Appium HTTP server after {@link AppiumInitializer.init}: loads drivers/plugins, binds, grid register, signals.
28
+ */
29
+ export class AppiumMainRunner {
30
+ /**
31
+ * For server init: builds listeners, registers with Grid 3 if configured, and returns the server.
32
+ * For non-server commands, `initResult` is empty and this resolves to `undefined`.
33
+ *
34
+ * @param initResult - Output of {@link AppiumInitializer.init}
35
+ * @param args - Original args (used to describe `appiumHome` source in logs)
36
+ */
37
+ async run<
38
+ Cmd extends CliCommand = CliCommandServer,
39
+ SubCmd extends CliExtensionSubcommand | CliCommandSetupSubcommand | void = void,
40
+ >(initResult: InitResult<Cmd>, args?: Args<Cmd, SubCmd>): Promise<Cmd extends CliCommandServer ? AppiumServer : void> {
41
+ if (_.isEmpty(initResult)) {
42
+ return undefined as Cmd extends CliCommandServer ? AppiumServer : void;
43
+ }
44
+
45
+ const {appiumDriver, pluginConfig, driverConfig, parsedArgs, appiumHome} = initResult as ServerInitData;
46
+
47
+ const pluginClasses = await getActivePlugins(
48
+ pluginConfig,
49
+ parsedArgs.pluginsImportChunkSize,
50
+ parsedArgs.usePlugins,
51
+ );
52
+ for (const [pluginClass, name] of pluginClasses) {
53
+ appiumDriver.pluginClasses.set(pluginClass, name);
54
+ }
55
+
56
+ await logStartupInfo(parsedArgs);
57
+
58
+ appiumDriver.configureGlobalFeatures();
59
+
60
+ const appiumHomeSourceName = determineAppiumHomeSource(args?.appiumHome);
61
+ logger.debug(`The ${appiumHomeSourceName}: ${appiumHome}`);
62
+
63
+ const driverClasses = await getActiveDrivers(
64
+ driverConfig,
65
+ parsedArgs.driversImportChunkSize,
66
+ parsedArgs.useDrivers,
67
+ );
68
+ const {serverOpts, normalizedBasePath} = buildServerOpts(
69
+ appiumDriver,
70
+ parsedArgs,
71
+ driverClasses,
72
+ pluginClasses,
73
+ );
74
+
75
+ const server = await this.startHttpServer(serverOpts, appiumDriver, normalizedBasePath);
76
+ if (!server) {
77
+ return undefined as Cmd extends CliCommandServer ? AppiumServer : void;
78
+ }
79
+
80
+ this.warnIfCorsEnabled(parsedArgs);
81
+ appiumDriver.server = server;
82
+
83
+ await this.registerGridOrClose(server, parsedArgs, normalizedBasePath);
84
+ this.attachSignalHandlers(appiumDriver, server);
85
+ this.logListeningUrl(server, parsedArgs, normalizedBasePath);
86
+
87
+ driverConfig.print();
88
+ pluginConfig.print([...pluginClasses.values()]);
89
+
90
+ return server as Cmd extends CliCommandServer ? AppiumServer : void;
91
+ }
92
+
93
+ private async startHttpServer(
94
+ serverOpts: ServerOpts,
95
+ appiumDriver: ServerInitData['appiumDriver'],
96
+ normalizedBasePath: string,
97
+ ): Promise<AppiumServer | undefined> {
98
+ try {
99
+ return await createAppiumServer(serverOpts, appiumDriver, normalizedBasePath);
100
+ } catch (err: unknown) {
101
+ const message = err instanceof Error ? err.message : String(err);
102
+ const stack = err instanceof Error ? err.stack : undefined;
103
+ logger.error(
104
+ `Could not configure Appium server. It's possible that a driver or plugin tried ` +
105
+ `to update the server and failed. Original error: ${message}`,
106
+ );
107
+ logger.debug(stack);
108
+ process.exit(1);
109
+ return undefined;
110
+ }
111
+ }
112
+
113
+ private warnIfCorsEnabled(parsedArgs: ServerInitData['parsedArgs']): void {
114
+ if (parsedArgs.allowCors) {
115
+ logger.warn(
116
+ 'You have enabled CORS requests from any host. Be careful not ' +
117
+ 'to visit sites which could maliciously try to start Appium ' +
118
+ 'sessions on your machine',
119
+ );
120
+ }
121
+ }
122
+
123
+ private async registerGridOrClose(
124
+ server: AppiumServer,
125
+ parsedArgs: ServerInitData['parsedArgs'],
126
+ normalizedBasePath: string,
127
+ ): Promise<void> {
128
+ try {
129
+ if (parsedArgs.nodeconfig) {
130
+ await registerNode(
131
+ parsedArgs.nodeconfig,
132
+ parsedArgs.address,
133
+ parsedArgs.port,
134
+ normalizedBasePath,
135
+ );
136
+ }
137
+ } catch (err: unknown) {
138
+ await server.close();
139
+ throw err;
140
+ }
141
+ }
142
+
143
+ private attachSignalHandlers(
144
+ appiumDriver: ServerInitData['appiumDriver'],
145
+ server: AppiumServer,
146
+ ): void {
147
+ process.setMaxListeners(MAX_SERVER_PROCESS_LISTENERS);
148
+ for (const signal of ['SIGINT', 'SIGTERM'] as const) {
149
+ process.once(signal, async function onSignal() {
150
+ logger.info(`Received ${signal} - shutting down`);
151
+ try {
152
+ await appiumDriver.shutdown(`The process has received ${signal} signal`);
153
+ await server.close();
154
+ process.exit(0);
155
+ } catch (e: unknown) {
156
+ logger.warn(e);
157
+ process.exit(1);
158
+ }
159
+ });
160
+ }
161
+ }
162
+
163
+ private logListeningUrl(
164
+ server: AppiumServer,
165
+ parsedArgs: ServerInitData['parsedArgs'],
166
+ normalizedBasePath: string,
167
+ ): void {
168
+ const protocol = server.isSecure() ? 'https' : 'http';
169
+ const address = net.isIPv6(parsedArgs.address) ? `[${parsedArgs.address}]` : parsedArgs.address;
170
+ logServerAddress(`${protocol}://${address}:${parsedArgs.port}${normalizedBasePath}`);
171
+ }
172
+ }
@@ -0,0 +1,178 @@
1
+ import type {IOutputError} from '@sidvind/better-ajv-errors';
2
+ import type {ErrorObject, SchemaObject} from 'ajv';
3
+ import {lilconfig, type LoaderSync, type LilconfigResult} from 'lilconfig';
4
+ import _ from 'lodash';
5
+ import * as yaml from 'yaml';
6
+ import type {AppiumConfig, NormalizedAppiumConfig} from '@appium/types';
7
+ import {getSchema, validate} from '../schema/schema';
8
+ import {formatErrors} from '../schema/format-errors';
9
+
10
+ /**
11
+ * A cache of the raw config file (a JSON string) at a filepath.
12
+ * This is used for better error reporting.
13
+ * Note that config files needn't be JSON, but it helps if they are.
14
+ */
15
+ const rawConfig = new Map<string, string>();
16
+
17
+ /**
18
+ * Result of calling {@link readConfigFile}.
19
+ */
20
+ export interface ReadConfigFileResult {
21
+ errors?: ErrorObject[];
22
+ filepath?: string;
23
+ isEmpty?: boolean;
24
+ config?: NormalizedAppiumConfig;
25
+ reason?: string | IOutputError[];
26
+ }
27
+
28
+ /**
29
+ * Options for {@link readConfigFile}.
30
+ */
31
+ export interface ReadConfigFileOptions {
32
+ pretty?: boolean;
33
+ }
34
+
35
+ /**
36
+ * This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
37
+ */
38
+ type LilconfigAsyncSearcher = ReturnType<typeof lilconfig>;
39
+
40
+ /**
41
+ * Given an optional path, read a config file. Validates the config file.
42
+ *
43
+ * Call {@link validate} if you already have a config object.
44
+ * @public
45
+ * @param filepath - Explicit config path; when omitted, searches with lilconfig
46
+ * @param opts - e.g. `pretty` for formatted validation errors
47
+ * @returns Contains config and filepath, if found, and any errors
48
+ */
49
+ export async function readConfigFile(
50
+ filepath?: string,
51
+ opts: ReadConfigFileOptions = {}
52
+ ): Promise<ReadConfigFileResult> {
53
+ const lc = lilconfig('appium', {
54
+ loaders: {
55
+ '.yaml': yamlLoader as LoaderSync,
56
+ '.yml': yamlLoader as LoaderSync,
57
+ '.json': jsonLoader as LoaderSync,
58
+ noExt: jsonLoader as LoaderSync,
59
+ },
60
+ packageProp: 'appiumConfig',
61
+ });
62
+
63
+ const result = filepath ? await loadConfigFile(lc, filepath) : await searchConfigFile(lc);
64
+
65
+ if (result?.filepath && !result?.isEmpty) {
66
+ const {pretty = true} = opts;
67
+ try {
68
+ let configResult: ReadConfigFileResult;
69
+ const errors = validate(result.config) as ErrorObject[];
70
+ if (_.isEmpty(errors)) {
71
+ configResult = {...result, errors};
72
+ } else {
73
+ const reason = formatErrors(errors, result.config as Record<string, unknown>, {
74
+ json: rawConfig.get(result.filepath),
75
+ pretty,
76
+ });
77
+ configResult = reason ? {...result, errors, reason} : {...result, errors};
78
+ }
79
+
80
+ // normalize (to camel case) all top-level property names of the config file
81
+ configResult.config = normalizeConfig(configResult.config as AppiumConfig);
82
+
83
+ return configResult;
84
+ } finally {
85
+ // clean up the raw config file cache, which is only kept to better report errors.
86
+ rawConfig.delete(result.filepath);
87
+ }
88
+ }
89
+ return result ?? {};
90
+ }
91
+
92
+ /**
93
+ * Converts schema property names to either the `appiumCliDest` value, if any, or camelCase.
94
+ *
95
+ * @param config - Raw config object as parsed from file
96
+ * @returns New object with camel-cased keys (or CLI `dest` keys).
97
+ */
98
+ export function normalizeConfig(config: AppiumConfig): NormalizedAppiumConfig {
99
+ const schema = getSchema();
100
+
101
+ const isSchemaTypeObject = (schemaObj: SchemaObject | Record<string, unknown> | undefined): boolean =>
102
+ Boolean((schemaObj as SchemaObject | undefined)?.properties || (schemaObj as SchemaObject | undefined)?.type === 'object');
103
+
104
+ const normalize = (rootConfig: AppiumConfig, section?: string): Record<string, unknown> => {
105
+ const obj = _.isUndefined(section)
106
+ ? rootConfig
107
+ : (_.get(rootConfig, section, rootConfig) as Record<string, unknown>);
108
+
109
+ const mappedObj = _.mapKeys(obj, (_v, prop) =>
110
+ _.get(schema, `properties.server.properties[${prop}].appiumCliDest`, _.camelCase(prop))
111
+ );
112
+
113
+ return _.mapValues(mappedObj, (value, property) => {
114
+ const nextSection = section ? `${section}.${property}` : property;
115
+ return isSchemaTypeObject((schema as any).properties?.[property])
116
+ ? normalize(rootConfig, nextSection)
117
+ : value;
118
+ });
119
+ };
120
+
121
+ return normalize(config) as NormalizedAppiumConfig;
122
+ }
123
+
124
+ /**
125
+ * lilconfig loader to handle `.yaml` files
126
+ */
127
+ function yamlLoader(filepath: string, content: string): unknown {
128
+ try {
129
+ return yaml.parse(content);
130
+ } catch (e) {
131
+ throw new Error(
132
+ `The YAML config at '${filepath}' cannot be loaded. Original error: ${(e as Error).message}`
133
+ );
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Custom JSON loader that caches the raw config file (for use with `better-ajv-errors`).
139
+ * If it weren't for this cache, this would be unnecessary.
140
+ */
141
+ function jsonLoader(filepath: string, content: string): unknown {
142
+ rawConfig.set(filepath, content);
143
+ try {
144
+ return JSON.parse(content);
145
+ } catch (e) {
146
+ rawConfig.delete(filepath);
147
+ throw new Error(
148
+ `The JSON config at '${filepath}' cannot be loaded. Original error: ${(e as Error).message}`
149
+ );
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Loads a config file from an explicit path
155
+ */
156
+ async function loadConfigFile(lc: LilconfigAsyncSearcher, filepath: string): Promise<LilconfigResult> {
157
+ try {
158
+ // removing "await" will cause any rejection to _not_ be caught in this block!
159
+ return await lc.load(filepath);
160
+ } catch (err) {
161
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
162
+ (err as NodeJS.ErrnoException).message = `Config file not found at user-provided path: ${filepath}`;
163
+ throw err;
164
+ } else if (err instanceof SyntaxError) {
165
+ // generally invalid JSON
166
+ err.message = `Config file at user-provided path ${filepath} is invalid:\n${err.message}`;
167
+ throw err;
168
+ }
169
+ throw err;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Searches for a config file
175
+ */
176
+ async function searchConfigFile(lc: LilconfigAsyncSearcher): Promise<LilconfigResult> {
177
+ return await lc.search();
178
+ }