appium 3.3.0 → 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 (196) 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} +9 -26
  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/{grid-register.js → bootstrap/grid-v3-register.js} +28 -13
  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.map +1 -1
  41. package/build/lib/cli/args.js +9 -9
  42. package/build/lib/cli/args.js.map +1 -1
  43. package/build/lib/cli/extension-command.d.ts +95 -95
  44. package/build/lib/cli/extension-command.d.ts.map +1 -1
  45. package/build/lib/cli/extension-command.js +18 -18
  46. package/build/lib/cli/extension-command.js.map +1 -1
  47. package/build/lib/cli/extension.d.ts +1 -1
  48. package/build/lib/cli/extension.d.ts.map +1 -1
  49. package/build/lib/cli/extension.js +5 -5
  50. package/build/lib/cli/extension.js.map +1 -1
  51. package/build/lib/cli/parser.d.ts +8 -8
  52. package/build/lib/cli/parser.d.ts.map +1 -1
  53. package/build/lib/cli/parser.js +49 -49
  54. package/build/lib/cli/parser.js.map +1 -1
  55. package/build/lib/cli/setup-command.js +6 -6
  56. package/build/lib/cli/setup-command.js.map +1 -1
  57. package/build/lib/cli/utils.d.ts +17 -17
  58. package/build/lib/cli/utils.d.ts.map +1 -1
  59. package/build/lib/cli/utils.js +29 -29
  60. package/build/lib/cli/utils.js.map +1 -1
  61. package/build/lib/doctor/doctor.d.ts +2 -2
  62. package/build/lib/doctor/doctor.d.ts.map +1 -1
  63. package/build/lib/doctor/doctor.js +6 -6
  64. package/build/lib/doctor/doctor.js.map +1 -1
  65. package/build/lib/extension/driver-config.d.ts +18 -77
  66. package/build/lib/extension/driver-config.d.ts.map +1 -1
  67. package/build/lib/extension/driver-config.js +37 -125
  68. package/build/lib/extension/driver-config.js.map +1 -1
  69. package/build/lib/extension/extension-config.d.ts +103 -210
  70. package/build/lib/extension/extension-config.d.ts.map +1 -1
  71. package/build/lib/extension/extension-config.js +180 -342
  72. package/build/lib/extension/extension-config.js.map +1 -1
  73. package/build/lib/extension/index.d.ts +12 -29
  74. package/build/lib/extension/index.d.ts.map +1 -1
  75. package/build/lib/extension/index.js +33 -75
  76. package/build/lib/extension/index.js.map +1 -1
  77. package/build/lib/extension/manifest-migrations.d.ts +3 -20
  78. package/build/lib/extension/manifest-migrations.d.ts.map +1 -1
  79. package/build/lib/extension/manifest-migrations.js +20 -101
  80. package/build/lib/extension/manifest-migrations.js.map +1 -1
  81. package/build/lib/extension/manifest.d.ts +61 -107
  82. package/build/lib/extension/manifest.d.ts.map +1 -1
  83. package/build/lib/extension/manifest.js +181 -356
  84. package/build/lib/extension/manifest.js.map +1 -1
  85. package/build/lib/extension/package-changed.d.ts +1 -3
  86. package/build/lib/extension/package-changed.d.ts.map +1 -1
  87. package/build/lib/extension/package-changed.js +8 -15
  88. package/build/lib/extension/package-changed.js.map +1 -1
  89. package/build/lib/extension/plugin-config.d.ts +10 -52
  90. package/build/lib/extension/plugin-config.d.ts.map +1 -1
  91. package/build/lib/extension/plugin-config.js +11 -63
  92. package/build/lib/extension/plugin-config.js.map +1 -1
  93. package/build/lib/helpers/build.d.ts +22 -0
  94. package/build/lib/helpers/build.d.ts.map +1 -0
  95. package/build/lib/helpers/build.js +109 -0
  96. package/build/lib/helpers/build.js.map +1 -0
  97. package/build/lib/helpers/capability.d.ts +38 -0
  98. package/build/lib/helpers/capability.d.ts.map +1 -0
  99. package/build/lib/helpers/capability.js +128 -0
  100. package/build/lib/helpers/capability.js.map +1 -0
  101. package/build/lib/helpers/network.d.ts +14 -0
  102. package/build/lib/helpers/network.d.ts.map +1 -0
  103. package/build/lib/helpers/network.js +35 -0
  104. package/build/lib/helpers/network.js.map +1 -0
  105. package/build/lib/insecure-features.js +6 -6
  106. package/build/lib/insecure-features.js.map +1 -1
  107. package/build/lib/inspector-commands.d.ts +6 -0
  108. package/build/lib/inspector-commands.d.ts.map +1 -1
  109. package/build/lib/inspector-commands.js +6 -0
  110. package/build/lib/inspector-commands.js.map +1 -1
  111. package/build/lib/logger.d.ts +2 -3
  112. package/build/lib/logger.d.ts.map +1 -1
  113. package/build/lib/logger.js +2 -3
  114. package/build/lib/logger.js.map +1 -1
  115. package/build/lib/main.d.ts +15 -58
  116. package/build/lib/main.d.ts.map +1 -1
  117. package/build/lib/main.js +25 -425
  118. package/build/lib/main.js.map +1 -1
  119. package/build/lib/schema/cli-args-guards.d.ts +34 -0
  120. package/build/lib/schema/cli-args-guards.d.ts.map +1 -0
  121. package/build/lib/schema/cli-args-guards.js +49 -0
  122. package/build/lib/schema/cli-args-guards.js.map +1 -0
  123. package/build/lib/schema/cli-args.js +2 -2
  124. package/build/lib/schema/cli-args.js.map +1 -1
  125. package/build/lib/schema/format-errors.d.ts +28 -0
  126. package/build/lib/schema/format-errors.d.ts.map +1 -0
  127. package/build/lib/schema/format-errors.js +29 -0
  128. package/build/lib/schema/format-errors.js.map +1 -0
  129. package/build/lib/schema/index.d.ts +2 -0
  130. package/build/lib/schema/index.d.ts.map +1 -1
  131. package/build/lib/schema/index.js +2 -0
  132. package/build/lib/schema/index.js.map +1 -1
  133. package/build/lib/schema/schema.d.ts +15 -15
  134. package/build/lib/schema/schema.d.ts.map +1 -1
  135. package/build/lib/schema/schema.js +37 -37
  136. package/build/lib/schema/schema.js.map +1 -1
  137. package/build/lib/utils.d.ts +0 -81
  138. package/build/lib/utils.d.ts.map +1 -1
  139. package/build/lib/utils.js +1 -248
  140. package/build/lib/utils.js.map +1 -1
  141. package/lib/{appium.js → appium.ts} +297 -341
  142. package/lib/bidi-commands.ts +10 -14
  143. package/lib/bootstrap/appium-initializer.ts +212 -0
  144. package/lib/bootstrap/appium-main-runner.ts +172 -0
  145. package/lib/{config-file.ts → bootstrap/config-file.ts} +29 -63
  146. package/lib/{grid-register.ts → bootstrap/grid-v3-register.ts} +35 -35
  147. package/lib/bootstrap/init-types.ts +31 -0
  148. package/lib/bootstrap/main-helpers.ts +223 -0
  149. package/lib/bootstrap/node-helpers.ts +180 -0
  150. package/lib/bootstrap/startup-config.ts +143 -0
  151. package/lib/cli/args.ts +10 -10
  152. package/lib/cli/extension-command.ts +132 -132
  153. package/lib/cli/extension.ts +7 -7
  154. package/lib/cli/parser.ts +50 -50
  155. package/lib/cli/setup-command.ts +2 -2
  156. package/lib/cli/utils.ts +33 -33
  157. package/lib/doctor/doctor.ts +8 -8
  158. package/lib/extension/driver-config.ts +165 -0
  159. package/lib/extension/{extension-config.js → extension-config.ts} +291 -405
  160. package/lib/extension/index.ts +143 -0
  161. package/lib/extension/manifest-migrations.ts +57 -0
  162. package/lib/extension/manifest.ts +369 -0
  163. package/lib/extension/{package-changed.js → package-changed.ts} +9 -18
  164. package/lib/extension/plugin-config.ts +62 -0
  165. package/lib/helpers/build.ts +111 -0
  166. package/lib/helpers/capability.ts +171 -0
  167. package/lib/helpers/network.ts +30 -0
  168. package/lib/insecure-features.ts +1 -1
  169. package/lib/inspector-commands.ts +6 -1
  170. package/lib/{logger.js → logger.ts} +1 -2
  171. package/lib/main.ts +60 -0
  172. package/lib/schema/cli-args-guards.ts +67 -0
  173. package/lib/schema/cli-args.ts +1 -1
  174. package/lib/schema/format-errors.ts +43 -0
  175. package/lib/schema/index.ts +2 -0
  176. package/lib/schema/schema.ts +51 -52
  177. package/lib/utils.ts +0 -331
  178. package/package.json +12 -13
  179. package/scripts/autoinstall-extensions.js +3 -0
  180. package/build/lib/config-file.d.ts +0 -57
  181. package/build/lib/config-file.d.ts.map +0 -1
  182. package/build/lib/config-file.js.map +0 -1
  183. package/build/lib/config.d.ts +0 -68
  184. package/build/lib/config.d.ts.map +0 -1
  185. package/build/lib/config.js +0 -358
  186. package/build/lib/config.js.map +0 -1
  187. package/build/lib/grid-register.d.ts +0 -35
  188. package/build/lib/grid-register.d.ts.map +0 -1
  189. package/build/lib/grid-register.js.map +0 -1
  190. package/lib/config.ts +0 -377
  191. package/lib/extension/driver-config.js +0 -245
  192. package/lib/extension/index.js +0 -169
  193. package/lib/extension/manifest-migrations.js +0 -136
  194. package/lib/extension/manifest.js +0 -550
  195. package/lib/extension/plugin-config.js +0 -112
  196. package/lib/main.js +0 -545
