1 | 'use strict'
|
2 |
|
3 | const chunk = require('lodash/chunk')
|
4 | const dedent = require('dedent')
|
5 | const isWindows = require('is-windows')
|
6 | const execa = require('execa')
|
7 | const chalk = require('chalk')
|
8 | const symbols = require('log-symbols')
|
9 | const pMap = require('p-map')
|
10 | const calcChunkSize = require('./calcChunkSize')
|
11 | const findBin = require('./findBin')
|
12 |
|
13 | const debug = require('debug')('lint-staged:task')
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function execLinter(bin, args, execaOptions, pathsToLint) {
|
26 | const binArgs = args.concat(pathsToLint)
|
27 |
|
28 | debug('bin:', bin)
|
29 | debug('args: %O', binArgs)
|
30 | debug('opts: %o', execaOptions)
|
31 |
|
32 | return execa(bin, binArgs, { ...execaOptions })
|
33 | }
|
34 |
|
35 | const successMsg = linter => `${symbols.success} ${linter} passed!`
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | function throwError(message) {
|
47 | const err = new Error()
|
48 | err.privateMsg = `\n\n\n${message}`
|
49 | return err
|
50 | }
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | function makeErr(linter, result, context = {}) {
|
66 |
|
67 | context.hasErrors = true
|
68 | const { stdout, stderr, killed, signal } = result
|
69 | if (killed || (signal && signal !== '')) {
|
70 | return throwError(
|
71 | `${symbols.warning} ${chalk.yellow(`${linter} was terminated with ${signal}`)}`
|
72 | )
|
73 | }
|
74 | return throwError(dedent`${symbols.error} ${chalk.redBright(
|
75 | `${linter} found some errors. Please fix them and try committing again.`
|
76 | )}
|
77 | ${stdout}
|
78 | ${stderr}
|
79 | `)
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | module.exports = function resolveTaskFn(options) {
|
95 | const { linter, gitDir, pathsToLint } = options
|
96 | const { bin, args } = findBin(linter)
|
97 |
|
98 | const execaOptions = { reject: false }
|
99 |
|
100 |
|
101 | if (/git(\.exe)?$/i.test(bin) && gitDir !== process.cwd()) {
|
102 | execaOptions.cwd = gitDir
|
103 | }
|
104 |
|
105 | if (!isWindows()) {
|
106 | debug('%s OS: %s; File path chunking unnecessary', symbols.success, process.platform)
|
107 | return ctx =>
|
108 | execLinter(bin, args, execaOptions, pathsToLint).then(result => {
|
109 | if (result.failed || result.killed || result.signal != null) {
|
110 | throw makeErr(linter, result, ctx)
|
111 | }
|
112 |
|
113 | return successMsg(linter)
|
114 | })
|
115 | }
|
116 |
|
117 | const { chunkSize, subTaskConcurrency: concurrency } = options
|
118 |
|
119 | const filePathChunks = chunk(pathsToLint, calcChunkSize(pathsToLint, chunkSize))
|
120 | const mapper = execLinter.bind(null, bin, args, execaOptions)
|
121 |
|
122 | debug(
|
123 | 'OS: %s; Creating linter task with %d chunked file paths',
|
124 | process.platform,
|
125 | filePathChunks.length
|
126 | )
|
127 | return ctx =>
|
128 | pMap(filePathChunks, mapper, { concurrency })
|
129 | .catch(err => {
|
130 |
|
131 | throw new Error(dedent`
|
132 | ${symbols.error} ${linter} got an unexpected error.
|
133 | ${err.message}
|
134 | `)
|
135 | })
|
136 | .then(results => {
|
137 | const errors = results.filter(res => res.failed || res.killed)
|
138 | const failed = results.some(res => res.failed)
|
139 | const killed = results.some(res => res.killed)
|
140 | const signals = results.map(res => res.signal).filter(Boolean)
|
141 |
|
142 | if (failed || killed || signals.length > 0) {
|
143 | const finalResult = {
|
144 | stdout: errors.map(err => err.stdout).join(''),
|
145 | stderr: errors.map(err => err.stderr).join(''),
|
146 | failed,
|
147 | killed,
|
148 | signal: signals.join(', ')
|
149 | }
|
150 |
|
151 | throw makeErr(linter, finalResult, ctx)
|
152 | }
|
153 |
|
154 | return successMsg(linter)
|
155 | })
|
156 | }
|