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 +70 -0
- package/README.md +46 -104
- package/bin/lint-staged.js +12 -18
- package/lib/execGit.js +13 -8
- package/lib/getFunctionTask.js +38 -0
- package/lib/{resolveTaskFn.js → getSpawnedTask.js} +41 -65
- package/lib/{makeCmdTasks.js → getSpawnedTasks.js} +7 -7
- package/lib/getStagedFiles.js +3 -3
- package/lib/index.d.ts +13 -8
- package/lib/index.js +4 -3
- package/lib/runAll.js +17 -13
- package/lib/state.js +7 -4
- package/lib/validateConfig.js +19 -31
- package/lib/validateOptions.js +0 -13
- package/package.json +20 -19
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
129
|
-
|
|
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.
|
|
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
|
-
- **`--
|
|
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
|
-
###
|
|
353
|
+
### JavaScript Functions
|
|
367
354
|
|
|
368
|
-
|
|
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
|
-
```
|
|
371
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1021
|
+
#### Root Cause
|
|
1038
1022
|
|
|
1039
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
package/bin/lint-staged.js
CHANGED
|
@@ -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 `--
|
|
65
|
-
* negatable flag `--no-
|
|
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('--
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
19
|
+
const result = await spawn('git', [...NO_SUBMODULE_RECURSE, ...cmd], {
|
|
19
20
|
...options,
|
|
20
|
-
|
|
21
|
-
cwd: options.cwd || process.cwd(),
|
|
21
|
+
cwd: options?.cwd ?? process.cwd(),
|
|
22
22
|
stdin: 'ignore',
|
|
23
23
|
})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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:
|
|
13
|
+
const debugLog = debug('lint-staged:getSpawnedTask')
|
|
19
14
|
|
|
20
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
56
|
-
* @param {
|
|
42
|
+
* Kill subprocess along with all its child processes.
|
|
43
|
+
* @param {import('nano-spawn').Subprocess} subprocess
|
|
57
44
|
*/
|
|
58
|
-
const
|
|
45
|
+
const killSubprocess = async (subprocess) => {
|
|
46
|
+
const childProcess = await subprocess.nodeChildProcess
|
|
47
|
+
|
|
59
48
|
try {
|
|
60
|
-
const childPids = await pidTree(
|
|
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`,
|
|
60
|
+
debugLog(`Failed to kill process with pid "%d": %o`, childProcess.pid, error)
|
|
72
61
|
}
|
|
73
62
|
|
|
74
|
-
// The
|
|
75
|
-
|
|
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
|
|
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 {
|
|
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,
|
|
76
|
+
const interruptExecutionOnError = (ctx, subprocess) => {
|
|
88
77
|
let killPromise
|
|
89
78
|
|
|
90
79
|
const errorListener = async () => {
|
|
91
|
-
killPromise =
|
|
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 {
|
|
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,
|
|
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,
|
|
123
|
-
const tag = getTag(
|
|
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
|
|
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
|
|
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('
|
|
143
|
+
debugLog('Spawn options:', spawnOptions)
|
|
164
144
|
|
|
165
145
|
return async (ctx = getInitialState()) => {
|
|
166
|
-
const
|
|
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
|
-
|
|
182
|
-
|
|
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:
|
|
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
|
|
20
|
-
debugLog('Creating
|
|
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 =
|
|
46
|
+
const task = getSpawnedTask({ command, cwd, files, topLevelDir, isFn, verbose })
|
|
47
47
|
cmdTasks.push({ title: command, command, task })
|
|
48
48
|
}
|
|
49
49
|
}
|
package/lib/getStagedFiles.js
CHANGED
|
@@ -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
|
|
42
|
-
* @see https://github.com/git/git/blob/
|
|
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
|
|
1
|
+
type SyncGenerateTask = (stagedFileNames: string[]) => string | string[]
|
|
2
2
|
|
|
3
|
-
type
|
|
3
|
+
type AsyncGenerateTask = (stagedFileNames: string[]) => Promise<string | string[]>
|
|
4
4
|
|
|
5
|
-
type
|
|
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 |
|
|
9
|
-
|
|
|
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
|
-
*
|
|
66
|
-
* @default
|
|
70
|
+
* Revert to original state in case of errors
|
|
71
|
+
* @default true
|
|
67
72
|
*/
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 `
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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.
|
|
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
|
package/lib/validateConfig.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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, '
|
|
47
|
+
configurationError(pattern, 'Should be an array of strings or functions.', task)
|
|
62
48
|
)
|
|
63
49
|
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
)
|
package/lib/validateOptions.js
CHANGED
|
@@ -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": "
|
|
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": ">=
|
|
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": "^
|
|
51
|
-
"debug": "^4.4.
|
|
52
|
-
"execa": "^8.0.1",
|
|
51
|
+
"commander": "^14.0.0",
|
|
52
|
+
"debug": "^4.4.1",
|
|
53
53
|
"lilconfig": "^3.1.3",
|
|
54
|
-
"listr2": "^8.
|
|
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.
|
|
59
|
+
"yaml": "^2.8.0"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@changesets/changelog-github": "0.5.1",
|
|
62
|
-
"@changesets/cli": "2.
|
|
63
|
-
"@commitlint/cli": "19.8.
|
|
64
|
-
"@commitlint/config-conventional": "19.8.
|
|
65
|
-
"@eslint/js": "9.
|
|
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.
|
|
69
|
-
"eslint-config-prettier": "10.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.
|
|
72
|
-
"eslint-plugin-prettier": "5.
|
|
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.
|
|
80
|
-
"typescript": "5.8.
|
|
80
|
+
"semver": "7.7.2",
|
|
81
|
+
"typescript": "5.8.3"
|
|
81
82
|
},
|
|
82
83
|
"keywords": [
|
|
83
84
|
"lint",
|