-
-
Notifications
You must be signed in to change notification settings - Fork 412
/
resolveTaskFn.js
126 lines (111 loc) Β· 3.93 KB
/
resolveTaskFn.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
'use strict'
const chalk = require('chalk')
const dedent = require('dedent')
const execa = require('execa')
const symbols = require('log-symbols')
const stringArgv = require('string-argv')
const debug = require('debug')('lint-staged:task')
/**
* Execute the given linter cmd using execa and
* return the promise.
*
* @param {string} cmd
* @return {Promise} child_process
*/
const execLinter = (cmd, args, execaOptions = {}) => {
debug('cmd:', cmd)
if (args) debug('args:', args)
debug('execaOptions:', execaOptions)
return args ? execa(cmd, args, execaOptions) : execa(cmd, execaOptions)
}
const successMsg = linter => `${symbols.success} ${linter} passed!`
/**
* Create and returns an error instance with a given message.
* If we set the message on the error instance, it gets logged multiple times(see #142).
* So we set the actual error message in a private field and extract it later,
* log only once.
*
* @param {string} message
* @returns {Error}
*/
function throwError(message) {
const err = new Error()
err.privateMsg = `\n\n\n${message}`
return err
}
/**
* Create a failure message dependding on process result.
*
* @param {string} linter
* @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} context (see https://github.com/SamVerschueren/listr#context)
* @returns {Error}
*/
function makeErr(linter, result, context = {}) {
// Indicate that some linter will fail so we don't update the index with formatting changes
context.hasErrors = true // eslint-disable-line no-param-reassign
const { stdout, stderr, killed, signal } = result
if (killed || (signal && signal !== '')) {
return throwError(
`${symbols.warning} ${chalk.yellow(`${linter} was terminated with ${signal}`)}`
)
}
return throwError(dedent`${symbols.error} ${chalk.redBright(
`${linter} found some errors. Please fix them and try committing again.`
)}
${stdout}
${stderr}
`)
}
/**
* Returns the task function for the linter. It handles chunking for file paths
* if the OS is Windows.
*
* @param {Object} options
* @param {string} options.linter
* @param {Boolean} options.shellMode
* @param {string} options.gitDir
* @param {Array<string>} options.pathsToLint
* @returns {function(): Promise<Array<string>>}
*/
module.exports = function resolveTaskFn({ gitDir, linter, pathsToLint, shell = false }) {
// If `linter` is a function, it should return a string when evaluated with `pathsToLint`.
// Else, it's a already a string
const fnLinter = typeof linter === 'function'
const linterString = fnLinter ? linter(pathsToLint) : linter
// Support arrays of strings/functions by treating everything as arrays
const linters = Array.isArray(linterString) ? linterString : [linterString]
const execaOptions = { preferLocal: true, reject: false, shell }
const tasks = linters.map(command => {
let cmd
let args
if (shell) {
execaOptions.shell = true
// If `shell`, passed command shouldn't be parsed
// If `linter` is a function, command already includes `pathsToLint`.
cmd = fnLinter ? command : `${command} ${pathsToLint.join(' ')}`
} else {
const [parsedCmd, ...parsedArgs] = stringArgv.parseArgsStringToArgv(command)
cmd = parsedCmd
args = fnLinter ? parsedArgs : parsedArgs.concat(pathsToLint)
}
// Only use gitDir as CWD if we are using the git binary
// e.g `npm` should run tasks in the actual CWD
if (/^git(\.exe)?/i.test(command) && gitDir !== process.cwd()) {
execaOptions.cwd = gitDir
}
return ctx =>
execLinter(cmd, args, execaOptions).then(result => {
if (result.failed || result.killed || result.signal != null) {
throw makeErr(linter, result, ctx)
}
return successMsg(linter)
})
})
return ctx => Promise.all(tasks.map(task => task(ctx)))
}