appium 2.0.0-beta.25 → 2.0.0-beta.28

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 (191) hide show
  1. package/build/lib/appium.d.ts +215 -0
  2. package/build/lib/appium.d.ts.map +1 -0
  3. package/build/lib/appium.js +94 -101
  4. package/build/lib/cli/args.d.ts +20 -0
  5. package/build/lib/cli/args.d.ts.map +1 -0
  6. package/build/lib/cli/args.js +19 -39
  7. package/build/lib/cli/driver-command.d.ts +36 -0
  8. package/build/lib/cli/driver-command.d.ts.map +1 -0
  9. package/build/lib/cli/driver-command.js +10 -13
  10. package/build/lib/cli/extension-command.d.ts +345 -0
  11. package/build/lib/cli/extension-command.d.ts.map +1 -0
  12. package/build/lib/cli/extension-command.js +117 -94
  13. package/build/lib/cli/extension.d.ts +14 -0
  14. package/build/lib/cli/extension.d.ts.map +1 -0
  15. package/build/lib/cli/extension.js +14 -22
  16. package/build/lib/cli/parser.d.ts +79 -0
  17. package/build/lib/cli/parser.d.ts.map +1 -0
  18. package/build/lib/cli/parser.js +9 -19
  19. package/build/lib/cli/plugin-command.d.ts +39 -0
  20. package/build/lib/cli/plugin-command.d.ts.map +1 -0
  21. package/build/lib/cli/plugin-command.js +9 -14
  22. package/build/lib/cli/utils.d.ts +29 -0
  23. package/build/lib/cli/utils.d.ts.map +1 -0
  24. package/build/lib/cli/utils.js +2 -4
  25. package/build/lib/config-file.d.ts +100 -0
  26. package/build/lib/config-file.d.ts.map +1 -0
  27. package/build/lib/config-file.js +2 -4
  28. package/build/lib/config.d.ts +40 -0
  29. package/build/lib/config.d.ts.map +1 -0
  30. package/build/lib/config.js +8 -7
  31. package/build/lib/constants.d.ts +48 -0
  32. package/build/lib/constants.d.ts.map +1 -0
  33. package/build/lib/constants.js +60 -0
  34. package/build/lib/extension/driver-config.d.ts +84 -0
  35. package/build/lib/extension/driver-config.d.ts.map +1 -0
  36. package/build/lib/extension/driver-config.js +190 -0
  37. package/build/lib/extension/extension-config.d.ts +170 -0
  38. package/build/lib/extension/extension-config.d.ts.map +1 -0
  39. package/build/lib/extension/extension-config.js +297 -0
  40. package/build/lib/extension/index.d.ts +39 -0
  41. package/build/lib/extension/index.d.ts.map +1 -0
  42. package/build/lib/extension/index.js +77 -0
  43. package/build/lib/extension/manifest.d.ts +174 -0
  44. package/build/lib/extension/manifest.d.ts.map +1 -0
  45. package/build/lib/extension/manifest.js +246 -0
  46. package/build/lib/extension/package-changed.d.ts +11 -0
  47. package/build/lib/extension/package-changed.d.ts.map +1 -0
  48. package/build/lib/extension/package-changed.js +68 -0
  49. package/build/lib/extension/plugin-config.d.ts +62 -0
  50. package/build/lib/extension/plugin-config.d.ts.map +1 -0
  51. package/build/lib/extension/plugin-config.js +87 -0
  52. package/build/lib/grid-register.d.ts +10 -0
  53. package/build/lib/grid-register.d.ts.map +1 -0
  54. package/build/lib/grid-register.js +2 -4
  55. package/build/lib/logger.d.ts +3 -0
  56. package/build/lib/logger.d.ts.map +1 -0
  57. package/build/lib/logger.js +2 -4
  58. package/build/lib/logsink.d.ts +4 -0
  59. package/build/lib/logsink.d.ts.map +1 -0
  60. package/build/lib/logsink.js +2 -4
  61. package/build/lib/main.d.ts +51 -0
  62. package/build/lib/main.d.ts.map +1 -0
  63. package/build/lib/main.js +40 -68
  64. package/build/lib/schema/arg-spec.d.ts +143 -0
  65. package/build/lib/schema/arg-spec.d.ts.map +1 -0
  66. package/build/lib/schema/arg-spec.js +11 -14
  67. package/build/lib/schema/cli-args.d.ts +19 -0
  68. package/build/lib/schema/cli-args.d.ts.map +1 -0
  69. package/build/lib/schema/cli-args.js +2 -4
  70. package/build/lib/schema/cli-transformers.d.ts +5 -0
  71. package/build/lib/schema/cli-transformers.d.ts.map +1 -0
  72. package/build/lib/schema/cli-transformers.js +2 -4
  73. package/build/lib/schema/index.d.ts +3 -0
  74. package/build/lib/schema/index.d.ts.map +1 -0
  75. package/build/lib/schema/index.js +2 -4
  76. package/build/lib/schema/keywords.d.ts +24 -0
  77. package/build/lib/schema/keywords.d.ts.map +1 -0
  78. package/build/lib/schema/keywords.js +2 -4
  79. package/build/lib/schema/schema.d.ts +259 -0
  80. package/build/lib/schema/schema.d.ts.map +1 -0
  81. package/build/lib/schema/schema.js +57 -39
  82. package/build/lib/utils.d.ts +66 -0
  83. package/build/lib/utils.d.ts.map +1 -0
  84. package/build/lib/utils.js +6 -35
  85. package/build/tsconfig.tsbuildinfo +1 -0
  86. package/lib/appium.js +188 -117
  87. package/lib/cli/args.js +19 -24
  88. package/lib/cli/driver-command.js +19 -8
  89. package/lib/cli/extension-command.js +314 -184
  90. package/lib/cli/extension.js +18 -16
  91. package/lib/cli/parser.js +7 -16
  92. package/lib/cli/plugin-command.js +16 -7
  93. package/lib/cli/utils.js +1 -1
  94. package/lib/config-file.js +6 -7
  95. package/lib/config.js +17 -12
  96. package/lib/constants.js +78 -0
  97. package/lib/extension/driver-config.js +249 -0
  98. package/lib/extension/extension-config.js +458 -0
  99. package/lib/extension/index.js +102 -0
  100. package/lib/extension/manifest.js +486 -0
  101. package/lib/extension/package-changed.js +63 -0
  102. package/lib/extension/plugin-config.js +113 -0
  103. package/lib/grid-register.js +4 -4
  104. package/lib/logsink.js +4 -0
  105. package/lib/main.js +54 -92
  106. package/lib/schema/arg-spec.js +11 -7
  107. package/lib/schema/cli-args.js +1 -1
  108. package/lib/schema/cli-transformers.js +0 -1
  109. package/lib/schema/keywords.js +1 -2
  110. package/lib/schema/schema.js +62 -31
  111. package/lib/utils.js +48 -45
  112. package/package.json +30 -24
  113. package/{postinstall.js → scripts/postinstall.js} +1 -1
  114. package/types/appium-manifest.d.ts +61 -0
  115. package/types/cli.d.ts +134 -0
  116. package/types/extension.d.ts +56 -0
  117. package/types/external-manifest.d.ts +58 -0
  118. package/types/index.d.ts +7 -0
  119. package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
  120. package/build/check-npm-pack-files.js +0 -23
  121. package/build/commands-yml/parse.js +0 -319
  122. package/build/commands-yml/validator.js +0 -130
  123. package/build/index.js +0 -19
  124. package/build/lib/appium-config.schema.json +0 -0
  125. package/build/lib/cli/npm.js +0 -220
  126. package/build/lib/driver-config.js +0 -100
  127. package/build/lib/drivers.js +0 -100
  128. package/build/lib/ext-config-io.js +0 -165
  129. package/build/lib/extension-config.js +0 -320
  130. package/build/lib/plugin-config.js +0 -69
  131. package/build/lib/plugins.js +0 -18
  132. package/build/lib/schema/appium-config-schema.js +0 -253
  133. package/build/postinstall.js +0 -90
  134. package/build/test/cli/cli-e2e-specs.js +0 -221
  135. package/build/test/cli/cli-helpers.js +0 -86
  136. package/build/test/cli/cli-specs.js +0 -71
  137. package/build/test/cli/fixtures/test-driver/package.json +0 -27
  138. package/build/test/cli/schema-args-specs.js +0 -48
  139. package/build/test/cli/schema-e2e-specs.js +0 -47
  140. package/build/test/config-e2e-specs.js +0 -112
  141. package/build/test/config-file-e2e-specs.js +0 -191
  142. package/build/test/config-file-specs.js +0 -281
  143. package/build/test/config-specs.js +0 -258
  144. package/build/test/driver-e2e-specs.js +0 -435
  145. package/build/test/driver-specs.js +0 -386
  146. package/build/test/ext-config-io-specs.js +0 -181
  147. package/build/test/extension-config-specs.js +0 -365
  148. package/build/test/fixtures/allow-feat.txt +0 -5
  149. package/build/test/fixtures/caps.json +0 -3
  150. package/build/test/fixtures/config/allow-insecure.txt +0 -3
  151. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +0 -5
  152. package/build/test/fixtures/config/appium.config.bad.json +0 -32
  153. package/build/test/fixtures/config/appium.config.ext-good.json +0 -9
  154. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +0 -10
  155. package/build/test/fixtures/config/appium.config.good.js +0 -40
  156. package/build/test/fixtures/config/appium.config.good.json +0 -33
  157. package/build/test/fixtures/config/appium.config.good.yaml +0 -30
  158. package/build/test/fixtures/config/appium.config.invalid.json +0 -31
  159. package/build/test/fixtures/config/appium.config.security-array.json +0 -5
  160. package/build/test/fixtures/config/appium.config.security-delimited.json +0 -5
  161. package/build/test/fixtures/config/appium.config.security-path.json +0 -5
  162. package/build/test/fixtures/config/driver-fake.config.json +0 -8
  163. package/build/test/fixtures/config/nodeconfig.json +0 -3
  164. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  165. package/build/test/fixtures/default-args.js +0 -35
  166. package/build/test/fixtures/deny-feat.txt +0 -5
  167. package/build/test/fixtures/driver.schema.js +0 -20
  168. package/build/test/fixtures/extensions.yaml +0 -27
  169. package/build/test/fixtures/flattened-schema.js +0 -532
  170. package/build/test/fixtures/plugin.schema.js +0 -20
  171. package/build/test/fixtures/schema-with-extensions.js +0 -28
  172. package/build/test/grid-register-specs.js +0 -74
  173. package/build/test/helpers.js +0 -75
  174. package/build/test/logger-specs.js +0 -76
  175. package/build/test/npm-specs.js +0 -20
  176. package/build/test/parser-specs.js +0 -319
  177. package/build/test/plugin-e2e-specs.js +0 -316
  178. package/build/test/schema/arg-spec-specs.js +0 -70
  179. package/build/test/schema/cli-args-specs.js +0 -408
  180. package/build/test/schema/schema-specs.js +0 -407
  181. package/build/test/utils-specs.js +0 -288
  182. package/lib/cli/npm.js +0 -251
  183. package/lib/driver-config.js +0 -101
  184. package/lib/drivers.js +0 -84
  185. package/lib/ext-config-io.js +0 -287
  186. package/lib/extension-config.js +0 -366
  187. package/lib/plugin-config.js +0 -63
  188. package/lib/plugins.js +0 -13
  189. package/lib/schema/appium-config-schema.js +0 -287
  190. package/types/appium-config.d.ts +0 -197
  191. package/types/types.d.ts +0 -206
