@appium/support 2.55.4 → 2.57.0

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 (96) hide show
  1. package/build/lib/env.d.ts +54 -0
  2. package/build/lib/env.d.ts.map +1 -0
  3. package/build/lib/env.js +102 -0
  4. package/build/lib/fs.d.ts +221 -0
  5. package/build/lib/fs.d.ts.map +1 -0
  6. package/build/lib/fs.js +74 -56
  7. package/build/lib/image-util.d.ts +56 -0
  8. package/build/lib/image-util.d.ts.map +1 -0
  9. package/build/lib/image-util.js +3 -6
  10. package/build/lib/index.d.ts +38 -0
  11. package/build/lib/index.d.ts.map +1 -0
  12. package/build/lib/index.js +36 -18
  13. package/build/lib/log-internal.d.ts +74 -0
  14. package/build/lib/log-internal.d.ts.map +1 -0
  15. package/build/lib/log-internal.js +11 -21
  16. package/build/lib/logger.d.ts +3 -0
  17. package/build/lib/logger.d.ts.map +1 -0
  18. package/build/lib/logger.js +2 -4
  19. package/build/lib/logging.d.ts +45 -0
  20. package/build/lib/logging.d.ts.map +1 -0
  21. package/build/lib/logging.js +12 -16
  22. package/build/lib/mjpeg.d.ts +65 -0
  23. package/build/lib/mjpeg.d.ts.map +1 -0
  24. package/build/lib/mjpeg.js +12 -7
  25. package/build/lib/mkdirp.d.ts +3 -0
  26. package/build/lib/mkdirp.d.ts.map +1 -0
  27. package/build/lib/mkdirp.js +7 -11
  28. package/build/lib/net.d.ts +95 -0
  29. package/build/lib/net.d.ts.map +1 -0
  30. package/build/lib/net.js +42 -26
  31. package/build/lib/node.d.ts +26 -0
  32. package/build/lib/node.d.ts.map +1 -0
  33. package/build/lib/node.js +102 -3
  34. package/build/lib/npm.d.ts +123 -0
  35. package/build/lib/npm.d.ts.map +1 -0
  36. package/build/lib/npm.js +217 -0
  37. package/build/lib/plist.d.ts +43 -0
  38. package/build/lib/plist.d.ts.map +1 -0
  39. package/build/lib/plist.js +2 -4
  40. package/build/lib/process.d.ts +3 -0
  41. package/build/lib/process.d.ts.map +1 -0
  42. package/build/lib/process.js +2 -4
  43. package/build/lib/system.d.ts +7 -0
  44. package/build/lib/system.d.ts.map +1 -0
  45. package/build/lib/system.js +2 -4
  46. package/build/lib/tempdir.d.ts +63 -0
  47. package/build/lib/tempdir.d.ts.map +1 -0
  48. package/build/lib/tempdir.js +4 -9
  49. package/build/lib/timing.d.ts +46 -0
  50. package/build/lib/timing.d.ts.map +1 -0
  51. package/build/lib/timing.js +2 -4
  52. package/build/lib/util.d.ts +183 -0
  53. package/build/lib/util.d.ts.map +1 -0
  54. package/build/lib/util.js +9 -15
  55. package/build/lib/zip.d.ts +180 -0
  56. package/build/lib/zip.d.ts.map +1 -0
  57. package/build/lib/zip.js +9 -9
  58. package/build/tsconfig.tsbuildinfo +1 -0
  59. package/lib/env.js +162 -0
  60. package/lib/fs.js +198 -69
  61. package/lib/image-util.js +23 -7
  62. package/lib/index.js +6 -6
  63. package/lib/log-internal.js +31 -38
  64. package/lib/logging.js +41 -17
  65. package/lib/mjpeg.js +14 -5
  66. package/lib/mkdirp.js +3 -6
  67. package/lib/net.js +116 -60
  68. package/lib/node.js +107 -4
  69. package/lib/npm.js +278 -0
  70. package/lib/plist.js +3 -1
  71. package/lib/tempdir.js +14 -13
  72. package/lib/util.js +36 -33
  73. package/lib/zip.js +31 -21
  74. package/package.json +31 -13
  75. package/build/test/assets/sample_binary.plist +0 -0
  76. package/build/test/assets/sample_text.plist +0 -28
  77. package/build/test/fs-specs.js +0 -264
  78. package/build/test/helpers.js +0 -35
  79. package/build/test/image-util-e2e-specs.js +0 -78
  80. package/build/test/index-specs.js +0 -49
  81. package/build/test/log-internals-specs.js +0 -97
  82. package/build/test/logger/helpers.js +0 -71
  83. package/build/test/logger/logger-force-specs.js +0 -41
  84. package/build/test/logger/logger-normal-specs.js +0 -113
  85. package/build/test/logger/logger-test-specs.js +0 -40
  86. package/build/test/mjpeg-e2e-specs.js +0 -96
  87. package/build/test/net-e2e-specs.js +0 -32
  88. package/build/test/node-e2e-specs.js +0 -22
  89. package/build/test/plist-specs.js +0 -54
  90. package/build/test/process-specs.js +0 -104
  91. package/build/test/system-specs.js +0 -136
  92. package/build/test/tempdir-specs.js +0 -86
  93. package/build/test/timing-specs.js +0 -125
  94. package/build/test/util-e2e-specs.js +0 -136
  95. package/build/test/util-specs.js +0 -537
  96. package/build/test/zip-e2e-specs.js +0 -233
