a0ea344add19da772da6577a556a48d5df576ad576071a768833e989500611758468144255e8e8d73c4b0c35b874a5ee1cad5294c9c74d1e222baffbe8c228 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. 'use strict'
  2. const { redBright, dim } = require('chalk')
  3. const execa = require('execa')
  4. const debug = require('debug')('lint-staged:task')
  5. const { parseArgsStringToArgv } = require('string-argv')
  6. const { error, info } = require('log-symbols')
  7. const { getInitialState } = require('./state')
  8. const { TaskError } = require('./symbols')
  9. const getTag = ({ code, killed, signal }) => signal || (killed && 'KILLED') || code || 'FAILED'
  10. /**
  11. * Handle task console output.
  12. *
  13. * @param {string} command
  14. * @param {Object} result
  15. * @param {string} result.stdout
  16. * @param {string} result.stderr
  17. * @param {boolean} result.failed
  18. * @param {boolean} result.killed
  19. * @param {string} result.signal
  20. * @param {Object} ctx
  21. * @returns {Error}
  22. */
  23. const handleOutput = (command, result, ctx, isError = false) => {
  24. const { stderr, stdout } = result
  25. const hasOutput = !!stderr || !!stdout
  26. if (hasOutput) {
  27. const outputTitle = isError ? redBright(`${error} ${command}:`) : `${info} ${command}:`
  28. const output = []
  29. .concat(ctx.quiet ? [] : ['', outputTitle])
  30. .concat(stderr ? stderr : [])
  31. .concat(stdout ? stdout : [])
  32. ctx.output.push(output.join('\n'))
  33. } else if (isError) {
  34. // Show generic error when task had no output
  35. const tag = getTag(result)
  36. const message = redBright(`\n${error} ${command} failed without output (${tag}).`)
  37. if (!ctx.quiet) ctx.output.push(message)
  38. }
  39. }
  40. /**
  41. * Create a error output dependding on process result.
  42. *
  43. * @param {string} command
  44. * @param {Object} result
  45. * @param {string} result.stdout
  46. * @param {string} result.stderr
  47. * @param {boolean} result.failed
  48. * @param {boolean} result.killed
  49. * @param {string} result.signal
  50. * @param {Object} ctx
  51. * @returns {Error}
  52. */
  53. const makeErr = (command, result, ctx) => {
  54. ctx.errors.add(TaskError)
  55. handleOutput(command, result, ctx, true)
  56. const tag = getTag(result)
  57. return new Error(`${redBright(command)} ${dim(`[${tag}]`)}`)
  58. }
  59. /**
  60. * Returns the task function for the linter.
  61. *
  62. * @param {Object} options
  63. * @param {string} options.command — Linter task
  64. * @param {String} options.gitDir - Current git repo path
  65. * @param {Boolean} options.isFn - Whether the linter task is a function
  66. * @param {Array<string>} options.files — Filepaths to run the linter task against
  67. * @param {Boolean} [options.relative] — Whether the filepaths should be relative
  68. * @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
  69. * @param {Boolean} [options.verbose] — Always show task verbose
  70. * @returns {function(): Promise<Array<string>>}
  71. */
  72. module.exports = function resolveTaskFn({
  73. command,
  74. files,
  75. gitDir,
  76. isFn,
  77. relative,
  78. shell = false,
  79. verbose = false,
  80. }) {
  81. const [cmd, ...args] = parseArgsStringToArgv(command)
  82. debug('cmd:', cmd)
  83. debug('args:', args)
  84. const execaOptions = { preferLocal: true, reject: false, shell }
  85. if (relative) {
  86. execaOptions.cwd = process.cwd()
  87. } else if (/^git(\.exe)?/i.test(cmd) && gitDir !== process.cwd()) {
  88. // Only use gitDir as CWD if we are using the git binary
  89. // e.g `npm` should run tasks in the actual CWD
  90. execaOptions.cwd = gitDir
  91. }
  92. debug('execaOptions:', execaOptions)
  93. return async (ctx = getInitialState()) => {
  94. const result = await (shell
  95. ? execa.command(isFn ? command : `${command} ${files.join(' ')}`, execaOptions)
  96. : execa(cmd, isFn ? args : args.concat(files), execaOptions))
  97. if (result.failed || result.killed || result.signal != null) {
  98. throw makeErr(command, result, ctx)
  99. }
  100. if (verbose) {
  101. handleOutput(command, result, ctx)
  102. }
  103. }
  104. }