appium 2.0.0-beta.2 → 2.0.0-beta.23

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 (122) hide show
  1. package/README.md +9 -9
  2. package/build/check-npm-pack-files.js +23 -0
  3. package/build/commands-yml/parse.js +319 -0
  4. package/build/commands-yml/validator.js +130 -0
  5. package/build/index.js +19 -0
  6. package/build/lib/appium-config.schema.json +0 -0
  7. package/build/lib/appium.js +160 -53
  8. package/build/lib/cli/args.js +115 -279
  9. package/build/lib/cli/driver-command.js +11 -1
  10. package/build/lib/cli/extension-command.js +60 -8
  11. package/build/lib/cli/extension.js +30 -7
  12. package/build/lib/cli/npm.js +43 -29
  13. package/build/lib/cli/parser.js +156 -89
  14. package/build/lib/cli/plugin-command.js +11 -1
  15. package/build/lib/cli/utils.js +29 -3
  16. package/build/lib/config-file.js +141 -0
  17. package/build/lib/config.js +53 -65
  18. package/build/lib/driver-config.js +42 -19
  19. package/build/lib/drivers.js +8 -4
  20. package/build/lib/ext-config-io.js +165 -0
  21. package/build/lib/extension-config.js +130 -61
  22. package/build/lib/grid-register.js +22 -24
  23. package/build/lib/logger.js +3 -3
  24. package/build/lib/logsink.js +11 -13
  25. package/build/lib/main.js +197 -77
  26. package/build/lib/plugin-config.js +21 -11
  27. package/build/lib/plugins.js +4 -2
  28. package/build/lib/schema/appium-config-schema.js +253 -0
  29. package/build/lib/schema/arg-spec.js +120 -0
  30. package/build/lib/schema/cli-args.js +188 -0
  31. package/build/lib/schema/cli-transformers.js +76 -0
  32. package/build/lib/schema/index.js +36 -0
  33. package/build/lib/schema/keywords.js +72 -0
  34. package/build/lib/schema/schema.js +357 -0
  35. package/build/lib/utils.js +44 -99
  36. package/build/postinstall.js +90 -0
  37. package/build/test/cli/cli-e2e-specs.js +221 -0
  38. package/build/test/cli/cli-helpers.js +86 -0
  39. package/build/test/cli/cli-specs.js +71 -0
  40. package/build/test/cli/fixtures/test-driver/package.json +27 -0
  41. package/build/test/cli/schema-args-specs.js +48 -0
  42. package/build/test/cli/schema-e2e-specs.js +47 -0
  43. package/build/test/config-e2e-specs.js +112 -0
  44. package/build/test/config-file-e2e-specs.js +209 -0
  45. package/build/test/config-file-specs.js +281 -0
  46. package/build/test/config-specs.js +159 -0
  47. package/build/test/driver-e2e-specs.js +435 -0
  48. package/build/test/driver-specs.js +321 -0
  49. package/build/test/ext-config-io-specs.js +181 -0
  50. package/build/test/extension-config-specs.js +365 -0
  51. package/build/test/fixtures/allow-feat.txt +5 -0
  52. package/build/test/fixtures/caps.json +3 -0
  53. package/build/test/fixtures/config/allow-insecure.txt +3 -0
  54. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
  55. package/build/test/fixtures/config/appium.config.bad.json +32 -0
  56. package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
  57. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
  58. package/build/test/fixtures/config/appium.config.good.js +40 -0
  59. package/build/test/fixtures/config/appium.config.good.json +33 -0
  60. package/build/test/fixtures/config/appium.config.good.yaml +30 -0
  61. package/build/test/fixtures/config/appium.config.invalid.json +31 -0
  62. package/build/test/fixtures/config/appium.config.security-array.json +5 -0
  63. package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
  64. package/build/test/fixtures/config/appium.config.security-path.json +5 -0
  65. package/build/test/fixtures/config/driver-fake.config.json +8 -0
  66. package/build/test/fixtures/config/nodeconfig.json +3 -0
  67. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  68. package/build/test/fixtures/default-args.js +35 -0
  69. package/build/test/fixtures/deny-feat.txt +5 -0
  70. package/build/test/fixtures/driver.schema.js +20 -0
  71. package/build/test/fixtures/extensions.yaml +27 -0
  72. package/build/test/fixtures/flattened-schema.js +504 -0
  73. package/build/test/fixtures/plugin.schema.js +20 -0
  74. package/build/test/fixtures/schema-with-extensions.js +28 -0
  75. package/build/test/grid-register-specs.js +74 -0
  76. package/build/test/helpers.js +75 -0
  77. package/build/test/logger-specs.js +76 -0
  78. package/build/test/npm-specs.js +20 -0
  79. package/build/test/parser-specs.js +314 -0
  80. package/build/test/plugin-e2e-specs.js +316 -0
  81. package/build/test/schema/arg-spec-specs.js +70 -0
  82. package/build/test/schema/cli-args-specs.js +431 -0
  83. package/build/test/schema/schema-specs.js +389 -0
  84. package/build/test/utils-specs.js +266 -0
  85. package/index.js +11 -0
  86. package/lib/appium-config.schema.json +278 -0
  87. package/lib/appium.js +207 -65
  88. package/lib/cli/args.js +174 -375
  89. package/lib/cli/driver-command.js +4 -0
  90. package/lib/cli/extension-command.js +70 -5
  91. package/lib/cli/extension.js +25 -5
  92. package/lib/cli/npm.js +86 -18
  93. package/lib/cli/parser.js +257 -79
  94. package/lib/cli/plugin-command.js +4 -0
  95. package/lib/cli/utils.js +21 -1
  96. package/lib/config-file.js +227 -0
  97. package/lib/config.js +84 -63
  98. package/lib/driver-config.js +66 -11
  99. package/lib/drivers.js +4 -1
  100. package/lib/ext-config-io.js +287 -0
  101. package/lib/extension-config.js +225 -67
  102. package/lib/grid-register.js +27 -24
  103. package/lib/logger.js +1 -1
  104. package/lib/logsink.js +10 -7
  105. package/lib/main.js +214 -77
  106. package/lib/plugin-config.js +35 -6
  107. package/lib/plugins.js +1 -0
  108. package/lib/schema/appium-config-schema.js +287 -0
  109. package/lib/schema/arg-spec.js +222 -0
  110. package/lib/schema/cli-args.js +285 -0
  111. package/lib/schema/cli-transformers.js +123 -0
  112. package/lib/schema/index.js +2 -0
  113. package/lib/schema/keywords.js +135 -0
  114. package/lib/schema/schema.js +577 -0
  115. package/lib/utils.js +42 -88
  116. package/package.json +55 -84
  117. package/postinstall.js +71 -0
  118. package/types/appium-config.d.ts +197 -0
  119. package/types/types.d.ts +201 -0
  120. package/CHANGELOG.md +0 -3515
  121. package/build/lib/cli/parser-helpers.js +0 -82
  122. package/lib/cli/parser-helpers.js +0 -79
