appium 2.0.0-beta.25 → 2.0.0-beta.26

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 (124) hide show
  1. package/build/lib/appium-config.schema.json +278 -0
  2. package/build/lib/appium.js +45 -66
  3. package/build/lib/cli/args.js +19 -39
  4. package/build/lib/cli/driver-command.js +5 -9
  5. package/build/lib/cli/extension-command.js +73 -64
  6. package/build/lib/cli/extension.js +10 -23
  7. package/build/lib/cli/parser.js +9 -19
  8. package/build/lib/cli/plugin-command.js +5 -9
  9. package/build/lib/cli/utils.js +2 -4
  10. package/build/lib/config-file.js +2 -4
  11. package/build/lib/config.js +7 -6
  12. package/build/lib/constants.js +60 -0
  13. package/build/lib/extension/driver-config.js +190 -0
  14. package/build/lib/extension/extension-config.js +297 -0
  15. package/build/lib/extension/index.js +77 -0
  16. package/build/lib/extension/manifest.js +246 -0
  17. package/build/lib/extension/package-changed.js +68 -0
  18. package/build/lib/extension/plugin-config.js +87 -0
  19. package/build/lib/grid-register.js +2 -4
  20. package/build/lib/logger.js +2 -4
  21. package/build/lib/logsink.js +2 -4
  22. package/build/lib/main.js +40 -68
  23. package/build/lib/schema/appium-config-schema.js +2 -4
  24. package/build/lib/schema/arg-spec.js +11 -14
  25. package/build/lib/schema/cli-args.js +2 -4
  26. package/build/lib/schema/cli-transformers.js +2 -4
  27. package/build/lib/schema/index.js +2 -4
  28. package/build/lib/schema/keywords.js +2 -4
  29. package/build/lib/schema/schema.js +55 -37
  30. package/build/lib/utils.js +1 -32
  31. package/lib/appium.js +50 -68
  32. package/lib/cli/args.js +19 -23
  33. package/lib/cli/driver-command.js +10 -2
  34. package/lib/cli/extension-command.js +216 -135
  35. package/lib/cli/extension.js +7 -15
  36. package/lib/cli/parser.js +6 -14
  37. package/lib/cli/plugin-command.js +1 -2
  38. package/lib/config-file.js +3 -3
  39. package/lib/config.js +5 -4
  40. package/lib/constants.js +79 -0
  41. package/lib/extension/driver-config.js +230 -0
  42. package/lib/extension/extension-config.js +459 -0
  43. package/lib/extension/index.js +103 -0
  44. package/lib/extension/manifest.js +590 -0
  45. package/lib/extension/package-changed.js +64 -0
  46. package/lib/extension/plugin-config.js +111 -0
  47. package/lib/grid-register.js +4 -4
  48. package/lib/main.js +51 -88
  49. package/lib/schema/arg-spec.js +2 -2
  50. package/lib/schema/cli-args.js +1 -0
  51. package/lib/schema/keywords.js +1 -1
  52. package/lib/schema/schema.js +60 -28
  53. package/lib/utils.js +2 -32
  54. package/package.json +29 -21
  55. package/{postinstall.js → scripts/postinstall.js} +1 -1
  56. package/types/types.d.ts +70 -31
  57. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  58. package/build/check-npm-pack-files.js +0 -23
  59. package/build/commands-yml/parse.js +0 -319
  60. package/build/commands-yml/validator.js +0 -130
  61. package/build/index.js +0 -19
  62. package/build/lib/cli/npm.js +0 -220
  63. package/build/lib/driver-config.js +0 -100
  64. package/build/lib/drivers.js +0 -100
  65. package/build/lib/ext-config-io.js +0 -165
  66. package/build/lib/extension-config.js +0 -320
  67. package/build/lib/plugin-config.js +0 -69
  68. package/build/lib/plugins.js +0 -18
  69. package/build/postinstall.js +0 -90
  70. package/build/test/cli/cli-e2e-specs.js +0 -221
  71. package/build/test/cli/cli-helpers.js +0 -86
  72. package/build/test/cli/cli-specs.js +0 -71
  73. package/build/test/cli/fixtures/test-driver/package.json +0 -27
  74. package/build/test/cli/schema-args-specs.js +0 -48
  75. package/build/test/cli/schema-e2e-specs.js +0 -47
  76. package/build/test/config-e2e-specs.js +0 -112
  77. package/build/test/config-file-e2e-specs.js +0 -191
  78. package/build/test/config-file-specs.js +0 -281
  79. package/build/test/config-specs.js +0 -258
  80. package/build/test/driver-e2e-specs.js +0 -435
  81. package/build/test/driver-specs.js +0 -386
  82. package/build/test/ext-config-io-specs.js +0 -181
  83. package/build/test/extension-config-specs.js +0 -365
  84. package/build/test/fixtures/allow-feat.txt +0 -5
  85. package/build/test/fixtures/caps.json +0 -3
  86. package/build/test/fixtures/config/allow-insecure.txt +0 -3
  87. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +0 -5
  88. package/build/test/fixtures/config/appium.config.bad.json +0 -32
  89. package/build/test/fixtures/config/appium.config.ext-good.json +0 -9
  90. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +0 -10
  91. package/build/test/fixtures/config/appium.config.good.js +0 -40
  92. package/build/test/fixtures/config/appium.config.good.json +0 -33
  93. package/build/test/fixtures/config/appium.config.good.yaml +0 -30
  94. package/build/test/fixtures/config/appium.config.invalid.json +0 -31
  95. package/build/test/fixtures/config/appium.config.security-array.json +0 -5
  96. package/build/test/fixtures/config/appium.config.security-delimited.json +0 -5
  97. package/build/test/fixtures/config/appium.config.security-path.json +0 -5
  98. package/build/test/fixtures/config/driver-fake.config.json +0 -8
  99. package/build/test/fixtures/config/nodeconfig.json +0 -3
  100. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  101. package/build/test/fixtures/default-args.js +0 -35
  102. package/build/test/fixtures/deny-feat.txt +0 -5
  103. package/build/test/fixtures/driver.schema.js +0 -20
  104. package/build/test/fixtures/extensions.yaml +0 -27
  105. package/build/test/fixtures/flattened-schema.js +0 -532
  106. package/build/test/fixtures/plugin.schema.js +0 -20
  107. package/build/test/fixtures/schema-with-extensions.js +0 -28
  108. package/build/test/grid-register-specs.js +0 -74
  109. package/build/test/helpers.js +0 -75
  110. package/build/test/logger-specs.js +0 -76
  111. package/build/test/npm-specs.js +0 -20
  112. package/build/test/parser-specs.js +0 -319
  113. package/build/test/plugin-e2e-specs.js +0 -316
  114. package/build/test/schema/arg-spec-specs.js +0 -70
  115. package/build/test/schema/cli-args-specs.js +0 -408
  116. package/build/test/schema/schema-specs.js +0 -407
  117. package/build/test/utils-specs.js +0 -288
  118. package/lib/cli/npm.js +0 -251
  119. package/lib/driver-config.js +0 -101
  120. package/lib/drivers.js +0 -84
  121. package/lib/ext-config-io.js +0 -287
  122. package/lib/extension-config.js +0 -366
  123. package/lib/plugin-config.js +0 -63
  124. package/lib/plugins.js +0 -13
