'use strict'
|
|
const { redBright, dim } = require('chalk')
|
const execa = require('execa')
|
const debug = require('debug')('lint-staged:task')
|
const { parseArgsStringToArgv } = require('string-argv')
|
const { error, info } = require('log-symbols')
|
|
const { getInitialState } = require('./state')
|
const { TaskError } = require('./symbols')
|
|
const getTag = ({ code, killed, signal }) => signal || (killed && 'KILLED') || code || 'FAILED'
|
|
/**
|
* Handle task console output.
|
*
|
* @param {string} command
|
* @param {Object} result
|
* @param {string} result.stdout
|
* @param {string} result.stderr
|
* @param {boolean} result.failed
|
* @param {boolean} result.killed
|
* @param {string} result.signal
|
* @param {Object} ctx
|
* @returns {Error}
|
*/
|
const handleOutput = (command, result, ctx, isError = false) => {
|
const { stderr, stdout } = result
|
const hasOutput = !!stderr || !!stdout
|
|
if (hasOutput) {
|
const outputTitle = isError ? redBright(`${error} ${command}:`) : `${info} ${command}:`
|
const output = []
|
.concat(ctx.quiet ? [] : ['', outputTitle])
|
.concat(stderr ? stderr : [])
|
.concat(stdout ? stdout : [])
|
ctx.output.push(output.join('\n'))
|
} else if (isError) {
|
// Show generic error when task had no output
|
const tag = getTag(result)
|
const message = redBright(`\n${error} ${command} failed without output (${tag}).`)
|
if (!ctx.quiet) ctx.output.push(message)
|
}
|
}
|
|
/**
|
* Create a error output dependding on process result.
|
*
|
* @param {string} command
|
* @param {Object} result
|
* @param {string} result.stdout
|
* @param {string} result.stderr
|
* @param {boolean} result.failed
|
* @param {boolean} result.killed
|
* @param {string} result.signal
|
* @param {Object} ctx
|
* @returns {Error}
|
*/
|
const makeErr = (command, result, ctx) => {
|
ctx.errors.add(TaskError)
|
handleOutput(command, result, ctx, true)
|
const tag = getTag(result)
|
return new Error(`${redBright(command)} ${dim(`[${tag}]`)}`)
|
}
|
|
/**
|
* Returns the task function for the linter.
|
*
|
* @param {Object} options
|
* @param {string} options.command — Linter task
|
* @param {String} options.gitDir - Current git repo path
|
* @param {Boolean} options.isFn - Whether the linter task is a function
|
* @param {Array<string>} options.files — Filepaths to run the linter task against
|
* @param {Boolean} [options.relative] — Whether the filepaths should be relative
|
* @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
|
* @param {Boolean} [options.verbose] — Always show task verbose
|
* @returns {function(): Promise<Array<string>>}
|
*/
|
module.exports = function resolveTaskFn({
|
command,
|
files,
|
gitDir,
|
isFn,
|
relative,
|
shell = false,
|
verbose = false,
|
}) {
|
const [cmd, ...args] = parseArgsStringToArgv(command)
|
debug('cmd:', cmd)
|
debug('args:', args)
|
|
const execaOptions = { preferLocal: true, reject: false, shell }
|
if (relative) {
|
execaOptions.cwd = process.cwd()
|
} else if (/^git(\.exe)?/i.test(cmd) && gitDir !== process.cwd()) {
|
// Only use gitDir as CWD if we are using the git binary
|
// e.g `npm` should run tasks in the actual CWD
|
execaOptions.cwd = gitDir
|
}
|
debug('execaOptions:', execaOptions)
|
|
return async (ctx = getInitialState()) => {
|
const result = await (shell
|
? execa.command(isFn ? command : `${command} ${files.join(' ')}`, execaOptions)
|
: execa(cmd, isFn ? args : args.concat(files), execaOptions))
|
|
if (result.failed || result.killed || result.signal != null) {
|
throw makeErr(command, result, ctx)
|
}
|
|
if (verbose) {
|
handleOutput(command, result, ctx)
|
}
|
}
|
}
|