lint-staged 16.3.4 → 17.0.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 +20 -0
- package/README.md +48 -44
- package/bin/lint-staged.js +16 -135
- package/lib/assertGitVersion.js +48 -0
- package/lib/cli.js +242 -0
- package/lib/colors.js +8 -103
- package/lib/debug.js +3 -5
- package/lib/generateTasks.js +6 -28
- package/lib/getRenderer.js +1 -1
- package/lib/getSpawnedTask.js +2 -2
- package/lib/gitWorkflow.js +55 -68
- package/lib/index.d.ts +5 -0
- package/lib/index.js +9 -1
- package/lib/matchFiles.js +22 -0
- package/lib/messages.js +8 -2
- package/lib/runAll.js +23 -25
- package/lib/state.js +33 -17
- package/package.json +19 -19
package/lib/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { assertGitVersion, MIN_GIT_VERSION } from './assertGitVersion.js'
|
|
1
2
|
import { SUPPORTS_COLOR } from './colors.js'
|
|
2
3
|
import { createDebug, enableDebug } from './debug.js'
|
|
3
4
|
import { execGit } from './execGit.js'
|
|
4
5
|
import {
|
|
5
6
|
GIT_ERROR,
|
|
7
|
+
minGitVersionRequired,
|
|
6
8
|
NO_CONFIGURATION,
|
|
7
9
|
PREVENTED_EMPTY_COMMIT,
|
|
8
10
|
PREVENTED_TASK_MODIFICATIONS,
|
|
@@ -86,8 +88,9 @@ const lintStaged = async (
|
|
|
86
88
|
diff,
|
|
87
89
|
diffFilter,
|
|
88
90
|
failOnChanges = false,
|
|
91
|
+
hideAll = false,
|
|
89
92
|
hideUnstaged = false,
|
|
90
|
-
hidePartiallyStaged = !hideUnstaged,
|
|
93
|
+
hidePartiallyStaged = !(hideAll || hideUnstaged),
|
|
91
94
|
maxArgLength = getMaxArgLength() / 2,
|
|
92
95
|
quiet = false,
|
|
93
96
|
relative = false,
|
|
@@ -114,6 +117,10 @@ const lintStaged = async (
|
|
|
114
117
|
const gitVersion = await execGit(['version', '--build-options'], { cwd })
|
|
115
118
|
debugLog('%s', gitVersion)
|
|
116
119
|
|
|
120
|
+
if (!assertGitVersion(gitVersion)) {
|
|
121
|
+
throw new Error(minGitVersionRequired(MIN_GIT_VERSION, gitVersion), { cause: gitVersion })
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
const options = {
|
|
118
125
|
allowEmpty,
|
|
119
126
|
color,
|
|
@@ -126,6 +133,7 @@ const lintStaged = async (
|
|
|
126
133
|
diff,
|
|
127
134
|
diffFilter,
|
|
128
135
|
failOnChanges,
|
|
136
|
+
hideAll,
|
|
129
137
|
hidePartiallyStaged,
|
|
130
138
|
hideUnstaged,
|
|
131
139
|
maxArgLength,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import picomatch from 'picomatch'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Match list of files against a pattern.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} pattern
|
|
7
|
+
* @param {import('./getStagedFiles.js').StagedFile[]} files
|
|
8
|
+
*/
|
|
9
|
+
export const matchFiles = (files, pattern, cwd = process.cwd()) => {
|
|
10
|
+
const isMatch = picomatch(pattern, {
|
|
11
|
+
cwd,
|
|
12
|
+
dot: true,
|
|
13
|
+
// If the pattern doesn't look like a path, enable `matchBase` to
|
|
14
|
+
// match against filenames in every directory. This makes `*.js`
|
|
15
|
+
// match both `test.js` and `subdirectory/test.js`.
|
|
16
|
+
matchBase: !pattern.includes('/'),
|
|
17
|
+
posixSlashes: true,
|
|
18
|
+
strictBrackets: true,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return files.filter((file) => isMatch(file.filepath))
|
|
22
|
+
}
|
package/lib/messages.js
CHANGED
|
@@ -67,12 +67,13 @@ export const PREVENTED_EMPTY_COMMIT = `
|
|
|
67
67
|
`
|
|
68
68
|
|
|
69
69
|
export const restoreStashExample = (
|
|
70
|
-
hash = '
|
|
70
|
+
hash = '<git-hash>'
|
|
71
71
|
) => `Any lost modifications can be restored from a git stash:
|
|
72
72
|
|
|
73
73
|
> git stash list --format="%h %s"
|
|
74
74
|
${hash} On main: lint-staged automatic backup
|
|
75
|
-
> git apply --index ${hash}
|
|
75
|
+
> git apply --index ${hash}
|
|
76
|
+
`
|
|
76
77
|
|
|
77
78
|
export const CONFIG_STDIN_ERROR = red(`${error} Failed to read config from stdin.`)
|
|
78
79
|
|
|
@@ -89,3 +90,8 @@ ${error}
|
|
|
89
90
|
See https://github.com/okonet/lint-staged#configuration.`
|
|
90
91
|
|
|
91
92
|
export const UNSTAGED_CHANGES_BACKUP_STASH_LOCATION = `Unstaged changes have been kept back in a patch file:`
|
|
93
|
+
|
|
94
|
+
export const minGitVersionRequired = (expected) =>
|
|
95
|
+
red(`${error} lint-staged requires at least Git version ${bold(expected)}.
|
|
96
|
+
|
|
97
|
+
Please update Git: https://git-scm.com/downloads`)
|
package/lib/runAll.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path'
|
|
|
5
5
|
import { Listr } from 'listr2'
|
|
6
6
|
|
|
7
7
|
import { chunkFiles } from './chunkFiles.js'
|
|
8
|
-
import {
|
|
8
|
+
import { dim } from './colors.js'
|
|
9
9
|
import { createDebug } from './debug.js'
|
|
10
10
|
import { execGit } from './execGit.js'
|
|
11
11
|
import { generateTasks } from './generateTasks.js'
|
|
@@ -30,7 +30,6 @@ import { normalizePath } from './normalizePath.js'
|
|
|
30
30
|
import { resolveGitRepo } from './resolveGitRepo.js'
|
|
31
31
|
import { searchConfigs } from './searchConfigs.js'
|
|
32
32
|
import {
|
|
33
|
-
applyModificationsSkipped,
|
|
34
33
|
cleanupEnabled,
|
|
35
34
|
cleanupSkipped,
|
|
36
35
|
getInitialState,
|
|
@@ -39,6 +38,8 @@ import {
|
|
|
39
38
|
restoreUnstagedChangesSkipped,
|
|
40
39
|
shouldHidePartiallyStagedFiles,
|
|
41
40
|
shouldRestoreUnstagedChanges,
|
|
41
|
+
shouldRestoreUntrackedFiles,
|
|
42
|
+
updateIndexSkipped,
|
|
42
43
|
} from './state.js'
|
|
43
44
|
import { ConfigNotFoundError, GetStagedFilesError, GitError, GitRepoError } from './symbols.js'
|
|
44
45
|
|
|
@@ -90,8 +91,9 @@ export const runAll = async (
|
|
|
90
91
|
diff,
|
|
91
92
|
diffFilter,
|
|
92
93
|
failOnChanges = false,
|
|
94
|
+
hideAll = false,
|
|
93
95
|
hideUnstaged = false,
|
|
94
|
-
hidePartiallyStaged = !hideUnstaged,
|
|
96
|
+
hidePartiallyStaged = !(hideAll || hideUnstaged),
|
|
95
97
|
maxArgLength,
|
|
96
98
|
quiet = false,
|
|
97
99
|
relative = false,
|
|
@@ -112,8 +114,9 @@ export const runAll = async (
|
|
|
112
114
|
|
|
113
115
|
const ctx = getInitialState({
|
|
114
116
|
failOnChanges,
|
|
115
|
-
|
|
117
|
+
hideAll,
|
|
116
118
|
hideUnstaged,
|
|
119
|
+
hidePartiallyStaged,
|
|
117
120
|
quiet,
|
|
118
121
|
revert,
|
|
119
122
|
})
|
|
@@ -138,7 +141,7 @@ export const runAll = async (
|
|
|
138
141
|
logger.warn(skippingBackup(hasInitialCommit, diff))
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
if (!ctx.shouldHidePartiallyStaged && !ctx.shouldHideUnstaged && !quiet) {
|
|
144
|
+
if (!ctx.shouldHidePartiallyStaged && !ctx.shouldHideUnstaged && !ctx.shouldHideAll && !quiet) {
|
|
142
145
|
logger.warn(SKIPPING_HIDE_PARTIALLY_CHANGED)
|
|
143
146
|
}
|
|
144
147
|
|
|
@@ -204,7 +207,7 @@ export const runAll = async (
|
|
|
204
207
|
// Use actual cwd if it's specified, or there's only a single config file.
|
|
205
208
|
// Otherwise use the directory of the config file for each config group,
|
|
206
209
|
// to make sure tasks are separated from each other.
|
|
207
|
-
const groupCwd =
|
|
210
|
+
const groupCwd = hasExplicitCwd || !hasMultipleConfigs ? cwd : path.dirname(configPath)
|
|
208
211
|
|
|
209
212
|
const chunkCount = stagedFileChunks.length
|
|
210
213
|
if (chunkCount > 1) {
|
|
@@ -248,7 +251,7 @@ export const runAll = async (
|
|
|
248
251
|
const fileCount = task.fileList.length
|
|
249
252
|
|
|
250
253
|
return {
|
|
251
|
-
title: `${task.pattern}${
|
|
254
|
+
title: `${task.pattern}${dim(
|
|
252
255
|
` — ${fileCount} ${fileCount === 1 ? 'file' : 'files'}`
|
|
253
256
|
)}`,
|
|
254
257
|
task: async (ctx, task) =>
|
|
@@ -260,7 +263,7 @@ export const runAll = async (
|
|
|
260
263
|
skip: () => {
|
|
261
264
|
// Skip task when no files matched
|
|
262
265
|
if (fileCount === 0) {
|
|
263
|
-
return `${task.pattern}${
|
|
266
|
+
return `${task.pattern}${dim(' — no files')}`
|
|
264
267
|
}
|
|
265
268
|
return false
|
|
266
269
|
},
|
|
@@ -271,8 +274,8 @@ export const runAll = async (
|
|
|
271
274
|
|
|
272
275
|
listrTasks.push({
|
|
273
276
|
title:
|
|
274
|
-
`${configName}${
|
|
275
|
-
(chunkCount > 1 ?
|
|
277
|
+
`${configName}${dim(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` +
|
|
278
|
+
(chunkCount > 1 ? dim(` (chunk ${index + 1}/${chunkCount})...`) : ''),
|
|
276
279
|
task: (ctx, task) =>
|
|
277
280
|
task.newListr(chunkListrTasks, { concurrent, exitOnError: !continueOnError }),
|
|
278
281
|
skip: () => {
|
|
@@ -280,7 +283,7 @@ export const runAll = async (
|
|
|
280
283
|
if (ctx.errors.has(GitError)) return SKIPPED_GIT_ERROR
|
|
281
284
|
// Skip chunk when no every task is skipped (due to no matches)
|
|
282
285
|
if (chunkListrTasks.every((task) => task.skip())) {
|
|
283
|
-
return `${configName}${
|
|
286
|
+
return `${configName}${dim(' — no tasks to run')}`
|
|
284
287
|
}
|
|
285
288
|
return false
|
|
286
289
|
},
|
|
@@ -299,23 +302,12 @@ export const runAll = async (
|
|
|
299
302
|
return ctx
|
|
300
303
|
}
|
|
301
304
|
|
|
302
|
-
// Chunk matched files for better Windows compatibility
|
|
303
|
-
/** @type {import('./getStagedFiles.js').StagedFile[][]} */
|
|
304
|
-
const matchedFileChunks = chunkFiles({
|
|
305
|
-
// matched files are relative to `cwd`, not `topLevelDir`, when `relative` is used
|
|
306
|
-
baseDir: cwd,
|
|
307
|
-
files: Array.from(matchedFiles),
|
|
308
|
-
maxArgLength,
|
|
309
|
-
relative: false,
|
|
310
|
-
})
|
|
311
|
-
|
|
312
305
|
const git = new GitWorkflow({
|
|
313
306
|
allowEmpty,
|
|
314
307
|
diff,
|
|
315
308
|
diffFilter,
|
|
316
309
|
failOnChanges,
|
|
317
310
|
gitConfigDir,
|
|
318
|
-
matchedFileChunks,
|
|
319
311
|
topLevelDir,
|
|
320
312
|
})
|
|
321
313
|
|
|
@@ -336,9 +328,9 @@ export const runAll = async (
|
|
|
336
328
|
skip: () => listrTasks.every((task) => task.skip()),
|
|
337
329
|
},
|
|
338
330
|
{
|
|
339
|
-
title: '
|
|
340
|
-
task: (ctx) => git.
|
|
341
|
-
skip:
|
|
331
|
+
title: 'Updating Git index again...',
|
|
332
|
+
task: (ctx) => git.updateIndex(ctx),
|
|
333
|
+
skip: updateIndexSkipped,
|
|
342
334
|
},
|
|
343
335
|
{
|
|
344
336
|
title: 'Restoring unstaged changes...',
|
|
@@ -346,6 +338,12 @@ export const runAll = async (
|
|
|
346
338
|
enabled: shouldRestoreUnstagedChanges,
|
|
347
339
|
skip: restoreUnstagedChangesSkipped,
|
|
348
340
|
},
|
|
341
|
+
{
|
|
342
|
+
title: 'Restoring untracked files...',
|
|
343
|
+
task: (ctx) => git.restoreUntrackedFiles(ctx),
|
|
344
|
+
enabled: shouldRestoreUntrackedFiles,
|
|
345
|
+
skip: restoreUnstagedChangesSkipped,
|
|
346
|
+
},
|
|
349
347
|
{
|
|
350
348
|
title: 'Reverting to original state because of errors...',
|
|
351
349
|
task: (ctx) => git.restoreOriginalState(ctx),
|
package/lib/state.js
CHANGED
|
@@ -9,32 +9,48 @@ import {
|
|
|
9
9
|
|
|
10
10
|
export const getInitialState = ({
|
|
11
11
|
failOnChanges = false,
|
|
12
|
+
hideAll = false,
|
|
12
13
|
hideUnstaged = false,
|
|
13
|
-
hidePartiallyStaged = !hideUnstaged,
|
|
14
|
+
hidePartiallyStaged = !(hideAll || hideUnstaged),
|
|
14
15
|
quiet = false,
|
|
15
16
|
revert = true,
|
|
16
|
-
} = {}) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
} = {}) => {
|
|
18
|
+
const initialState = {
|
|
19
|
+
backupHash: null,
|
|
20
|
+
errors: new Set([]),
|
|
21
|
+
shouldFailOnChanges: failOnChanges,
|
|
22
|
+
hasFilesToHide: null,
|
|
23
|
+
output: [],
|
|
24
|
+
quiet,
|
|
25
|
+
shouldBackup: null,
|
|
26
|
+
shouldHideAll: hideAll,
|
|
27
|
+
shouldHideUnstaged: hideUnstaged,
|
|
28
|
+
shouldHidePartiallyStaged: hidePartiallyStaged,
|
|
29
|
+
shouldRevert: revert,
|
|
30
|
+
unstagedDiffSha256: null,
|
|
31
|
+
unstagedPatch: null,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (initialState.shouldHideAll) {
|
|
35
|
+
initialState.shouldHideUnstaged = false // becomes redundant
|
|
36
|
+
initialState.shouldHidePartiallyStaged = false // becomes redundant
|
|
37
|
+
} else if (initialState.shouldHideUnstaged) {
|
|
38
|
+
initialState.shouldHidePartiallyStaged = false // becomes redundant
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return initialState
|
|
42
|
+
}
|
|
30
43
|
|
|
31
44
|
export const shouldHidePartiallyStagedFiles = (ctx) =>
|
|
32
45
|
ctx.shouldHidePartiallyStaged && ctx.hasFilesToHide
|
|
33
46
|
|
|
34
47
|
export const shouldRestoreUnstagedChanges = (ctx) =>
|
|
35
|
-
(ctx.shouldHideUnstaged || ctx.shouldHidePartiallyStaged) &&
|
|
48
|
+
(ctx.shouldHideAll || ctx.shouldHideUnstaged || ctx.shouldHidePartiallyStaged) &&
|
|
49
|
+
ctx.hasFilesToHide
|
|
50
|
+
|
|
51
|
+
export const shouldRestoreUntrackedFiles = (ctx) => !!ctx.shouldHideAll
|
|
36
52
|
|
|
37
|
-
export const
|
|
53
|
+
export const updateIndexSkipped = (ctx) => {
|
|
38
54
|
// Always apply back unstaged modifications when skipping revert or backup
|
|
39
55
|
if (!ctx.shouldRevert || !ctx.shouldBackup) return false
|
|
40
56
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lint-staged",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "Lint files staged by git",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"url": "https://opencollective.com/lint-staged"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=22.22.1"
|
|
23
23
|
},
|
|
24
24
|
"type": "module",
|
|
25
25
|
"bin": {
|
|
@@ -48,34 +48,34 @@
|
|
|
48
48
|
"tag": "npx changeset tag"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"micromatch": "^4.0.8",
|
|
51
|
+
"listr2": "^10.2.1",
|
|
52
|
+
"picomatch": "^4.0.4",
|
|
54
53
|
"string-argv": "^0.3.2",
|
|
55
|
-
"tinyexec": "^1.
|
|
56
|
-
|
|
54
|
+
"tinyexec": "^1.1.2"
|
|
55
|
+
},
|
|
56
|
+
"optionalDependencies": {
|
|
57
|
+
"yaml": "^2.8.4"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
|
-
"@changesets/changelog-github": "0.
|
|
60
|
-
"@changesets/cli": "2.
|
|
61
|
-
"@commitlint/cli": "20.
|
|
62
|
-
"@commitlint/config-conventional": "20.
|
|
60
|
+
"@changesets/changelog-github": "0.7.0",
|
|
61
|
+
"@changesets/cli": "2.31.0",
|
|
62
|
+
"@commitlint/cli": "20.5.3",
|
|
63
|
+
"@commitlint/config-conventional": "20.5.3",
|
|
63
64
|
"@eslint/js": "10.0.1",
|
|
64
|
-
"@vitest/coverage-
|
|
65
|
-
"@vitest/eslint-plugin": "1.6.
|
|
65
|
+
"@vitest/coverage-istanbul": "4.1.5",
|
|
66
|
+
"@vitest/eslint-plugin": "1.6.16",
|
|
66
67
|
"consolemock": "1.1.0",
|
|
67
68
|
"cross-env": "10.1.0",
|
|
68
|
-
"eslint": "10.0
|
|
69
|
+
"eslint": "10.3.0",
|
|
69
70
|
"eslint-config-prettier": "10.1.8",
|
|
70
|
-
"eslint-plugin-n": "
|
|
71
|
+
"eslint-plugin-n": "18.0.1",
|
|
71
72
|
"eslint-plugin-prettier": "5.5.5",
|
|
72
|
-
"eslint-plugin-simple-import-sort": "
|
|
73
|
+
"eslint-plugin-simple-import-sort": "13.0.0",
|
|
73
74
|
"husky": "9.1.7",
|
|
74
75
|
"mock-stdin": "1.0.0",
|
|
75
|
-
"prettier": "3.8.
|
|
76
|
+
"prettier": "3.8.3",
|
|
76
77
|
"semver": "7.7.4",
|
|
77
|
-
"
|
|
78
|
-
"vitest": "4.0.18"
|
|
78
|
+
"vitest": "4.1.5"
|
|
79
79
|
},
|
|
80
80
|
"keywords": [
|
|
81
81
|
"lint",
|