package/lib/npm.js ADDED
@@ -0,0 +1,278 @@
1
+ // @ts-check
2
+
3
+ import path from 'path';
4
+ import semver from 'semver';
5
+ import { hasAppiumDependency } from './env';
6
+ import { exec } from 'teen_process';
7
+ import { fs } from './fs';
8
+ import * as util from './util';
9
+ import * as system from './system';
10
+ import resolveFrom from 'resolve-from';
11
+
12
+
13
+ /**
14
+ * Relative path to directory containing any Appium internal files
15
+ * XXX: this is duplicated in `appium/lib/constants.js`.
16
+ */
17
+ export const CACHE_DIR_RELATIVE_PATH = path.join(
18
+ 'node_modules',
19
+ '.cache',
20
+ 'appium',
21
+ );
22
+
23
+ /**
24
+ * Relative path to lockfile used when installing an extension via `appium`
25
+ */
26
+ export const INSTALL_LOCKFILE_RELATIVE_PATH = path.join(
27
+ CACHE_DIR_RELATIVE_PATH,
28
+ '.install.lock',
29
+ );
30
+
31
+ /**
32
+ * XXX: This should probably be a singleton, but it isn't. Maybe this module should just export functions?
33
+ */
34
+ export class NPM {
35
+ /**
36
+ * Returns path to "install" lockfile
37
+ * @private
38
+ * @param {string} cwd
39
+ */
40
+ _getInstallLockfilePath (cwd) {
41
+ return path.join(cwd, INSTALL_LOCKFILE_RELATIVE_PATH);
42
+ }
43
+
44
+ /**
45
+ * Execute `npm` with given args.
46
+ *
47
+ * If the process exits with a nonzero code, the contents of `STDOUT` and `STDERR` will be in the
48
+ * `message` of the {@link TeenProcessExecError} rejected.
49
+ * @param {string} cmd
50
+ * @param {string[]} args
51
+ * @param {ExecOpts} opts
52
+ * @param {ExecOpts} [execOpts]
53
+ */
54
+ async exec (cmd, args, opts, execOpts = /** @type {ExecOpts} */({})) {
55
+ let { cwd, json, lockFile } = opts;
56
+
57
+ // make sure we perform the current operation in cwd
58
+ execOpts = {...execOpts, cwd};
59
+
60
+ args.unshift(cmd);
61
+ if (json) {
62
+ args.push('--json');
63
+ }
64
+ const npmCmd = system.isWindows() ? 'npm.cmd' : 'npm';
65
+ let runner = async () => await exec(npmCmd, args, execOpts);
66
+ if (lockFile) {
67
+ const acquireLock = util.getLockFileGuard(lockFile);
68
+ const _runner = runner;
69
+ runner = async () => await acquireLock(_runner);
70
+ }
71
+
72
+ /** @type {import('teen_process').ExecResult<string> & {json?: any}} */
73
+ let ret;
74
+ try {
75
+ const {stdout, stderr, code} = await runner();
76
+ ret = {stdout, stderr, code};
77
+ // if possible, parse NPM's json output. During NPM install 3rd-party
78
+ // packages can write to stdout, so sometimes the json output can't be
79
+ // guaranteed to be parseable
80
+ try {
81
+ ret.json = JSON.parse(stdout);
82
+ } catch (ign) {}
83
+ } catch (e) {
84
+ const {stdout = '', stderr = '', code = null} = /** @type {TeenProcessExecError} */(e);
85
+ const err = new Error(`npm command '${args.join(' ')}' failed with code ${code}.\n\nSTDOUT:\n${stdout.trim()}\n\nSTDERR:\n${stderr.trim()}`);
86
+ throw err;
87
+ }
88
+ return ret;
89
+ }
90
+
91
+ /**
92
+ * @param {string} cwd
93
+ * @param {string} pkg
94
+ */
95
+ async getLatestVersion (cwd, pkg) {
96
+ return (await this.exec('view', [pkg, 'dist-tags'], {
97
+ json: true,
98
+ cwd
99
+ })).json?.latest;
100
+ }
101
+
102
+ /**
103
+ * @param {string} cwd
104
+ * @param {string} pkg
105
+ * @param {string} curVersion
106
+ */
107
+ async getLatestSafeUpgradeVersion (cwd, pkg, curVersion) {
108
+ const allVersions = (await this.exec('view', [pkg, 'versions'], {
109
+ json: true,
110
+ cwd
111
+ })).json;
112
+ return this.getLatestSafeUpgradeFromVersions(curVersion, allVersions);
113
+ }
114
+
115
+ /**
116
+ * Runs `npm ls`, optionally for a particular package.
117
+ * @param {string} cwd
118
+ * @param {string} [pkg]
119
+ */
120
+ async list (cwd, pkg) {
121
+ return (await this.exec('list', pkg ? [pkg] : [], {cwd, json: true})).json;
122
+ }
123
+
124
+ /**
125
+ * Given a current version and a list of all versions for a package, return the version which is
126
+ * the highest safely-upgradable version (meaning not crossing any major revision boundaries, and
127
+ * not including any alpha/beta/rc versions)
128
+ *
129
+ * @param {string} curVersion - the current version of a package
130
+ * @param {Array<string>} allVersions - a list of version strings
131
+ *
132
+ * @return {string|null} - the highest safely-upgradable version, or null if there isn't one
133
+ */
134
+ getLatestSafeUpgradeFromVersions (curVersion, allVersions) {
135
+ let safeUpgradeVer = null;
136
+ const curSemver = semver.parse(curVersion);
137
+ if (curSemver === null) {
138
+ throw new Error(`Could not parse current version '${curVersion}'`);
139
+ }
140
+ for (const testVer of allVersions) {
141
+ const testSemver = semver.parse(testVer);
142
+ if (testSemver === null) {
143
+ throw new Error(`Could not parse version to test against: '${testVer}'`);
144
+ }
145
+ // if the test version is a prerelease, ignore it
146
+ if (testSemver.prerelease.length > 0) {
147
+ continue;
148
+ }
149
+ // if the current version is later than the test version, skip this test version
150
+ if (curSemver.compare(testSemver) === 1) {
151
+ continue;
152
+ }
153
+ // if the test version is newer, but crosses a major revision boundary, also skip it
154
+ if (testSemver.major > curSemver.major) {
155
+ continue;
156
+ }
157
+ // otherwise this version is safe to upgrade to. But there might be multiple ones of this
158
+ // kind, so keep iterating and keeping the highest
159
+ if (safeUpgradeVer === null || testSemver.compare(safeUpgradeVer) === 1) {
160
+ safeUpgradeVer = testSemver;
161
+ }
162
+ }
163
+ if (safeUpgradeVer) {
164
+ safeUpgradeVer = safeUpgradeVer.format();
165
+ }
166
+ return safeUpgradeVer;
167
+ }
168
+
169
+ /**
170
+ * Installs a package w/ `npm`
171
+ * @param {string} cwd
172
+ * @param {string} pkgName
173
+ * @param {InstallPackageOpts} [opts]
174
+ * @returns {Promise<import('type-fest').PackageJson>}
175
+ */
176
+ async installPackage (cwd, pkgName, {pkgVer} = {}) {
177
+ /** @type {any} */
178
+ let dummyPkgJson;
179
+ const dummyPkgPath = path.join(cwd, 'package.json');
180
+ try {
181
+ dummyPkgJson = JSON.parse(await fs.readFile(dummyPkgPath, 'utf8'));
182
+ } catch (err) {
183
+ if (err.code === 'ENOENT') {
184
+ dummyPkgJson = {};
185
+ await fs.writeFile(dummyPkgPath, JSON.stringify(dummyPkgJson, null, 2), 'utf8');
186
+ } else {
187
+ throw err;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * If we've found a `package.json` containined the `appiumCreated` property,
193
+ * then we can do whatever we please with it, since we created it. This is
194
+ * likely when `APPIUM_HOME` is the default (in `~/.appium`). In that case,
195
+ * we want `--global-style` to avoid deduping, and we also do not need a
196
+ * `package-lock.json`.
197
+ *
198
+ * If we _haven't_ found such a key, then this `package.json` isn't a
199
+ * "dummy" and is controlled by the user. So we'll just add it as a dev
200
+ * dep; whatever else it does is up to the user's npm config.
201
+ */
202
+ const installOpts = await hasAppiumDependency(cwd) ?
203
+ ['--save-dev'] :
204
+ ['--save-dev', '--save-exact', '--global-style', '--no-package-lock'];
205
+
206
+ const res = await this.exec('install', [
207
+ ...installOpts,
208
+ pkgVer ? `${pkgName}@${pkgVer}` : pkgName
209
+ ], {
210
+ cwd,
211
+ json: true,
212
+ lockFile: this._getInstallLockfilePath(cwd)
213
+ });
214
+
215
+ if (res.json) {
216
+ // we parsed a valid json response, so if we got an error here, return that
217
+ // message straightaway
218
+ if (res.json.error) {
219
+ throw new Error(res.json.error);
220
+ }
221
+ }
222
+
223
+ // Now read package data from the installed package to return, and make sure
224
+ // everything got installed ok. Remember, pkgName might end up with a / in it due to an npm
225
+ // org, so if so, that will get correctly exploded into multiple directories, by path.resolve here
226
+ // (even on Windows!)
227
+ const pkgJsonPath = resolveFrom(cwd, `${pkgName}/package.json`);
228
+ try {
229
+ return require(pkgJsonPath);
230
+ } catch {
231
+ throw new Error('The package was not downloaded correctly; its package.json ' +
232
+ 'did not exist or was unreadable. We looked for it at ' +
233
+ pkgJsonPath);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * @param {string} cwd
239
+ * @param {string} pkg
240
+ */
241
+ async uninstallPackage (cwd, pkg) {
242
+ await this.exec('uninstall', [pkg], {
243
+ cwd,
244
+ lockFile: this._getInstallLockfilePath(cwd)
245
+ });
246
+ }
247
+ }
248
+
249
+ export const npm = new NPM();
250
+
251
+ /**
252
+ * Options for {@link NPM.installPackage}
253
+ * @typedef InstallPackageOpts
254
+ * @property {string} [pkgVer] - the version of the package to install
255
+ */
256
+
257
+ /**
258
+ * Options for {@link NPM.exec}
259
+ * @typedef ExecOpts
260
+ * @property {string} cwd - Current working directory
261
+ * @property {boolean} [json] - If `true`, supply `--json` flag to npm and resolve w/ parsed JSON
262
+ * @property {string} [lockFile] - Path to lockfile to use
263
+ */
264
+
265
+ // THESE TYPES SHOULD BE IN TEEN PROCESS, NOT HERE
266
+
267
+ /**
268
+ * Extra props `teen_process.exec` adds to its error objects
269
+ * @typedef TeenProcessExecErrorProps
270
+ * @property {string} stdout - STDOUT
271
+ * @property {string} stderr - STDERR
272
+ * @property {number?} code - Exit code
273
+ */
274
+
275
+ /**
276
+ * Error thrown by `teen_process.exec`
277
+ * @typedef {Error & TeenProcessExecErrorProps} TeenProcessExecError
278
+ */
package/lib/plist.js CHANGED
@@ -26,7 +26,7 @@ async function parseXmlPlistFile (plistFilename) {
26
26
  * @param {string} plist The plist file path
27
27
  * @param {boolean} mustExist If set to false, this method will return an empty object when the file doesn't exist
28
28
  * @param {boolean} quiet If set to false, the plist path will be logged in debug level
29
- * @returns {Object} parsed plist JS Object
29
+ * @returns {Promise<any>} parsed plist JS Object
30
30
  */
31
31
  async function parsePlistFile (plist, mustExist = true, quiet = true) {
32
32
  // handle nonexistant file
@@ -103,6 +103,8 @@ function createBinaryPlist (data) {
103
103
  * @param {Buffer} data The beffer of a binary plist
104
104
  */
105
105
  function parseBinaryPlist (data) {
106
+ // this function exists, but is not in the type declarations.
107
+ // @ts-expect-error
106
108
  return bplistParse.parseBuffer(data);
107
109
  }
108
110
 
package/lib/tempdir.js CHANGED
@@ -13,7 +13,7 @@ const RDWR_EXCL = cnst.O_CREAT | cnst.O_TRUNC | cnst.O_RDWR | cnst.O_EXCL;
13
13
  * - No `process.env.APPIUM_TMP_DIR`: `/var/folders/34/2222sh8n27d6rcp7jqlkw8km0000gn/T/xxxxxxxx.yyyy`
14
14
  * - With `process.env.APPIUM_TMP_DIR = '/path/to/root'`: `/path/to/root/xxxxxxxx.yyyy`
15
15
  *
16
- * @returns {string} A path to the temporary directory
16
+ * @returns {Promise<string>} A path to the temporary directory
17
17
  */
18
18
  async function tempDir () {
19
19
  const now = new Date();
@@ -33,9 +33,9 @@ async function tempDir () {
33
33
  }
34
34
 
35
35
  /**
36
- * @typedef {Object} Affixes
37
- * @property {string} prefix - prefix of the temp directory name
38
- * @property {string} suffix - suffix of the temp directory name
36
+ * @typedef Affixes
37
+ * @property {string} [prefix] - prefix of the temp directory name
38
+ * @property {string} [suffix] - suffix of the temp directory name
39
39
  */
40
40
 
41
41
  /**
@@ -43,8 +43,8 @@ async function tempDir () {
43
43
  * with arbitrary prefix/suffix for the directory name.
44
44
  *
45
45
  * @param {string|Affixes} rawAffixes
46
- * @param {?string} defaultPrefix
47
- * @returns {string} A path to the temporary directory with rawAffixes and defaultPrefix
46
+ * @param {string} [defaultPrefix]
47
+ * @returns {Promise<string>} A path to the temporary directory with rawAffixes and defaultPrefix
48
48
  */
49
49
  async function path (rawAffixes, defaultPrefix) {
50
50
  const affixes = parseAffixes(rawAffixes, defaultPrefix);
@@ -54,9 +54,9 @@ async function path (rawAffixes, defaultPrefix) {
54
54
  }
55
55
 
56
56
  /**
57
- * @typedef {Object} OpenedAffixes
57
+ * @typedef OpenedAffixes
58
58
  * @property {string} path - The path to file
59
- * @property {integer} fd - The file descriptor opened
59
+ * @property {number} fd - The file descriptor opened
60
60
  */
61
61
 
62
62
  /**
@@ -64,7 +64,7 @@ async function path (rawAffixes, defaultPrefix) {
64
64
  * with arbitrary prefix/suffix for the directory name and return it as open.
65
65
  *
66
66
  * @param {Affixes} affixes
67
- * @returns {OpenedAffixes}
67
+ * @returns {Promise<OpenedAffixes>}
68
68
  */
69
69
  async function open (affixes) {
70
70
  const filePath = await path(affixes, 'f-');
@@ -73,7 +73,7 @@ async function open (affixes) {
73
73
  // opens the file in mode 384
74
74
  return {path: filePath, fd};
75
75
  } catch (err) {
76
- log.errorAndThrow(err);
76
+ return log.errorAndThrow(err);
77
77
  }
78
78
  }
79
79
 
@@ -82,11 +82,12 @@ async function open (affixes) {
82
82
  * Returns prefix/suffix object
83
83
  *
84
84
  * @param {string|Affixes} rawAffixes
85
- * @param {?string} defaultPrefix
85
+ * @param {string} [defaultPrefix]
86
86
  * @returns {Affixes}
87
87
  */
88
88
  function parseAffixes (rawAffixes, defaultPrefix) {
89
- let affixes = {prefix: null, suffix: null};
89
+ /** @type {Affixes} */
90
+ let affixes = {};
90
91
  if (rawAffixes) {
91
92
  switch (typeof rawAffixes) {
92
93
  case 'string':
@@ -116,7 +117,7 @@ const openDir = tempDir;
116
117
  /**
117
118
  * Returns a path to a temporary directory whcih is defined as static in the same process
118
119
  *
119
- * @returns {string} A temp directory path whcih is defined as static in the same process
120
+ * @returns {Promise<string>} A temp directory path whcih is defined as static in the same process
120
121
  */
121
122
  async function staticDir () { // eslint-disable-line require-await
122
123
  return _static;
package/lib/util.js CHANGED
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import B from 'bluebird';
2
4
  import _ from 'lodash';
3
5
  import os from 'os';
@@ -75,6 +77,7 @@ function localIp () {
75
77
  let ip = _.chain(os.networkInterfaces())
76
78
  .values()
77
79
  .flatten()
80
+ // @ts-ignore
78
81
  .filter(function (val) {
79
82
  return (val.family === 'IPv4' && val.internal === false);
80
83
  })
@@ -217,16 +220,16 @@ function filterObject (obj, predicate) {
217
220
  * if it is less than zero.
218
221
  */
219
222
  function toReadableSizeString (bytes) {
220
- const intBytes = parseInt(bytes, 10);
223
+ const intBytes = parseInt(String(bytes), 10);
221
224
  if (isNaN(intBytes) || intBytes < 0) {
222
225
  throw new Error(`Cannot convert '${bytes}' to a readable size format`);
223
226
  }
224
227
  if (intBytes >= GiB) {
225
- return `${parseFloat(intBytes / (GiB * 1.0)).toFixed(2)} GB`;
228
+ return `${(intBytes / (GiB * 1.0)).toFixed(2)} GB`;
226
229
  } else if (intBytes >= MiB) {
227
- return `${parseFloat(intBytes / (MiB * 1.0)).toFixed(2)} MB`;
230
+ return `${(intBytes / (MiB * 1.0)).toFixed(2)} MB`;
228
231
  } else if (intBytes >= KiB) {
229
- return `${parseFloat(intBytes / (KiB * 1.0)).toFixed(2)} KB`;
232
+ return `${(intBytes / (KiB * 1.0)).toFixed(2)} KB`;
230
233
  }
231
234
  return `${intBytes} B`;
232
235
  }
@@ -260,7 +263,7 @@ function isSubPath (originalPath, root, forcePosix = null) {
260
263
  * @param {string} path1 - Absolute or relative path to a file/folder
261
264
  * @param {string} path2 - Absolute or relative path to a file/folder
262
265
  * @param {...string} pathN - Zero or more absolute or relative paths to files/folders
263
- * @returns {boolean} true if all paths are pointing to the same file system item
266
+ * @returns {Promise<boolean>} true if all paths are pointing to the same file system item
264
267
  */
265
268
  async function isSameDestination (path1, path2, ...pathN) {
266
269
  const allPaths = [path1, path2, ...pathN];
@@ -273,34 +276,29 @@ async function isSameDestination (path1, path2, ...pathN) {
273
276
  return true;
274
277
  }
275
278
 
276
- // Node 10.5.0 introduced bigint support in stat, which allows for more precision
277
- // however below that the options get interpreted as the callback
278
- // TODO: remove when Node 10 is no longer supported
279
- let mapCb = async (x) => await fs.stat(x, {
279
+ let mapCb = async (x) => (await fs.stat(x, {
280
280
  bigint: true,
281
- }).ino;
282
- if (semver.lt(process.version, '10.5.0')) {
283
- mapCb = async (x) => await fs.stat(x).ino;
284
- }
281
+ })).ino;
285
282
  return areAllItemsEqual(await B.map(allPaths, mapCb));
286
283
  }
287
284
 
288
285
  /**
289
286
  * Coerces the given number/string to a valid version string
290
287
  *
291
- * @param {string|number} ver - Version string to coerce
292
- * @param {boolean} strict [true] - If true then an exception will be thrown
288
+ * @template {boolean} [Strict=true]
289
+ * @param {string} ver - Version string to coerce
290
+ * @param {Strict} [strict] - If `true` then an exception will be thrown
293
291
  * if `ver` cannot be coerced
294
- * @returns {string} Coerced version number or null if the string cannot be
292
+ * @returns {Strict extends true ? string : string|null} Coerced version number or null if the string cannot be
295
293
  * coerced and strict mode is disabled
296
294
  * @throws {Error} if strict mode is enabled and `ver` cannot be coerced
297
295
  */
298
- function coerceVersion (ver, strict = true) {
296
+ function coerceVersion (ver, strict = /** @type {Strict} */(true)) {
299
297
  const result = semver.valid(semver.coerce(`${ver}`));
300
298
  if (strict && !result) {
301
299
  throw new Error(`'${ver}' cannot be coerced to a valid version number`);
302
300
  }
303
- return result;
301
+ return /** @type {Strict extends true ? string : string?} */(result);
304
302
  }
305
303
 
306
304
  const SUPPORTED_OPERATORS = ['==', '!=', '>', '<', '>=', '<=', '='];
@@ -308,9 +306,9 @@ const SUPPORTED_OPERATORS = ['==', '!=', '>', '<', '>=', '<=', '='];
308
306
  /**
309
307
  * Compares two version strings
310
308
  *
311
- * @param {string|number} ver1 - The first version number to compare. Should be a valid
309
+ * @param {string} ver1 - The first version number to compare. Should be a valid
312
310
  * version number supported by semver parser.
313
- * @param {string|number} ver2 - The second version number to compare. Should be a valid
311
+ * @param {string} ver2 - The second version number to compare. Should be a valid
314
312
  * version number supported by semver parser.
315
313
  * @param {string} operator - One of supported version number operators:
316
314
  * ==, !=, >, <, <=, >=, =
@@ -333,11 +331,11 @@ function compareVersions (ver1, operator, ver2) {
333
331
  * Add appropriate quotes to command arguments. See https://github.com/substack/node-shell-quote
334
332
  * for more details
335
333
  *
336
- * @param {string|Array<string>} - The arguments that will be parsed
334
+ * @param {string|string[]} args - The arguments that will be parsed
337
335
  * @returns {string} - The arguments, quoted
338
336
  */
339
337
  function quote (args) {
340
- return shellQuote(args);
338
+ return shellQuote(_.castArray(args));
341
339
  }
342
340
 
343
341
  /**
@@ -354,8 +352,8 @@ function unleakString (s) {
354
352
 
355
353
 
356
354
  /**
357
- * @typedef {Object} PluralizeOptions
358
- * @property {?boolean} inclusive [false] - Whether to prefix with the number (e.g., 3 ducks)
355
+ * @typedef PluralizeOptions
356
+ * @property {boolean} [inclusive=false] - Whether to prefix with the number (e.g., 3 ducks)
359
357
  */
360
358
 
361
359
  /**
@@ -363,7 +361,7 @@ function unleakString (s) {
363
361
  *
364
362
  * @param {string} word - The word to pluralize
365
363
  * @param {number} count - How many of the word exist
366
- * @param {?PluralizeOptions|boolean} options|inclusive - options for word pluralization,
364
+ * @param {PluralizeOptions|boolean} options - options for word pluralization,
367
365
  * or a boolean indicating the options.inclusive property
368
366
  * @returns {string} The word pluralized according to the number
369
367
  */
@@ -380,8 +378,8 @@ function pluralize (word, count, options = {}) {
380
378
  }
381
379
 
382
380
  /**
383
- * @typedef {Object} EncodingOptions
384
- * @property {number} maxSize [1073741824] The maximum size of
381
+ * @typedef EncodingOptions
382
+ * @property {number} [maxSize=1073741824] The maximum size of
385
383
  * the resulting buffer in bytes. This is set to 1GB by default, because
386
384
  * Appium limits the maximum HTTP body size to 1GB. Also, the NodeJS heap
387
385
  * size must be enough to keep the resulting object (usually this size is
@@ -395,7 +393,7 @@ function pluralize (word, count, options = {}) {
395
393
  *
396
394
  * @param {string} srcPath The full path to the file being encoded
397
395
  * @param {EncodingOptions} opts
398
- * @returns {Buffer} base64-encoded content of the source file as memory buffer
396
+ * @returns {Promise<Buffer>} base64-encoded content of the source file as memory buffer
399
397
  * @throws {Error} if there was an error while reading the source file
400
398
  * or the source file is too
401
399
  */
@@ -445,9 +443,9 @@ async function toInMemoryBase64 (srcPath, opts = {}) {
445
443
  }
446
444
 
447
445
  /**
448
- * @typedef {Object} LockFileOptions
449
- * @property {number} timeout [120] The max time in seconds to wait for the lock
450
- * @property {boolean} tryRecovery [false] Whether to try lock recovery if
446
+ * @typedef LockFileOptions
447
+ * @property {number} [timeout=120] The max time in seconds to wait for the lock
448
+ * @property {boolean} [tryRecovery=false] Whether to try lock recovery if
451
449
  * the first attempt to acquire it timed out.
452
450
  */
453
451
 
@@ -456,9 +454,10 @@ async function toInMemoryBase64 (srcPath, opts = {}) {
456
454
  * longer present on the system. This allows for preventing concurrent behavior across processes
457
455
  * using a known lockfile path.
458
456
  *
457
+ * @template T
459
458
  * @param {string} lockFile The full path to the file used for the lock
460
459
  * @param {LockFileOptions} opts
461
- * @returns {AsyncFunction} async function that takes another async function defining the locked
460
+ * @returns async function that takes another async function defining the locked
462
461
  * behavior
463
462
  */
464
463
  function getLockFileGuard (lockFile, opts = {}) {
@@ -467,10 +466,14 @@ function getLockFileGuard (lockFile, opts = {}) {
467
466
  tryRecovery = false,
468
467
  } = opts;
469
468
 
470
- const lock = B.promisify(_lockfile.lock);
469
+ const lock = /** @type {(lockfile: string, opts: import('lockfile').Options)=>B<void>} */(B.promisify(_lockfile.lock));
471
470
  const check = B.promisify(_lockfile.check);
472
471
  const unlock = B.promisify(_lockfile.unlock);
473
472
 
473
+ /**
474
+ * @param {(...args: any[]) => T} behavior
475
+ * @returns {Promise<T>}
476
+ */
474
477
  const guard = async (behavior) => {
475
478
  let triedRecovery = false;
476
479
  do {