cli.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. 'use strict';
  2. const path = require('path');
  3. const del = require('del');
  4. const updateNotifier = require('update-notifier');
  5. const figures = require('figures');
  6. const arrify = require('arrify');
  7. const meow = require('meow');
  8. const Promise = require('bluebird');
  9. const isCi = require('is-ci');
  10. const loadConf = require('./load-config');
  11. // Bluebird specific
  12. Promise.longStackTraces();
  13. function exit(message) {
  14. console.error(`\n${require('./chalk').get().red(figures.cross)} ${message}`);
  15. process.exit(1); // eslint-disable-line unicorn/no-process-exit
  16. }
  17. exports.run = () => { // eslint-disable-line complexity
  18. let conf = {};
  19. let confError = null;
  20. try {
  21. conf = loadConf();
  22. } catch (err) {
  23. confError = err;
  24. }
  25. const cli = meow(`
  26. Usage
  27. ava [<file|directory|glob> ...]
  28. Options
  29. --watch, -w Re-run tests when tests and source files change
  30. --match, -m Only run tests with matching title (Can be repeated)
  31. --update-snapshots, -u Update snapshots
  32. --fail-fast Stop after first test failure
  33. --timeout, -T Set global timeout
  34. --serial, -s Run tests serially
  35. --concurrency, -c Max number of test files running at the same time (Default: CPU cores)
  36. --verbose, -v Enable verbose output
  37. --tap, -t Generate TAP output
  38. --color Force color output
  39. --no-color Disable color output
  40. --reset-cache Reset AVA's compilation cache and exit
  41. Examples
  42. ava
  43. ava test.js test2.js
  44. ava test-*.js
  45. ava test
  46. Default patterns when no arguments:
  47. test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js
  48. `, {
  49. flags: {
  50. watch: {
  51. type: 'boolean',
  52. alias: 'w'
  53. },
  54. match: {
  55. type: 'string',
  56. alias: 'm',
  57. default: conf.match
  58. },
  59. 'update-snapshots': {
  60. type: 'boolean',
  61. alias: 'u'
  62. },
  63. 'fail-fast': {
  64. type: 'boolean',
  65. default: conf.failFast
  66. },
  67. timeout: {
  68. type: 'string',
  69. alias: 'T',
  70. default: conf.timeout
  71. },
  72. serial: {
  73. type: 'boolean',
  74. alias: 's',
  75. default: conf.serial
  76. },
  77. concurrency: {
  78. type: 'string',
  79. alias: 'c',
  80. default: conf.concurrency
  81. },
  82. verbose: {
  83. type: 'boolean',
  84. alias: 'v',
  85. default: conf.verbose
  86. },
  87. tap: {
  88. type: 'boolean',
  89. alias: 't',
  90. default: conf.tap
  91. },
  92. color: {
  93. type: 'boolean',
  94. default: 'color' in conf ? conf.color : require('supports-color').stdout !== false
  95. },
  96. 'reset-cache': {
  97. type: 'boolean',
  98. default: false
  99. },
  100. '--': {
  101. type: 'string'
  102. }
  103. }
  104. });
  105. updateNotifier({pkg: cli.pkg}).notify();
  106. const chalk = require('./chalk').set({enabled: cli.flags.color});
  107. if (confError) {
  108. if (confError.parent) {
  109. exit(`${confError.message}\n\n${chalk.gray((confError.parent && confError.parent.stack) || confError.parent)}`);
  110. } else {
  111. exit(confError.message);
  112. }
  113. }
  114. const {projectDir} = conf;
  115. if (cli.flags.resetCache) {
  116. const cacheDir = path.join(projectDir, 'node_modules', '.cache', 'ava');
  117. del('*', {
  118. cwd: cacheDir,
  119. nodir: true
  120. }).then(() => {
  121. console.error(`\n${chalk.green(figures.tick)} Removed AVA cache files in ${cacheDir}`);
  122. process.exit(0); // eslint-disable-line unicorn/no-process-exit
  123. }, err => {
  124. exit(`Error removing AVA cache files in ${cacheDir}\n\n${chalk.gray((err && err.stack) || err)}`);
  125. });
  126. return;
  127. }
  128. if (cli.flags.watch && cli.flags.tap && !conf.tap) {
  129. exit('The TAP reporter is not available when using watch mode.');
  130. }
  131. if (cli.flags.watch && isCi) {
  132. exit('Watch mode is not available in CI, as it prevents AVA from terminating.');
  133. }
  134. if (
  135. cli.flags.concurrency === '' ||
  136. (cli.flags.concurrency && (!Number.isInteger(Number.parseFloat(cli.flags.concurrency)) || parseInt(cli.flags.concurrency, 10) < 0))
  137. ) {
  138. exit('The --concurrency or -c flag must be provided with a nonnegative integer.');
  139. }
  140. if ('source' in conf) {
  141. exit('The \'source\' option has been renamed. Use \'sources\' instead.');
  142. }
  143. const Api = require('../api');
  144. const VerboseReporter = require('./reporters/verbose');
  145. const MiniReporter = require('./reporters/mini');
  146. const TapReporter = require('./reporters/tap');
  147. const Watcher = require('./watcher');
  148. const babelPipeline = require('./babel-pipeline');
  149. const normalizeExtensions = require('./extensions');
  150. let babelConfig = null;
  151. try {
  152. babelConfig = babelPipeline.validate(conf.babel);
  153. } catch (err) {
  154. exit(err.message);
  155. }
  156. let extensions;
  157. try {
  158. extensions = normalizeExtensions(conf.extensions || [], babelConfig);
  159. } catch (err) {
  160. exit(err.message);
  161. }
  162. // Copy resultant cli.flags into conf for use with Api and elsewhere
  163. Object.assign(conf, cli.flags);
  164. const match = arrify(conf.match);
  165. const api = new Api({
  166. failFast: conf.failFast,
  167. failWithoutAssertions: conf.failWithoutAssertions !== false,
  168. serial: conf.serial,
  169. require: arrify(conf.require),
  170. cacheEnabled: conf.cache !== false,
  171. compileEnhancements: conf.compileEnhancements !== false,
  172. extensions,
  173. match,
  174. babelConfig,
  175. resolveTestsFrom: cli.input.length === 0 ? projectDir : process.cwd(),
  176. projectDir,
  177. timeout: conf.timeout,
  178. concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0,
  179. updateSnapshots: conf.updateSnapshots,
  180. snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null,
  181. color: conf.color,
  182. workerArgv: cli.flags['--']
  183. });
  184. let reporter;
  185. if (conf.tap && !conf.watch) {
  186. reporter = new TapReporter({
  187. reportStream: process.stdout,
  188. stdStream: process.stderr
  189. });
  190. } else if (conf.verbose || isCi) {
  191. reporter = new VerboseReporter({
  192. reportStream: process.stdout,
  193. stdStream: process.stderr,
  194. watching: conf.watch
  195. });
  196. } else {
  197. reporter = new MiniReporter({
  198. reportStream: process.stdout,
  199. stdStream: process.stderr,
  200. watching: conf.watch
  201. });
  202. }
  203. api.on('run', plan => reporter.startRun(plan));
  204. const files = cli.input.length ? cli.input : arrify(conf.files);
  205. if (conf.watch) {
  206. const watcher = new Watcher(reporter, api, files, arrify(conf.sources));
  207. watcher.observeStdin(process.stdin);
  208. } else {
  209. api.run(files).then(runStatus => {
  210. process.exitCode = runStatus.suggestExitCode({matching: match.length > 0});
  211. reporter.endRun();
  212. });
  213. }
  214. };