lint-staged 15.5.2 → 16.1.0

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/MIGRATION.md ADDED
@@ -0,0 +1,70 @@
1
+ ## v16
2
+
3
+ #### Updated Node.js version requirement
4
+
5
+ The lowest supported Node.js version is `18.19.0` or `20.5.0`, following requirements of `execa@9`. Please upgrade your Node.js version.
6
+
7
+ #### Removed validation for removed advanced configuration file options
8
+
9
+ Advanced configuration options (removed in v9) are no longer validated separately, and might be treated as valid globs for tasks. Please do not try to use advanced config options anymore, they haven't been supported since v8.
10
+
11
+ #### Removed the `--shell` option
12
+
13
+ The `--shell` flag has been removed and _lint-staged_ no longer supports evaluating commands directly via a shell. To migrate existing commands, you can create a shell script and invoke it instead. Lint-staged will pass matched staged files as a list of arguments, accessible via `"$@"`:
14
+
15
+ ```shell
16
+ # my-script.sh
17
+ #!/bin/bash
18
+
19
+ echo "Staged files: $@"
20
+ ```
21
+
22
+ and
23
+
24
+ ```json
25
+ { "*.js": "my-script.sh" }
26
+ ```
27
+
28
+ If you were using the shell option to avoid passing filenames to tasks, for example `bash -c 'tsc --noEmit'`, use the function syntax instead:
29
+
30
+ ```js
31
+ export default { '*.ts': () => 'tsc --noEmit' }
32
+ ```
33
+
34
+ #### Processes are spawned using `nano-spawn`
35
+
36
+ Processes are spawned using [nano-spawn](https://github.com/sindresorhus/nano-spawn) instead of [execa](https://github.com/sindresorhus/execa). If you are using Node.js scripts as tasks, you might need to explicitly run them with `node`, especially when using Windows:
37
+
38
+ ```json
39
+ {
40
+ "*.js": "node my-js-linter.js"
41
+ }
42
+ ```
43
+
44
+ ## v15
45
+
46
+ - Since `v15.0.0` _lint-staged_ no longer supports Node.js 16. Please upgrade your Node.js version to at least `18.12.0`.
47
+
48
+ ## v14
49
+
50
+ - Since `v14.0.0` _lint-staged_ no longer supports Node.js 14. Please upgrade your Node.js version to at least `16.14.0`.
51
+
52
+ ## v13
53
+
54
+ - Since `v13.0.0` _lint-staged_ no longer supports Node.js 12. Please upgrade your Node.js version to at least `14.13.1`, or `16.0.0` onward.
55
+ - Version `v13.3.0` was incorrectly released including code of version `v14.0.0`. This means the breaking changes of `v14` are also included in `v13.3.0`, the last `v13` version released
56
+
57
+ ## v12
58
+
59
+ - Since `v12.0.0` _lint-staged_ is a pure ESM module, so make sure your Node.js version is at least `12.20.0`, `14.13.1`, or `16.0.0`. Read more about ESM modules from the official [Node.js Documentation site here](https://nodejs.org/api/esm.html#introduction).
60
+
61
+ ## v10
62
+
63
+ - From `v10.0.0` onwards any new modifications to originally staged files will be automatically added to the commit.
64
+ If your task previously contained a `git add` step, please remove this.
65
+ The automatic behaviour ensures there are less race-conditions,
66
+ since trying to run multiple git operations at the same time usually results in an error.
67
+ - From `v10.0.0` onwards, lint-staged uses git stashes to improve speed and provide backups while running.
68
+ Since git stashes require at least an initial commit, you shouldn't run lint-staged in an empty repo.
69
+ - From `v10.0.0` onwards, lint-staged requires Node.js version 10.13.0 or later.
70
+ - From `v10.0.0` onwards, lint-staged will abort the commit if linter tasks undo all staged changes. To allow creating an empty commit, please use the `--allow-empty` option.
package/README.md CHANGED
@@ -34,13 +34,28 @@ $ git commit
34
34
 
35
35
  </details>
36
36
 
37
+ ## Table of Contents
38
+
39
+ - [Why](#why)
40
+ - [Installation and setup](#installation-and-setup)
41
+ - [Changelog](#changelog)
42
+ - [Command line flags](#command-line-flags)
43
+ - [Configuration](#configuration)
44
+ - [Filtering files](#filtering-files)
45
+ - [What commands are supported?](#what-commands-are-supported)
46
+ - [Running multiple commands in a sequence](#running-multiple-commands-in-a-sequence)
47
+ - [Using JS configuration files](#using-js-configuration-files)
48
+ - [Reformatting the code](#reformatting-the-code)
49
+ - [Examples](#examples)
50
+ - [Frequently Asked Questions](#frequently-asked-questions)
51
+
37
52
  ## Why
38
53
 
39
54
  Code quality tasks like formatters and linters make more sense when run before committing your code. By doing so you can ensure no errors go into the repository and enforce code style. But running a task on a whole project can be slow, and opinionated tasks such as linting can sometimes produce irrelevant results. Ultimately you only want to check files that will be committed.
40
55
 
41
56
  This project contains a script that will run arbitrary shell tasks with a list of staged files as an argument, filtered by a specified glob pattern.
42
57
 
43
- ## Related blog posts and talks
58
+ ### Related blog posts and talks
44
59
 
45
60
  - [Introductory Medium post - Andrey Okonetchnikov, 2016](https://medium.com/@okonetchnikov/make-linting-great-again-f3890e1ad6b8#.8qepn2b5l)
46
61
  - [Running Jest Tests Before Each Git Commit - Ben McCormick, 2017](https://benmccormick.org/2017/02/26/running-jest-tests-before-each-git-commit/)
@@ -70,7 +85,7 @@ Now change a few files, `git add` or `git add --patch` some of them to your comm
70
85
 
71
86
  See [examples](#examples) and [configuration](#configuration) for more information.
72
87
 
73
- > [!CAUTION]
88
+ > [!CAUTION]
74
89
  > _Lint-staged_ runs `git` operations affecting the files in your repository. By default _lint-staged_ creates a `git stash` as a backup of the original state before running any configured tasks to help prevent data loss.
75
90
 
76
91
  ## Changelog
@@ -79,33 +94,7 @@ See [Releases](https://github.com/okonet/lint-staged/releases).
79
94
 
80
95
  ### Migration
81
96
 
82
- #### v15
83
-
84
- - Since `v15.0.0` _lint-staged_ no longer supports Node.js 16. Please upgrade your Node.js version to at least `18.12.0`.
85
-
86
- #### v14
87
-
88
- - Since `v14.0.0` _lint-staged_ no longer supports Node.js 14. Please upgrade your Node.js version to at least `16.14.0`.
89
-
90
- #### v13
91
-
92
- - Since `v13.0.0` _lint-staged_ no longer supports Node.js 12. Please upgrade your Node.js version to at least `14.13.1`, or `16.0.0` onward.
93
- - Version `v13.3.0` was incorrectly released including code of version `v14.0.0`. This means the breaking changes of `v14` are also included in `v13.3.0`, the last `v13` version released
94
-
95
- #### v12
96
-
97
- - Since `v12.0.0` _lint-staged_ is a pure ESM module, so make sure your Node.js version is at least `12.20.0`, `14.13.1`, or `16.0.0`. Read more about ESM modules from the official [Node.js Documentation site here](https://nodejs.org/api/esm.html#introduction).
98
-
99
- #### v10
100
-
101
- - From `v10.0.0` onwards any new modifications to originally staged files will be automatically added to the commit.
102
- If your task previously contained a `git add` step, please remove this.
103
- The automatic behaviour ensures there are less race-conditions,
104
- since trying to run multiple git operations at the same time usually results in an error.
105
- - From `v10.0.0` onwards, lint-staged uses git stashes to improve speed and provide backups while running.
106
- Since git stashes require at least an initial commit, you shouldn't run lint-staged in an empty repo.
107
- - From `v10.0.0` onwards, lint-staged requires Node.js version 10.13.0 or later.
108
- - From `v10.0.0` onwards, lint-staged will abort the commit if linter tasks undo all staged changes. To allow creating an empty commit, please use the `--allow-empty` option.
97
+ For breaking changes, see [MIGRATION.md](./MIGRATION.md).
109
98
 
110
99
  ## Command line flags
111
100
 
@@ -120,17 +109,15 @@ Options:
120
109
  -c, --config [path] path to configuration file, or - to read from stdin
121
110
  --cwd [path] run all tasks in specific directory, instead of the current
122
111
  -d, --debug print additional debug information (default: false)
123
- --diff [string] override the default "--staged" flag of "git diff" to get list of files.
124
- Implies "--no-stash".
125
- --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of
126
- files
112
+ --diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies
113
+ "--no-stash".
114
+ --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
127
115
  --max-arg-length [number] maximum length of the command-line argument string (default: 0)
128
- --no-stash disable the backup stash, and do not revert in case of errors. Implies
129
- "--no-hide-partially-staged".
116
+ --no-revert do not revert to original state in case of errors.
117
+ --no-stash disable the backup stash. Implies "--no-revert".
130
118
  --no-hide-partially-staged disable hiding unstaged changes from partially staged files
131
119
  -q, --quiet disable lint-staged’s own console output (default: false)
132
120
  -r, --relative pass relative filepaths to tasks (default: false)
133
- -x, --shell [path] skip parsing of tasks for better shell support (default: false)
134
121
  -v, --verbose show task output even when tasks succeed; by default only failed output is
135
122
  shown (default: false)
136
123
  -h, --help display help for command
@@ -156,11 +143,11 @@ Any lost modifications can be restored from a git stash:
156
143
  - **`--diff`**: By default tasks are filtered against all files staged in git, generated from `git diff --staged`. This option allows you to override the `--staged` flag with arbitrary revisions. For example to get a list of changed files between two branches, use `--diff="branch1...branch2"`. You can also read more from about [git diff](https://git-scm.com/docs/git-diff) and [gitrevisions](https://git-scm.com/docs/gitrevisions). This option also implies `--no-stash`.
157
144
  - **`--diff-filter`**: By default only files that are _added_, _copied_, _modified_, or _renamed_ are included. Use this flag to override the default `ACMR` value with something else: _added_ (`A`), _copied_ (`C`), _deleted_ (`D`), _modified_ (`M`), _renamed_ (`R`), _type changed_ (`T`), _unmerged_ (`U`), _unknown_ (`X`), or _pairing broken_ (`B`). See also the `git diff` docs for [--diff-filter](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203).
158
145
  - **`--max-arg-length`**: long commands (a lot of files) are automatically split into multiple chunks when it detects the current shell cannot handle them. Use this flag to override the maximum length of the generated command string.
159
- - **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. Can be re-enabled with `--stash`. This option also implies `--no-hide-partially-staged`.
146
+ - **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. This option also implies `--no-hide-partially-staged`.
160
147
  - **`--no-hide-partially-staged`**: By default, unstaged changes from partially staged files will be hidden. This option will disable this behavior and include all unstaged changes in partially staged files. Can be re-enabled with `--hide-partially-staged`
161
148
  - **`--quiet`**: Supress all CLI output, except from tasks.
162
149
  - **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
163
- - **`--shell`**: By default task commands will be parsed for speed and security. This has the side-effect that regular shell scripts might not work as expected. You can skip parsing of commands with this option. To use a specific shell, use a path like `--shell "/bin/bash"`.
150
+ - **`--no-revert`**: By default all task modifications will be reverted in case of an error. This option will disable the behavior, and apply task modifications to the index before aborting the commit.
164
151
  - **`--verbose`**: Show task output even when tasks succeed. By default only failed output is shown.
165
152
 
166
153
  ## Configuration
@@ -180,7 +167,7 @@ _Lint-staged_ can be configured in many ways:
180
167
  whether your project's _package.json_ contains the `"type": "module"` option or not.
181
168
  - Pass a configuration file using the `--config` or `-c` flag
182
169
 
183
- Configuration should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [micromatch](https://github.com/micromatch/micromatch) for glob patterns. JavaScript files can also export advanced configuration as a function. See [Using JS configuration files](#using-js-configuration-files) for more info.
170
+ Configuration should be an object where each value is a **command** to run and its key is a glob pattern to use for this command. This package uses [micromatch](https://github.com/micromatch/micromatch) for glob patterns. JavaScript files can also export advanced configuration as a function. See [Using JS configuration files](#using-js-configuration-files) for more info.
184
171
 
185
172
  You can also place multiple configuration files in different directories inside a project. For a given staged file, the closest configuration file will always be used. See ["How to use `lint-staged` in a multi-package monorepo?"](#how-to-use-lint-staged-in-a-multi-package-monorepo) for more info and an example.
186
173
 
@@ -363,12 +350,19 @@ export default {
363
350
 
364
351
  This will result in _lint-staged_ first running `eslint .` (matching _all_ files), and if it passes, `prettier --write file-1.js file-2.js`, when you have staged files `file-1.js`, `file-2.js` and `README.md`.
365
352
 
366
- ### Function signature
353
+ ### JavaScript Functions
367
354
 
368
- The function can also be async:
355
+ You can also configure _lint-staged_ to run a JavaScript/Node.js script directly, passing the list of staged files as an argument:
369
356
 
370
- ```ts
371
- (filenames: string[]) => string | string[] | Promise<string | string[]>
357
+ ```js
358
+ export default {
359
+ '*.js': {
360
+ title: 'Log staged JS files to console',
361
+ task: async (files) => {
362
+ console.log('Staged JS files:', files)
363
+ },
364
+ },
365
+ }
372
366
  ```
373
367
 
374
368
  ### Example: Export a function to build your own matchers
@@ -777,7 +771,6 @@ const success = await lintStaged({
777
771
  maxArgLength: null,
778
772
  quiet: false,
779
773
  relative: false,
780
- shell: false,
781
774
  stash: true,
782
775
  verbose: false,
783
776
  })
@@ -795,7 +788,6 @@ const success = await lintStaged({
795
788
  maxArgLength: null,
796
789
  quiet: false,
797
790
  relative: false,
798
- shell: false,
799
791
  stash: true,
800
792
  verbose: false,
801
793
  })
@@ -1021,27 +1013,19 @@ ESLint v8.51.0 introduced [`--no-warn-ignored` CLI flag](https://eslint.org/docs
1021
1013
 
1022
1014
  When running `lint-staged` via Husky hooks, TypeScript may ignore `tsconfig.json`, leading to errors like:
1023
1015
 
1024
- > **TS17004:** Cannot use JSX unless the '--jsx' flag is provided.
1025
- > **TS1056:** Accessors are only available when targeting ECMAScript 5 and higher.
1026
-
1027
- See issue [#825](https://github.com/okonet/lint-staged/issues/825) for more details.
1028
-
1029
- #### Root Cause
1030
-
1031
- <details>
1032
- <summary>Click to expand</summary>
1016
+ > **TS17004:** Cannot use JSX unless the '--jsx' flag is provided.
1017
+ > **TS1056:** Accessors are only available when targeting ECMAScript 5 and higher.
1033
1018
 
1034
- 1. `lint-staged` automatically passes matched staged files as arguments to commands.
1035
- 2. Certain input files can cause TypeScript to ignore `tsconfig.json`. For more details, see this TypeScript issue: [Allow tsconfig.json when input files are specified](https://github.com/microsoft/TypeScript/issues/27379).
1019
+ See issue [#825](https://github.com/okonet/lint-staged/issues/825) for more details.
1036
1020
 
1037
- </details>
1021
+ #### Root Cause
1038
1022
 
1039
- #### Workaround 1: Use a [function signature](https://github.com/lint-staged/lint-staged?tab=readme-ov-file#example-run-tsc-on-changes-to-typescript-files-but-do-not-pass-any-filename-arguments) for the `tsc` command
1023
+ 1. `lint-staged` automatically passes matched staged files as arguments to commands.
1024
+ 2. Certain input files can cause TypeScript to ignore `tsconfig.json`. For more details, see this TypeScript issue: [Allow tsconfig.json when input files are specified](https://github.com/microsoft/TypeScript/issues/27379).
1040
1025
 
1041
- <details>
1042
- <summary>Click to expand</summary>
1026
+ #### Workaround: Use a [function signature](https://github.com/lint-staged/lint-staged?tab=readme-ov-file#example-run-tsc-on-changes-to-typescript-files-but-do-not-pass-any-filename-arguments) for the `tsc` command
1043
1027
 
1044
- As suggested by @antoinerousseau in [#825 (comment)](https://github.com/lint-staged/lint-staged/issues/825#issuecomment-620018284), using a function prevents `lint-staged` from appending file arguments:
1028
+ As suggested by @antoinerousseau in [#825 (comment)](https://github.com/lint-staged/lint-staged/issues/825#issuecomment-620018284), using a function prevents `lint-staged` from appending file arguments:
1045
1029
 
1046
1030
  **Before:**
1047
1031
 
@@ -1061,50 +1045,8 @@ As suggested by @antoinerousseau in [#825 (comment)](https://github.com/lint-sta
1061
1045
  ```js
1062
1046
  // lint-staged.config.js
1063
1047
  module.exports = {
1064
- "*.{ts,tsx}": [
1065
- () => "tsc --noEmit",
1066
- "prettier --write"
1067
- ],
1048
+ '*.{ts,tsx}': [() => 'tsc --noEmit', 'prettier --write'],
1068
1049
  }
1069
1050
  ```
1070
1051
 
1071
1052
  </details>
1072
-
1073
- #### Workaround 2: Take the `sh` or `bash` to wrap the `tsc` command
1074
-
1075
- <details>
1076
- <summary>Click to expand</summary>
1077
-
1078
- As suggested by @sombreroEnPuntas in [#825 (comment)](https://github.com/lint-staged/lint-staged/issues/825#issuecomment-674575655), wrapping `tsc` in a shell command prevents `lint-staged` from modifying its arguments:
1079
-
1080
- **Before:**
1081
-
1082
- ```js
1083
- // package.json
1084
-
1085
- "lint-staged": {
1086
- "*.{ts,tsx}":[
1087
- "tsc --noEmit",
1088
- "prettier --write"
1089
- ]
1090
- }
1091
- ```
1092
-
1093
- **After:**
1094
-
1095
- ```js
1096
- // package.json
1097
-
1098
- "lint-staged": {
1099
- "*.{ts,tsx}":[
1100
- "bash -c 'tsc --noEmit'"
1101
- "prettier --write"
1102
- ]
1103
- }
1104
- ```
1105
-
1106
- **Note:** This approach may have cross-platform compatibility issues.
1107
-
1108
- </details>
1109
-
1110
- </details>
@@ -61,30 +61,26 @@ program.option(
61
61
  program.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
62
62
 
63
63
  /**
64
- * We don't want to show the `--stash` flag because it's on by default, and only show the
65
- * negatable flag `--no-stash` in stead. There seems to be a bug in Commander.js where
64
+ * We don't want to show the `--revert` flag because it's on by default, and only show the
65
+ * negatable flag `--no-rever` instead. There seems to be a bug in Commander.js where
66
66
  * configuring only the latter won't actually set the default value.
67
67
  */
68
68
  program
69
69
  .addOption(
70
- new Option('--stash', 'enable the backup stash, and revert in case of errors')
71
- .default(true)
72
- .hideHelp()
70
+ new Option('--revert', 'revert to original state in case of errors').default(true).hideHelp()
73
71
  )
74
72
  .addOption(
75
- new Option(
76
- '--no-stash',
77
- 'disable the backup stash, and do not revert in case of errors. Implies "--no-hide-partially-staged".'
78
- )
73
+ new Option('--no-revert', 'do not revert to original state in case of errors.').default(false)
74
+ )
75
+
76
+ program
77
+ .addOption(new Option('--stash', 'enable the backup stash').default(true).hideHelp())
78
+ .addOption(
79
+ new Option('--no-stash', 'disable the backup stash. Implies "--no-revert".')
79
80
  .default(false)
80
- .implies({ hidePartiallyStaged: false })
81
+ .implies({ revert: false, hidePartiallyStaged: false })
81
82
  )
82
83
 
83
- /**
84
- * We don't want to show the `--hide-partially-staged` flag because it's on by default, and only show the
85
- * negatable flag `--no-hide-partially-staged` in stead. There seems to be a bug in Commander.js where
86
- * configuring only the latter won't actually set the default value.
87
- */
88
84
  program
89
85
  .addOption(
90
86
  new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
@@ -102,8 +98,6 @@ program.option('-q, --quiet', 'disable lint-staged’s own console output', fals
102
98
 
103
99
  program.option('-r, --relative', 'pass relative filepaths to tasks', false)
104
100
 
105
- program.option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
106
-
107
101
  program.option(
108
102
  '-v, --verbose',
109
103
  'show task output even when tasks succeed; by default only failed output is shown',
@@ -129,7 +123,7 @@ const options = {
129
123
  maxArgLength: cliOptions.maxArgLength || undefined,
130
124
  quiet: !!cliOptions.quiet,
131
125
  relative: !!cliOptions.relative,
132
- shell: cliOptions.shell /* Either a boolean or a string pointing to the shell */,
126
+ revert: !!cliOptions.revert, // commander inverts `no-<x>` flags to `!x`
133
127
  stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
134
128
  hidePartiallyStaged: !!cliOptions.hidePartiallyStaged, // commander inverts `no-<x>` flags to `!x`
135
129
  verbose: !!cliOptions.verbose,
package/lib/execGit.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import debug from 'debug'
2
- import { execa } from 'execa'
2
+ import spawn, { SubprocessError } from 'nano-spawn'
3
3
 
4
4
  const debugLog = debug('lint-staged:execGit')
5
5
 
@@ -12,17 +12,22 @@ const NO_SUBMODULE_RECURSE = ['-c', 'submodule.recurse=false']
12
12
  // exported for tests
13
13
  export const GIT_GLOBAL_OPTIONS = [...NO_SUBMODULE_RECURSE]
14
14
 
15
- export const execGit = async (cmd, options = {}) => {
15
+ /** @type {(cmd: string[], options?: import('nano-spawn').Options) => Promise<string>} */
16
+ export const execGit = async (cmd, options) => {
16
17
  debugLog('Running git command', cmd)
17
18
  try {
18
- const { stdout } = await execa('git', GIT_GLOBAL_OPTIONS.concat(cmd), {
19
+ const result = await spawn('git', [...NO_SUBMODULE_RECURSE, ...cmd], {
19
20
  ...options,
20
- all: true,
21
- cwd: options.cwd || process.cwd(),
21
+ cwd: options?.cwd ?? process.cwd(),
22
22
  stdin: 'ignore',
23
23
  })
24
- return stdout
25
- } catch ({ all }) {
26
- throw new Error(all)
24
+
25
+ return result.stdout
26
+ } catch (error) {
27
+ if (error instanceof SubprocessError) {
28
+ throw new Error(error.output, { cause: error })
29
+ }
30
+
31
+ throw error
27
32
  }
28
33
  }
@@ -0,0 +1,38 @@
1
+ import debug from 'debug'
2
+
3
+ import { makeErr } from './getSpawnedTask.js'
4
+
5
+ const debugLog = debug('lint-staged:getFunctionTasks')
6
+
7
+ /**
8
+ * @typedef {{ title: string; task: Function }} FunctionTask
9
+ * @type {(commands: FunctionTask|Array<string|Function>|string|Function) => boolean}
10
+ * @returns `true` if command is a function task
11
+ */
12
+ export const isFunctionTask = (commands) => typeof commands === 'object' && !Array.isArray(commands)
13
+
14
+ /**
15
+ * Handles function configuration and pushes the tasks into the task array
16
+ *
17
+ * @param {object} command
18
+ * @param {Array<string>} files
19
+ * @throws {Error} If the function configuration is not valid
20
+ */
21
+ export const getFunctionTask = async (command, files) => {
22
+ debugLog('Creating Listr tasks for function %o', command)
23
+
24
+ const task = async (ctx) => {
25
+ try {
26
+ await command.task(files)
27
+ } catch (e) {
28
+ throw makeErr(command.title, e, ctx)
29
+ }
30
+ }
31
+
32
+ return [
33
+ {
34
+ title: command.title,
35
+ task,
36
+ },
37
+ ]
38
+ }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk'
2
2
  import debug from 'debug'
3
- import { execa, execaCommand } from 'execa'
3
+ import spawn from 'nano-spawn'
4
4
  import pidTree from 'pidtree'
5
5
  import { parseArgsStringToArgv } from 'string-argv'
6
6
 
@@ -8,40 +8,27 @@ import { error, info } from './figures.js'
8
8
  import { getInitialState } from './state.js'
9
9
  import { TaskError } from './symbols.js'
10
10
 
11
- /**
12
- * @see https://github.com/sindresorhus/execa/blob/f4b8b3ab601c94d1503f1010822952758dcc6350/lib/command.js#L32-L37
13
- */
14
- const escapeSpaces = (input) => input.replaceAll(' ', '\\ ')
15
-
16
11
  const TASK_ERROR = 'lint-staged:taskError'
17
12
 
18
- const debugLog = debug('lint-staged:resolveTaskFn')
13
+ const debugLog = debug('lint-staged:getSpawnedTask')
19
14
 
20
- const getTag = ({ code, killed, signal }) => (killed && 'KILLED') || signal || code || 'FAILED'
15
+ /** @type {(error: import('nano-spawn').SubprocessError) => string} */
16
+ const getTag = (error) => {
17
+ return error.signalName ?? 'FAILED'
18
+ }
21
19
 
22
20
  /**
23
21
  * Handle task console output.
24
22
  *
25
23
  * @param {string} command
26
- * @param {Object} result
27
- * @param {string} result.stdout
28
- * @param {string} result.stderr
29
- * @param {boolean} result.failed
30
- * @param {boolean} result.killed
31
- * @param {string} result.signal
24
+ * @param {import('nano-spawn').Result | import('nano-spawn').SubprocessError} result
32
25
  * @param {Object} ctx
33
26
  * @returns {Error}
34
27
  */
35
28
  const handleOutput = (command, result, ctx, isError = false) => {
36
- const { stderr, stdout } = result
37
- const hasOutput = !!stderr || !!stdout
38
-
39
- if (hasOutput) {
29
+ if (result.output) {
40
30
  const outputTitle = isError ? chalk.redBright(`${error} ${command}:`) : `${info} ${command}:`
41
- const output = []
42
- .concat(ctx.quiet ? [] : ['', outputTitle])
43
- .concat(stderr ? stderr : [])
44
- .concat(stdout ? stdout : [])
31
+ const output = [...(ctx.quiet ? [] : ['', outputTitle]), result.output]
45
32
  ctx.output.push(output.join('\n'))
46
33
  } else if (isError) {
47
34
  // Show generic error when task had no output
@@ -52,12 +39,14 @@ const handleOutput = (command, result, ctx, isError = false) => {
52
39
  }
53
40
 
54
41
  /**
55
- * Kill an execa process along with all its child processes.
56
- * @param {execa.ExecaChildProcess<string>} execaProcess
42
+ * Kill subprocess along with all its child processes.
43
+ * @param {import('nano-spawn').Subprocess} subprocess
57
44
  */
58
- const killExecaProcess = async (execaProcess) => {
45
+ const killSubprocess = async (subprocess) => {
46
+ const childProcess = await subprocess.nodeChildProcess
47
+
59
48
  try {
60
- const childPids = await pidTree(execaProcess.pid)
49
+ const childPids = await pidTree(childProcess.pid)
61
50
  for (const childPid of childPids) {
62
51
  try {
63
52
  process.kill(childPid)
@@ -68,27 +57,27 @@ const killExecaProcess = async (execaProcess) => {
68
57
  } catch (error) {
69
58
  // Suppress "No matching pid found" error. This probably means
70
59
  // the process already died before executing.
71
- debugLog(`Failed to kill process with pid "%d": %o`, execaProcess.pid, error)
60
+ debugLog(`Failed to kill process with pid "%d": %o`, childProcess.pid, error)
72
61
  }
73
62
 
74
- // The execa process is killed separately in order to get the `KILLED` status.
75
- execaProcess.kill()
63
+ // The child process is terminated separately in order to get the `KILLED` status.
64
+ childProcess.kill('SIGKILL')
76
65
  }
77
66
 
78
67
  /**
79
- * Interrupts the execution of the execa process that we spawned if
68
+ * Interrupts the execution of the subprocess that we spawned if
80
69
  * another task adds an error to the context.
81
70
  *
82
71
  * @param {Object} ctx
83
- * @param {execa.ExecaChildProcess<string>} execaChildProcess
72
+ * @param {import('nano-spawn').Subprocess} subprocess
84
73
  * @returns {() => Promise<void>} Function that clears the interval that
85
74
  * checks the context.
86
75
  */
87
- const interruptExecutionOnError = (ctx, execaChildProcess) => {
76
+ const interruptExecutionOnError = (ctx, subprocess) => {
88
77
  let killPromise
89
78
 
90
79
  const errorListener = async () => {
91
- killPromise = killExecaProcess(execaChildProcess)
80
+ killPromise = killSubprocess(subprocess)
92
81
  await killPromise
93
82
  }
94
83
 
@@ -104,23 +93,18 @@ const interruptExecutionOnError = (ctx, execaChildProcess) => {
104
93
  * Create a error output depending on process result.
105
94
  *
106
95
  * @param {string} command
107
- * @param {Object} result
108
- * @param {string} result.stdout
109
- * @param {string} result.stderr
110
- * @param {boolean} result.failed
111
- * @param {boolean} result.killed
112
- * @param {string} result.signal
96
+ * @param {import('nano-spawn').SubprocessError} error
113
97
  * @param {Object} ctx
114
98
  * @returns {Error}
115
99
  */
116
- const makeErr = (command, result, ctx) => {
100
+ export const makeErr = (command, error, ctx) => {
117
101
  ctx.errors.add(TaskError)
118
102
 
119
103
  // https://nodejs.org/api/events.html#error-events
120
104
  ctx.events.emit(TASK_ERROR, TaskError)
121
105
 
122
- handleOutput(command, result, ctx, true)
123
- const tag = getTag(result)
106
+ handleOutput(command, error, ctx, true)
107
+ const tag = getTag(error)
124
108
  return new Error(`${chalk.redBright(command)} ${chalk.dim(`[${tag}]`)}`)
125
109
  }
126
110
 
@@ -133,53 +117,45 @@ const makeErr = (command, result, ctx) => {
133
117
  * @param {String} options.topLevelDir - Current git repo top-level path
134
118
  * @param {Boolean} options.isFn - Whether the linter task is a function
135
119
  * @param {Array<string>} options.files — Filepaths to run the linter task against
136
- * @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
137
120
  * @param {Boolean} [options.verbose] — Always show task verbose
138
121
  * @returns {() => Promise<Array<string>>}
139
122
  */
140
- export const resolveTaskFn = ({
123
+ export const getSpawnedTask = ({
141
124
  command,
142
125
  cwd = process.cwd(),
143
126
  files,
144
127
  topLevelDir,
145
128
  isFn,
146
- shell = false,
147
129
  verbose = false,
148
130
  }) => {
149
131
  const [cmd, ...args] = parseArgsStringToArgv(command)
150
132
  debugLog('cmd:', cmd)
151
133
  debugLog('args:', args)
152
134
 
153
- const execaOptions = {
135
+ const spawnOptions = {
154
136
  // Only use topLevelDir as CWD if we are using the git binary
155
137
  // e.g `npm` should run tasks in the actual CWD
156
138
  cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
157
139
  preferLocal: true,
158
- reject: false,
159
- shell,
160
140
  stdin: 'ignore',
161
141
  }
162
142
 
163
- debugLog('execaOptions:', execaOptions)
143
+ debugLog('Spawn options:', spawnOptions)
164
144
 
165
145
  return async (ctx = getInitialState()) => {
166
- const execaChildProcess = shell
167
- ? execaCommand(
168
- isFn ? command : `${command} ${files.map(escapeSpaces).join(' ')}`,
169
- execaOptions
170
- )
171
- : execa(cmd, isFn ? args : args.concat(files), execaOptions)
172
-
173
- const quitInterruptCheck = interruptExecutionOnError(ctx, execaChildProcess)
174
- const result = await execaChildProcess
175
- await quitInterruptCheck()
176
-
177
- if (result.failed || result.killed || result.signal != null) {
178
- throw makeErr(command, result, ctx)
179
- }
146
+ const subprocess = spawn(cmd, isFn ? args : args.concat(files), spawnOptions)
180
147
 
181
- if (verbose) {
182
- handleOutput(command, result, ctx)
148
+ const quitInterruptCheck = interruptExecutionOnError(ctx, subprocess)
149
+
150
+ try {
151
+ const result = await subprocess
152
+ if (verbose) {
153
+ handleOutput(command, result, ctx)
154
+ }
155
+ } catch (error) {
156
+ throw makeErr(command, error, ctx)
157
+ } finally {
158
+ await quitInterruptCheck()
183
159
  }
184
160
  }
185
161
  }
@@ -1,9 +1,9 @@
1
1
  import debug from 'debug'
2
2
 
3
+ import { getSpawnedTask } from './getSpawnedTask.js'
3
4
  import { configurationError } from './messages.js'
4
- import { resolveTaskFn } from './resolveTaskFn.js'
5
5
 
6
- const debugLog = debug('lint-staged:makeCmdTasks')
6
+ const debugLog = debug('lint-staged:getSpawnedTasks')
7
7
 
8
8
  /**
9
9
  * Creates and returns an array of listr tasks which map to the given commands.
@@ -13,14 +13,14 @@ const debugLog = debug('lint-staged:makeCmdTasks')
13
13
  * @param {string} options.cwd
14
14
  * @param {Array<string>} options.files
15
15
  * @param {string} options.topLevelDir
16
- * @param {Boolean} shell
17
16
  * @param {Boolean} verbose
18
17
  */
19
- export const makeCmdTasks = async ({ commands, cwd, files, topLevelDir, shell, verbose }) => {
20
- debugLog('Creating listr tasks for commands %o', commands)
21
- const commandArray = Array.isArray(commands) ? commands : [commands]
18
+ export const getSpawnedTasks = async ({ commands, cwd, files, topLevelDir, verbose }) => {
19
+ debugLog('Creating Listr tasks for commands %o', commands)
22
20
  const cmdTasks = []
23
21
 
22
+ const commandArray = Array.isArray(commands) ? commands : [commands]
23
+
24
24
  for (const cmd of commandArray) {
25
25
  // command function may return array of commands that already include `stagedFiles`
26
26
  const isFn = typeof cmd === 'function'
@@ -43,7 +43,7 @@ export const makeCmdTasks = async ({ commands, cwd, files, topLevelDir, shell, v
43
43
  )
44
44
  }
45
45
 
46
- const task = resolveTaskFn({ command, cwd, files, topLevelDir, isFn, shell, verbose })
46
+ const task = getSpawnedTask({ command, cwd, files, topLevelDir, isFn, verbose })
47
47
  cmdTasks.push({ title: command, command, task })
48
48
  }
49
49
  }
@@ -38,10 +38,10 @@ export const getStagedFiles = async ({ cwd = process.cwd(), diff, diffFilter } =
38
38
  const [, dstMode, , , ,] = info.split(' ')
39
39
 
40
40
  /**
41
- * Filter out submodule root directory. "160000" is the object mode for submodules.
42
- * @see https://github.com/git/git/blob/485f5f863615e670fd97ae40af744e14072cfe18/object.h#L114-L120
41
+ * Filter out submodules and symlinks
42
+ * @see https://github.com/git/git/blob/cb96e1697ad6e54d11fc920c95f82977f8e438f8/Documentation/git-fast-import.adoc?plain=1#L634-L646
43
43
  */
44
- if (dstMode === '160000') {
44
+ if (dstMode === '160000' || dstMode === '120000') {
45
45
  return []
46
46
  }
47
47
 
package/lib/index.d.ts CHANGED
@@ -1,12 +1,17 @@
1
- type SyncFunctionTask = (stagedFileNames: string[]) => string | string[]
1
+ type SyncGenerateTask = (stagedFileNames: string[]) => string | string[]
2
2
 
3
- type AsyncFunctionTask = (stagedFileNames: string[]) => Promise<string | string[]>
3
+ type AsyncGenerateTask = (stagedFileNames: string[]) => Promise<string | string[]>
4
4
 
5
- type FunctionTask = SyncFunctionTask | AsyncFunctionTask
5
+ type GenerateTask = SyncGenerateTask | AsyncGenerateTask
6
+
7
+ type TaskFunction = {
8
+ title: string
9
+ task: (stagedFileNames: string[]) => void | Promise<void>
10
+ }
6
11
 
7
12
  export type Configuration =
8
- | Record<string, string | FunctionTask | (string | FunctionTask)[]>
9
- | FunctionTask
13
+ | Record<string, string | TaskFunction | GenerateTask | (string | GenerateTask)[]>
14
+ | GenerateTask
10
15
 
11
16
  export type Options = {
12
17
  /**
@@ -62,10 +67,10 @@ export type Options = {
62
67
  */
63
68
  relative?: boolean
64
69
  /**
65
- * Skip parsing of tasks for better shell support
66
- * @default false
70
+ * Revert to original state in case of errors
71
+ * @default true
67
72
  */
68
- shell?: boolean
73
+ revert?: boolean
69
74
  /**
70
75
  * Enable the backup stash, and revert in case of errors.
71
76
  * @warn Disabling this also implies `hidePartiallyStaged: false`.
package/lib/index.js CHANGED
@@ -57,7 +57,7 @@ const getMaxArgLength = () => {
57
57
  * @param {number} [options.maxArgLength] - Maximum argument string length
58
58
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
59
59
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
60
- * @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
60
+ * @param {boolean} [options.revert] - revert to original state in case of errors
61
61
  * @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
62
62
  * @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
63
63
  * @param {Logger} [logger]
@@ -77,9 +77,10 @@ const lintStaged = async (
77
77
  maxArgLength = getMaxArgLength() / 2,
78
78
  quiet = false,
79
79
  relative = false,
80
- shell = false,
81
80
  // Stashing should be disabled by default when the `diff` option is used
82
81
  stash = diff === undefined,
82
+ // Cannot revert to original state without stash
83
+ revert = stash,
83
84
  hidePartiallyStaged = stash,
84
85
  verbose = false,
85
86
  } = {},
@@ -112,7 +113,7 @@ const lintStaged = async (
112
113
  maxArgLength,
113
114
  quiet,
114
115
  relative,
115
- shell,
116
+ revert,
116
117
  stash,
117
118
  hidePartiallyStaged,
118
119
  verbose,
package/lib/runAll.js CHANGED
@@ -9,11 +9,12 @@ import { Listr } from 'listr2'
9
9
  import { chunkFiles } from './chunkFiles.js'
10
10
  import { execGit } from './execGit.js'
11
11
  import { generateTasks } from './generateTasks.js'
12
+ import { getFunctionTask, isFunctionTask } from './getFunctionTask.js'
12
13
  import { getRenderer } from './getRenderer.js'
14
+ import { getSpawnedTasks } from './getSpawnedTasks.js'
13
15
  import { getStagedFiles } from './getStagedFiles.js'
14
16
  import { GitWorkflow } from './gitWorkflow.js'
15
17
  import { groupFilesByConfig } from './groupFilesByConfig.js'
16
- import { makeCmdTasks } from './makeCmdTasks.js'
17
18
  import {
18
19
  DEPRECATED_GIT_ADD,
19
20
  FAILED_GET_STAGED_FILES,
@@ -58,7 +59,7 @@ const createError = (ctx) => Object.assign(new Error('lint-staged failed'), { ct
58
59
  * @param {number} [options.maxArgLength] - Maximum argument string length
59
60
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
60
61
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
61
- * @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
62
+ * @param {boolean} [options.revert] - revert to original state in case of errors
62
63
  * @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
63
64
  * @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
64
65
  * @param {Logger} logger
@@ -77,9 +78,10 @@ export const runAll = async (
77
78
  maxArgLength,
78
79
  quiet = false,
79
80
  relative = false,
80
- shell = false,
81
81
  // Stashing should be disabled by default when the `diff` option is used
82
82
  stash = diff === undefined,
83
+ // Cannot revert to original state without stash
84
+ revert = stash,
83
85
  hidePartiallyStaged = stash,
84
86
  verbose = false,
85
87
  },
@@ -92,7 +94,7 @@ export const runAll = async (
92
94
  cwd = hasExplicitCwd ? path.resolve(cwd) : process.cwd()
93
95
  debugLog('Using working directory `%s`', cwd)
94
96
 
95
- const ctx = getInitialState({ quiet })
97
+ const ctx = getInitialState({ quiet, revert })
96
98
 
97
99
  const { topLevelDir, gitConfigDir } = await resolveGitRepo(cwd)
98
100
  if (!topLevelDir) {
@@ -165,7 +167,7 @@ export const runAll = async (
165
167
  * This is used to set max event listener count to the total number
166
168
  * of generated tasks. The event listener is used to keep track of
167
169
  * the interrupt signal and kill all tasks when it happens. See the
168
- * `interruptExecutionOnError` in `resolveTaskFn`.
170
+ * `interruptExecutionOnError` in `getSpawnedTask`.
169
171
  */
170
172
  let listrTaskCount = 0
171
173
 
@@ -192,14 +194,16 @@ export const runAll = async (
192
194
  for (const [index, files] of stagedFileChunks.entries()) {
193
195
  const chunkListrTasks = await Promise.all(
194
196
  generateTasks({ config, cwd: groupCwd, files, relative }).map((task) =>
195
- makeCmdTasks({
196
- commands: task.commands,
197
- cwd: groupCwd,
198
- files: task.fileList,
199
- topLevelDir,
200
- shell,
201
- verbose,
202
- }).then((subTasks) => {
197
+ (isFunctionTask(task.commands)
198
+ ? getFunctionTask(task.commands, files)
199
+ : getSpawnedTasks({
200
+ commands: task.commands,
201
+ cwd: groupCwd,
202
+ files: task.fileList,
203
+ topLevelDir,
204
+ verbose,
205
+ })
206
+ ).then((subTasks) => {
203
207
  // Add files from task to match set
204
208
  task.fileList.forEach((file) => {
205
209
  // Make sure relative files are normalized to the
package/lib/state.js CHANGED
@@ -8,9 +8,10 @@ import {
8
8
  TaskError,
9
9
  } from './symbols.js'
10
10
 
11
- export const getInitialState = ({ quiet = false } = {}) => ({
11
+ export const getInitialState = ({ quiet = false, revert = true } = {}) => ({
12
12
  hasPartiallyStagedFiles: null,
13
13
  shouldBackup: null,
14
+ shouldRevert: revert,
14
15
  backupHash: null,
15
16
  shouldHidePartiallyStaged: true,
16
17
  errors: new Set([]),
@@ -23,8 +24,8 @@ export const shouldHidePartiallyStagedFiles = (ctx) =>
23
24
  ctx.hasPartiallyStagedFiles && ctx.shouldHidePartiallyStaged
24
25
 
25
26
  export const applyModificationsSkipped = (ctx) => {
26
- // Always apply back unstaged modifications when skipping backup
27
- if (!ctx.shouldBackup) return false
27
+ // Always apply back unstaged modifications when skipping revert or backup
28
+ if (!ctx.shouldRevert || !ctx.shouldBackup) return false
28
29
  // Should be skipped in case of git errors
29
30
  if (ctx.errors.has(GitError)) {
30
31
  return GIT_ERROR
@@ -48,7 +49,9 @@ export const restoreUnstagedChangesSkipped = (ctx) => {
48
49
  }
49
50
 
50
51
  export const restoreOriginalStateEnabled = (ctx) =>
51
- ctx.shouldBackup && (ctx.errors.has(TaskError) || ctx.errors.has(RestoreUnstagedChangesError))
52
+ !!ctx.shouldRevert &&
53
+ !!ctx.shouldBackup &&
54
+ (ctx.errors.has(TaskError) || ctx.errors.has(RestoreUnstagedChangesError))
52
55
 
53
56
  export const restoreOriginalStateSkipped = (ctx) => {
54
57
  // Should be skipped in case of unknown git errors
@@ -10,19 +10,6 @@ import { validateBraces } from './validateBraces.js'
10
10
 
11
11
  const debugLog = debug('lint-staged:validateConfig')
12
12
 
13
- const isObject = (test) => test && typeof test === 'object' && !Array.isArray(test)
14
-
15
- const TEST_DEPRECATED_KEYS = new Map([
16
- ['concurrent', (key) => typeof key === 'boolean'],
17
- ['chunkSize', (key) => typeof key === 'number'],
18
- ['globOptions', isObject],
19
- ['linters', isObject],
20
- ['ignore', (key) => Array.isArray(key)],
21
- ['subTaskConcurrency', (key) => typeof key === 'number'],
22
- ['renderer', (key) => typeof key === 'string'],
23
- ['relative', (key) => typeof key === 'boolean'],
24
- ])
25
-
26
13
  export const validateConfigLogic = (config, configPath, logger) => {
27
14
  debugLog('Validating config from `%s`...', configPath)
28
15
 
@@ -35,7 +22,7 @@ export const validateConfigLogic = (config, configPath, logger) => {
35
22
  * They are not further validated here to make sure the function gets
36
23
  * evaluated only once.
37
24
  *
38
- * @see makeCmdTasks
25
+ * @see getSpawnedTasks
39
26
  */
40
27
  if (typeof config === 'function') {
41
28
  return { '*': config }
@@ -53,29 +40,30 @@ export const validateConfigLogic = (config, configPath, logger) => {
53
40
  * it can be used for validating the values at the same time.
54
41
  */
55
42
  const validatedConfig = Object.entries(config).reduce((collection, [pattern, task]) => {
56
- /** Versions < 9 had more complex configuration options that are no longer supported. */
57
- if (TEST_DEPRECATED_KEYS.has(pattern)) {
58
- const testFn = TEST_DEPRECATED_KEYS.get(pattern)
59
- if (testFn(task)) {
43
+ if (Array.isArray(task)) {
44
+ /** Array with invalid values */
45
+ if (task.some((item) => typeof item !== 'string' && typeof item !== 'function')) {
60
46
  errors.push(
61
- configurationError(pattern, 'Advanced configuration has been deprecated.', task)
47
+ configurationError(pattern, 'Should be an array of strings or functions.', task)
62
48
  )
63
49
  }
64
-
65
- /** Return early for deprecated keys to skip validating their (deprecated) values */
66
- return collection
67
- }
68
-
69
- if (
70
- (!Array.isArray(task) ||
71
- task.some((item) => typeof item !== 'string' && typeof item !== 'function')) &&
72
- typeof task !== 'string' &&
73
- typeof task !== 'function'
74
- ) {
50
+ } else if (typeof task === 'object') {
51
+ /** Invalid function task */
52
+ if (typeof task.title !== 'string' || typeof task.task !== 'function') {
53
+ errors.push(
54
+ configurationError(
55
+ pattern,
56
+ 'Function task should contain `title` and `task` fields, where `title` should be a string and `task` should be a function.',
57
+ task
58
+ )
59
+ )
60
+ }
61
+ } else if (typeof task !== 'string' && typeof task !== 'function') {
62
+ /** Singular invalid value */
75
63
  errors.push(
76
64
  configurationError(
77
65
  pattern,
78
- 'Should be a string, a function, or an array of strings and functions.',
66
+ 'Should be a string, a function, an object or an array of strings and functions.',
79
67
  task
80
68
  )
81
69
  )
@@ -13,8 +13,6 @@ const debugLog = debug('lint-staged:validateOptions')
13
13
  * Validate lint-staged options, either from the Node.js API or the command line flags.
14
14
  * @param {*} options
15
15
  * @param {boolean|string} [options.cwd] - Current working directory
16
- * @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
17
- *
18
16
  * @throws {InvalidOptionsError}
19
17
  */
20
18
  export const validateOptions = async (options = {}, logger) => {
@@ -32,16 +30,5 @@ export const validateOptions = async (options = {}, logger) => {
32
30
  }
33
31
  }
34
32
 
35
- /** Ensure the passed shell option is executable */
36
- if (typeof options.shell === 'string') {
37
- try {
38
- await fs.access(options.shell, constants.X_OK)
39
- } catch (error) {
40
- debugLog('Failed to validate options: %o', options)
41
- logger.error(invalidOption('shell', options.shell, error.message))
42
- throw InvalidOptionsError
43
- }
44
- }
45
-
46
33
  debugLog('Validated options: %o', options)
47
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "15.5.2",
3
+ "version": "16.1.0",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  "url": "https://opencollective.com/lint-staged"
22
22
  },
23
23
  "engines": {
24
- "node": ">=18.12.0"
24
+ "node": ">=20.17"
25
25
  },
26
26
  "type": "module",
27
27
  "bin": {
@@ -33,8 +33,9 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "files": [
36
- "bin",
37
- "lib"
36
+ "bin/",
37
+ "lib/",
38
+ "MIGRATION.md"
38
39
  ],
39
40
  "scripts": {
40
41
  "lint": "eslint .",
@@ -47,37 +48,37 @@
47
48
  },
48
49
  "dependencies": {
49
50
  "chalk": "^5.4.1",
50
- "commander": "^13.1.0",
51
- "debug": "^4.4.0",
52
- "execa": "^8.0.1",
51
+ "commander": "^14.0.0",
52
+ "debug": "^4.4.1",
53
53
  "lilconfig": "^3.1.3",
54
- "listr2": "^8.2.5",
54
+ "listr2": "^8.3.3",
55
55
  "micromatch": "^4.0.8",
56
+ "nano-spawn": "^1.0.2",
56
57
  "pidtree": "^0.6.0",
57
58
  "string-argv": "^0.3.2",
58
- "yaml": "^2.7.0"
59
+ "yaml": "^2.8.0"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@changesets/changelog-github": "0.5.1",
62
- "@changesets/cli": "2.28.1",
63
- "@commitlint/cli": "19.8.0",
64
- "@commitlint/config-conventional": "19.8.0",
65
- "@eslint/js": "9.22.0",
63
+ "@changesets/cli": "2.29.4",
64
+ "@commitlint/cli": "19.8.1",
65
+ "@commitlint/config-conventional": "19.8.1",
66
+ "@eslint/js": "9.27.0",
66
67
  "consolemock": "1.1.0",
67
68
  "cross-env": "7.0.3",
68
- "eslint": "9.22.0",
69
- "eslint-config-prettier": "10.1.1",
69
+ "eslint": "9.27.0",
70
+ "eslint-config-prettier": "10.1.5",
70
71
  "eslint-plugin-jest": "28.11.0",
71
- "eslint-plugin-n": "17.16.2",
72
- "eslint-plugin-prettier": "5.2.3",
72
+ "eslint-plugin-n": "17.18.0",
73
+ "eslint-plugin-prettier": "5.4.0",
73
74
  "eslint-plugin-simple-import-sort": "12.1.1",
74
75
  "husky": "9.1.7",
75
76
  "jest": "29.7.0",
76
77
  "jest-snapshot-serializer-ansi": "2.2.1",
77
78
  "mock-stdin": "1.0.0",
78
79
  "prettier": "3.5.3",
79
- "semver": "7.7.1",
80
- "typescript": "5.8.2"
80
+ "semver": "7.7.2",
81
+ "typescript": "5.8.3"
81
82
  },
82
83
  "keywords": [
83
84
  "lint",