lint-staged 16.2.7 → 16.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/lib/colors.js +4 -4
- package/lib/execGit.js +20 -17
- package/lib/getAbortController.js +18 -0
- package/lib/getFunctionTask.js +2 -2
- package/lib/getSpawnedTask.js +87 -114
- package/lib/getSpawnedTasks.js +3 -0
- package/lib/killSubprocesses.js +42 -0
- package/lib/runAll.js +4 -13
- package/lib/state.js +0 -3
- package/lib/validateBraces.js +7 -3
- package/package.json +17 -18
package/README.md
CHANGED
|
@@ -34,6 +34,10 @@ $ git commit
|
|
|
34
34
|
|
|
35
35
|
</details>
|
|
36
36
|
|
|
37
|
+
> [!Tip]
|
|
38
|
+
> Do you only want to check staged files for errors, but not edit them automatically?
|
|
39
|
+
> You might be interested in this simpler shell script: [`lint-staged.sh`](https://github.com/lint-staged/lint-staged.sh).
|
|
40
|
+
|
|
37
41
|
## Table of Contents
|
|
38
42
|
|
|
39
43
|
- [Why](#why)
|
|
@@ -362,7 +366,7 @@ Supported are any executables installed locally or globally via `npm` as well as
|
|
|
362
366
|
|
|
363
367
|
> Using globally installed scripts is discouraged, since lint-staged may not work for someone who doesn't have it installed.
|
|
364
368
|
|
|
365
|
-
`lint-staged` uses [
|
|
369
|
+
`lint-staged` uses [tinyexec](https://github.com/tinylibs/tinyexec) to spawn locally installed commands. So in your `.lintstagedrc` you can write:
|
|
366
370
|
|
|
367
371
|
```json
|
|
368
372
|
{
|
package/lib/colors.js
CHANGED
|
@@ -2,14 +2,14 @@ import nodeTty from 'node:tty'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @example NO_COLOR
|
|
5
|
-
* @
|
|
6
|
-
* @
|
|
5
|
+
* @example NO_COLOR=1
|
|
6
|
+
* @example NO_COLOR=true
|
|
7
7
|
*/
|
|
8
8
|
const TRUTHRY_ENV_VAR_VALUES = ['', '1', 'true']
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @
|
|
12
|
-
* @
|
|
11
|
+
* @example FORCE_COLOR=0
|
|
12
|
+
* @example FORCE_COLOR=false
|
|
13
13
|
*/
|
|
14
14
|
const FALSY_ENV_VAR_VALUES = ['0', 'false']
|
|
15
15
|
|
package/lib/execGit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { exec } from 'tinyexec'
|
|
2
2
|
|
|
3
3
|
import { createDebug } from './debug.js'
|
|
4
4
|
|
|
@@ -13,22 +13,25 @@ const NO_SUBMODULE_RECURSE = ['-c', 'submodule.recurse=false']
|
|
|
13
13
|
// exported for tests
|
|
14
14
|
export const GIT_GLOBAL_OPTIONS = [...NO_SUBMODULE_RECURSE]
|
|
15
15
|
|
|
16
|
-
/** @type {(cmd: string[], options?:
|
|
16
|
+
/** @type {(cmd: string[], options?: { cwd?: string }) => Promise<string>} */
|
|
17
17
|
export const execGit = async (cmd, options) => {
|
|
18
|
-
debugLog('Running git command', cmd)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
debugLog('Running git command:', cmd)
|
|
19
|
+
const result = exec('git', [...NO_SUBMODULE_RECURSE, ...cmd], {
|
|
20
|
+
nodeOptions: {
|
|
21
|
+
cwd: options?.cwd,
|
|
22
|
+
stdio: ['ignore'],
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
let output = ''
|
|
27
|
+
for await (const line of result) {
|
|
28
|
+
output += line + '\n'
|
|
29
|
+
}
|
|
30
|
+
output = output.trimEnd()
|
|
31
|
+
|
|
32
|
+
if (result.exitCode > 0) {
|
|
33
|
+
throw new Error(output, { cause: result })
|
|
33
34
|
}
|
|
35
|
+
|
|
36
|
+
return output
|
|
34
37
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const Signal = {
|
|
2
|
+
SIGINT: 'SIGINT',
|
|
3
|
+
SIGKILL: 'SIGKILL',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get an AbortController used to cancel running tasks on failure/interruption.
|
|
8
|
+
* @returns AbortController
|
|
9
|
+
*/
|
|
10
|
+
export const getAbortController = (nodeProcess = process) => {
|
|
11
|
+
const abortController = new AbortController()
|
|
12
|
+
|
|
13
|
+
nodeProcess.on(Signal.SIGINT, () => {
|
|
14
|
+
abortController.abort(Signal.SIGINT)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return abortController
|
|
18
|
+
}
|
package/lib/getFunctionTask.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createDebug } from './debug.js'
|
|
2
|
-
import {
|
|
2
|
+
import { createTaskError } from './getSpawnedTask.js'
|
|
3
3
|
|
|
4
4
|
const debugLog = createDebug('lint-staged:getFunctionTasks')
|
|
5
5
|
|
|
@@ -24,7 +24,7 @@ export const getFunctionTask = async (command, files) => {
|
|
|
24
24
|
try {
|
|
25
25
|
await command.task(files.map((file) => file.filepath))
|
|
26
26
|
} catch (e) {
|
|
27
|
-
throw
|
|
27
|
+
throw createTaskError(command.title, e, ctx)
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
package/lib/getSpawnedTask.js
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
|
-
import spawn, { SubprocessError } from 'nano-spawn'
|
|
2
|
-
import pidtree from 'pidtree'
|
|
3
1
|
import { parseArgsStringToArgv } from 'string-argv'
|
|
2
|
+
import { exec } from 'tinyexec'
|
|
4
3
|
|
|
5
4
|
import { blackBright, red } from './colors.js'
|
|
6
5
|
import { createDebug } from './debug.js'
|
|
7
6
|
import { error, info } from './figures.js'
|
|
7
|
+
import { Signal } from './getAbortController.js'
|
|
8
|
+
import { killSubProcesses } from './killSubprocesses.js'
|
|
8
9
|
import { getInitialState } from './state.js'
|
|
9
10
|
import { TaskError } from './symbols.js'
|
|
10
11
|
|
|
11
|
-
const TASK_ERROR = 'lint-staged:taskError'
|
|
12
|
-
|
|
13
12
|
const debugLog = createDebug('lint-staged:getSpawnedTask')
|
|
14
13
|
|
|
15
|
-
/** @type {(error: import('nano-spawn').SubprocessError) => string} */
|
|
16
|
-
const getTag = (error) => {
|
|
17
|
-
return error.signalName ?? 'FAILED'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
/**
|
|
21
15
|
* Handle task console output.
|
|
22
16
|
*
|
|
23
17
|
* @param {string} command
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {
|
|
26
|
-
* @
|
|
18
|
+
* @param {string} output
|
|
19
|
+
* @param {ReturnType<typeof getInitialState>} ctx context
|
|
20
|
+
* @param {keyof typeof Signal | undefined} signal
|
|
21
|
+
* @param {import('tinyexec').Result} [errorResult]
|
|
27
22
|
*/
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
const outputTitle =
|
|
31
|
-
|
|
32
|
-
ctx.output.push(output.join('\n'))
|
|
23
|
+
const handleTaskOutput = (command, output, ctx, signal, errorResult) => {
|
|
24
|
+
if (output) {
|
|
25
|
+
const outputTitle = errorResult ? red(`${error} ${command}:`) : `${info} ${command}:`
|
|
26
|
+
ctx.output.push([...(ctx.quiet ? [] : ['', outputTitle]), output].join('\n'))
|
|
33
27
|
return
|
|
34
28
|
}
|
|
35
29
|
|
|
@@ -37,73 +31,12 @@ const handleOutput = (command, result, ctx, isError = false) => {
|
|
|
37
31
|
return
|
|
38
32
|
}
|
|
39
33
|
|
|
40
|
-
if (
|
|
41
|
-
ctx.output.push(red(`\n${error} ${command}
|
|
42
|
-
} else if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ctx.output.push(message)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Kill subprocess along with all its child processes.
|
|
52
|
-
* @param {import('nano-spawn').Subprocess} subprocess
|
|
53
|
-
*/
|
|
54
|
-
const killSubprocess = async (subprocess) => {
|
|
55
|
-
let childProcess
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
childProcess = await subprocess.nodeChildProcess
|
|
59
|
-
} catch {
|
|
60
|
-
/** ignore internal nano-spawn errors, if child process isn't available it can't be killed */
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (childProcess?.pid !== undefined) {
|
|
64
|
-
try {
|
|
65
|
-
for (const childPid of await pidtree(childProcess.pid)) {
|
|
66
|
-
try {
|
|
67
|
-
process.kill(childPid, 'SIGKILL')
|
|
68
|
-
} catch (error) {
|
|
69
|
-
debugLog(`Failed to kill process with pid "%d": %o`, childPid, error)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
// Suppress "No matching pid found" error. This probably means
|
|
74
|
-
// the process already died before executing.
|
|
75
|
-
debugLog(`Failed to list child processes of pid "%d": %o`, childProcess.pid, error)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// The child process is terminated separately in order to get the `KILLED` status.
|
|
80
|
-
childProcess?.kill('SIGKILL')
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Interrupts the execution of the subprocess that we spawned if
|
|
85
|
-
* another task adds an error to the context.
|
|
86
|
-
*
|
|
87
|
-
* @param {Object} ctx
|
|
88
|
-
* @param {import('nano-spawn').Subprocess} subprocess
|
|
89
|
-
* @returns {() => Promise<void>} Function that clears the interval that
|
|
90
|
-
* checks the context.
|
|
91
|
-
*/
|
|
92
|
-
const interruptExecutionOnError = (ctx, subprocess) => {
|
|
93
|
-
let killPromise
|
|
94
|
-
|
|
95
|
-
const errorListener = async () => {
|
|
96
|
-
killPromise = killSubprocess(subprocess)
|
|
97
|
-
await killPromise
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
ctx.events.on(TASK_ERROR, errorListener, { once: true })
|
|
101
|
-
|
|
102
|
-
return async () => {
|
|
103
|
-
ctx.events.off(TASK_ERROR, errorListener)
|
|
104
|
-
if (killPromise) {
|
|
105
|
-
await killPromise
|
|
106
|
-
}
|
|
34
|
+
if (signal === 'SIGINT') {
|
|
35
|
+
ctx.output.push(red(`\n${error} Task interrupted: ${command}`))
|
|
36
|
+
} else if (signal === 'SIGKILL') {
|
|
37
|
+
ctx.output.push(red(`\n${error} Task killed: ${command}`))
|
|
38
|
+
} else if (errorResult) {
|
|
39
|
+
ctx.output.push(red(`\n${error} Task failed to spawn: ${command}`), signal)
|
|
107
40
|
}
|
|
108
41
|
}
|
|
109
42
|
|
|
@@ -111,25 +44,21 @@ const interruptExecutionOnError = (ctx, subprocess) => {
|
|
|
111
44
|
* Create a error output depending on process result.
|
|
112
45
|
*
|
|
113
46
|
* @param {string} command
|
|
114
|
-
* @param {import('
|
|
115
|
-
* @param {
|
|
47
|
+
* @param {import('tinyexec').Result} result
|
|
48
|
+
* @param {ReturnType<typeof getInitialState>} ctx context
|
|
49
|
+
* @param {keyof typeof Signal | undefined} signal
|
|
116
50
|
* @returns {Error}
|
|
117
51
|
*/
|
|
118
|
-
export const
|
|
52
|
+
export const createTaskError = (command, result, ctx, signal = 'FAILED') => {
|
|
119
53
|
ctx.errors.add(TaskError)
|
|
120
|
-
|
|
121
|
-
// https://nodejs.org/api/events.html#error-events
|
|
122
|
-
ctx.events.emit(TASK_ERROR, TaskError)
|
|
123
|
-
|
|
124
|
-
handleOutput(command, error, ctx, true)
|
|
125
|
-
const tag = getTag(error)
|
|
126
|
-
return new Error(`${red(command)} ${blackBright(`[${tag}]`)}`)
|
|
54
|
+
return new Error(`${red(command)} ${blackBright(`[${signal}]`)}`, { cause: result })
|
|
127
55
|
}
|
|
128
56
|
|
|
129
57
|
/**
|
|
130
58
|
* Returns the task function for the linter.
|
|
131
59
|
*
|
|
132
60
|
* @param {Object} options
|
|
61
|
+
* @param {AbortController} options.abortController
|
|
133
62
|
* @param {boolean} [options.color]
|
|
134
63
|
* @param {string} options.command — Linter task
|
|
135
64
|
* @param {string} [options.continueOnError]
|
|
@@ -141,6 +70,7 @@ export const makeErr = (command, error, ctx) => {
|
|
|
141
70
|
* @returns {() => Promise<Array<string>>}
|
|
142
71
|
*/
|
|
143
72
|
export const getSpawnedTask = ({
|
|
73
|
+
abortController,
|
|
144
74
|
color,
|
|
145
75
|
command,
|
|
146
76
|
continueOnError = false,
|
|
@@ -154,35 +84,78 @@ export const getSpawnedTask = ({
|
|
|
154
84
|
debugLog('cmd:', cmd)
|
|
155
85
|
debugLog('args:', args)
|
|
156
86
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
87
|
+
/** @type {import('tinyexec').Options}*/
|
|
88
|
+
const tinyExecOptions = {
|
|
89
|
+
nodeOptions: {
|
|
90
|
+
// Only use topLevelDir as CWD if we are using the git binary
|
|
91
|
+
// e.g `npm` should run tasks in the actual CWD
|
|
92
|
+
cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
|
|
93
|
+
detached: true,
|
|
94
|
+
env: color ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
|
|
95
|
+
stdio: ['ignore'],
|
|
96
|
+
},
|
|
164
97
|
}
|
|
165
98
|
|
|
166
|
-
debugLog('
|
|
99
|
+
debugLog('Tinyexec options:', tinyExecOptions)
|
|
167
100
|
|
|
101
|
+
/** @param {ReturnType<typeof getInitialState>} ctx context */
|
|
168
102
|
return async (ctx = getInitialState()) => {
|
|
169
|
-
|
|
103
|
+
const result = exec(cmd, isFn ? args : args.concat(files), tinyExecOptions)
|
|
104
|
+
|
|
105
|
+
const taskFailed = () => result.exitCode > 0 || result.process?.signalCode
|
|
106
|
+
|
|
107
|
+
/** @type {keyof typeof Signal | undefined} */
|
|
108
|
+
let signal
|
|
170
109
|
|
|
110
|
+
abortController.signal.addEventListener(
|
|
111
|
+
'abort',
|
|
112
|
+
async () => {
|
|
113
|
+
if (taskFailed() || !result.process) return
|
|
114
|
+
|
|
115
|
+
signal = abortController.signal.reason
|
|
116
|
+
const pid = result.process.pid
|
|
117
|
+
result.process.kill(abortController.signal.reason)
|
|
118
|
+
await killSubProcesses(pid)
|
|
119
|
+
},
|
|
120
|
+
{ once: true }
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
let output = ''
|
|
171
124
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
quitInterruptCheck = interruptExecutionOnError(ctx, subprocess)
|
|
175
|
-
}
|
|
176
|
-
const result = await subprocess
|
|
177
|
-
if (verbose) {
|
|
178
|
-
handleOutput(command, result, ctx)
|
|
125
|
+
for await (const line of result) {
|
|
126
|
+
output += line + '\n'
|
|
179
127
|
}
|
|
180
128
|
} catch (error) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
129
|
+
/** Probably failed to spawn (ENOENT) */
|
|
130
|
+
const errorSignal = (error instanceof Error && error.code) || 'FAILED'
|
|
131
|
+
|
|
132
|
+
if (continueOnError !== true) {
|
|
133
|
+
/** Other tasks should be killed */
|
|
134
|
+
abortController.abort(Signal.SIGKILL)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
handleTaskOutput(command, output, ctx, errorSignal, result)
|
|
138
|
+
throw createTaskError(command, result, ctx, errorSignal)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
output = output.trimEnd()
|
|
142
|
+
|
|
143
|
+
if (taskFailed()) {
|
|
144
|
+
if (continueOnError !== true) {
|
|
145
|
+
/** Other tasks should be killed */
|
|
146
|
+
abortController.abort(Signal.SIGKILL)
|
|
185
147
|
}
|
|
148
|
+
|
|
149
|
+
if (result.process?.pid) {
|
|
150
|
+
await killSubProcesses(result.process.pid)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
handleTaskOutput(command, output, ctx, signal, result)
|
|
154
|
+
throw createTaskError(command, result, ctx, result.process?.signalCode ?? signal)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (verbose) {
|
|
158
|
+
handleTaskOutput(command, output, ctx, signal)
|
|
186
159
|
}
|
|
187
160
|
}
|
|
188
161
|
}
|
package/lib/getSpawnedTasks.js
CHANGED
|
@@ -8,6 +8,7 @@ const debugLog = createDebug('lint-staged:getSpawnedTasks')
|
|
|
8
8
|
* Creates and returns an array of listr tasks which map to the given commands.
|
|
9
9
|
*
|
|
10
10
|
* @param {object} options
|
|
11
|
+
* @param {AbortController} options.abortController
|
|
11
12
|
* @param {boolean} [options.color]
|
|
12
13
|
* @param {Array<string|Function>|string|Function} options.commands
|
|
13
14
|
* @param {string} options.continueOnError
|
|
@@ -17,6 +18,7 @@ const debugLog = createDebug('lint-staged:getSpawnedTasks')
|
|
|
17
18
|
* @param {Boolean} verbose
|
|
18
19
|
*/
|
|
19
20
|
export const getSpawnedTasks = async ({
|
|
21
|
+
abortController,
|
|
20
22
|
color,
|
|
21
23
|
commands,
|
|
22
24
|
continueOnError,
|
|
@@ -55,6 +57,7 @@ export const getSpawnedTasks = async ({
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const task = getSpawnedTask({
|
|
60
|
+
abortController,
|
|
58
61
|
color,
|
|
59
62
|
command,
|
|
60
63
|
continueOnError,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { exec } from 'node:child_process'
|
|
2
|
+
import { promisify } from 'node:util'
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* End process by pid, forcefully, including child processes
|
|
8
|
+
*
|
|
9
|
+
* @see {@link https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill}
|
|
10
|
+
*
|
|
11
|
+
* @param {number} pid
|
|
12
|
+
*/
|
|
13
|
+
const killWin32Subprocesses = async (pid) => {
|
|
14
|
+
await execAsync(`taskkill /pid ${pid} /T /F`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Kill all processes in the group by using negative pid
|
|
19
|
+
*
|
|
20
|
+
* @see {@link https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html}
|
|
21
|
+
*
|
|
22
|
+
* @param {number} pid
|
|
23
|
+
*/
|
|
24
|
+
const killUnixSubprocesses = async (pid) => {
|
|
25
|
+
process.kill(-pid, 'SIGKILL')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {number} pid
|
|
30
|
+
* @param {boolean} [isWin32]
|
|
31
|
+
*/
|
|
32
|
+
export const killSubProcesses = async (pid, isWin32 = process.platform === 'win32') => {
|
|
33
|
+
try {
|
|
34
|
+
if (isWin32) {
|
|
35
|
+
await killWin32Subprocesses(pid)
|
|
36
|
+
} else {
|
|
37
|
+
await killUnixSubprocesses(pid)
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
/** ignore errors */
|
|
41
|
+
}
|
|
42
|
+
}
|
package/lib/runAll.js
CHANGED
|
@@ -9,6 +9,7 @@ import { blackBright } from './colors.js'
|
|
|
9
9
|
import { createDebug } from './debug.js'
|
|
10
10
|
import { execGit } from './execGit.js'
|
|
11
11
|
import { generateTasks } from './generateTasks.js'
|
|
12
|
+
import { getAbortController } from './getAbortController.js'
|
|
12
13
|
import { getFunctionTask, isFunctionTask } from './getFunctionTask.js'
|
|
13
14
|
import { getRenderer } from './getRenderer.js'
|
|
14
15
|
import { getSpawnedTasks } from './getSpawnedTasks.js'
|
|
@@ -187,20 +188,14 @@ export const runAll = async (
|
|
|
187
188
|
...getRenderer({ color, debug, quiet }, logger),
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
/**
|
|
191
|
-
* This is used to set max event listener count to the total number
|
|
192
|
-
* of generated tasks. The event listener is used to keep track of
|
|
193
|
-
* the interrupt signal and kill all tasks when it happens. See the
|
|
194
|
-
* `interruptExecutionOnError` in `getSpawnedTask`.
|
|
195
|
-
*/
|
|
196
|
-
let listrTaskCount = 0
|
|
197
|
-
|
|
198
191
|
const listrTasks = []
|
|
199
192
|
|
|
200
193
|
// Set of all staged files that matched a task glob. Values in a set are unique.
|
|
201
194
|
/** @type {Set<import('./getStagedFiles.js').StagedFile>} */
|
|
202
195
|
const matchedFiles = new Set()
|
|
203
196
|
|
|
197
|
+
const abortController = getAbortController()
|
|
198
|
+
|
|
204
199
|
for (const [configPath, { config, files }] of Object.entries(filesByConfig)) {
|
|
205
200
|
const configName = configPath ? normalizePath(path.relative(cwd, configPath)) : 'Config object'
|
|
206
201
|
|
|
@@ -222,6 +217,7 @@ export const runAll = async (
|
|
|
222
217
|
(isFunctionTask(task.commands)
|
|
223
218
|
? getFunctionTask(task.commands, task.fileList)
|
|
224
219
|
: getSpawnedTasks({
|
|
220
|
+
abortController,
|
|
225
221
|
color,
|
|
226
222
|
commands: task.commands,
|
|
227
223
|
continueOnError,
|
|
@@ -273,8 +269,6 @@ export const runAll = async (
|
|
|
273
269
|
)
|
|
274
270
|
)
|
|
275
271
|
|
|
276
|
-
listrTaskCount += chunkListrTasks.length
|
|
277
|
-
|
|
278
272
|
listrTasks.push({
|
|
279
273
|
title:
|
|
280
274
|
`${configName}${blackBright(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` +
|
|
@@ -368,9 +362,6 @@ export const runAll = async (
|
|
|
368
362
|
listrOptions
|
|
369
363
|
)
|
|
370
364
|
|
|
371
|
-
debugLog('Set max event listeners to the number of tasks: %i', listrTaskCount)
|
|
372
|
-
ctx.events.setMaxListeners(listrTaskCount)
|
|
373
|
-
|
|
374
365
|
await runner.run()
|
|
375
366
|
|
|
376
367
|
if (ctx.errors.size > 0) {
|
package/lib/state.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import EventEmitter from 'events'
|
|
2
|
-
|
|
3
1
|
import { GIT_ERROR, TASK_ERROR } from './messages.js'
|
|
4
2
|
import {
|
|
5
3
|
FailOnChangesError,
|
|
@@ -18,7 +16,6 @@ export const getInitialState = ({
|
|
|
18
16
|
} = {}) => ({
|
|
19
17
|
backupHash: null,
|
|
20
18
|
errors: new Set([]),
|
|
21
|
-
events: new EventEmitter(),
|
|
22
19
|
shouldFailOnChanges: failOnChanges,
|
|
23
20
|
hasFilesToHide: null,
|
|
24
21
|
output: [],
|
package/lib/validateBraces.js
CHANGED
|
@@ -28,7 +28,8 @@ import { incorrectBraces } from './messages.js'
|
|
|
28
28
|
* - *.${js} // dollar-sign inhibits expansion, so treated literally
|
|
29
29
|
* - *.{js\,ts} // the comma is escaped, so treated literally
|
|
30
30
|
*/
|
|
31
|
-
export const
|
|
31
|
+
export const getIncorrectBracesRegexp = () =>
|
|
32
|
+
/(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* @param {string} pattern
|
|
@@ -36,12 +37,15 @@ export const INCORRECT_BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).
|
|
|
36
37
|
*/
|
|
37
38
|
const stripIncorrectBraces = (pattern) => {
|
|
38
39
|
let output = `${pattern}`
|
|
39
|
-
|
|
40
|
+
const regexp = getIncorrectBracesRegexp()
|
|
41
|
+
let match = regexp.exec(output)
|
|
40
42
|
|
|
41
|
-
while (
|
|
43
|
+
while (match) {
|
|
42
44
|
const fullMatch = match[0]
|
|
43
45
|
const withoutBraces = fullMatch.replace(/{/, '').replace(/}/, '')
|
|
44
46
|
output = output.replace(fullMatch, withoutBraces)
|
|
47
|
+
regexp.lastIndex = 0 // restart iteration
|
|
48
|
+
match = regexp.exec(output)
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
return output
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lint-staged",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.3.1",
|
|
4
4
|
"description": "Lint files staged by git",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,35 +48,34 @@
|
|
|
48
48
|
"tag": "npx changeset tag"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"commander": "^14.0.
|
|
51
|
+
"commander": "^14.0.3",
|
|
52
52
|
"listr2": "^9.0.5",
|
|
53
53
|
"micromatch": "^4.0.8",
|
|
54
|
-
"nano-spawn": "^2.0.0",
|
|
55
|
-
"pidtree": "^0.6.0",
|
|
56
54
|
"string-argv": "^0.3.2",
|
|
57
|
-
"
|
|
55
|
+
"tinyexec": "^1.0.2",
|
|
56
|
+
"yaml": "^2.8.2"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
|
-
"@changesets/changelog-github": "0.5.
|
|
61
|
-
"@changesets/cli": "2.29.
|
|
62
|
-
"@commitlint/cli": "20.
|
|
63
|
-
"@commitlint/config-conventional": "20.
|
|
64
|
-
"@eslint/js": "
|
|
65
|
-
"@vitest/coverage-v8": "4.0.
|
|
66
|
-
"@vitest/eslint-plugin": "1.
|
|
59
|
+
"@changesets/changelog-github": "0.5.2",
|
|
60
|
+
"@changesets/cli": "2.29.8",
|
|
61
|
+
"@commitlint/cli": "20.4.2",
|
|
62
|
+
"@commitlint/config-conventional": "20.4.2",
|
|
63
|
+
"@eslint/js": "10.0.1",
|
|
64
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
65
|
+
"@vitest/eslint-plugin": "1.6.9",
|
|
67
66
|
"consolemock": "1.1.0",
|
|
68
67
|
"cross-env": "10.1.0",
|
|
69
|
-
"eslint": "
|
|
68
|
+
"eslint": "10.0.2",
|
|
70
69
|
"eslint-config-prettier": "10.1.8",
|
|
71
|
-
"eslint-plugin-n": "17.
|
|
72
|
-
"eslint-plugin-prettier": "5.5.
|
|
70
|
+
"eslint-plugin-n": "17.24.0",
|
|
71
|
+
"eslint-plugin-prettier": "5.5.5",
|
|
73
72
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
|
74
73
|
"husky": "9.1.7",
|
|
75
74
|
"mock-stdin": "1.0.0",
|
|
76
|
-
"prettier": "3.
|
|
77
|
-
"semver": "7.7.
|
|
75
|
+
"prettier": "3.8.1",
|
|
76
|
+
"semver": "7.7.4",
|
|
78
77
|
"typescript": "5.9.3",
|
|
79
|
-
"vitest": "4.0.
|
|
78
|
+
"vitest": "4.0.18"
|
|
80
79
|
},
|
|
81
80
|
"keywords": [
|
|
82
81
|
"lint",
|