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 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 [nano-spawn](https://github.com/sindresorhus/nano-spawn?tab=readme-ov-file#optionspreferlocal) to locate locally installed scripts. So in your `.lintstagedrc` you can write:
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
- * @exmaple NO_COLOR=1
6
- * @exmaple NO_COLOR=true
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
- * @exmaple FORCE_COLOR=0
12
- * @exmaple FORCE_COLOR=false
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 spawn, { SubprocessError } from 'nano-spawn'
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?: import('nano-spawn').Options) => Promise<string>} */
16
+ /** @type {(cmd: string[], options?: { cwd?: string }) => Promise<string>} */
17
17
  export const execGit = async (cmd, options) => {
18
- debugLog('Running git command', cmd)
19
- try {
20
- const result = await spawn('git', [...NO_SUBMODULE_RECURSE, ...cmd], {
21
- ...options,
22
- cwd: options?.cwd ?? process.cwd(),
23
- stdin: 'ignore',
24
- })
25
-
26
- return result.stdout
27
- } catch (error) {
28
- if (error instanceof SubprocessError) {
29
- throw new Error(error.output, { cause: error })
30
- }
31
-
32
- throw error
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
+ }
@@ -1,5 +1,5 @@
1
1
  import { createDebug } from './debug.js'
2
- import { makeErr } from './getSpawnedTask.js'
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 makeErr(command.title, e, ctx)
27
+ throw createTaskError(command.title, e, ctx)
28
28
  }
29
29
  }
30
30
 
@@ -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 {import('nano-spawn').Result | import('nano-spawn').SubprocessError} result
25
- * @param {Object} ctx
26
- * @returns {Error}
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 handleOutput = (command, result, ctx, isError = false) => {
29
- if (result.output) {
30
- const outputTitle = isError ? red(`${error} ${command}:`) : `${info} ${command}:`
31
- const output = [...(ctx.quiet ? [] : ['', outputTitle]), result.output]
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 (result instanceof SubprocessError) {
41
- ctx.output.push(red(`\n${error} ${command} failed to spawn:`), result.message, result.cause)
42
- } else if (isError) {
43
- // Show generic error when task had no output
44
- const tag = getTag(result)
45
- const message = red(`\n${error} ${command} failed without output (${tag}).`)
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('nano-spawn').SubprocessError} error
115
- * @param {Object} ctx
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 makeErr = (command, error, ctx) => {
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
- const spawnOptions = {
158
- // Only use topLevelDir as CWD if we are using the git binary
159
- // e.g `npm` should run tasks in the actual CWD
160
- cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
161
- preferLocal: true,
162
- stdin: 'ignore',
163
- env: color ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
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('Spawn options:', spawnOptions)
99
+ debugLog('Tinyexec options:', tinyExecOptions)
167
100
 
101
+ /** @param {ReturnType<typeof getInitialState>} ctx context */
168
102
  return async (ctx = getInitialState()) => {
169
- let quitInterruptCheck
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
- const subprocess = spawn(cmd, isFn ? args : args.concat(files), spawnOptions)
173
- if (!continueOnError) {
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
- throw makeErr(command, error, ctx)
182
- } finally {
183
- if (quitInterruptCheck) {
184
- await quitInterruptCheck()
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
  }
@@ -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: [],
@@ -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 INCORRECT_BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g
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
- let match = null
40
+ const regexp = getIncorrectBracesRegexp()
41
+ let match = regexp.exec(output)
40
42
 
41
- while ((match = INCORRECT_BRACES_REGEXP.exec(pattern))) {
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.2.7",
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.2",
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
- "yaml": "^2.8.1"
55
+ "tinyexec": "^1.0.2",
56
+ "yaml": "^2.8.2"
58
57
  },
59
58
  "devDependencies": {
60
- "@changesets/changelog-github": "0.5.1",
61
- "@changesets/cli": "2.29.7",
62
- "@commitlint/cli": "20.1.0",
63
- "@commitlint/config-conventional": "20.0.0",
64
- "@eslint/js": "9.39.1",
65
- "@vitest/coverage-v8": "4.0.10",
66
- "@vitest/eslint-plugin": "1.4.3",
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": "9.39.1",
68
+ "eslint": "10.0.2",
70
69
  "eslint-config-prettier": "10.1.8",
71
- "eslint-plugin-n": "17.23.1",
72
- "eslint-plugin-prettier": "5.5.4",
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.6.2",
77
- "semver": "7.7.3",
75
+ "prettier": "3.8.1",
76
+ "semver": "7.7.4",
78
77
  "typescript": "5.9.3",
79
- "vitest": "4.0.10"
78
+ "vitest": "4.0.18"
80
79
  },
81
80
  "keywords": [
82
81
  "lint",