appium 2.0.0-beta.19 → 2.0.0-beta.22

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 (105) hide show
  1. package/build/check-npm-pack-files.js +23 -0
  2. package/build/commands-yml/parse.js +319 -0
  3. package/build/commands-yml/validator.js +130 -0
  4. package/build/index.js +19 -0
  5. package/build/lib/appium-config.schema.json +0 -0
  6. package/build/lib/appium.js +84 -69
  7. package/build/lib/cli/args.js +87 -223
  8. package/build/lib/cli/extension-command.js +2 -2
  9. package/build/lib/cli/extension.js +14 -6
  10. package/build/lib/cli/parser.js +146 -106
  11. package/build/lib/config-file.js +141 -0
  12. package/build/lib/config.js +28 -77
  13. package/build/lib/driver-config.js +41 -20
  14. package/build/lib/ext-config-io.js +165 -0
  15. package/build/lib/extension-config.js +110 -60
  16. package/build/lib/grid-register.js +19 -21
  17. package/build/lib/main.js +135 -72
  18. package/build/lib/plugin-config.js +18 -9
  19. package/build/lib/schema/appium-config-schema.js +253 -0
  20. package/build/lib/schema/arg-spec.js +120 -0
  21. package/build/lib/schema/cli-args.js +188 -0
  22. package/build/lib/schema/cli-transformers.js +76 -0
  23. package/build/lib/schema/index.js +36 -0
  24. package/build/lib/schema/keywords.js +72 -0
  25. package/build/lib/schema/schema.js +357 -0
  26. package/build/lib/utils.js +24 -33
  27. package/build/postinstall.js +90 -0
  28. package/build/test/cli/cli-e2e-specs.js +221 -0
  29. package/build/test/cli/cli-helpers.js +86 -0
  30. package/build/test/cli/cli-specs.js +71 -0
  31. package/build/test/cli/fixtures/test-driver/package.json +27 -0
  32. package/build/test/cli/schema-args-specs.js +48 -0
  33. package/build/test/cli/schema-e2e-specs.js +47 -0
  34. package/build/test/config-e2e-specs.js +112 -0
  35. package/build/test/config-file-e2e-specs.js +209 -0
  36. package/build/test/config-file-specs.js +281 -0
  37. package/build/test/config-specs.js +159 -0
  38. package/build/test/driver-e2e-specs.js +435 -0
  39. package/build/test/driver-specs.js +321 -0
  40. package/build/test/ext-config-io-specs.js +181 -0
  41. package/build/test/extension-config-specs.js +365 -0
  42. package/build/test/fixtures/allow-feat.txt +5 -0
  43. package/build/test/fixtures/caps.json +3 -0
  44. package/build/test/fixtures/config/allow-insecure.txt +3 -0
  45. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
  46. package/build/test/fixtures/config/appium.config.bad.json +32 -0
  47. package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
  48. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
  49. package/build/test/fixtures/config/appium.config.good.js +40 -0
  50. package/build/test/fixtures/config/appium.config.good.json +33 -0
  51. package/build/test/fixtures/config/appium.config.good.yaml +30 -0
  52. package/build/test/fixtures/config/appium.config.invalid.json +31 -0
  53. package/build/test/fixtures/config/appium.config.security-array.json +5 -0
  54. package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
  55. package/build/test/fixtures/config/appium.config.security-path.json +5 -0
  56. package/build/test/fixtures/config/driver-fake.config.json +8 -0
  57. package/build/test/fixtures/config/nodeconfig.json +3 -0
  58. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  59. package/build/test/fixtures/default-args.js +35 -0
  60. package/build/test/fixtures/deny-feat.txt +5 -0
  61. package/build/test/fixtures/driver.schema.js +20 -0
  62. package/build/test/fixtures/extensions.yaml +27 -0
  63. package/build/test/fixtures/flattened-schema.js +504 -0
  64. package/build/test/fixtures/plugin.schema.js +20 -0
  65. package/build/test/fixtures/schema-with-extensions.js +28 -0
  66. package/build/test/grid-register-specs.js +74 -0
  67. package/build/test/helpers.js +75 -0
  68. package/build/test/logger-specs.js +76 -0
  69. package/build/test/npm-specs.js +20 -0
  70. package/build/test/parser-specs.js +314 -0
  71. package/build/test/plugin-e2e-specs.js +316 -0
  72. package/build/test/schema/arg-spec-specs.js +70 -0
  73. package/build/test/schema/cli-args-specs.js +431 -0
  74. package/build/test/schema/schema-specs.js +389 -0
  75. package/build/test/utils-specs.js +266 -0
  76. package/index.js +11 -0
  77. package/lib/appium-config.schema.json +278 -0
  78. package/lib/appium.js +99 -75
  79. package/lib/cli/args.js +138 -335
  80. package/lib/cli/extension-command.js +7 -6
  81. package/lib/cli/extension.js +12 -4
  82. package/lib/cli/parser.js +251 -96
  83. package/lib/config-file.js +227 -0
  84. package/lib/config.js +63 -79
  85. package/lib/driver-config.js +66 -11
  86. package/lib/ext-config-io.js +287 -0
  87. package/lib/extension-config.js +209 -66
  88. package/lib/grid-register.js +24 -21
  89. package/lib/main.js +145 -71
  90. package/lib/plugin-config.js +33 -3
  91. package/lib/schema/appium-config-schema.js +287 -0
  92. package/lib/schema/arg-spec.js +222 -0
  93. package/lib/schema/cli-args.js +285 -0
  94. package/lib/schema/cli-transformers.js +123 -0
  95. package/lib/schema/index.js +2 -0
  96. package/lib/schema/keywords.js +135 -0
  97. package/lib/schema/schema.js +577 -0
  98. package/lib/utils.js +29 -52
  99. package/package.json +17 -16
  100. package/types/appium-config.d.ts +197 -0
  101. package/types/types.d.ts +201 -0
  102. package/build/lib/cli/argparse-actions.js +0 -104
  103. package/build/lib/cli/parser-helpers.js +0 -106
  104. package/lib/cli/argparse-actions.js +0 -77
  105. package/lib/cli/parser-helpers.js +0 -106
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';
8
- import { asyncify } from 'asyncbox';
9
- import { default as getParser, getDefaultServerArgs } from './cli/parser';
10
- import { USE_ALL_PLUGINS } from './cli/args';
6
+ // @ts-ignore
7
+ import { routeConfiguringFunction as makeRouter, server as baseServer } from '@appium/base-driver';
11
8
  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';