@@ -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
+ }
@@ -1,48 +1,49 @@
1
- import betterAjvErrors, {type IOutputError} from '@sidvind/better-ajv-errors';
1
+ import type {IOutputError} from '@sidvind/better-ajv-errors';
2
2
  import type {ErrorObject, SchemaObject} from 'ajv';
3
3
  import {lilconfig, type LoaderSync, type LilconfigResult} from 'lilconfig';
4
4
  import _ from 'lodash';
5
5
  import * as yaml from 'yaml';
6
6
  import type {AppiumConfig, NormalizedAppiumConfig} from '@appium/types';
7
- import {getSchema, validate} from './schema/schema';
7
+ import {getSchema, validate} from '../schema/schema';
8
+ import {formatErrors} from '../schema/format-errors';
8
9
 
9
10
  /**
10
11
  * A cache of the raw config file (a JSON string) at a filepath.
11
12
  * This is used for better error reporting.
12
13
  * Note that config files needn't be JSON, but it helps if they are.
13
14
  */
14
- const rawConfig = new Map<string, RawJson>();
15
+ const rawConfig = new Map<string, string>();
15
16
 
16
17
  /**
17
- * Given an array of errors and the result of loading a config file, generate a
18
- * helpful string for the user.
19
- *
20
- * - If `opts` contains a `json` property, this should be the original JSON
21
- * _string_ of the config file. This is only applicable if the config file
22
- * was in JSON format. If present, it will associate line numbers with errors.
23
- * - If `errors` happens to be empty, this will throw.
24
- *
25
- * @throws {TypeError} If `errors` is empty
18
+ * Result of calling {@link readConfigFile}.
26
19
  */
