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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +9 -9
  2. package/build/check-npm-pack-files.js +23 -0
  3. package/build/commands-yml/parse.js +319 -0
  4. package/build/commands-yml/validator.js +130 -0
  5. package/build/index.js +19 -0
  6. package/build/lib/appium-config.schema.json +0 -0
  7. package/build/lib/appium.js +160 -53
  8. package/build/lib/cli/args.js +115 -279
  9. package/build/lib/cli/driver-command.js +11 -1
  10. package/build/lib/cli/extension-command.js +60 -8
  11. package/build/lib/cli/extension.js +30 -7
  12. package/build/lib/cli/npm.js +43 -29
  13. package/build/lib/cli/parser.js +156 -89
  14. package/build/lib/cli/plugin-command.js +11 -1
  15. package/build/lib/cli/utils.js +29 -3
  16. package/build/lib/config-file.js +141 -0
  17. package/build/lib/config.js +53 -65
  18. package/build/lib/driver-config.js +42 -19
  19. package/build/lib/drivers.js +8 -4
  20. package/build/lib/ext-config-io.js +165 -0
  21. package/build/lib/extension-config.js +130 -61
  22. package/build/lib/grid-register.js +22 -24
  23. package/build/lib/logger.js +3 -3
  24. package/build/lib/logsink.js +11 -13
  25. package/build/lib/main.js +197 -77
  26. package/build/lib/plugin-config.js +21 -11
  27. package/build/lib/plugins.js +4 -2
  28. package/build/lib/schema/appium-config-schema.js +253 -0
  29. package/build/lib/schema/arg-spec.js +120 -0
  30. package/build/lib/schema/cli-args.js +188 -0
  31. package/build/lib/schema/cli-transformers.js +76 -0
  32. package/build/lib/schema/index.js +36 -0
  33. package/build/lib/schema/keywords.js +72 -0
  34. package/build/lib/schema/schema.js +357 -0
  35. package/build/lib/utils.js +44 -99
  36. package/build/postinstall.js +90 -0
  37. package/build/test/cli/cli-e2e-specs.js +221 -0
  38. package/build/test/cli/cli-helpers.js +86 -0
  39. package/build/test/cli/cli-specs.js +71 -0
  40. package/build/test/cli/fixtures/test-driver/package.json +27 -0
  41. package/build/test/cli/schema-args-specs.js +48 -0
  42. package/build/test/cli/schema-e2e-specs.js +47 -0
  43. package/build/test/config-e2e-specs.js +112 -0
  44. package/build/test/config-file-e2e-specs.js +209 -0
  45. package/build/test/config-file-specs.js +281 -0
  46. package/build/test/config-specs.js +159 -0
  47. package/build/test/driver-e2e-specs.js +435 -0
  48. package/build/test/driver-specs.js +321 -0
  49. package/build/test/ext-config-io-specs.js +181 -0
  50. package/build/test/extension-config-specs.js +365 -0
  51. package/build/test/fixtures/allow-feat.txt +5 -0
  52. package/build/test/fixtures/caps.json +3 -0
  53. package/build/test/fixtures/config/allow-insecure.txt +3 -0
  54. package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
  55. package/build/test/fixtures/config/appium.config.bad.json +32 -0
  56. package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
  57. package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
  58. package/build/test/fixtures/config/appium.config.good.js +40 -0
  59. package/build/test/fixtures/config/appium.config.good.json +33 -0
  60. package/build/test/fixtures/config/appium.config.good.yaml +30 -0
  61. package/build/test/fixtures/config/appium.config.invalid.json +31 -0
  62. package/build/test/fixtures/config/appium.config.security-array.json +5 -0
  63. package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
  64. package/build/test/fixtures/config/appium.config.security-path.json +5 -0
  65. package/build/test/fixtures/config/driver-fake.config.json +8 -0
  66. package/build/test/fixtures/config/nodeconfig.json +3 -0
  67. package/build/test/fixtures/config/plugin-fake.config.json +0 -0
  68. package/build/test/fixtures/default-args.js +35 -0
  69. package/build/test/fixtures/deny-feat.txt +5 -0
  70. package/build/test/fixtures/driver.schema.js +20 -0
  71. package/build/test/fixtures/extensions.yaml +27 -0
  72. package/build/test/fixtures/flattened-schema.js +504 -0
  73. package/build/test/fixtures/plugin.schema.js +20 -0
  74. package/build/test/fixtures/schema-with-extensions.js +28 -0
  75. package/build/test/grid-register-specs.js +74 -0
  76. package/build/test/helpers.js +75 -0
  77. package/build/test/logger-specs.js +76 -0
  78. package/build/test/npm-specs.js +20 -0
  79. package/build/test/parser-specs.js +314 -0
  80. package/build/test/plugin-e2e-specs.js +316 -0
  81. package/build/test/schema/arg-spec-specs.js +70 -0
  82. package/build/test/schema/cli-args-specs.js +431 -0
  83. package/build/test/schema/schema-specs.js +389 -0
  84. package/build/test/utils-specs.js +266 -0
  85. package/index.js +11 -0
  86. package/lib/appium-config.schema.json +278 -0
  87. package/lib/appium.js +207 -65
  88. package/lib/cli/args.js +174 -375
  89. package/lib/cli/driver-command.js +4 -0
  90. package/lib/cli/extension-command.js +70 -5
  91. package/lib/cli/extension.js +25 -5
  92. package/lib/cli/npm.js +86 -18
  93. package/lib/cli/parser.js +257 -79
  94. package/lib/cli/plugin-command.js +4 -0
  95. package/lib/cli/utils.js +21 -1
  96. package/lib/config-file.js +227 -0
  97. package/lib/config.js +84 -63
  98. package/lib/driver-config.js +66 -11
  99. package/lib/drivers.js +4 -1
  100. package/lib/ext-config-io.js +287 -0
  101. package/lib/extension-config.js +225 -67
  102. package/lib/grid-register.js +27 -24
  103. package/lib/logger.js +1 -1
  104. package/lib/logsink.js +10 -7
  105. package/lib/main.js +214 -77
  106. package/lib/plugin-config.js +35 -6
  107. package/lib/plugins.js +1 -0
  108. package/lib/schema/appium-config-schema.js +287 -0
  109. package/lib/schema/arg-spec.js +222 -0
  110. package/lib/schema/cli-args.js +285 -0
  111. package/lib/schema/cli-transformers.js +123 -0
  112. package/lib/schema/index.js +2 -0
  113. package/lib/schema/keywords.js +135 -0
  114. package/lib/schema/schema.js +577 -0
  115. package/lib/utils.js +42 -88
  116. package/package.json +55 -84
  117. package/postinstall.js +71 -0
  118. package/types/appium-config.d.ts +197 -0
  119. package/types/types.d.ts +201 -0
  120. package/CHANGELOG.md +0 -3515
  121. package/build/lib/cli/parser-helpers.js +0 -82
  122. package/lib/cli/parser-helpers.js +0 -79