@@ -1,47 +1,61 @@
1
+ // @ts-check
1
2
  /* eslint-disable no-console */
2
3
 
3
4
  import _ from 'lodash';
4
- import NPM from './npm';
5
- import path from 'path';
6
- import { fs, util } from '@appium/support';
5
+ import { npm, fs, util } from '@appium/support';
7
6
  import { log, spinWith, RingBuffer } from './utils';
8
- import { SubProcess} from 'teen_process';
7
+ import { SubProcess } from 'teen_process';
9
8
  import { INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB,
10
- INSTALL_TYPE_LOCAL } from '../extension-config';
9
+ INSTALL_TYPE_LOCAL } from '../extension/extension-config';
11
10
 
12
11
  const UPDATE_ALL = 'installed';
13
12
 
14
13
  class NotUpdatableError extends Error {}
15
14
  class NoUpdatesAvailableError extends Error {}
16
15
 
16
+ /**
17
+ * @template {ExtensionType} ExtType
18
+ */
17
19
  export default class ExtensionCommand {
20
+ /**
21
+ * This is the `DriverConfig` or `PluginConfig`, depending on `ExtType`.
22
+ * @type {ExtensionConfig<ExtType>}
23
+ */
24
+ config;
25
+
26
+ /**
27
+ * {@linkcode Record} of official plugins or drivers.
28
+ * @type {ExtType extends import('../extension/manifest').DriverType ? typeof import('../constants').KNOWN_DRIVERS : typeof import('../constants').KNOWN_PLUGINS}
29
+ */
30
+ knownExtensions;
18
31
 
19
32
  /**
20
- * @typedef {Object} ExtensionCommandConstructor
21
- * @property {Object} config - the DriverConfig or PluginConfig object used for this command
22
- * @property {boolean} json - whether the output of this command should be JSON or text
23
- * @property {string} type - DRIVER_TYPE or PLUGIN_TYPE
33
+ * If `true`, command output has been requested as JSON.
34
+ * @type {boolean}
24
35
  */
36
+ isJsonOutput;
25
37
 
26
38
  /**
27
39
  * Build an ExtensionCommand
28
- *
29
- * @param {ExtensionCommandConstructor} opts
30
- * @return {ExtensionCommand}
40
+ * @param {ExtensionCommandOptions<ExtType>} opts
31
41
  */
32
- constructor ({config, json, type}) {
42
+ constructor ({config, json}) {
33
43
  this.config = config;
34
- this.type = type;
35
44
  this.isJsonOutput = json;
36
- this.npm = new NPM(this.config.appiumHome);
37
- this.knownExtensions = {}; // this needs to be overridden in final class
45
+ }
46
+
47
+ /**
48
+ * `driver` or `plugin`, depending on the `ExtensionConfig`.
49
+ */
50
+ get type () {
51
+ return this.config.extensionType;
38
52
  }
39
53
 
40
54
  /**
41
55
  * Take a CLI parse and run an extension command based on its type
42
56
  *
43
57
  * @param {object} args - a key/value object with CLI flags and values
44
- * @return {object} the result of the specific command which is executed
58
+ * @return {Promise<object>} the result of the specific command which is executed
45
59
  */
46
60
  async execute (args) {
47
61
  const cmd = args[`${this.type}Command`];
@@ -53,7 +67,7 @@ export default class ExtensionCommand {
53
67
  }
54
68
 
55
69
  /**
56
- * @typedef {Object} ListArgs
70
+ * @typedef ListOptions
57
71
  * @property {boolean} showInstalled - whether should show only installed extensions
58
72
  * @property {boolean} showUpdates - whether should show available updates
59
73
  */
@@ -61,8 +75,8 @@ export default class ExtensionCommand {
61
75
  /**
62
76
  * List extensions
63
77
  *
64
- * @param {ListArgs} args
65
- * @return {object} map of extension names to extension data
78
+ * @param {ListOptions} opts
79
+ * @return {Promise<ExtensionListData>} map of extension names to extension data
66
80
  */
67
81
  async list ({showInstalled, showUpdates}) {
68
82
  const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
@@ -77,7 +91,7 @@ export default class ExtensionCommand {
77
91
  }
78
92
  }
79
93
  return acc;
80
- }, {});
94
+ }, /** @type {ExtensionListData} */({}));
81
95
 