package/lib/main.js CHANGED
@@ -1,29 +1,32 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  // transpile:main
4
+ // @ts-check
3
5
 
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';
6
+ // @ts-ignore
7
+ import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
8
+ import { logger as logFactory, util } 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 { driverConfig, pluginConfig, USE_ALL_PLUGINS } from './cli/args';
13
+ import { runExtensionCommand } from './cli/extension';
14
+ import { default as getParser, SERVER_SUBCOMMAND } from './cli/parser';
15
+ import { APPIUM_VER, checkNodeOk, getGitRev, getNonDefaultServerArgs, showConfig, validateTmpDir, warnNodeDeprecations } from './config';
16
+ import { readConfigFile } from './config-file';
17
+ import { DRIVER_TYPE, PLUGIN_TYPE } from './extension-config';
22
18
  import registerNode from './grid-register';
19
+ import logger from './logger'; // logger needs to remain first of imports
20
+ import { init as logsinkInit } from './logsink';
21
+ import { getDefaultsFromSchema, validate } from './schema/schema';
23
22
  import { inspectObject } from './utils';
24
23
 
25
-
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) {
@@ -34,9 +37,9 @@ async function preflightChecks ({parser, args, driverConfig, pluginConfig, throw
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
  }
@@ -69,7 +72,7 @@ async function logStartupInfo (parser, args) {
69
72
  }
70
73
  logger.info(welcome);
71
74
 
72
- let showArgs = getNonDefaultArgs(parser, args);
75
+ let showArgs = getNonDefaultServerArgs(parser, args);
73
76
  if (_.size(showArgs)) {
74
77
  logNonDefaultArgsWarning(showArgs);
75
78
  }
@@ -89,14 +92,88 @@ function logServerPort (address, port) {
89
92
  logger.info(logMessage);
90
93
  }
91
94
 
92
- async function main (args = null) {
93
- let parser = getParser();
95
+ /**
96
+ * Find any plugin name which has been installed, and which has been requested for activation by
97
+ * using the --use-plugins flag, and turn each one into its class, so we can send them as objects
98
+ * to the server init. We also want to send/assign them to the umbrella driver so it can use them
99
+ * to wrap command execution
100
+ *
101
+ * @param {Object} args - argparser parsed dict
102
+ * @param {import('./plugin-config').default} pluginConfig - a plugin extension config
103
+ * @returns {({pluginName: string} & ((...args: any[]) => unknown))[]}
104
+ */
105
+ function getActivePlugins (args, pluginConfig) {
106
+ return _.compact(Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
107
+ _.includes(args.usePlugins, pluginName) ||
108
+ (args.usePlugins.length === 1 && args.usePlugins[0] === USE_ALL_PLUGINS)
109
+ ).map((pluginName) => {
110
+ try {
111
+ logger.info(`Attempting to load plugin ${pluginName}...`);
112
+ const PluginClass = /** @type {{pluginName: string} & ((...args: any[]) => unknown)} */(pluginConfig.require(pluginName));
113
+
114
+ PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
115
+ return PluginClass;
116
+ } catch (err) {
117
+ logger.error(`Could not load plugin '${pluginName}', so it will not be available. Error ` +
118
+ `in loading the plugin was: ${err.message}`);
119
+ logger.debug(err.stack);
120
+ }
121
+ }));
122
+ }
123
+
124
+ /**
125
+ * Find any driver name which has been installed, and turn each one into its class, so we can send
126
+ * them as objects to the server init in case they need to add methods/routes or update the server.
127
+ * If the --drivers flag was given, this method only loads the given drivers.
128
+ *
129
+ * @param {Object} args - argparser parsed dict
130
+ * @param {import('./driver-config').default} driverConfig - a driver extension config
131
+ */
132
+ function getActiveDrivers (args, driverConfig) {
133
+ return _.compact(Object.keys(driverConfig.installedExtensions).filter((driverName) =>
134
+ _.includes(args.useDrivers, driverName) || args.useDrivers.length === 0
135
+ ).map((driverName) => {
136
+ try {
137
+ logger.info(`Attempting to load driver ${driverName}...`);
138
+ return driverConfig.require(driverName);
139
+ } catch (err) {
140
+ logger.error(`Could not load driver '${driverName}', so it will not be available. Error ` +
141
+ `in loading the driver was: ${err.message}`);
142
+ logger.debug(err.stack);
143
+ }
144
+ }));
145
+ }
146
+
147
+ function getServerUpdaters (driverClasses, pluginClasses) {
148
+ return [...driverClasses, ...pluginClasses].map((klass) => klass.updateServer).filter(Boolean);
149
+ }
150
+
151
+ function getExtraMethodMap (driverClasses, pluginClasses) {
152
+ return [...driverClasses, ...pluginClasses].reduce(
153
+ (map, klass) => ({...map, ...klass.newMethodMap}),
154
+ {}
155
+ );
156
+ }
157
+
158
+ /**
159
+ * Initializes Appium, but does not start the server.
160
+ *
161
+ * Use this to get at the configuration schema.
162
+ *
163
+ * @example
164
+ * import {init, getSchema} from 'appium';
165
+ * const options = {}; // config object
166
+ * await init(options);
167
+ * const schema = getSchema(); // entire config schema including plugins and drivers
168
+ * @param {ParsedArgs} [args] - Parsed args
169
+ * @returns {Promise<{parser: import('./cli/parser').ArgParser} & Partial<{appiumDriver: AppiumDriver, parsedArgs: ParsedArgs}>>}
170
+ */
171
+ async function init (args) {
172
+ const parser = await getParser();
94
173
  let throwInsteadOfExit = false;
174
+ /** @type {ParsedArgs} */
175
+ let parsedArgs;
95
176
  if (args) {
96
- // a containing package passed in their own args, let's fill them out
97
- // with defaults
98
- args = Object.assign({}, getDefaultServerArgs(), args);
99
-
100
177
  // if we have a containing package instead of running as a CLI process,
101
178
  // that package might not appreciate us calling 'process.exit' willy-
102
179
  // nilly, so give it the option to have us throw instead of exit
@@ -105,80 +182,129 @@ async function main (args = null) {
105
182
  // but remove it since it's not a real server arg per se
106
183
  delete args.throwInsteadOfExit;
107
184
  }
185
+ parsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
108
186
  } else {
109
187
  // otherwise parse from CLI
110
- args = parser.parseArgs();
188
+ parsedArgs = parser.parseArgs();
189
+ }
190
+
191
+ const configResult = await readConfigFile(parsedArgs.configFile);
192
+
193
+ if (!_.isEmpty(configResult.errors)) {
194
+ throw new Error(`Errors in config file ${configResult.filepath}:\n ${configResult.reason ?? configResult.errors}`);
195
+ }
196
+
197
+ // merge config and apply defaults.
198
+ // the order of precendece is:
199
+ // 1. command line args
200
+ // 2. config file
201
+ // 3. defaults from config file.
202
+ if (parsedArgs.subcommand === SERVER_SUBCOMMAND) {
203
+ parsedArgs = _.defaultsDeep(
204
+ parsedArgs,
205
+ configResult.config?.server,
206
+ getDefaultsFromSchema()
207
+ );
111
208
  }
112
- await logsinkInit(args);
209
+
210
+ parsedArgs = _.defaultsDeep(
211
+ parsedArgs,
212
+ configResult.config ?? {},
213
+ );
214
+
215
+ await logsinkInit(parsedArgs);
113
216
 
114
217
  // if the user has requested the 'driver' CLI, don't run the normal server,
115
218
  // 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();
219
+ if (parsedArgs.subcommand === DRIVER_TYPE) {
220
+ await runExtensionCommand(parsedArgs, parsedArgs.subcommand, driverConfig);
221
+ return {parser};
222
+ }
223
+ if (parsedArgs.subcommand === PLUGIN_TYPE) {
224
+ await runExtensionCommand(parsedArgs, parsedArgs.subcommand, pluginConfig);
225
+ return {parser};
119
226
  }
120
227
 
121
- if (args.logFilters) {
122
- const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(args.logFilters);
228
+ if (parsedArgs.logFilters) {
229
+ const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(parsedArgs.logFilters);
123
230
  if (!_.isEmpty(issues)) {
124
- throw new Error(`The log filtering rules config '${args.logFilters}' has issues: ` +
231
+ throw new Error(`The log filtering rules config '${parsedArgs.logFilters}' has issues: ` +
125
232
  JSON.stringify(issues, null, 2));
126
233
  }
127
234
  if (_.isEmpty(rules)) {
128
- logger.warn(`Found no log filtering rules in '${args.logFilters}'. Is that expected?`);
235
+ logger.warn(`Found no log filtering rules in '${parsedArgs.logFilters}'. Is that expected?`);
129
236
  } else {
130
- logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${args.logFilters}'`);
237
+ logger.info(`Loaded ${util.pluralize('filtering rule', rules.length, true)} from '${parsedArgs.logFilters}'`);
131
238
  }
132
239
  }
133
240
 
134
- let appiumDriver = new AppiumDriver(args);
135
- const driverConfig = new DriverConfig(args.appiumHome);
136
- const pluginConfig = new PluginConfig(args.appiumHome);
241
+ const appiumDriver = new AppiumDriver(parsedArgs);
242
+ // set the config on the umbrella driver so it can match drivers to caps
137
243
  appiumDriver.driverConfig = driverConfig;
138
- await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
139
- await logStartupInfo(parser, args);
244
+ await preflightChecks(parsedArgs, throwInsteadOfExit);
245
+
246
+ return {parser, appiumDriver, parsedArgs};
247
+ }
248
+
249
+ /**
250
+ *
251
+ * @param {ParsedArgs} [args]
252
+ * @returns
253
+ */
254
+ async function main (args) {
255
+ const {parser, appiumDriver, parsedArgs} = await init(args);
256
+
257
+ if (!appiumDriver || !parsedArgs) {
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(parsedArgs, pluginConfig);
264
+ // set the active plugins on the umbrella driver so it can use them for commands
265
+ appiumDriver.pluginClasses = pluginClasses;
266
+
267
+ await logStartupInfo(parser, 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(parsedArgs, driverConfig);
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
  }
301
+ // @ts-ignore
174
302
  appiumDriver.server = server;
175
303
  try {
176
- // TODO prelaunch if args.launch is set
177
- // TODO: startAlertSocket(server, appiumServer);
178
-
179
304
  // configure as node on grid, if necessary
180
- if (args.nodeconfig !== null) {
181
- await registerNode(args.nodeconfig, args.address, args.port);
305
+ // falsy values should not cause this to run
306
+ if (parsedArgs.nodeconfig) {
307
+ await registerNode(parsedArgs.nodeconfig, parsedArgs.address, parsedArgs.port, parsedArgs.basePath);
182
308
  }
183
309
  } catch (err) {
184
310
  await server.close();
@@ -202,15 +328,26 @@ async function main (args = null) {
202
328
  });
203
329
  }
204
330
 
205
- logServerPort(args.address, args.port);
331
+ logServerPort(parsedArgs.address, parsedArgs.port);
206
332
  driverConfig.print();
207
- pluginConfig.print(plugins);
333
+ pluginConfig.print(pluginClasses.map((p) => p.pluginName));
208
334
 
209
335
  return server;
210
336
  }
211
337
 
338
+ // NOTE: this is here for backwards compat for any scripts referencing `main.js` directly
339
+ // (more specifically, `build/lib/main.js`)
340
+ // the executable is now `../index.js`, so that module will typically be `require.main`.
212
341
  if (require.main === module) {
213
342
  asyncify(main);
214
343
  }
215
344
 
216
- export { main };
345
+ // everything below here is intended to be a public API.
346
+ export { main, init };
347
+ export { APPIUM_HOME } from './extension-config';
348
+ export { getSchema, validate, finalizeSchema } from './schema/schema';
349
+ export { readConfigFile } from './config-file';
350
+
351
+ /**
352
+ * @typedef {import('../types/types').ParsedArgs} ParsedArgs
353
+ */
@@ -1,19 +1,48 @@
1
1
  import _ from 'lodash';
2
- import ExtensionConfig, { PLUGIN_TYPE } from './extension-config';
2
+ import ExtensionConfig from './extension-config';
3
+ import { PLUGIN_TYPE } from './ext-config-io';
3
4
  import log from './logger';
4
5
 
5
6
  export default class PluginConfig extends ExtensionConfig {
6
- constructor (appiumHome, logFn = null) {
7
+
8
+ /**
9
+ * A mapping of `APPIUM_HOME` values to {@link PluginConfig} instances.
10
+ * Each `APPIUM_HOME` should only have one associated `PluginConfig` instance.
11
+ * @type {Record<string,PluginConfig>}
12
+ * @private
13
+ */
14
+ static _instances = {};
15
+
16
+ /**
17
+ * Call {@link PluginConfig.getInstance} instead.
18
+ *
19
+ * Just calls the superclass' constructor with the correct extension type
20
+ * @private
21
+ * @param {string} appiumHome - `APPIUM_HOME` path
22
+ * @param {(...args: any[]) => void)} [logFn] - Optional logging function
23
+ */
24
+ constructor (appiumHome, logFn) {
7
25
  super(appiumHome, PLUGIN_TYPE, logFn);
8
26
  }
9
27
 
28
+ /**
29
+ * Creates or gets an instance of {@link PluginConfig} based value of `appiumHome`
30
+ * @param {string} appiumHome - `APPIUM_HOME` path
31
+ * @param {(...args: any[]) => void} [logFn] - Optional logging function
32
+ * @returns {PluginConfig}
33
+ */
34
+ static getInstance (appiumHome, logFn) {
35
+ const instance = PluginConfig._instances[appiumHome] ?? new PluginConfig(appiumHome, logFn);
36
+ PluginConfig._instances[appiumHome] = instance;
37
+ return instance;
38
+ }
39
+
10
40
  extensionDesc (pluginName, {version}) {
11
41
  return `${pluginName}@${version}`;
12
42
  }
13
43
 
14
- print (activePlugins) {
44
+ print (activeNames) {
15
45
  const pluginNames = Object.keys(this.installedExtensions);
16
- const activeNames = activePlugins.map((p) => p.name);
17
46
 
18
47
  if (_.isEmpty(pluginNames)) {
19
48
  log.info(`No plugins have been installed. Use the "appium plugin" ` +
@@ -27,8 +56,8 @@ export default class PluginConfig extends ExtensionConfig {
27
56
  log.info(` - ${this.extensionDesc(pluginName, pluginData)}${activeTxt}`);
28
57
  }
29
58
 
30
- if (_.isEmpty(activePlugins)) {
31
- log.info('No plugins activated. Use the --plugins flag with names of plugins to activate');
59
+ if (_.isEmpty(activeNames)) {
60
+ log.info('No plugins activated. Use the --use-plugins flag with names of plugins to activate');
32
61
  }
33
62
  }
34
63
  }
package/lib/plugins.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // type 'appium plugin install 'name'', rather than having to specify the full
4
4
  // npm package. I.e., these are the officially recognized plugins.
5
5
  const KNOWN_PLUGINS = {
6
+ images: '@appium/images-plugin',
6
7
  };
7
8
 
8
9
  export {