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/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 = 'h0a0s0h0'
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 { blackBright } from './colors.js'
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
- hidePartiallyStaged,
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 = hasMultipleConfigs && !hasExplicitCwd ? path.dirname(configPath) : cwd
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}${blackBright(
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}${blackBright(' — no files')}`
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}${blackBright(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` +
275
- (chunkCount > 1 ? blackBright(` (chunk ${index + 1}/${chunkCount})...`) : ''),
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}${blackBright(' — no tasks to run')}`
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: 'Applying modifications from tasks...',
340
- task: (ctx) => git.applyModifications(ctx),
341
- skip: applyModificationsSkipped,
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
- backupHash: null,
18
- errors: new Set([]),
19
- shouldFailOnChanges: failOnChanges,
20
- hasFilesToHide: null,
21
- output: [],
22
- quiet,
23
- shouldBackup: null,
24
- shouldHidePartiallyStaged: hidePartiallyStaged,
25
- shouldHideUnstaged: hideUnstaged,
26
- shouldRevert: revert,
27
- unstagedDiffSha256: null,
28
- unstagedPatch: null,
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) && ctx.hasFilesToHide
48
+ (ctx.shouldHideAll || ctx.shouldHideUnstaged || ctx.shouldHidePartiallyStaged) &&
49
+ ctx.hasFilesToHide
50
+
51
+ export const shouldRestoreUntrackedFiles = (ctx) => !!ctx.shouldHideAll
36
52
 
37
- export const applyModificationsSkipped = (ctx) => {
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": "16.3.4",
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": ">=20.17"
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
- "commander": "^14.0.3",
52
- "listr2": "^9.0.5",
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.0.4",
56
- "yaml": "^2.8.2"
54
+ "tinyexec": "^1.1.2"
55
+ },
56
+ "optionalDependencies": {
57
+ "yaml": "^2.8.4"
57
58
  },
58
59
  "devDependencies": {
59
- "@changesets/changelog-github": "0.6.0",
60
- "@changesets/cli": "2.30.0",
61
- "@commitlint/cli": "20.4.4",
62
- "@commitlint/config-conventional": "20.4.4",
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-v8": "4.0.18",
65
- "@vitest/eslint-plugin": "1.6.9",
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.2",
69
+ "eslint": "10.3.0",
69
70
  "eslint-config-prettier": "10.1.8",
70
- "eslint-plugin-n": "17.24.0",
71
+ "eslint-plugin-n": "18.0.1",
71
72
  "eslint-plugin-prettier": "5.5.5",
72
- "eslint-plugin-simple-import-sort": "12.1.1",
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.1",
76
+ "prettier": "3.8.3",
76
77
  "semver": "7.7.4",
77
- "typescript": "5.9.3",
78
- "vitest": "4.0.18"
78
+ "vitest": "4.1.5"
79
79
  },
80
80
  "keywords": [
81
81
  "lint",