9
+ import { asyncify } from 'asyncbox';
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
  }
@@ -91,30 +94,31 @@ function logServerPort (address, port) {
91
94
 
92
95
  /**
93
96
  * Find any plugin name which has been installed, and which has been requested for activation by
94
- * using the --plugins flag, and turn each one into its class, so we can send them as objects to
95
- * the server init. We also want to send/assign them to the umbrella driver so it can use them to
96
- * wrap command execution
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
97
100
  *
98
101
  * @param {Object} args - argparser parsed dict
99
- * @param {PluginConfig} pluginConfig - a plugin extension config
102
+ * @param {import('./plugin-config').default} pluginConfig - a plugin extension config
103
+ * @returns {({pluginName: string} & ((...args: any[]) => unknown))[]}
100
104
  */
101
105
  function getActivePlugins (args, pluginConfig) {
102
- return Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
103
- _.includes(args.plugins, pluginName) ||
104
- (args.plugins.length === 1 && args.plugins[0] === USE_ALL_PLUGINS)
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)
105
109
  ).map((pluginName) => {
106
110
  try {
107
111
  logger.info(`Attempting to load plugin ${pluginName}...`);
108
- const PluginClass = pluginConfig.require(pluginName);
112
+ const PluginClass = /** @type {{pluginName: string} & ((...args: any[]) => unknown)} */(pluginConfig.require(pluginName));
113
+
109
114
  PluginClass.pluginName = pluginName; // store the plugin name on the class so it can be used later
110
115
  return PluginClass;
111
116
  } catch (err) {
112
117
  logger.error(`Could not load plugin '${pluginName}', so it will not be available. Error ` +
113
118
  `in loading the plugin was: ${err.message}`);
114
119
  logger.debug(err.stack);
115
- return false;
116
120
  }
117
- }).filter(Boolean);
121
+ }));
118
122
  }
