1 | import { redBright, dim } from 'colorette'
|
2 | import { execa, execaCommand } from 'execa'
|
3 | import debug from 'debug'
|
4 | import { parseArgsStringToArgv } from 'string-argv'
|
5 | import pidTree from 'pidtree'
|
6 |
|
7 | import { error, info } from './figures.js'
|
8 | import { getInitialState } from './state.js'
|
9 | import { TaskError } from './symbols.js'
|
10 |
|
11 | const TASK_ERROR = 'lint-staged:taskError'
|
12 |
|
13 | const debugLog = debug('lint-staged:resolveTaskFn')
|
14 |
|
15 | const getTag = ({ code, killed, signal }) => (killed && 'KILLED') || signal || code || 'FAILED'
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | const handleOutput = (command, result, ctx, isError = false) => {
|
31 | const { stderr, stdout } = result
|
32 | const hasOutput = !!stderr || !!stdout
|
33 |
|
34 | if (hasOutput) {
|
35 | const outputTitle = isError ? redBright(`${error} ${command}:`) : `${info} ${command}:`
|
36 | const output = []
|
37 | .concat(ctx.quiet ? [] : ['', outputTitle])
|
38 | .concat(stderr ? stderr : [])
|
39 | .concat(stdout ? stdout : [])
|
40 | ctx.output.push(output.join('\n'))
|
41 | } else if (isError) {
|
42 |
|
43 | const tag = getTag(result)
|
44 | const message = redBright(`\n${error} ${command} failed without output (${tag}).`)
|
45 | if (!ctx.quiet) ctx.output.push(message)
|
46 | }
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | const killExecaProcess = async (execaProcess) => {
|
54 | try {
|
55 | const childPids = await pidTree(execaProcess.pid)
|
56 | for (const childPid of childPids) {
|
57 | try {
|
58 | process.kill(childPid)
|
59 | } catch (error) {
|
60 | debugLog(`Failed to kill process with pid "%d": %o`, childPid, error)
|
61 | }
|
62 | }
|
63 | } catch (error) {
|
64 |
|
65 |
|
66 | debugLog(`Failed to kill process with pid "%d": %o`, execaProcess.pid, error)
|
67 | }
|
68 |
|
69 |
|
70 | execaProcess.kill()
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | const interruptExecutionOnError = (ctx, execaChildProcess) => {
|
83 | let killPromise
|
84 |
|
85 | const errorListener = async () => {
|
86 | killPromise = killExecaProcess(execaChildProcess)
|
87 | await killPromise
|
88 | }
|
89 |
|
90 | ctx.events.on(TASK_ERROR, errorListener, { once: true })
|
91 |
|
92 | return async () => {
|
93 | ctx.events.off(TASK_ERROR, errorListener)
|
94 | await killPromise
|
95 | }
|
96 | }
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | const makeErr = (command, result, ctx) => {
|
112 | ctx.errors.add(TaskError)
|
113 |
|
114 |
|
115 | ctx.events.emit(TASK_ERROR, TaskError)
|
116 |
|
117 | handleOutput(command, result, ctx, true)
|
118 | const tag = getTag(result)
|
119 | return new Error(`${redBright(command)} ${dim(`[${tag}]`)}`)
|
120 | }
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | export const resolveTaskFn = ({
|
136 | command,
|
137 | cwd = process.cwd(),
|
138 | files,
|
139 | gitDir,
|
140 | isFn,
|
141 | shell = false,
|
142 | verbose = false,
|
143 | }) => {
|
144 | const [cmd, ...args] = parseArgsStringToArgv(command)
|
145 | debugLog('cmd:', cmd)
|
146 | debugLog('args:', args)
|
147 |
|
148 | const execaOptions = {
|
149 |
|
150 |
|
151 | cwd: /^git(\.exe)?/i.test(cmd) ? gitDir : cwd,
|
152 | preferLocal: true,
|
153 | reject: false,
|
154 | shell,
|
155 | }
|
156 |
|
157 | debugLog('execaOptions:', execaOptions)
|
158 |
|
159 | return async (ctx = getInitialState()) => {
|
160 | const execaChildProcess = shell
|
161 | ? execaCommand(isFn ? command : `${command} ${files.join(' ')}`, execaOptions)
|
162 | : execa(cmd, isFn ? args : args.concat(files), execaOptions)
|
163 |
|
164 | const quitInterruptCheck = interruptExecutionOnError(ctx, execaChildProcess)
|
165 | const result = await execaChildProcess
|
166 | await quitInterruptCheck()
|
167 |
|
168 | if (result.failed || result.killed || result.signal != null) {
|
169 | throw makeErr(command, result, ctx)
|
170 | }
|
171 |
|
172 | if (verbose) {
|
173 | handleOutput(command, result, ctx)
|
174 | }
|
175 | }
|
176 | }
|