@@ -3,8 +3,9 @@
3
3
  import _ from 'lodash';
4
4
  import NPM from './npm';
5
5
  import path from 'path';
6
- import { fs, util } from 'appium-support';
7
- import { log, spinWith } from './utils';
6
+ import { fs, util } from '@appium/support';
7
+ import { log, spinWith, RingBuffer } from './utils';
8
+ import { SubProcess} from 'teen_process';
8
9
  import { INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB,
9
10
  INSTALL_TYPE_LOCAL } from '../extension-config';
10
11
 
@@ -482,8 +483,72 @@ export default class ExtensionCommand {
482
483
  */
483
484
  async updateExtension (ext, version) {
484
485
  const {pkgName} = this.config.installedExtensions[ext];
485
- await this.installViaNpm({ext, pkgName, pkgVer: version});
486
- this.config.installedExtensions[ext].version = version;
487
- await this.config.write();
486
+ await fs.rimraf(this.config.getInstallPath(ext));
487
+ const extData = await this.installViaNpm({ext, pkgName, pkgVer: version});
488
+ delete extData[`${this.type}Name`];
489
+ await this.config.updateExtension(ext, extData);
490
+ }
491
+
492
+ /**
493
+ * Runs a script cached inside the "scripts" field under "appium"
494
+ * inside of the driver/plugins "package.json" file. Will throw
495
+ * an error if the driver/plugin does not contain a "scripts" field
496
+ * underneath the "appium" field in its package.json, if the
497
+ * "scripts" field is not a plain object, or if the scriptName is
498
+ * not found within "scripts" object.
499
+ *
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}
503
+ */
504
+ async run ({ext, scriptName}) {
505
+ if (!_.has(this.config.installedExtensions, ext)) {
506
+ throw new Error(`please install the ${this.type} first`);
507
+ }
508
+
509
+ const extConfig = this.config.installedExtensions[ext];
510
+
511
+ if (!_.has(extConfig, 'scripts')) {
512
+ throw new Error(`The ${this.type} named '${ext}' does not contain the ` +
513
+ `"scripts" field underneath the "appium" field in its package.json`);
514
+ }
515
+
516
+ const extScripts = extConfig.scripts;
517
+
518
+ if (!_.isPlainObject(extScripts)) {
519
+ throw new Error(`The ${this.type} named '${ext}' "scripts" field must be a plain object`);
520
+ }
521
+
522
+ if (!_.has(extScripts, scriptName)) {
523
+ throw new Error(`The ${this.type} named '${ext}' does not support the script: '${scriptName}'`);
524
+ }
525
+
526
+ const runner = new SubProcess(process.execPath, [extScripts[scriptName]], {
527
+ cwd: this.config.getExtensionRequirePath(ext)
528
+ });
529
+
530
+ const output = new RingBuffer(50);
531
+
532
+ runner.on('stream-line', (line) => {
533
+ output.enqueue(line);
534
+ log(this.isJsonOutput, line);
535
+ });
536
+
537
+ await runner.start(0);
538
+
539
+ try {
540
+ await runner.join();
541
+ log(this.isJsonOutput, `${scriptName} successfully ran`.green);
542
+ return {output: output.getBuff()};
543
+ } catch (err) {
544
+ log(this.isJsonOutput, `Encountered an error when running '${scriptName}': ${err.message}`.red);
545
+ return {error: err.message, output: output.getBuff()};
546
+ }
488
547
  }
489
548
  }