119
123
 
120
124
  /**
@@ -123,11 +127,11 @@ function getActivePlugins (args, pluginConfig) {
123
127
  * If the --drivers flag was given, this method only loads the given drivers.
124
128
  *
125
129
  * @param {Object} args - argparser parsed dict
126
- * @param {DriverConfig} driverConfig - a driver extension config
130
+ * @param {import('./driver-config').default} driverConfig - a driver extension config
127
131
  */
128
132
  function getActiveDrivers (args, driverConfig) {
129
- return Object.keys(driverConfig.installedExtensions).filter((driverName) =>
130
- _.includes(args.drivers, driverName) || args.drivers.length === 0
133
+ return _.compact(Object.keys(driverConfig.installedExtensions).filter((driverName) =>
134
+ _.includes(args.useDrivers, driverName) || args.useDrivers.length === 0
131
135
  ).map((driverName) => {
132
136
  try {
133
137
  logger.info(`Attempting to load driver ${driverName}...`);
@@ -136,9 +140,8 @@ function getActiveDrivers (args, driverConfig) {
136
140
  logger.error(`Could not load driver '${driverName}', so it will not be available. Error ` +
137
141
  `in loading the driver was: ${err.message}`);
138
142
  logger.debug(err.stack);
139
- return false;
140
143
  }
141
- }).filter(Boolean);
144
+ }));
142
145
  }
143
146
 
144
147
  function getServerUpdaters (driverClasses, pluginClasses) {
@@ -152,14 +155,25 @@ function getExtraMethodMap (driverClasses, pluginClasses) {
152
155
  );
153
156
  }
154
157
 
155
- async function main (args = null) {
156
- let parser = getParser();
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();
157
173
  let throwInsteadOfExit = false;
174
+ /** @type {ParsedArgs} */
175
+ let parsedArgs;
158
176
  if (args) {
159
- // a containing package passed in their own args, let's fill them out
160
- // with defaults
161
- args = Object.assign({}, getDefaultServerArgs(), args);
162
-
163
177
  // if we have a containing package instead of running as a CLI process,
164
178
  // that package might not appreciate us calling 'process.exit' willy-
165
179
  // nilly, so give it the option to have us throw instead of exit
@@ -168,59 +182,106 @@ async function main (args = null) {
168
182
  // but remove it since it's not a real server arg per se
169
183
  delete args.throwInsteadOfExit;
170
184
  }
185
+ parsedArgs = {...args, subcommand: args.subcommand ?? SERVER_SUBCOMMAND};
171
186
  } else {
172
187
  // otherwise parse from CLI
173
- args = parser.parse_args();
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
+ );
174
208
  }
175
- await logsinkInit(args);
209
+
210
+ parsedArgs = _.defaultsDeep(
211
+ parsedArgs,
212
+ configResult.config ?? {},
213
+ );
214
+
215
+ await logsinkInit(parsedArgs);
176
216
 
177
217
  // if the user has requested the 'driver' CLI, don't run the normal server,
178
218
  // but instead pass control to the driver CLI
179
- if (args.subcommand === DRIVER_TYPE || args.subcommand === PLUGIN_TYPE) {
180
- await runExtensionCommand(args, args.subcommand);
181
- 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};
182
226
  }
183
227
 
184
- if (args.logFilters) {
185
- const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(args.logFilters);
228
+ if (parsedArgs.logFilters) {
229
+ const {issues, rules} = await logFactory.loadSecureValuesPreprocessingRules(parsedArgs.logFilters);
186
230
  if (!_.isEmpty(issues)) {
187
- 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: ` +
188
232
  JSON.stringify(issues, null, 2));
189
233
  }
190
234
  if (_.isEmpty(rules)) {
191
- 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?`);
192
236
  } else {
193
- 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}'`);
194
238
  }
195
239
  }
196
240
 
