cli.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. 'use strict'
  2. const path = require('path')
  3. const assert = require('assert')
  4. const optimist = require('optimist')
  5. const fs = require('graceful-fs')
  6. const Server = require('./server')
  7. const helper = require('./helper')
  8. const constant = require('./constants')
  9. function processArgs (argv, options, fs, path) {
  10. if (argv.help) {
  11. console.log(optimist.help())
  12. process.exit(0)
  13. }
  14. if (argv.version) {
  15. console.log(`Karma version: ${constant.VERSION}`)
  16. process.exit(0)
  17. }
  18. // TODO(vojta): warn/throw when unknown argument (probably mispelled)
  19. Object.getOwnPropertyNames(argv).forEach(function (name) {
  20. let argumentValue = argv[name]
  21. if (name !== '_' && name !== '$0') {
  22. assert(!name.includes('_'), `Bad argument: ${name} did you mean ${name.replace('_', '-')}`)
  23. if (Array.isArray(argumentValue)) {
  24. argumentValue = argumentValue.pop() // If the same argument is defined multiple times, override.
  25. }
  26. options[helper.dashToCamel(name)] = argumentValue
  27. }
  28. })
  29. if (helper.isString(options.autoWatch)) {
  30. options.autoWatch = options.autoWatch === 'true'
  31. }
  32. if (helper.isString(options.colors)) {
  33. options.colors = options.colors === 'true'
  34. }
  35. if (helper.isString(options.failOnEmptyTestSuite)) {
  36. options.failOnEmptyTestSuite = options.failOnEmptyTestSuite === 'true'
  37. }
  38. if (helper.isString(options.failOnFailingTestSuite)) {
  39. options.failOnFailingTestSuite = options.failOnFailingTestSuite === 'true'
  40. }
  41. if (helper.isString(options.formatError)) {
  42. let required
  43. try {
  44. required = require(options.formatError)
  45. } catch (err) {
  46. console.error('Could not require formatError: ' + options.formatError, err)
  47. }
  48. // support exports.formatError and module.exports = function
  49. options.formatError = required.formatError || required
  50. if (!helper.isFunction(options.formatError)) {
  51. console.error(`Format error must be a function, got: ${typeof options.formatError}`)
  52. process.exit(1)
  53. }
  54. }
  55. if (helper.isString(options.logLevel)) {
  56. const logConstant = constant['LOG_' + options.logLevel.toUpperCase()]
  57. if (helper.isDefined(logConstant)) {
  58. options.logLevel = logConstant
  59. } else {
  60. console.error('Log level must be one of disable, error, warn, info, or debug.')
  61. process.exit(1)
  62. }
  63. } else if (helper.isDefined(options.logLevel)) {
  64. console.error('Log level must be one of disable, error, warn, info, or debug.')
  65. process.exit(1)
  66. }
  67. if (helper.isString(options.singleRun)) {
  68. options.singleRun = options.singleRun === 'true'
  69. }
  70. if (helper.isString(options.browsers)) {
  71. options.browsers = options.browsers.split(',')
  72. }
  73. if (options.reportSlowerThan === false) {
  74. options.reportSlowerThan = 0
  75. }
  76. if (helper.isString(options.reporters)) {
  77. options.reporters = options.reporters.split(',')
  78. }
  79. if (helper.isString(options.removedFiles)) {
  80. options.removedFiles = options.removedFiles.split(',')
  81. }
  82. if (helper.isString(options.addedFiles)) {
  83. options.addedFiles = options.addedFiles.split(',')
  84. }
  85. if (helper.isString(options.changedFiles)) {
  86. options.changedFiles = options.changedFiles.split(',')
  87. }
  88. if (helper.isString(options.refresh)) {
  89. options.refresh = options.refresh === 'true'
  90. }
  91. let configFile = argv._.shift()
  92. if (!configFile) {
  93. // default config file (if exists)
  94. if (fs.existsSync('./karma.conf.js')) {
  95. configFile = './karma.conf.js'
  96. } else if (fs.existsSync('./karma.conf.coffee')) {
  97. configFile = './karma.conf.coffee'
  98. } else if (fs.existsSync('./karma.conf.ts')) {
  99. configFile = './karma.conf.ts'
  100. } else if (fs.existsSync('./.config/karma.conf.js')) {
  101. configFile = './.config/karma.conf.js'
  102. } else if (fs.existsSync('./.config/karma.conf.coffee')) {
  103. configFile = './.config/karma.conf.coffee'
  104. } else if (fs.existsSync('./.config/karma.conf.ts')) {
  105. configFile = './.config/karma.conf.ts'
  106. }
  107. }
  108. options.configFile = configFile ? path.resolve(configFile) : null
  109. return options
  110. }
  111. function parseClientArgs (argv) {
  112. // extract any args after '--' as clientArgs
  113. let clientArgs = []
  114. argv = argv.slice(2)
  115. const idx = argv.indexOf('--')
  116. if (idx !== -1) {
  117. clientArgs = argv.slice(idx + 1)
  118. }
  119. return clientArgs
  120. }
  121. // return only args that occur before `--`
  122. function argsBeforeDoubleDash (argv) {
  123. const idx = argv.indexOf('--')
  124. return idx === -1 ? argv : argv.slice(0, idx)
  125. }
  126. function describeShared () {
  127. optimist
  128. .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
  129. 'Usage:\n' +
  130. ' $0 <command>\n\n' +
  131. 'Commands:\n' +
  132. ' start [<configFile>] [<options>] Start the server / do single run.\n' +
  133. ' init [<configFile>] Initialize a config file.\n' +
  134. ' run [<options>] [ -- <clientArgs>] Trigger a test run.\n' +
  135. ' completion Shell completion for karma.\n\n' +
  136. 'Run --help with particular command to see its description and available options.')
  137. .describe('help', 'Print usage and options.')
  138. .describe('version', 'Print current version.')
  139. }
  140. function describeInit () {
  141. optimist
  142. .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
  143. 'INIT - Initialize a config file.\n\n' +
  144. 'Usage:\n' +
  145. ' $0 init [<configFile>]')
  146. .describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
  147. .describe('colors', 'Use colors when reporting and printing logs.')
  148. .describe('no-colors', 'Do not use colors when reporting or printing logs.')
  149. .describe('help', 'Print usage and options.')
  150. }
  151. function describeStart () {
  152. optimist
  153. .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
  154. 'START - Start the server / do a single run.\n\n' +
  155. 'Usage:\n' +
  156. ' $0 start [<configFile>] [<options>]')
  157. .describe('port', '<integer> Port where the server is running.')
  158. .describe('auto-watch', 'Auto watch source files and run on change.')
  159. .describe('detached', 'Detach the server.')
  160. .describe('no-auto-watch', 'Do not watch source files.')
  161. .describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
  162. .describe('colors', 'Use colors when reporting and printing logs.')
  163. .describe('no-colors', 'Do not use colors when reporting or printing logs.')
  164. .describe('reporters', 'List of reporters (available: dots, progress, junit, growl, coverage).')
  165. .describe('browsers', 'List of browsers to start (eg. --browsers Chrome,ChromeCanary,Firefox).')
  166. .describe('capture-timeout', '<integer> Kill browser if does not capture in given time [ms].')
  167. .describe('single-run', 'Run the test when browsers captured and exit.')
  168. .describe('no-single-run', 'Disable single-run.')
  169. .describe('report-slower-than', '<integer> Report tests that are slower than given time [ms].')
  170. .describe('fail-on-empty-test-suite', 'Fail on empty test suite.')
  171. .describe('no-fail-on-empty-test-suite', 'Do not fail on empty test suite.')
  172. .describe('fail-on-failing-test-suite', 'Fail on failing test suite.')
  173. .describe('no-fail-on-failing-test-suite', 'Do not fail on failing test suite.')
  174. .describe('help', 'Print usage and options.')
  175. }
  176. function describeRun () {
  177. optimist
  178. .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
  179. 'RUN - Run the tests (requires running server).\n\n' +
  180. 'Usage:\n' +
  181. ' $0 run [<configFile>] [<options>] [ -- <clientArgs>]')
  182. .describe('port', '<integer> Port where the server is listening.')
  183. .describe('no-refresh', 'Do not re-glob all the patterns.')
  184. .describe('fail-on-empty-test-suite', 'Fail on empty test suite.')
  185. .describe('no-fail-on-empty-test-suite', 'Do not fail on empty test suite.')
  186. .describe('help', 'Print usage.')
  187. .describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
  188. .describe('colors', 'Use colors when reporting and printing logs.')
  189. .describe('no-colors', 'Do not use colors when reporting or printing logs.')
  190. }
  191. function describeStop () {
  192. optimist
  193. .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
  194. 'STOP - Stop the server (requires running server).\n\n' +
  195. 'Usage:\n' +
  196. ' $0 run [<configFile>] [<options>]')
  197. .describe('port', '<integer> Port where the server is listening.')
  198. .describe('log-level', '<disable | error | warn | info | debug> Level of logging.')
  199. .describe('help', 'Print usage.')
  200. }
  201. function describeCompletion () {
  202. optimist
  203. .usage('Karma - Spectacular Test Runner for JavaScript.\n\n' +
  204. 'COMPLETION - Bash/ZSH completion for karma.\n\n' +
  205. 'Installation:\n' +
  206. ' $0 completion >> ~/.bashrc\n')
  207. .describe('help', 'Print usage.')
  208. }
  209. exports.process = function () {
  210. const argv = optimist.parse(argsBeforeDoubleDash(process.argv.slice(2)))
  211. const options = {
  212. cmd: argv._.shift()
  213. }
  214. switch (options.cmd) {
  215. case 'start':
  216. describeStart()
  217. break
  218. case 'run':
  219. describeRun()
  220. options.clientArgs = parseClientArgs(process.argv)
  221. break
  222. case 'stop':
  223. describeStop()
  224. break
  225. case 'init':
  226. describeInit()
  227. break
  228. case 'completion':
  229. describeCompletion()
  230. break
  231. default:
  232. describeShared()
  233. if (!options.cmd) {
  234. processArgs(argv, options, fs, path)
  235. console.error('Command not specified.')
  236. } else {
  237. console.error('Unknown command "' + options.cmd + '".')
  238. }
  239. optimist.showHelp()
  240. process.exit(1)
  241. }
  242. return processArgs(argv, options, fs, path)
  243. }
  244. exports.run = function () {
  245. const config = exports.process()
  246. switch (config.cmd) {
  247. case 'start':
  248. new Server(config).start()
  249. break
  250. case 'run':
  251. require('./runner').run(config)
  252. break
  253. case 'stop':
  254. require('./stopper').stop(config)
  255. break
  256. case 'init':
  257. require('./init').init(config)
  258. break
  259. case 'completion':
  260. require('./completion').completion(config)
  261. break
  262. }
  263. }
  264. // just for testing
  265. exports.processArgs = processArgs
  266. exports.parseClientArgs = parseClientArgs
  267. exports.argsBeforeDoubleDash = argsBeforeDoubleDash