82
96
  // if we want to show whether updates are available, put that behind a spinner
83
97
  await spinWith(this.isJsonOutput, lsMsg, async () => {
@@ -85,8 +99,7 @@ export default class ExtensionCommand {
85
99
  return;
86
100
  }
87
101
  for (const [ext, data] of _.toPairs(exts)) {
88
- const {installed, installType} = data;
89
- if (!installed || installType !== INSTALL_TYPE_NPM) {
102
+ if (!data.installed || data.installType !== INSTALL_TYPE_NPM) {
90
103
  // don't need to check for updates on exts that aren't installed
91
104
  // also don't need to check for updates on non-npm exts
92
105
  continue;
@@ -106,32 +119,40 @@ export default class ExtensionCommand {
106
119
 
107
120
  for (const [
108
121
  name,
109
- {installType, installSpec, installed, updateVersion, unsafeUpdateVersion, version, upToDate}
122
+ data
110
123
  ] of _.toPairs(exts)) {
111
- let typeTxt;
112
- switch (installType) {
113
- case INSTALL_TYPE_GIT:
114
- case INSTALL_TYPE_GITHUB:
115
- typeTxt = `(cloned from ${installSpec})`.yellow;
116
- break;
117
- case INSTALL_TYPE_LOCAL:
118
- typeTxt = `(linked from ${installSpec})`.magenta;
119
- break;
120
- default:
121
- typeTxt = '(NPM)';
124
+ let installTxt = ' [not installed]'.grey;
125
+ let updateTxt = '';
126
+ let upToDateTxt = '';
127
+ let unsafeUpdateTxt = '';
128
+ if (data.installed) {
129
+ const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} = data;
130
+ let typeTxt;
131
+ switch (installType) {
132
+ case INSTALL_TYPE_GIT:
133
+ case INSTALL_TYPE_GITHUB:
134
+ typeTxt = `(cloned from ${installSpec})`.yellow;
135
+ break;
136
+ case INSTALL_TYPE_LOCAL:
137
+ typeTxt = `(linked from ${installSpec})`.magenta;
138
+ break;
139
+ default:
140
+ typeTxt = '(NPM)';
141
+ }
142
+ installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
143
+
144
+ if (showUpdates) {
145
+ if (updateVersion) {
146
+ updateTxt = ` [${updateVersion} available]`.magenta;
147
+ }
148
+ if (upToDate) {
149
+ upToDateTxt = ` [Up to date]`.green;
150
+ }
151
+ if (unsafeUpdateVersion) {
152
+ unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
153
+ }
154
+ }
122
155
  }
123
- const installTxt = installed ?
124
- `@${version.yellow} ${('[installed ' + typeTxt + ']').green}` :
125
- ' [not installed]'.grey;
126
- const updateTxt = showUpdates && updateVersion ?
127
- ` [${updateVersion} available]`.magenta :
128
- '';
129
- const upToDateTxt = showUpdates && upToDate ?
130
- ` [Up to date]`.green :
131
- '';
132
- const unsafeUpdateTxt = showUpdates && unsafeUpdateVersion ?
133
- ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan :
134
- '';
135
156
 
136
157
  console.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`);
137
158
  }
@@ -139,22 +160,14 @@ export default class ExtensionCommand {
139
160
  return exts;
140
161
  }
141
162
 
142
- /**
143
- * @typedef {Object} InstallArgs
144
- * @property {string} ext - the name or spec of an extension to install
145
- * @property {string} installType - how to install this extension. One of the INSTALL_TYPES
146
- * @property {string} [packageName] - for git/github installs, the extension node package name
147
- */
148
-
149
163
  /**
150
164
  * Install an extension
151
165
  *
152
166
  * @param {InstallArgs} args
153
- * @return {object} map of all installed extension names to extension data
167
+ * @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
154
168
  */
155
169
  async install ({ext, installType, packageName}) {
156
- log(this.isJsonOutput, `Attempting to find and install ${this.type} '${ext}'`);
157
-
170
+ /** @type {ExtData<ExtType>} */
158
171
  let extData;
159
172
  let installSpec = ext;
160
173
 
@@ -167,23 +180,23 @@ export default class ExtensionCommand {
167
180
  }
168
181
 
169
182
  if (installType === INSTALL_TYPE_LOCAL) {
170
- const msg = `Linking ${this.type} from local path`;
183
+ const msg = `Linking ${this.type} from local path into ${this.config.appiumHome}`;
171
184
  const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () => (
172
- await this.npm.linkPackage(installSpec))
185
+ await npm.linkPackage(this.config.appiumHome, installSpec))
173
186
  );
174
187
  extData = this.getExtensionFields(pkgJsonData);
175
- extData.installPath = extData.pkgName;
188
+ log(this.isJsonOutput, `Successfully linked ${extData.pkgName} into ${this.config.appiumHome}`);
176
189
  } else if (installType === INSTALL_TYPE_GITHUB) {
177
190
  if (installSpec.split('/').length !== 2) {
178
191
  throw new Error(`Github ${this.type} spec ${installSpec} appeared to be invalid; ` +
179
192
  'it should be of the form <org>/<repo>');
180
193
  }
181
- extData = await this.installViaNpm({ext: installSpec, pkgName: packageName});
194
+ extData = await this.installViaNpm({ext: installSpec, pkgName: /** @type {string} */(packageName)});
182
195
  } else if (installType === INSTALL_TYPE_GIT) {
183
196
  // git urls can have '.git' at the end, but this is not necessary and would complicate the
184
197
  // way we download and name directories, so we can just remove it
185
198
  installSpec = installSpec.replace(/\.git$/, '');
186
- extData = await this.installViaNpm({ext: installSpec, pkgName: packageName});
199
+ extData = await this.installViaNpm({ext: installSpec, pkgName: /** @type {string} */(packageName)});
187
200
  } else {
188
201
  // at this point we have either an npm package or an appium verified extension
189
202
  // name. both of which will be installed via npm.
@@ -224,8 +237,8 @@ export default class ExtensionCommand {
224
237
  extData = await this.installViaNpm({ext, pkgName, pkgVer});
225
238
  }
226
239
 
227
- const extName = extData[`${this.type}Name`];
228
- delete extData[`${this.type}Name`];
240
+ const extName = extData[/** @type {string} */(`${this.type}Name`)];
241
+ delete extData[/** @type {string} */(`${this.type}Name`)];
229
242
 
230
243
  if (this.config.isInstalled(extName)) {
231
244
  throw new Error(`A ${this.type} named '${extName}' is already installed. ` +
@@ -243,17 +256,11 @@ export default class ExtensionCommand {
243
256
  return this.config.installedExtensions;
244
257
  }
245
258
 
246
- /**
247
- * @typedef {Object} InstallViaNpmArgs
248
- * @property {string} ext - the name or spec of an extension to install
249
- * @property {string} pkgName - the NPM package name of the extension
250
- * @property {string} [pkgVer] - the specific version of the NPM package
251
- */
252
-
253
259
  /**
254
260
  * Install an extension via NPM
255
261
  *
256
262
  * @param {InstallViaNpmArgs} args
263
+ * @returns {Promise<ExtData<ExtType>>}
257
264
  */
258
265
  async installViaNpm ({ext, pkgName, pkgVer}) {
259
266
  const npmSpec = `${pkgName}${pkgVer ? '@' + pkgVer : ''}`;
@@ -261,33 +268,25 @@ export default class ExtensionCommand {
261
268
  const msg = `Installing '${ext}'${specMsg}`;
262
269
  try {
263
270
  const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () => (
264
- await this.npm.installPackage({
265
- pkgDir: path.resolve(this.config.appiumHome, pkgName),
266
- pkgName,
271
+ await npm.installPackage(this.config.appiumHome, pkgName, {
267
272
  pkgVer
268
273
  })
269
274
  ));
270
- const extData = this.getExtensionFields(pkgJsonData);
271
- extData.installPath = pkgName;
272
- return extData;
275
+ return this.getExtensionFields(pkgJsonData);
273
276
  } catch (err) {
274
277
  throw new Error(`Encountered an error when installing package: ${err.message}`);
275
278
  }
276
279
  }
277
280
 
278
- /**
279
- * @typedef {Object} ExtensionArgs
280
- * @property {string} extName - the name of an extension
281
- * @property {object} extData - the data for an installed extension
282
- */
283
-
284
281
  /**
285
282
  * Get the text which should be displayed to the user after an extension has been installed. This
286
283
  * is designed to be overridden by drivers/plugins with their own particular text.
287
284
  *
288
285
  * @param {ExtensionArgs} args
286
+ * @returns {string}
289
287
  */
290
- getPostInstallText (/*{extName, extData}*/) {
288
+ // eslint-disable-next-line no-unused-vars
289
+ getPostInstallText (args) {
291
290
  throw new Error('Must be implemented in final class');
292
291
  }
293
292
 
@@ -316,30 +315,25 @@ export default class ExtensionCommand {
316
315
  * presence and form of those fields on the package.json data, throwing an error if anything is
317
316
  * amiss.
318
317
  *
319
- * @param {object} appiumPkgData - the data in the "appium" field of package.json for an
320
- * extension
318
+ * @param {import('../extension/manifest').ExternalData<ExtType>} appiumPkgData - the data in the "appium" field of package.json for an extension
321
319
  */
322
- validateExtensionFields (/*appiumPkgData*/) {
320
+ // eslint-disable-next-line no-unused-vars
321
+ validateExtensionFields (appiumPkgData) {
323
322
  throw new Error('Must be implemented in final class');
324
323
  }
325
-
326
- /**
327
- * @typedef {Object} UninstallArgs
328
- * @property {string} ext - the name or spec of an extension to uninstall
329
- */
330
-
331
324
  /**
332
325
  * Uninstall an extension
333
326
  *
334
- * @param {UninstallArgs} args
335
- * @return {object} map of all installed extension names to extension data
327
+ * @param {UninstallOpts} opts
328
+ * @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
336
329
  */
337
330
  async uninstall ({ext}) {
338
331
  if (!this.config.isInstalled(ext)) {
339
332
  throw new Error(`Can't uninstall ${this.type} '${ext}'; it is not installed`);
340
333
  }
334
+ const installPath = this.config.getInstallPath(ext);
341
335
  try {
342
- await fs.rimraf(this.config.getInstallPath(ext));
336
+ await fs.rimraf(installPath);
343
337
  } finally {
344
338
  await this.config.removeExtension(ext);
345
339
  }
@@ -347,30 +341,11 @@ export default class ExtensionCommand {
347
341
  return this.config.installedExtensions;
348
342
  }
349
343
 
350
- /**
351
- * @typedef {Object} ExtensionUpdateOpts
352
- * @property {string} ext - the name of the extension to update
353
- * @property {boolean} unsafe - if true, will perform unsafe updates past major revision
354
- * boundaries
355
- */
356
-
357
- /**
358
- * @typedef {Object} UpdateReport
359
- * @property {string} from - version updated from
360
- * @property {string} to - version updated to
361
- */
362
-
363
- /**
364
- * @typedef {Object} ExtensionUpdateResult
365
- * @property {Object} errors - map of ext names to error objects
366
- * @property {Object} updates - map of ext names to {@link UpdateReport}s
367
- */
368
-
369
344
  /**
370
345
  * Attempt to update one or more drivers using NPM
371
346
  *
372
347
  * @param {ExtensionUpdateOpts} updateSpec
373
- * @return {ExtensionUpdateResult}
348
+ * @return {Promise<ExtensionUpdateResult>}
374
349
  */
375
350
  async update ({ext, unsafe}) {
376
351
  const shouldUpdateAll = ext === UPDATE_ALL;
@@ -381,10 +356,12 @@ export default class ExtensionCommand {
381
356
  const extsToUpdate = shouldUpdateAll ? Object.keys(this.config.installedExtensions) : [ext];
382
357
 
383
358
  // 'errors' will have ext names as keys and error objects as values
359
+ /** @type {Record<string,Error>} */
384
360
  const errors = {};
385
361
 
386
362
  // 'updates' will have ext names as keys and update objects as values, where an update
387
363
  // object is of the form {from: versionString, to: versionString}
364
+ /** @type {Record<string,UpdateReport>} */
388
365
  const updates = {};
389
366
 
390
367
  for (const e of extsToUpdate) {
@@ -437,27 +414,20 @@ export default class ExtensionCommand {
437
414
  return {updates, errors};
438
415
  }
439
416
 
440
- /**
441
- * @typedef PossibleUpdates
442
- * @property {string} current - current version
443
- * @property {string|null} safeUpdate - version we can safely update to if it exists, or null
444
- * @property {string|null} unsafeUpdate - version we can unsafely update to if it exists, or null
445
- */
446
-
447
417
  /**
448
418
  * Given an extension name, figure out what its highest possible version upgrade is, and also the
449
419
  * highest possible safe upgrade.
450
420
  *
451
421
  * @param {string} ext - name of extension
452
- * @return {PossibleUpdates}
422
+ * @return {Promise<PossibleUpdates>}
453
423
  */
454
424
  async checkForExtensionUpdate (ext) {
455
425
  // TODO decide how we want to handle beta versions?
456
426
  // this is a helper method, 'ext' is assumed to already be installed here, and of the npm
457
427
  // install type
458
428
  const {version, pkgName} = this.config.installedExtensions[ext];
459
- let unsafeUpdate = await this.npm.getLatestVersion(pkgName);
460
- let safeUpdate = await this.npm.getLatestSafeUpgradeVersion(pkgName, version);
429
+ let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
430
+ let safeUpdate = await npm.getLatestSafeUpgradeVersion(this.config.appiumHome, pkgName, version);
461
431
  if (!util.compareVersions(unsafeUpdate, '>', version)) {
462
432
  // the latest version is not greater than the current version, so there's no possible update
463
433
  unsafeUpdate = null;
@@ -485,7 +455,7 @@ export default class ExtensionCommand {
485
455
  const {pkgName} = this.config.installedExtensions[ext];
486
456
  await fs.rimraf(this.config.getInstallPath(ext));
487
457
  const extData = await this.installViaNpm({ext, pkgName, pkgVer: version});
488
- delete extData[`${this.type}Name`];
458
+ delete extData[/** @type {string} */(`${this.type}Name`)];
489
459
  await this.config.updateExtension(ext, extData);
490
460
  }
491
461
 
@@ -497,9 +467,8 @@ export default class ExtensionCommand {
497
467
  * "scripts" field is not a plain object, or if the scriptName is
498
468
  * not found within "scripts" object.
499
469
  *
500
- * @param {string} ext - name of the extension to run a script from
501
- * @param {string} scriptName - name of the script to run
502
- * @return {RunOutput}
470
+ * @param {RunOptions} opts
471
+ * @return {Promise<RunOutput>}
503
472
  */
504
473
  async run ({ext, scriptName}) {
505
474
  if (!_.has(this.config.installedExtensions, ext)) {
@@ -508,7 +477,8 @@ export default class ExtensionCommand {
508
477
 
509
478
  const extConfig = this.config.installedExtensions[ext];
510
479
 
511
- if (!_.has(extConfig, 'scripts')) {
480
+ // note: TS cannot understand that _.has() is a type guard
481
+ if (!extConfig.scripts) {
512
482
  throw new Error(`The ${this.type} named '${ext}' does not contain the ` +
513
483
  `"scripts" field underneath the "appium" field in its package.json`);
514
484
  }
@@ -523,8 +493,10 @@ export default class ExtensionCommand {
523
493
  throw new Error(`The ${this.type} named '${ext}' does not support the script: '${scriptName}'`);
524
494
  }
525
495
 
526
- const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
527
- cwd: this.config.getExtensionRequirePath(ext)
496
+ const runner = new SubProcess(process.execPath, [
497
+ extScripts[scriptName]
498
+ ], {
499
+ cwd: this.config.getInstallPath(ext)
528
500
  });
529
501
 
530
502
  const output = new RingBuffer(50);
@@ -548,7 +520,116 @@ export default class ExtensionCommand {
548
520
  }
549
521
 
550
522
  /**
551
- * @typedef {Object} RunOutput
552
- * @property {string|undefined} error - error message if script ran unsuccessfully, otherwise undefined
523
+ * @template {ExtensionType} ExtType
524
+ * @typedef {import('../extension/extension-config').ExtensionConfig<ExtType>} ExtensionConfig
525
+ */
526
+
527
+ /**
528
+ * Options for the {@linkcode ExtensionCommand} constructor
529
+ * @template {ExtensionType} ExtType
530
+ * @typedef ExtensionCommandOptions
531
+ * @property {ExtensionConfig<ExtType>} config - the `DriverConfig` or `PluginConfig` instance used for this command
532
+ * @property {boolean} json - whether the output of this command should be JSON or text
533
+ */
534
+
535
+ /**
536
+ * Extra stuff about extensions; used indirectly by {@linkcode ExtensionCommand.list}.
537
+ *
538
+ * @typedef ExtensionMetadata
539
+ * @property {boolean} installed - If `true`, the extension is installed
540
+ * @property {string|null} [updateVersion] - If the extension is installed, the version it can be updated to
541
+ * @property {string|null} [unsafeUpdateVersion] - Same as above, but a major version bump
542
+ * @property {boolean} [upToDate] - If the extension is installed and the latest
543
+ */
544
+
545
+ /**
546
+ * @typedef {import('../extension/manifest').ExtensionType} ExtensionType
547
+ */
548
+
549
+ /**
550
+ * @template {ExtensionType} ExtType
551
+ * @typedef {import('../extension/manifest').ExtRecord<ExtType>} ExtRecord
552
+ */
553
+
554
+ /**
555
+ * @template {ExtensionType} ExtType
556
+ * @typedef {import('../extension/manifest').ExtData<ExtType>} ExtData
557
+ */
558
+
559
+ /**
560
+ * Return value of {@linkcode ExtensionCommand.list}.
561
+ * @typedef {Record<string, (import('../extension/manifest').InternalData & ExtensionMetadata) | { pkgName: string, installed: false }>} ExtensionListData
562
+ */
563
+
564
+ /**
565
+ * Options for {@linkcode ExtensionCommand.run}.
566
+ * @typedef RunOptions
567
+ * @property {string} ext - name of the extension to run a script from
568
+ * @property {string} scriptName - name of the script to run
569
+ */
570
+
571
+ /**
572
+ * Return value of {@linkcode ExtensionCommand.run}
573
+ *
574
+ * @typedef RunOutput
575
+ * @property {string} [error] - error message if script ran unsuccessfully, otherwise undefined
553
576
  * @property {string[]} output - script output
554
577
  */
578
+
579
+ /**
580
+ * Options for {@linkcode ExtensionCommand.update}.
581
+ * @typedef ExtensionUpdateOpts
582
+ * @property {string} ext - the name of the extension to update
583
+ * @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
584
+ */
585
+
586
+ /**
587
+ * Return value of {@linkcode ExtensionCommand.update}.
588
+ * @typedef ExtensionUpdateResult
589
+ * @property {Record<string,Error>} errors - map of ext names to error objects
590
+ * @property {Record<string,UpdateReport>} updates - map of ext names to {@linkcode UpdateReport}s
591
+ */
592
+
593
+ /**
594
+ * Part of result of {@linkcode ExtensionCommand.update}.
595
+ * @typedef UpdateReport
596
+ * @property {string} from - version the extension was updated from
597
+ * @property {string} to - version the extension was updated to
598
+ */
599
+
600
+ /**
601
+ * Options for {@linkcode ExtensionCommand.uninstall}.
602
+ * @typedef UninstallOpts
603
+ * @property {string} ext - the name or spec of an extension to uninstall
604
+ */
605
+
606
+ /**
607
+ * Used by {@linkcode ExtensionCommand.getPostInstallText}
608
+ * @typedef ExtensionArgs
609
+ * @property {string} extName - the name of an extension
610
+ * @property {object} extData - the data for an installed extension
611
+ */
612
+
613
+ /**
614
+ * Options for {@linkcode ExtensionCommand.installViaNpm}
615
+ * @typedef InstallViaNpmArgs
616
+ * @property {string} ext - the name or spec of an extension to install
617
+ * @property {string} pkgName - the NPM package name of the extension
618
+ * @property {string} [pkgVer] - the specific version of the NPM package
619
+ */
620
+
621
+ /**
622
+ * Object returned by {@linkcode ExtensionCommand.checkForExtensionUpdate}
623
+ * @typedef PossibleUpdates
624
+ * @property {string} current - current version
625
+ * @property {string?} safeUpdate - version we can safely update to if it exists, or null
626
+ * @property {string?} unsafeUpdate - version we can unsafely update to if it exists, or null
627
+ */
628
+
629
+ /**
630
+ * Options for {@linkcode ExtensionCommand.install}
631
+ * @typedef InstallArgs
632
+ * @property {string} ext - the name or spec of an extension to install
633
+ * @property {import('../extension/extension-config').InstallType} installType - how to install this extension. One of the INSTALL_TYPES
634
+ * @property {string} [packageName] - for git/github installs, the extension node package name
635
+ */
@@ -2,11 +2,8 @@
2
2
 
3
3
  import DriverCommand from './driver-command';
4
4
  import PluginCommand from './plugin-command';
5
- import DriverConfig from '../driver-config';
6
- import PluginConfig from '../plugin-config';
7
- import { DRIVER_TYPE } from '../extension-config';
5
+ import { DRIVER_TYPE } from '../constants';
8
6
  import { errAndQuit, log, JSON_SPACES } from './utils';
9
- import { APPIUM_HOME } from './args';
10
7
 
11
8
  /**
12
9
  * Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
@@ -14,13 +11,14 @@ import { APPIUM_HOME } from './args';
14
11
  *
15
12
  * @param {Object} args - JS object where the key is the parameter name (as defined in
16
13
  * driver-parser.js)
17
- * @param {import('../ext-config-io').ExtensionType} type - Extension type
18
- * @oaram {ExtensionConfig} [configObject] - Extension config object, if we have one
14
+ * @template {ExtensionType} ExtType
15
+ * @param {import('../extension/extension-config').ExtensionConfig<ExtType>} configObject - Extension config object
19
16
  */
20
- async function runExtensionCommand (args, type, configObject) {
17
+ async function runExtensionCommand (args, configObject) {
21
18
  // TODO driver config file should be locked while any of these commands are
22
19
  // running to prevent weird situations
23
20
  let jsonResult = null;
21
+ const {extensionType: type} = configObject;
24
22
  const extCmd = args[`${type}Command`];
25
23
  if (!extCmd) {
26
24
  throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
@@ -30,17 +28,11 @@ async function runExtensionCommand (args, type, configObject) {
30
28
  json = true;
31
29
  }
32
30
  const logFn = (msg) => log(json, msg);
33
- let config;
34
- if (configObject) {
35
- config = configObject;
36
- config.log = logFn;
37
- } else {
38
- config = (type === DRIVER_TYPE ? DriverConfig : PluginConfig).getInstance(APPIUM_HOME, logFn);
39
- }
31
+ let config = configObject;
32
+ config.log = logFn;
40
33
  const CommandClass = type === DRIVER_TYPE ? DriverCommand : PluginCommand;
41
34
  const cmd = new CommandClass({config, json});
42
35
  try {
43
- await config.read();
44
36
  jsonResult = await cmd.execute(args);
45
37
  } catch (err) {
46
38
  // in the suppress output case, we are calling this function internally and should
package/lib/cli/parser.js CHANGED
@@ -2,21 +2,16 @@
2
2
 
3
3
  import { fs } from '@appium/support';
4
4
  import { ArgumentParser } from 'argparse';
5
- import B from 'bluebird';
6
5
  import _ from 'lodash';
7
6
  import path from 'path';
8
- import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
7
+ import { DRIVER_TYPE, PLUGIN_TYPE, SERVER_SUBCOMMAND } from '../constants';
9
8
  import { finalizeSchema, getArgSpec, hasArgSpec } from '../schema';
10
- import { rootDir } from '../utils';
9
+ import { rootDir } from '../config';
11
10
  import {
12
- driverConfig,
13
11
  getExtensionArgs,
14
- getServerArgs,
15
- pluginConfig
12
+ getServerArgs
16
13
  } from './args';
17
14
 
18
- export const SERVER_SUBCOMMAND = 'server';
19
-
20
15
  /**
21
16
  * If the parsed args do not contain any of these values, then we
22
17
  * will automatially inject the `server` subcommand.
@@ -258,19 +253,16 @@ class ArgParser {
258
253
  }
259
254
 
260
255
  /**
261
- * Creates a {@link ArgParser} instance. Necessarily reads extension configuration
262
- * beforehand, and finalizes the config schema.
256
+ * Creates a {@link ArgParser} instance; finalizes the config schema.
263
257
  *
264
258
  * @constructs ArgParser
265
259
  * @param {boolean} [debug] - If `true`, throw instead of exit upon parsing error
266
- * @returns {Promise<ArgParser>}
260
+ * @returns {ArgParser}
267
261
  */
268
- async function getParser (debug = false) {
269
- await B.all([driverConfig.read(), pluginConfig.read()]);
262
+ function getParser (debug) {
270
263
  finalizeSchema();
271
264
 
272
265
  return new ArgParser(debug);
273
266
  }
274
267
 
275
- export default getParser;
276
268
  export { getParser, ArgParser };
@@ -1,7 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import ExtensionCommand from './extension-command';
3
- import { PLUGIN_TYPE } from '../extension-config';
4
- import { KNOWN_PLUGINS } from '../plugins';
3
+ import { PLUGIN_TYPE, KNOWN_PLUGINS } from '../constants';
5
4
 
6
5
  const REQ_PLUGIN_FIELDS = ['pluginName', 'mainClass'];
7
6