UNPKG

3.15 kBJavaScriptView Raw
1'use strict'
2
3const chalk = require('chalk')
4const { parseArgsStringToArgv } = require('string-argv')
5const dedent = require('dedent')
6const execa = require('execa')
7const symbols = require('log-symbols')
8
9const debug = require('debug')('lint-staged:task')
10
11const successMsg = linter => `${symbols.success} ${linter} passed!`
12
13/**
14 * Create and returns an error instance with a given message.
15 * If we set the message on the error instance, it gets logged multiple times(see #142).
16 * So we set the actual error message in a private field and extract it later,
17 * log only once.
18 *
19 * @param {string} message
20 * @returns {Error}
21 */
22function throwError(message) {
23 const err = new Error()
24 err.privateMsg = `\n\n\n${message}`
25 return err
26}
27
28/**
29 * Create a failure message dependding on process result.
30 *
31 * @param {string} linter
32 * @param {Object} result
33 * @param {string} result.stdout
34 * @param {string} result.stderr
35 * @param {boolean} result.failed
36 * @param {boolean} result.killed
37 * @param {string} result.signal
38 * @param {Object} context (see https://github.com/SamVerschueren/listr#context)
39 * @returns {Error}
40 */
41function makeErr(linter, result, context = {}) {
42 context.taskError = true
43 const { stdout, stderr, killed, signal } = result
44 if (killed || (signal && signal !== '')) {
45 return throwError(
46 `${symbols.warning} ${chalk.yellow(`${linter} was terminated with ${signal}`)}`
47 )
48 }
49 return throwError(dedent`${symbols.error} ${chalk.redBright(
50 `${linter} found some errors. Please fix them and try committing again.`
51 )}
52 ${stdout}
53 ${stderr}
54 `)
55}
56
57/**
58 * Returns the task function for the linter.
59 *
60 * @param {Object} options
61 * @param {string} options.command — Linter task
62 * @param {String} options.gitDir - Current git repo path
63 * @param {Boolean} options.isFn - Whether the linter task is a function
64 * @param {Array<string>} options.files — Filepaths to run the linter task against
65 * @param {Boolean} [options.relative] — Whether the filepaths should be relative
66 * @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
67 * @returns {function(): Promise<Array<string>>}
68 */
69module.exports = function resolveTaskFn({ command, files, gitDir, isFn, relative, shell = false }) {
70 const [cmd, ...args] = parseArgsStringToArgv(command)
71 debug('cmd:', cmd)
72 debug('args:', args)
73
74 const execaOptions = { preferLocal: true, reject: false, shell }
75 if (relative) {
76 execaOptions.cwd = process.cwd()
77 } else if (/^git(\.exe)?/i.test(cmd) && gitDir !== process.cwd()) {
78 // Only use gitDir as CWD if we are using the git binary
79 // e.g `npm` should run tasks in the actual CWD
80 execaOptions.cwd = gitDir
81 }
82 debug('execaOptions:', execaOptions)
83
84 return async ctx => {
85 const promise = shell
86 ? execa.command(isFn ? command : `${command} ${files.join(' ')}`, execaOptions)
87 : execa(cmd, isFn ? args : args.concat(files), execaOptions)
88 const result = await promise
89
90 if (result.failed || result.killed || result.signal != null) {
91 throw makeErr(cmd, result, ctx)
92 }
93
94 return successMsg(cmd)
95 }
96}