549
+
550
+ /**
551
+ * @typedef {Object} RunOutput
552
+ * @property {string|undefined} error - error message if script ran unsuccessfully, otherwise undefined
553
+ * @property {string[]} output - script output
554
+ */
@@ -6,6 +6,7 @@ import DriverConfig from '../driver-config';
6
6
  import PluginConfig from '../plugin-config';
7
7
  import { DRIVER_TYPE } from '../extension-config';
8
8
  import { errAndQuit, log, JSON_SPACES } from './utils';
9
+ import { APPIUM_HOME } from './args';
9
10
 
10
11
  /**
11
12
  * Run a subcommand of the 'appium driver' type. Each subcommand has its own set of arguments which
@@ -13,25 +14,44 @@ import { errAndQuit, log, JSON_SPACES } from './utils';
13
14
  *
14
15
  * @param {Object} args - JS object where the key is the parameter name (as defined in
15
16
  * driver-parser.js)
17
+ * @param {import('../ext-config-io').ExtensionType} type - Extension type
18
+ * @oaram {ExtensionConfig} [configObject] - Extension config object, if we have one
16
19
  */
17
- async function runExtensionCommand (args, type) {
20
+ async function runExtensionCommand (args, type, configObject) {
18
21
  // TODO driver config file should be locked while any of these commands are
19
22
  // running to prevent weird situations
20
23
  let jsonResult = null;
21
- const {json, appiumHome} = args;
24
+ const extCmd = args[`${type}Command`];
25
+ if (!extCmd) {
26
+ throw new TypeError(`Cannot call ${type} command without a subcommand like 'install'`);
27
+ }
28
+ let {json, suppressOutput} = args;
29
+ if (suppressOutput) {
30
+ json = true;
31
+ }
22
32
  const logFn = (msg) => log(json, msg);
23
- const ConfigClass = type === DRIVER_TYPE ? DriverConfig : PluginConfig;
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
+ }
24
40
  const CommandClass = type === DRIVER_TYPE ? DriverCommand : PluginCommand;
25
- const config = new ConfigClass(appiumHome, logFn);
26
41
  const cmd = new CommandClass({config, json});
27
42
  try {
28
43
  await config.read();
29
44
  jsonResult = await cmd.execute(args);
30
45
  } catch (err) {
46
+ // in the suppress output case, we are calling this function internally and should
47
+ // just throw instead of printing an error and ending the process
48
+ if (suppressOutput) {
49
+ throw err;
50
+ }
31
51
  errAndQuit(json, err);
32
52
  }
33
53
 
34
- if (json) {
54
+ if (json && !suppressOutput) {
35
55
  console.log(JSON.stringify(jsonResult, null, JSON_SPACES));
36
56
  }
37
57
 
package/lib/cli/npm.js CHANGED
@@ -1,18 +1,32 @@
1
- import _ from 'lodash';
1
+ // @ts-check
2
+
2
3
  import path from 'path';
3
4
  import semver from 'semver';
4
5
  import { exec } from 'teen_process';
5
- import { mkdirp, util } from 'appium-support';
6
+ import { fs, mkdirp, util, system } from '@appium/support';
6
7
 
7
8
  const INSTALL_LOCKFILE = '.appium.install.lock';
8
9
  const LINK_LOCKFILE = '.appium.link.lock';
9
10
 
10
11
  export default class NPM {
11
12
 
13
+ /**
14
+ * @param {string} appiumHome
15
+ */
12
16
  constructor (appiumHome) {
17
+ /** @type {string} */
13
18
  this.appiumHome = appiumHome;
14
19
  }
15
20
 
21
+ /**
22
+ * Execute `npm` with given args.
23
+ *
24
+ * If the process exits with a nonzero code, the contents of `STDOUT` and `STDERR` will be in the
25
+ * `message` of the {@link TeenProcessExecError} rejected.
26
+ * @param {string} cmd
27
+ * @param {string[]} args
28
+ * @param {{ json?: boolean; cwd?: string; lockFile?: string; }} opts
29
+ */
16
30
  async exec (cmd, args, opts, execOpts = {}) {
17
31
  let { cwd, json, lockFile } = opts;
18
32
  if (!cwd) {
@@ -21,40 +35,60 @@ export default class NPM {
21
35
  // ensure the directory we want to install inside of exists
22
36
  await mkdirp(cwd);
23
37
 
38
+ // not only this, this directory needs a 'package.json' inside of it, otherwise, if any
39
+ // directory in the filesystem tree ABOVE cwd happens to have a package.json or a node_modules
40
+ // dir in it, NPM will install the module up there instead (silly NPM)
41
+ const dummyPkgJson = path.resolve(cwd, 'package.json');
42
+ if (!await fs.exists(dummyPkgJson)) {
43
+ await fs.writeFile(dummyPkgJson, '{}');
44
+ }
45
+
24
46
  // make sure we perform the current operation in cwd
25
47
  execOpts = {...execOpts, cwd};
26
48
 
27
49
  args.unshift(cmd);
28
50
  if (json) {
29
- args.push('-json');
51
+ args.push('--json');
30
52
  }
31
- let runner = async () => await exec('npm', args, execOpts);
53
+ const npmCmd = system.isWindows() ? 'npm.cmd' : 'npm';
54
+ let runner = async () => await exec(npmCmd, args, execOpts);
32
55
  if (lockFile) {
33
56
  const acquireLock = util.getLockFileGuard(path.resolve(cwd, lockFile));
34
57
  const _runner = runner;
35
58
  runner = async () => await acquireLock(_runner);
36
59
  }
37
- const {stdout, stderr, code} = await runner();
38
- const ret = {stdout, stderr, code, json: null};
39
60
 
40
- if (json) {
61
+ let ret;
62
+ try {
63
+ const {stdout, stderr, code} = await runner();
64
+ ret = /** @type {TeenProcessExecResult} */({stdout, stderr, code});
41
65
  // if possible, parse NPM's json output. During NPM install 3rd-party
42
66
  // packages can write to stdout, so sometimes the json output can't be
43
67
  // guaranteed to be parseable
44
68
  try {
45
69
  ret.json = JSON.parse(stdout);
46
70
  } catch (ign) {}
71
+ } catch (e) {
72
+ const {stdout, stderr, code} = /** @type {TeenProcessExecError} */(e);
73
+ const err = new Error(`npm command '${cmd} ${args.join(' ')}' failed with code ${code}.\n\nSTDOUT:\n${stdout.trim()}\n\nSTDERR:\n${stderr.trim()}`);
74
+ throw err;
47
75
  }
48
-
49
76
  return ret;
50
77
  }
51
78
 
79
+ /**
80
+ * @param {string} pkg
81
+ */
52
82
  async getLatestVersion (pkg) {
53
83
  return (await this.exec('view', [pkg, 'dist-tags'], {
54
84
  json: true
55
- })).json.latest;
85
+ })).json?.latest;
56
86
  }
57
87
 
88
+ /**
89
+ * @param {string} pkg
90
+ * @param {string} curVersion
91
+ */
58
92
  async getLatestSafeUpgradeVersion (pkg, curVersion) {
59
93
  const allVersions = (await this.exec('view', [pkg, 'versions'], {
60
94
  json: true
@@ -107,6 +141,11 @@ export default class NPM {
107
141
  return safeUpgradeVer;
108
142
  }
109
143
 
144
+ /**
145
+ *
146
+ * @param {{pkgDir: string, pkgName: string, pkgVer?: string}} param0
147
+ * @returns {Promise<import('type-fest').PackageJson>}
148
+ */
110
149
  async installPackage ({pkgDir, pkgName, pkgVer}) {
111
150
  const res = await this.exec('install', [
112
151
  '--no-save',
@@ -124,14 +163,6 @@ export default class NPM {
124
163
  if (res.json.error) {
125
164
  throw new Error(res.json.error);
126
165
  }
127
-
128
- // if we tried to install a package via git/github, we might not have been
129
- // certain of the package name, so if we can retrieve it unambiguously from
130
- // the json report of the install, do so
131
- const names = _.uniq([...res.json.added, ...res.json.updated].map((x) => x.name));
132
- if (names.length === 1) {
133
- pkgName = names[0];
134
- }
135
166
  }
136
167
 
137
168
  // Now read package data from the installed package to return, and make sure
@@ -148,6 +179,9 @@ export default class NPM {
148
179
  }
149
180
  }
150
181
 
182
+ /**
183
+ * @param {string} pkgPath
184
+ */
151
185
  async linkPackage (pkgPath) {
152
186
  // from the path alone we don't know the npm package name, so we need to
153
187
  // look in package.json
@@ -159,8 +193,14 @@ export default class NPM {
159
193
  `provided: ${pkgPath}`);
160
194
  }
161
195
 
196
+ // this is added to handle commands with relative paths
197
+ // ie: "node . driver install --source=local ../fake-driver"
198
+ pkgPath = path.resolve(process.cwd(), pkgPath);
199
+
162
200
  const pkgHome = path.resolve(this.appiumHome, pkgName);
163
- const res = await this.exec('link', [pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
201
+
202
+ // call link with --no-package-lock to ensure no corruption while installing local packages
203
+ const res = await this.exec('link', ['--no-package-lock', pkgPath], {cwd: pkgHome, lockFile: LINK_LOCKFILE});
164
204
  if (res.json && res.json.error) {
165
205
  throw new Error(res.json.error);
166
206
  }
@@ -174,6 +214,10 @@ export default class NPM {
174
214
  }
175
215
  }
176
216
 
217
+ /**
218
+ * @param {string} pkgDir
219
+ * @param {string} pkg
220
+ */
177
221
  async uninstallPackage (pkgDir, pkg) {
178
222
  await this.exec('uninstall', [pkg], {
179
223
  cwd: pkgDir,
@@ -181,3 +225,27 @@ export default class NPM {
181
225
  });
182
226
  }
183
227
  }
228
+
229
+
230
+ /**
231
+ * Result from a non-zero-exit execution of `appium`
232
+ * @typedef {Object} TeenProcessExecResult
233
+ * @property {string} stdout - Stdout
234
+ * @property {string} stderr - Stderr
235
+ * @property {number?} code - Exit code
236
+ * @property {any} json - JSON parsed from stdout
237
+ */
238
+
239
+ /**
240
+ * Extra props `teen_process.exec` adds to its error objects
241
+ * @typedef {Object} TeenProcessExecErrorProps
242
+ * @property {string} stdout - STDOUT
243
+ * @property {string} stderr - STDERR
244
+ * @property {number?} code - Exit code
245
+ */
246
+
247
+
248
+ /**
249
+ * Error thrown by `teen_process.exec`
250
+ * @typedef {Error & TeenProcessExecErrorProps} TeenProcessExecError
251
+ */