27
- export function formatErrors(
28
- errors: ErrorObject[] = [],
29
- config: ReadConfigFileResult['config'] | Record<string, unknown> | string = {},
30
- opts: FormatConfigErrorsOptions = {}
31
- ): string | IOutputError[] {
32
- if (errors && !errors.length) {
33
- throw new TypeError('Array of errors must be non-empty');
34
- }
35
- return betterAjvErrors(getSchema(opts.schemaId), config, errors, {
36
- json: opts.json,
37
- format: opts.pretty === false ? 'js' : 'cli',
38
- });
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;
39
33
  }
40
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
+
41
40
  /**
42
41
  * Given an optional path, read a config file. Validates the config file.
43
42
  *
44
43
  * Call {@link validate} if you already have a config object.
45
44
  * @public
45
+ * @param filepath - Explicit config path; when omitted, searches with lilconfig
46
+ * @param opts - e.g. `pretty` for formatted validation errors
46
47
  * @returns Contains config and filepath, if found, and any errors
47
48
  */
48
49
  export async function readConfigFile(
@@ -89,8 +90,10 @@ export async function readConfigFile(
89
90
  }
90
91
 
91
92
  /**
92
- * Convert schema property names to either a) the value of the `appiumCliDest` property, if any; or b) camel-case
93
- * @returns New object with camel-cased keys (or `dest` keys).
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).
94
97
  */
95
98
  export function normalizeConfig(config: AppiumConfig): NormalizedAppiumConfig {
96
99
  const schema = getSchema();
@@ -173,40 +176,3 @@ async function loadConfigFile(lc: LilconfigAsyncSearcher, filepath: string): Pro
173
176
  async function searchConfigFile(lc: LilconfigAsyncSearcher): Promise<LilconfigResult> {
174
177
  return await lc.search();
175
178
  }
176
-
177
- /**
178
- * Result of calling {@link readConfigFile}.
179
- */
180
- export interface ReadConfigFileResult {
181
- errors?: ErrorObject[];
182
- filepath?: string;
183
- isEmpty?: boolean;
184
- config?: NormalizedAppiumConfig;
185
- reason?: string | IOutputError[];
186
- }
187
-
188
- /**
189
- * Options for {@link readConfigFile}.
190
- */
191
- export interface ReadConfigFileOptions {
192
- pretty?: boolean;
193
- }
194
-
195
- /**
196
- * The string should be a raw JSON string.
197
- */
198
- export type RawJson = string;
199
-
200
- /**
201
- * Options for {@link formatErrors}.
202
- */
203
- export interface FormatConfigErrorsOptions {
204
- json?: RawJson;
205
- pretty?: boolean;
206
- schemaId?: string;
207
- }
208
-
209
- /**
210
- * This is an `AsyncSearcher` which is inexplicably _not_ exported by the `lilconfig` type definition.
211
- */
212
- type LilconfigAsyncSearcher = ReturnType<typeof lilconfig>;
@@ -2,7 +2,7 @@ import axios from 'axios';
2
2
  import {fs} from '@appium/support';
3
3
  import type {StringRecord} from '@appium/types';
4
4
  import _ from 'lodash';
5
- import logger from './logger';
5
+ import {log as logger} from '../logger';
6
6
 
7
7
  /**
8
8
  * Selenium **Grid 3** (legacy hub) node integration.
@@ -18,6 +18,40 @@ import logger from './logger';
18
18
  const GRID_V3_REGISTER_PATH = '/grid/register';
19
19
  const GRID_V3_PROXY_API_PATH = '/grid/api/proxy';
20
20
 
21
+ interface Grid3HubConfiguration {
22
+ hubProtocol?: string;
23
+ hubHost?: string;
24
+ hubPort?: number;
25
+ url?: string;
26
+ host?: string;
27
+ port?: number;
28
+ id?: string;
29
+ register?: boolean;
30
+ registerCycle?: number;
31
+ }
32
+
33
+ interface Grid3NodeConfig extends StringRecord {
34
+ configuration?: Grid3HubConfiguration;
35
+ capabilities?: unknown;
36
+ }
37
+
38
+ interface Grid3ProxyApiResponse {
39
+ success: boolean;
40
+ msg?: string;
41
+ }
42
+
43
+ export default async function registerNode(
44
+ data: string,
45
+ addr: string,
46
+ port: number,
47
+ basePath: string
48
+ ): Promise<void>;
49
+ export default async function registerNode(
50
+ data: Grid3NodeConfig,
51
+ addr?: string,
52
+ port?: number,
53
+ basePath?: string
54
+ ): Promise<void>;
21
55
  /**
22
56
  * Registers this server as a node with a **Selenium Grid 3** hub.
23
57
  *
@@ -33,18 +67,6 @@ const GRID_V3_PROXY_API_PATH = '/grid/api/proxy';
33
67
  * @param port - Bind to this port
34
68
  * @param basePath - Base path for the Appium server (used in the node URL sent to the hub; may be `''`)
35
69
  */
36
- export default async function registerNode(
37
- data: string,
38
- addr: string,
39
- port: number,
40
- basePath: string
41
- ): Promise<void>;
42
- export default async function registerNode(
43
- data: Grid3NodeConfig,
44
- addr?: string,
45
- port?: number,
46
- basePath?: string
47
- ): Promise<void>;
48
70
  export default async function registerNode(
49
71
  data: string | Grid3NodeConfig,
50
72
  addr?: string,
@@ -226,25 +248,3 @@ async function isAlreadyRegistered(configHolder: Grid3NodeConfig): Promise<boole
226
248
  logger.debug(`Selenium Grid 3 hub down or not responding: ${(err as Error).message}`);
227
249
  }
228
250
  }
229
-
230
- interface Grid3HubConfiguration {
231
- hubProtocol?: string;
232
- hubHost?: string;
233
- hubPort?: number;
234
- url?: string;
235
- host?: string;
236
- port?: number;
237
- id?: string;
238
- register?: boolean;
239
- registerCycle?: number;
240
- }
241
-
242
- interface Grid3NodeConfig extends StringRecord {
243
- configuration?: Grid3HubConfiguration;
244
- capabilities?: unknown;
245
- }
246
-
247
- interface Grid3ProxyApiResponse {
248
- success: boolean;
249
- msg?: string;
250
- }
@@ -0,0 +1,31 @@
1
+ import type {AppiumDriver} from '../appium';
2
+ import type {
3
+ Args,
4
+ CliCommand,
5
+ CliCommandServer,
6
+ CliCommandSetupSubcommand,
7
+ CliExtensionSubcommand,
8
+ ParsedArgs,
9
+ } from 'appium/types';
10
+ import type {ExtensionConfigs} from '../extension';
11
+
12
+ /** Empty object returned when `init` completes for non-server CLI flows (extension/setup). */
13
+ export type ExtCommandInitResult = Record<string, never>;
14
+
15
+ /** Driver/plugin config plus server instance and merged server args after successful server `init`. */
16
+ export type ServerInitData = ExtensionConfigs & {
17
+ appiumDriver: AppiumDriver;
18
+ parsedArgs: ParsedArgs<CliCommandServer>;
19
+ appiumHome: string;
20
+ };
21
+
22
+ /** Discriminated by CLI command: server subcommand yields {@link ServerInitData}; otherwise {@link ExtCommandInitResult}. */
23
+ export type InitResult<Cmd extends CliCommand = CliCommandServer> = Cmd extends CliCommandServer
24
+ ? ServerInitData
25
+ : ExtCommandInitResult;
26
+
27
+ /** CLI + programmatic args before `init` narrows by `subcommand`. */
28
+ export type PreConfigArgs = Args<
29
+ CliCommand,
30
+ CliExtensionSubcommand | CliCommandSetupSubcommand | void
31
+ >;