197
- let appiumDriver = new AppiumDriver(args);
198
- const driverConfig = new DriverConfig(args.appiumHome);
241
+ const appiumDriver = new AppiumDriver(parsedArgs);
199
242
  // set the config on the umbrella driver so it can match drivers to caps
200
243
  appiumDriver.driverConfig = driverConfig;
201
- const pluginConfig = new PluginConfig(args.appiumHome);
202
- await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
203
- const pluginClasses = getActivePlugins(args, pluginConfig);
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);
204
264
  // set the active plugins on the umbrella driver so it can use them for commands
205
265
  appiumDriver.pluginClasses = pluginClasses;
206
- await logStartupInfo(parser, args);
266
+
267
+ await logStartupInfo(parser, parsedArgs);
207
268
  let routeConfiguringFunction = makeRouter(appiumDriver);
208
269
 
209
- const driverClasses = getActiveDrivers(args, driverConfig);
270
+ const driverClasses = getActiveDrivers(parsedArgs, driverConfig);
210
271
  const serverUpdaters = getServerUpdaters(driverClasses, pluginClasses);
211
272
  const extraMethodMap = getExtraMethodMap(driverClasses, pluginClasses);
212
273
 
213
274
  const serverOpts = {
214
275
  routeConfiguringFunction,
215
- port: args.port,
216
- hostname: args.address,
217
- allowCors: args.allowCors,
218
- basePath: args.basePath,
276
+ port: parsedArgs.port,
277
+ hostname: parsedArgs.address,
278
+ allowCors: parsedArgs.allowCors,
279
+ basePath: parsedArgs.basePath,
219
280
  serverUpdaters,
220
281
  extraMethodMap,
221
282
  };
222
- if (args.keepAliveTimeout) {
223
- serverOpts.keepAliveTimeout = args.keepAliveTimeout * 1000;
283
+ if (parsedArgs.keepAliveTimeout) {
284
+ serverOpts.keepAliveTimeout = parsedArgs.keepAliveTimeout * 1000;
224
285
  }
225
286
  let server;
226
287
  try {
@@ -232,16 +293,18 @@ async function main (args = null) {
232
293
  return process.exit(1);
233
294
  }
234
295
 
235
- if (args.allowCors) {
296
+ if (parsedArgs.allowCors) {
236
297
  logger.warn('You have enabled CORS requests from any host. Be careful not ' +
237
298
  'to visit sites which could maliciously try to start Appium ' +
238
299
  'sessions on your machine');
239
300
  }
301
+ // @ts-ignore
240
302
  appiumDriver.server = server;
241
303
  try {
242
304
  // configure as node on grid, if necessary
243
- if (args.nodeconfig !== null) {
244
- await registerNode(args.nodeconfig, args.address, args.port, args.basePath);
305
+ // falsy values should not cause this to run
306
+ if (parsedArgs.nodeconfig) {
307
+ await registerNode(parsedArgs.nodeconfig, parsedArgs.address, parsedArgs.port, parsedArgs.basePath);
245
308
  }
246
309
  } catch (err) {
247
310
  await server.close();
@@ -265,15 +328,26 @@ async function main (args = null) {
265
328
  });
266
329
  }
267
330
 
268
- logServerPort(args.address, args.port);
331
+ logServerPort(parsedArgs.address, parsedArgs.port);
269
332
  driverConfig.print();
270
333
  pluginConfig.print(pluginClasses.map((p) => p.pluginName));
271
334
 
272
335
  return server;
273
336
  }
274
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`.
275
341
  if (require.main === module) {
276
342
  asyncify(main);
277
343
  }
278
344
 
279
- 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,12 +1,42 @@
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
  }
@@ -27,7 +57,7 @@ export default class PluginConfig extends ExtensionConfig {
27
57
  }
28
58
 
29
59
  if (_.isEmpty(activeNames)) {
30
- log.info('No plugins activated. Use the --plugins flag with names of plugins to activate');
60
+ log.info('No plugins activated. Use the --use-plugins flag with names of plugins to activate');
31
61
  }
32
62
  }
33
63
  }