@@ -1,51 +1,66 @@
1
1
  /* eslint-disable no-console */
2
2
 
3
3
  import _ from 'lodash';
4
- import NPM from './npm';
5
4
  import path from 'path';
6
- import { fs, util } from '@appium/support';
5
+ import { npm, fs, util, env } 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';
10
+ import { packageDidChange } from '../extension/package-changed';
11
11
 
12
12
  const UPDATE_ALL = 'installed';
13
13
 
14
14
  class NotUpdatableError extends Error {}
15
15
  class NoUpdatesAvailableError extends Error {}
16
16
 
17
- export default class ExtensionCommand {
17
+ /**
18
+ * @template {ExtensionType} ExtType
19
+ */
20
+ class ExtensionCommand {
21
+ /**
22
+ * This is the `DriverConfig` or `PluginConfig`, depending on `ExtType`.
23
+ * @type {ExtensionConfig<ExtType>}
24
+ */
25
+ config;
18
26
 
19
27
  /**
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
28
+ * {@linkcode Record} of official plugins or drivers.
29
+ * @type {KnownExtensions<ExtType>}
24
30
  */
31
+ knownExtensions;
32
+
33
+ /**
34
+ * If `true`, command output has been requested as JSON.
35
+ * @type {boolean}
36
+ */
37
+ isJsonOutput;
25
38
 
26
39
  /**
27
40
  * Build an ExtensionCommand
28
- *
29
- * @param {ExtensionCommandConstructor} opts
30
- * @return {ExtensionCommand}
41
+ * @param {ExtensionCommandOptions<ExtType>} opts
31
42
  */
32
- constructor ({config, json, type}) {
43
+ constructor ({config, json}) {
33
44
  this.config = config;
34
- this.type = type;
35
45
  this.isJsonOutput = json;
36
- this.npm = new NPM(this.config.appiumHome);
37
- this.knownExtensions = {}; // this needs to be overridden in final class
46
+ }
47
+
48
+ /**
49
+ * `driver` or `plugin`, depending on the `ExtensionConfig`.
50
+ */
51
+ get type () {
52
+ return this.config.extensionType;
38
53
  }
39
54
 
40
55
  /**
41
56
  * Take a CLI parse and run an extension command based on its type
42
57
  *
43
58
  * @param {object} args - a key/value object with CLI flags and values
44
- * @return {object} the result of the specific command which is executed
59
+ * @return {Promise<object>} the result of the specific command which is executed
45
60
  */
46
61
  async execute (args) {
47
62
  const cmd = args[`${this.type}Command`];
48
- if (!_.isFunction(ExtensionCommand.prototype[cmd])) {
63
+ if (!_.isFunction(this[cmd])) {
49
64
  throw new Error(`Cannot handle ${this.type} command ${cmd}`);
50
65
  }
51
66
  const executeCmd = this[cmd].bind(this);
@@ -53,7 +68,7 @@ export default class ExtensionCommand {
53
68
  }
54
69
 
55
70
  /**
56
- * @typedef {Object} ListArgs
71
+ * @typedef ListOptions
57
72
  * @property {boolean} showInstalled - whether should show only installed extensions
58
73
  * @property {boolean} showUpdates - whether should show available updates
59
74
  */
@@ -61,8 +76,8 @@ export default class ExtensionCommand {
61
76
  /**
62
77
  * List extensions
63
78
  *
64
- * @param {ListArgs} args
65
- * @return {object} map of extension names to extension data
79
+ * @param {ListOptions} opts
80
+ * @return {Promise<ExtensionListData>} map of extension names to extension data
66
81
  */
67
82
  async list ({showInstalled, showUpdates}) {
68
83
  const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} ${this.type}s`;
@@ -77,7 +92,12 @@ export default class ExtensionCommand {
77
92
  }
78
93
  }
79
94
  return acc;
80
- }, {});
95
+ },
96
+ /**
97
+ * This accumulator contains either {@linkcode UninstalledExtensionLIstData} _or_
98
+ * {@linkcode InstalledExtensionListData} without upgrade information (which is added by the below code block)
99
+ * @type {Record<string,Partial<InstalledExtensionListData>|UninstalledExtensionListData>}
100
+ */({}));
81
101
 
82
102
  // if we want to show whether updates are available, put that behind a spinner
83
103
  await spinWith(this.isJsonOutput, lsMsg, async () => {
@@ -85,8 +105,7 @@ export default class ExtensionCommand {
85
105
  return;
86
106
  }
87
107
  for (const [ext, data] of _.toPairs(exts)) {
88
- const {installed, installType} = data;
89
- if (!installed || installType !== INSTALL_TYPE_NPM) {
108
+ if (!data.installed || data.installType !== INSTALL_TYPE_NPM) {
90
109
  // don't need to check for updates on exts that aren't installed
91
110
  // also don't need to check for updates on non-npm exts
92
111
  continue;
@@ -98,63 +117,64 @@ export default class ExtensionCommand {
98
117
  }
99
118
  });
100
119
 
120
+ const listData = /** @type {ExtensionListData} */(exts);
121
+
101
122
  // if we're just getting the data, short circuit return here since we don't need to do any
102
123
  // formatting logic
103
124
  if (this.isJsonOutput) {
104
- return exts;
125
+ return listData;
105
126
  }
106
127
 
107
128
  for (const [
108
129
  name,
109
- {installType, installSpec, installed, updateVersion, unsafeUpdateVersion, version, upToDate}
110
- ] 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)';
130
+ data
131
+ ] of _.toPairs(listData)) {
132
+ let installTxt = ' [not installed]'.grey;
133
+ let updateTxt = '';
134
+ let upToDateTxt = '';
135
+ let unsafeUpdateTxt = '';
136
+ if (data.installed) {
137
+ const {installType, installSpec, updateVersion, unsafeUpdateVersion, version, upToDate} = data;
138
+ let typeTxt;
139
+ switch (installType) {
140
+ case INSTALL_TYPE_GIT:
141
+ case INSTALL_TYPE_GITHUB:
142
+ typeTxt = `(cloned from ${installSpec})`.yellow;
143
+ break;
144
+ case INSTALL_TYPE_LOCAL:
145
+ typeTxt = `(linked from ${installSpec})`.magenta;
146
+ break;
147
+ default:
148
+ typeTxt = '(NPM)';
149
+ }
150
+ installTxt = `@${version.yellow} ${('[installed ' + typeTxt + ']').green}`;
151
+
152
+ if (showUpdates) {
153
+ if (updateVersion) {
154
+ updateTxt = ` [${updateVersion} available]`.magenta;
155
+ }
156
+ if (upToDate) {
157
+ upToDateTxt = ` [Up to date]`.green;
158
+ }
159
+ if (unsafeUpdateVersion) {
160
+ unsafeUpdateTxt = ` [${unsafeUpdateVersion} available (potentially unsafe)]`.cyan;
161
+ }
162
+ }
122
163
  }
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
164
 
136
165
  console.log(`- ${name.yellow}${installTxt}${updateTxt}${upToDateTxt}${unsafeUpdateTxt}`);
137
166
  }
138
167
 
139
- return exts;
168
+ return listData;
140
169
  }
141
170
 
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
171
  /**
150
172
  * Install an extension
151
173
  *
152
174
  * @param {InstallArgs} args
153
- * @return {object} map of all installed extension names to extension data
175
+ * @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
154
176
  */
155
- async install ({ext, installType, packageName}) {
156
- log(this.isJsonOutput, `Attempting to find and install ${this.type} '${ext}'`);
157
-
177
+ async _install ({ext, installType, packageName}) {
158
178
  let extData;
159
179
  let installSpec = ext;
160
180
 
@@ -166,66 +186,63 @@ export default class ExtensionCommand {
166
186
  throw new Error(`When using --source=${installType}, must also use --package`);
167
187
  }
168
188
 
169
- if (installType === INSTALL_TYPE_LOCAL) {
170
- const msg = `Linking ${this.type} from local path`;
171
- const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () => (
172
- await this.npm.linkPackage(installSpec))
173
- );
174
- extData = this.getExtensionFields(pkgJsonData);
175
- extData.installPath = extData.pkgName;
176
- } else if (installType === INSTALL_TYPE_GITHUB) {
189
+ 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
- // at this point we have either an npm package or an appium verified extension
189
- // name. both of which will be installed via npm.
190
- // extensions installed via npm can include versions or tags after the '@'
191
- // sign, so check for that. We also need to be careful that package names themselves can
192
- // contain the '@' symbol, as in `npm install @appium/fake-driver@1.2.0`
193
- let name, pkgVer;
194
- const splits = installSpec.split('@');
195
- if (installSpec[0] === '@') {
196
- // this is the case where we have an npm org included in the package name
197
- [name, pkgVer] = [`@${splits[1]}`, splits[2]];
201
+ let pkgName, pkgVer;
202
+ if (installType === INSTALL_TYPE_LOCAL) {
203
+ pkgName = path.isAbsolute(installSpec) ? installSpec : path.resolve(installSpec);
198
204
  } else {
199
- // this is the case without an npm org
200
- [name, pkgVer] = splits;
201
- }
202
- let pkgName;
205
+ // at this point we have either an npm package or an appium verified extension
206
+ // name or a local path. both of which will be installed via npm.
207
+ // extensions installed via npm can include versions or tags after the '@'
208
+ // sign, so check for that. We also need to be careful that package names themselves can
209
+ // contain the '@' symbol, as in `npm install @appium/fake-driver@1.2.0`
210
+ let name;
211
+ const splits = installSpec.split('@');
212
+ if (installSpec[0] === '@') {
213
+ // this is the case where we have an npm org included in the package name
214
+ [name, pkgVer] = [`@${splits[1]}`, splits[2]];
215
+ } else {
216
+ // this is the case without an npm org
217
+ [name, pkgVer] = splits;
218
+ }
203
219
 
204
- if (installType === INSTALL_TYPE_NPM) {
205
- // if we're installing a named package from npm, we don't need to check
206
- // against the appium extension list; just use the installSpec as is
207
- pkgName = name;
208
- } else {
209
- // if we're installing a named appium driver (like 'xcuitest') we need to
210
- // dereference the actual npm package ('appiupm-xcuitest-driver'), so
211
- // check it exists and get the correct package
212
- const knownNames = Object.keys(this.knownExtensions);
213
- if (!_.includes(knownNames, name)) {
214
- const msg = `Could not resolve ${this.type}; are you sure it's in the list ` +
215
- `of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
216
- throw new Error(msg);
220
+ if (installType === INSTALL_TYPE_NPM) {
221
+ // if we're installing a named package from npm, we don't need to check
222
+ // against the appium extension list; just use the installSpec as is
223
+ pkgName = name;
224
+ } else {
225
+ // if we're installing a named appium driver (like 'xcuitest') we need to
226
+ // dereference the actual npm package ('appiupm-xcuitest-driver'), so
227
+ // check it exists and get the correct package
228
+ const knownNames = Object.keys(this.knownExtensions);
229
+ if (!_.includes(knownNames, name)) {
230
+ const msg = `Could not resolve ${this.type}; are you sure it's in the list ` +
231
+ `of supported ${this.type}s? ${JSON.stringify(knownNames)}`;
232
+ throw new Error(msg);
233
+ }
234
+ pkgName = this.knownExtensions[name];
235
+ // given that we'll use the install type in the driver json, store it as
236
+ // 'npm' now
237
+ installType = INSTALL_TYPE_NPM;
217
238
  }
218
- pkgName = this.knownExtensions[name];
219
- // given that we'll use the install type in the driver json, store it as
220
- // 'npm' now
221
- installType = INSTALL_TYPE_NPM;
222
239
  }
223
240
 
224
241
  extData = await this.installViaNpm({ext, pkgName, pkgVer});
225
242
  }
226
243
 
227
- const extName = extData[`${this.type}Name`];
228
- delete extData[`${this.type}Name`];
244
+ const extName = extData[/** @type {string} */(`${this.type}Name`)];
245
+ delete extData[/** @type {string} */(`${this.type}Name`)];
229
246
 
230
247
  if (this.config.isInstalled(extName)) {
231
248
  throw new Error(`A ${this.type} named '${extName}' is already installed. ` +
@@ -233,9 +250,13 @@ export default class ExtensionCommand {
233
250
  `installed ${this.type}s with 'appium ${this.type} list --installed'.`);
234
251
  }
235
252
 
236
- extData.installType = installType;
237
- extData.installSpec = installSpec;
238
- await this.config.addExtension(extName, extData);
253
+ const extManifest = {...extData, installType, installSpec};
254
+ await this.config.addExtension(extName, extManifest);
255
+
256
+ // update the if we've changed the local `package.json`
257
+ if (await env.hasAppiumDependency(this.config.appiumHome)) {
258
+ await packageDidChange(this.config.appiumHome);
259
+ }
239
260
 
240
261
  // log info for the user
241
262
  log(this.isJsonOutput, this.getPostInstallText({extName, extData}));
@@ -243,13 +264,6 @@ export default class ExtensionCommand {
243
264
  return this.config.installedExtensions;
244
265
  }
245
266
 
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
267
  /**
254
268
  * Install an extension via NPM
255
269
  *
@@ -261,33 +275,25 @@ export default class ExtensionCommand {
261
275
  const msg = `Installing '${ext}'${specMsg}`;
262
276
  try {
263
277
  const pkgJsonData = await spinWith(this.isJsonOutput, msg, async () => (
264
- await this.npm.installPackage({
265
- pkgDir: path.resolve(this.config.appiumHome, pkgName),
266
- pkgName,
278
+ await npm.installPackage(this.config.appiumHome, pkgName, {
267
279
  pkgVer
268
280
  })
269
281
  ));
270
- const extData = this.getExtensionFields(pkgJsonData);
271
- extData.installPath = pkgName;
272
- return extData;
282
+ return this.getExtensionFields(pkgJsonData);
273
283
  } catch (err) {
274
284
  throw new Error(`Encountered an error when installing package: ${err.message}`);
275
285
  }
276
286
  }
277
287
 
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
288
  /**
285
289
  * Get the text which should be displayed to the user after an extension has been installed. This
286
290
  * is designed to be overridden by drivers/plugins with their own particular text.
287
291
  *
288
292
  * @param {ExtensionArgs} args
293
+ * @returns {string}
289
294
  */
290
- getPostInstallText (/*{extName, extData}*/) {
295
+ // eslint-disable-next-line no-unused-vars
296
+ getPostInstallText (args) {
291
297
  throw new Error('Must be implemented in final class');
292
298
  }
293
299
 
@@ -297,8 +303,9 @@ export default class ExtensionCommand {
297
303
  * load as the main driver class, or to be able to detect incompatibilities between driver and
298
304
  * appium versions.
299
305
  *
300
- * @param {object} pkgJsonData - the package.json data for a driver module, as if it had been
306
+ * @param {ExtPackageJson<ExtType>} pkgJsonData - the package.json data for a driver module, as if it had been
301
307
  * straightforwardly 'require'd
308
+ * @returns {ExtensionFields<ExtType>}
302
309
  */
303
310
  getExtensionFields (pkgJsonData) {
304
311
  if (!pkgJsonData.appium) {
@@ -307,8 +314,9 @@ export default class ExtensionCommand {
307
314
  }
308
315
  const {appium, name, version} = pkgJsonData;
309
316
  this.validateExtensionFields(appium);
310
-
311
- return {...appium, pkgName: name, version};
317
+ /** @type {unknown} */
318
+ const result = {...appium, pkgName: name, version};
319
+ return /** @type {ExtensionFields<ExtType>} */(result);
312
320
  }
313
321
 
314
322
  /**
@@ -316,30 +324,26 @@ export default class ExtensionCommand {
316
324
  * presence and form of those fields on the package.json data, throwing an error if anything is
317
325
  * amiss.
318
326
  *
319
- * @param {object} appiumPkgData - the data in the "appium" field of package.json for an
320
- * extension
327
+ * @param {ExtMetadata<ExtType>} appiumPkgData - the data in the "appium" field of package.json for an extension
321
328
  */
322
- validateExtensionFields (/*appiumPkgData*/) {
329
+ // eslint-disable-next-line no-unused-vars
330
+ validateExtensionFields (appiumPkgData) {
323
331
  throw new Error('Must be implemented in final class');
324
332
  }
325
333
 
326
- /**
327
- * @typedef {Object} UninstallArgs
328
- * @property {string} ext - the name or spec of an extension to uninstall
329
- */
330
-
331
334
  /**
332
335
  * Uninstall an extension
333
336
  *
334
- * @param {UninstallArgs} args
335
- * @return {object} map of all installed extension names to extension data
337
+ * @param {UninstallOpts} opts
338
+ * @return {Promise<ExtRecord<ExtType>>} map of all installed extension names to extension data
336
339
  */
337
- async uninstall ({ext}) {
340
+ async _uninstall ({ext}) {
338
341
  if (!this.config.isInstalled(ext)) {
339
342
  throw new Error(`Can't uninstall ${this.type} '${ext}'; it is not installed`);
340
343
  }
344
+ const installPath = this.config.getInstallPath(ext);
341
345
  try {
342
- await fs.rimraf(this.config.getInstallPath(ext));
346
+ await fs.rimraf(installPath);
343
347
  } finally {
344
348
  await this.config.removeExtension(ext);
345
349
  }
@@ -347,32 +351,13 @@ export default class ExtensionCommand {
347
351
  return this.config.installedExtensions;
348
352
  }
349
353
 
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
354
  /**
370
355
  * Attempt to update one or more drivers using NPM
371
356
  *
372
357
  * @param {ExtensionUpdateOpts} updateSpec
373
- * @return {ExtensionUpdateResult}
358
+ * @return {Promise<ExtensionUpdateResult>}
374
359
  */
375
- async update ({ext, unsafe}) {
360
+ async _update ({ext, unsafe}) {
376
361
  const shouldUpdateAll = ext === UPDATE_ALL;
377
362
  // if we're specifically requesting an update for an extension, make sure it's installed
378
363
  if (!shouldUpdateAll && !this.config.isInstalled(ext)) {
@@ -381,10 +366,12 @@ export default class ExtensionCommand {
381
366
  const extsToUpdate = shouldUpdateAll ? Object.keys(this.config.installedExtensions) : [ext];
382
367
 
383
368
  // 'errors' will have ext names as keys and error objects as values
369
+ /** @type {Record<string,Error>} */
384
370
  const errors = {};
385
371
 
386
372
  // 'updates' will have ext names as keys and update objects as values, where an update
387
373
  // object is of the form {from: versionString, to: versionString}
374
+ /** @type {Record<string,UpdateReport>} */
388
375
  const updates = {};
389
376
 
390
377
  for (const e of extsToUpdate) {
@@ -437,27 +424,20 @@ export default class ExtensionCommand {
437
424
  return {updates, errors};
438
425
  }
439
426
 
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
427
  /**
448
428
  * Given an extension name, figure out what its highest possible version upgrade is, and also the
449
429
  * highest possible safe upgrade.
450
430
  *
451
431
  * @param {string} ext - name of extension
452
- * @return {PossibleUpdates}
432
+ * @return {Promise<PossibleUpdates>}
453
433
  */
454
434
  async checkForExtensionUpdate (ext) {
455
435
  // TODO decide how we want to handle beta versions?
456
436
  // this is a helper method, 'ext' is assumed to already be installed here, and of the npm
457
437
  // install type
458
438
  const {version, pkgName} = this.config.installedExtensions[ext];
459
- let unsafeUpdate = await this.npm.getLatestVersion(pkgName);
460
- let safeUpdate = await this.npm.getLatestSafeUpgradeVersion(pkgName, version);
439
+ let unsafeUpdate = await npm.getLatestVersion(this.config.appiumHome, pkgName);
440
+ let safeUpdate = await npm.getLatestSafeUpgradeVersion(this.config.appiumHome, pkgName, version);
461
441
  if (!util.compareVersions(unsafeUpdate, '>', version)) {
462
442
  // the latest version is not greater than the current version, so there's no possible update
463
443
  unsafeUpdate = null;
@@ -480,12 +460,13 @@ export default class ExtensionCommand {
480
460
  *
481
461
  * @param {string} ext - name of extension to update
482
462
  * @param {string} version - version string identifier to update extension to
463
+ * @returns {Promise<void>}
483
464
  */
484
465
  async updateExtension (ext, version) {
485
466
  const {pkgName} = this.config.installedExtensions[ext];
486
467
  await fs.rimraf(this.config.getInstallPath(ext));
487
468
  const extData = await this.installViaNpm({ext, pkgName, pkgVer: version});
488
- delete extData[`${this.type}Name`];
469
+ delete extData[/** @type {string} */(`${this.type}Name`)];
489
470
  await this.config.updateExtension(ext, extData);
490
471
  }
491
472
 
@@ -497,18 +478,18 @@ export default class ExtensionCommand {
497
478
  * "scripts" field is not a plain object, or if the scriptName is
498
479
  * not found within "scripts" object.
499
480
  *
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}
481
+ * @param {RunOptions} opts
482
+ * @return {Promise<RunOutput>}
503
483
  */
504
- async run ({ext, scriptName}) {
484
+ async _run ({ext, scriptName}) {
505
485
  if (!_.has(this.config.installedExtensions, ext)) {
506
486
  throw new Error(`please install the ${this.type} first`);
507
487
  }
508
488
 
509
489
  const extConfig = this.config.installedExtensions[ext];
510
490
 
511
- if (!_.has(extConfig, 'scripts')) {
491
+ // note: TS cannot understand that _.has() is a type guard
492
+ if (!extConfig.scripts) {
512
493
  throw new Error(`The ${this.type} named '${ext}' does not contain the ` +
513
494
  `"scripts" field underneath the "appium" field in its package.json`);
514
495
  }
@@ -523,8 +504,10 @@ export default class ExtensionCommand {
523
504
  throw new Error(`The ${this.type} named '${ext}' does not support the script: '${scriptName}'`);
524
505
  }
525
506
 
526
- const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
527
- cwd: this.config.getExtensionRequirePath(ext)
507
+ const runner = new SubProcess(process.execPath, [
508
+ extScripts[scriptName]
509
+ ], {
510
+ cwd: this.config.getInstallPath(ext)
528
511
  });
529
512
 
530
513
  const output = new RingBuffer(50);
@@ -547,8 +530,155 @@ export default class ExtensionCommand {
547
530
  }
548
531
  }
549
532
 
533
+ export default ExtensionCommand;
534
+ export {ExtensionCommand};
535
+
550
536
  /**
551
- * @typedef {Object} RunOutput
552
- * @property {string|undefined} error - error message if script ran unsuccessfully, otherwise undefined
537
+ * Options for the {@linkcode ExtensionCommand} constructor
538
+ * @template {ExtensionType} ExtType
539
+ * @typedef ExtensionCommandOptions
540
+ * @property {ExtensionConfig<ExtType>} config - the `DriverConfig` or `PluginConfig` instance used for this command
541
+ * @property {boolean} json - whether the output of this command should be JSON or text
542
+ */
543
+
544
+ /**
545
+ * Extra stuff about extensions; used indirectly by {@linkcode ExtensionCommand.list}.
546
+ *
547
+ * @typedef ExtensionMetadata
548
+ * @property {boolean} installed - If `true`, the extension is installed
549
+ * @property {string?} updateVersion - If the extension is installed, the version it can be updated to
550
+ * @property {string?} unsafeUpdateVersion - Same as above, but a major version bump
551
+ * @property {boolean} upToDate - If the extension is installed and the latest
552
+ */
553
+
554
+ /**
555
+ * @typedef {import('../../types').ExtensionType} ExtensionType
556
+ * @typedef {import('../../types').DriverType} DriverType
557
+ * @typedef {import('../../types').PluginType} PluginType
558
+ */
559
+
560
+ /**
561
+ * @template {ExtensionType} ExtType
562
+ * @typedef {import('../../types/appium-manifest').ExtRecord<ExtType>} ExtRecord
563
+ */
564
+
565
+ /**
566
+ * @template {ExtensionType} ExtType
567
+ * @typedef {import('../extension/extension-config').ExtensionConfig<ExtType>} ExtensionConfig
568
+ */
569
+
570
+ /**
571
+ * @template {ExtensionType} ExtType
572
+ * @typedef {import('../../types/external-manifest').ExtMetadata<ExtType>} ExtMetadata
573
+ */
574
+
575
+ /**
576
+ * @template {ExtensionType} ExtType
577
+ * @typedef {import('../../types/appium-manifest').ExtManifest<ExtType>} ExtManifest
578
+ */
579
+
580
+ /**
581
+ * @template {ExtensionType} ExtType
582
+ * @typedef {import('../../types/external-manifest').ExtPackageJson<ExtType>} ExtPackageJson
583
+ */
584
+
585
+ /**
586
+ * Possible return value for {@linkcode ExtensionCommand.list}
587
+ * @typedef UninstalledExtensionListData
588
+ * @property {string} pkgName
589
+ * @property {false} installed
590
+ */
591
+
592
+ /**
593
+ * Possible return value for {@linkcode ExtensionCommand.list}
594
+ * @typedef {import('../../types/appium-manifest').InternalMetadata & ExtensionMetadata} InstalledExtensionListData
595
+ */
596
+
597
+ /**
598
+ * Return value of {@linkcode ExtensionCommand.list}.
599
+ * @typedef {Record<string,InstalledExtensionListData|UninstalledExtensionListData>} ExtensionListData
600
+ */
601
+
602
+ /**
603
+ * Options for {@linkcode ExtensionCommand._run}.
604
+ * @typedef RunOptions
605
+ * @property {string} ext - name of the extension to run a script from
606
+ * @property {string} scriptName - name of the script to run
607
+ */
608
+
609
+ /**
610
+ * Return value of {@linkcode ExtensionCommand._run}
611
+ *
612
+ * @typedef RunOutput
613
+ * @property {string} [error] - error message if script ran unsuccessfully, otherwise undefined
553
614
  * @property {string[]} output - script output
554
615
  */
616
+
617
+ /**
618
+ * Options for {@linkcode ExtensionCommand._update}.
619
+ * @typedef ExtensionUpdateOpts
620
+ * @property {string} ext - the name of the extension to update
621
+ * @property {boolean} unsafe - if true, will perform unsafe updates past major revision boundaries
622
+ */
623
+
624
+ /**
625
+ * Return value of {@linkcode ExtensionCommand._update}.
626
+ * @typedef ExtensionUpdateResult
627
+ * @property {Record<string,Error>} errors - map of ext names to error objects
628
+ * @property {Record<string,UpdateReport>} updates - map of ext names to {@linkcode UpdateReport}s
629
+ */
630
+
631
+ /**
632
+ * Part of result of {@linkcode ExtensionCommand._update}.
633
+ * @typedef UpdateReport
634
+ * @property {string} from - version the extension was updated from
635
+ * @property {string} to - version the extension was updated to
636
+ */
637
+
638
+ /**
639
+ * Options for {@linkcode ExtensionCommand._uninstall}.
640
+ * @typedef UninstallOpts
641
+ * @property {string} ext - the name or spec of an extension to uninstall
642
+ */
643
+
644
+ /**
645
+ * Used by {@linkcode ExtensionCommand.getPostInstallText}
646
+ * @typedef ExtensionArgs
647
+ * @property {string} extName - the name of an extension
648
+ * @property {object} extData - the data for an installed extension
649
+ */
650
+
651
+ /**
652
+ * Options for {@linkcode ExtensionCommand.installViaNpm}
653
+ * @typedef InstallViaNpmArgs
654
+ * @property {string} ext - the name or spec of an extension to install
655
+ * @property {string} pkgName - the NPM package name of the extension
656
+ * @property {string} [pkgVer] - the specific version of the NPM package
657
+ */
658
+
659
+ /**
660
+ * Object returned by {@linkcode ExtensionCommand.checkForExtensionUpdate}
661
+ * @typedef PossibleUpdates
662
+ * @property {string} current - current version
663
+ * @property {string?} safeUpdate - version we can safely update to if it exists, or null
664
+ * @property {string?} unsafeUpdate - version we can unsafely update to if it exists, or null
665
+ */
666
+
667
+ /**
668
+ * Options for {@linkcode ExtensionCommand._install}
669
+ * @typedef InstallArgs
670
+ * @property {string} ext - the name or spec of an extension to install
671
+ * @property {import('../../types/appium-manifest').InstallType} installType - how to install this extension. One of the INSTALL_TYPES
672
+ * @property {string} [packageName] - for git/github installs, the extension node package name
673
+ */
674
+
675
+ /**
676
+ * Returned by {@linkcode ExtensionCommand.getExtensionFields}
677
+ * @template {ExtensionType} ExtType
678
+ * @typedef {ExtMetadata<ExtType> & { pkgName: string, version: string } & import('../../types/external-manifest').CommonMetadata} ExtensionFields
679
+ */
680
+
681
+ /**
682
+ * @template {ExtensionType} ExtType
683
+ * @typedef {ExtType extends DriverType ? typeof import('../constants').KNOWN_DRIVERS : ExtType extends PluginType ? typeof import('../constants').KNOWN_PLUGINS : never} KnownExtensions
684
+ */