lint-staged 16.1.0 → 16.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MIGRATION.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  #### Updated Node.js version requirement
4
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.
5
+ For version `lint-staged@16.0.0` the lowest supported Node.js version is `20.19.0`, following requirements of `nano-spawn`. Please upgrade your Node.js version.
6
+
7
+ For version `lint-staged@16.1.0` this is lowered to `20.17.0`, again following `nano-spawn`.
6
8
 
7
9
  #### Removed validation for removed advanced configuration file options
8
10
 
package/README.md CHANGED
@@ -85,7 +85,7 @@ Now change a few files, `git add` or `git add --patch` some of them to your comm
85
85
 
86
86
  See [examples](#examples) and [configuration](#configuration) for more information.
87
87
 
88
- > [!CAUTION]
88
+ > [!CAUTION]
89
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.
90
90
 
91
91
  ## Changelog
@@ -143,9 +143,9 @@ Any lost modifications can be restored from a git stash:
143
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`.
144
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).
145
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.
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`.
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`
148
- - **`--quiet`**: Supress all CLI output, except from tasks.
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.
147
+ - **`--no-hide-partially-staged`**: By default, unstaged changes from partially staged files will be hidden and applied back after running tasks. This option will disable this behavior, causing those changes to also be committed.
148
+ - **`--quiet`**: Suppress all CLI output, except from tasks.
149
149
  - **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
150
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.
151
151
  - **`--verbose`**: Show task output even when tasks succeed. By default only failed output is shown.
@@ -78,7 +78,7 @@ program
78
78
  .addOption(
79
79
  new Option('--no-stash', 'disable the backup stash. Implies "--no-revert".')
80
80
  .default(false)
81
- .implies({ revert: false, hidePartiallyStaged: false })
81
+ .implies({ revert: false })
82
82
  )
83
83
 
84
84
  program
package/lib/chunkFiles.js CHANGED
@@ -10,7 +10,7 @@ const debugLog = debug('lint-staged:chunkFiles')
10
10
  * Chunk array into sub-arrays
11
11
  * @param {Array} arr
12
12
  * @param {Number} chunkCount
13
- * @retuns {Array<Array>}
13
+ * @returns {Array<Array>}
14
14
  */
15
15
  const chunkArray = (arr, chunkCount) => {
16
16
  if (chunkCount === 1) return [arr]
@@ -27,24 +27,32 @@ const chunkArray = (arr, chunkCount) => {
27
27
 
28
28
  /**
29
29
  * Chunk files into sub-arrays based on the length of the resulting argument string
30
+ *
31
+ * @typedef {import('./getStagedFiles.js').StagedFile[]} StagedFile
32
+ *
30
33
  * @param {Object} opts
31
- * @param {Array<String>} opts.files
34
+ * @param {Array<StagedFile>} opts.files
32
35
  * @param {String} [opts.baseDir] The optional base directory to resolve relative paths.
33
36
  * @param {number} [opts.maxArgLength] the maximum argument string length
34
37
  * @param {Boolean} [opts.relative] whether files are relative to `topLevelDir` or should be resolved as absolute
35
- * @returns {Array<Array<String>>}
38
+ * @returns {Array<Array<StagedFile>>}
36
39
  */
37
40
  export const chunkFiles = ({ files, baseDir, maxArgLength = null, relative = false }) => {
38
- const normalizedFiles = files.map((file) =>
39
- normalizePath(relative || !baseDir ? file : path.resolve(baseDir, file))
40
- )
41
+ const normalizedFiles = files.map((file) => {
42
+ return {
43
+ filepath: normalizePath(
44
+ relative || !baseDir ? file.filepath : path.resolve(baseDir, file.filepath)
45
+ ),
46
+ status: file.status,
47
+ }
48
+ })
41
49
 
42
50
  if (!maxArgLength) {
43
51
  debugLog('Skip chunking files because of undefined maxArgLength')
44
52
  return [normalizedFiles] // wrap in an array to return a single chunk
45
53
  }
46
54
 
47
- const fileListLength = normalizedFiles.join(' ').length
55
+ const fileListLength = normalizedFiles.map((file) => file.filepath).join(' ').length
48
56
  debugLog(
49
57
  `Resolved an argument string length of ${fileListLength} characters from ${normalizedFiles.length} files`
50
58
  )
@@ -13,13 +13,17 @@ const debugLog = debug('lint-staged:generateTasks')
13
13
  * @param {object} options
14
14
  * @param {Object} [options.config] - Task configuration
15
15
  * @param {Object} [options.cwd] - Current working directory
16
- * @param {boolean} [options.files] - Staged filepaths
16
+ * @param {import('./getStagedFiles.js').StagedFile[]} [options.files] - Staged filepaths
17
17
  * @param {boolean} [options.relative] - Whether filepaths to should be relative to cwd
18
18
  */
19
19
  export const generateTasks = ({ config, cwd = process.cwd(), files, relative = false }) => {
20
20
  debugLog('Generating linter tasks')
21
21
 
22
- const relativeFiles = files.map((file) => normalizePath(path.relative(cwd, file)))
22
+ /** @type {StagedFile[]} */
23
+ const relativeFiles = files.map((file) => ({
24
+ filepath: normalizePath(path.relative(cwd, file.filepath)),
25
+ status: file.status,
26
+ }))
23
27
 
24
28
  return Object.entries(config).map(([pattern, commands]) => {
25
29
  const isParentDirPattern = pattern.startsWith('../')
@@ -28,21 +32,34 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f
28
32
  // specifies that it concerns a parent directory.
29
33
  const filteredFiles = relativeFiles.filter((file) => {
30
34
  if (isParentDirPattern) return true
31
- return !file.startsWith('..') && !path.isAbsolute(file)
35
+ return !file.filepath.startsWith('..') && !path.isAbsolute(file.filepath)
32
36
  })
33
37
 
34
- const matches = micromatch(filteredFiles, pattern, {
35
- cwd,
36
- dot: true,
37
- // If the pattern doesn't look like a path, enable `matchBase` to
38
- // match against filenames in every directory. This makes `*.js`
39
- // match both `test.js` and `subdirectory/test.js`.
40
- matchBase: !pattern.includes('/'),
41
- posixSlashes: true,
42
- strictBrackets: true,
43
- })
38
+ const matches = micromatch(
39
+ filteredFiles.map((file) => file.filepath),
40
+ pattern,
41
+ {
42
+ cwd,
43
+ dot: true,
44
+ // If the pattern doesn't look like a path, enable `matchBase` to
45
+ // match against filenames in every directory. This makes `*.js`
46
+ // match both `test.js` and `subdirectory/test.js`.
47
+ matchBase: !pattern.includes('/'),
48
+ posixSlashes: true,
49
+ strictBrackets: true,
50
+ }
51
+ )
44
52
 
45
- const fileList = matches.map((file) => normalizePath(relative ? file : path.resolve(cwd, file)))
53
+ const fileList = filteredFiles.flatMap((file) =>
54
+ matches.includes(file.filepath)
55
+ ? [
56
+ {
57
+ filepath: normalizePath(relative ? file.filepath : path.resolve(cwd, file.filepath)),
58
+ status: file.status,
59
+ },
60
+ ]
61
+ : []
62
+ )
46
63
 
47
64
  const task = { pattern, commands, fileList }
48
65
  debugLog('Generated task: \n%O', task)
@@ -15,7 +15,7 @@ export const isFunctionTask = (commands) => typeof commands === 'object' && !Arr
15
15
  * Handles function configuration and pushes the tasks into the task array
16
16
  *
17
17
  * @param {object} command
18
- * @param {Array<string>} files
18
+ * @param {import('./getStagedFiles.js').StagedFile[]} files
19
19
  * @throws {Error} If the function configuration is not valid
20
20
  */
21
21
  export const getFunctionTask = async (command, files) => {
@@ -23,7 +23,7 @@ export const getFunctionTask = async (command, files) => {
23
23
 
24
24
  const task = async (ctx) => {
25
25
  try {
26
- await command.task(files)
26
+ await command.task(files.map((file) => file.filepath))
27
27
  } catch (e) {
28
28
  throw makeErr(command.title, e, ctx)
29
29
  }
@@ -116,7 +116,7 @@ export const makeErr = (command, error, ctx) => {
116
116
  * @param {string} [options.cwd]
117
117
  * @param {String} options.topLevelDir - Current git repo top-level path
118
118
  * @param {Boolean} options.isFn - Whether the linter task is a function
119
- * @param {Array<string>} options.files — Filepaths to run the linter task against
119
+ * @param {string[]} options.files — Filepaths to run the linter task against
120
120
  * @param {Boolean} [options.verbose] — Always show task verbose
121
121
  * @returns {() => Promise<Array<string>>}
122
122
  */
@@ -11,7 +11,7 @@ const debugLog = debug('lint-staged:getSpawnedTasks')
11
11
  * @param {object} options
12
12
  * @param {Array<string|Function>|string|Function} options.commands
13
13
  * @param {string} options.cwd
14
- * @param {Array<string>} options.files
14
+ * @param {import('./getStagedFiles.js').StagedFile[]} options.files
15
15
  * @param {string} options.topLevelDir
16
16
  * @param {Boolean} verbose
17
17
  */
@@ -21,12 +21,14 @@ export const getSpawnedTasks = async ({ commands, cwd, files, topLevelDir, verbo
21
21
 
22
22
  const commandArray = Array.isArray(commands) ? commands : [commands]
23
23
 
24
+ const filepaths = files.map((f) => f.filepath)
25
+
24
26
  for (const cmd of commandArray) {
25
27
  // command function may return array of commands that already include `stagedFiles`
26
28
  const isFn = typeof cmd === 'function'
27
29
 
28
30
  /** Pass copy of file list to prevent mutation by function from config file. */
29
- const resolved = isFn ? await cmd([...files]) : cmd
31
+ const resolved = isFn ? await cmd([...filepaths]) : cmd
30
32
 
31
33
  const resolvedArray = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array command as array
32
34
 
@@ -43,7 +45,7 @@ export const getSpawnedTasks = async ({ commands, cwd, files, topLevelDir, verbo
43
45
  )
44
46
  }
45
47
 
46
- const task = getSpawnedTask({ command, cwd, files, topLevelDir, isFn, verbose })
48
+ const task = getSpawnedTask({ command, cwd, files: filepaths, topLevelDir, isFn, verbose })
47
49
  cmdTasks.push({ title: command, command, task })
48
50
  }
49
51
  }
@@ -5,6 +5,16 @@ import { getDiffCommand } from './getDiffCommand.js'
5
5
  import { normalizePath } from './normalizePath.js'
6
6
  import { parseGitZOutput } from './parseGitZOutput.js'
7
7
 
8
+ /**
9
+ * @typedef {'A'|'C'|'D'|'M'|'R'|'T'|'U'|'X'} FileSatus
10
+ * @typedef { { filepath: string; status: FileSatus }} StagedFile
11
+ *
12
+ * @param {Object} args
13
+ * @param {string} [args.cwd]
14
+ * @param {string} [args.diff]
15
+ * @param {string} [args.diffFilter]
16
+ * @retuns {Promise<StagedFile[] | null>}
17
+ */
8
18
  export const getStagedFiles = async ({ cwd = process.cwd(), diff, diffFilter } = {}) => {
9
19
  try {
10
20
  /**
@@ -35,7 +45,7 @@ export const getStagedFiles = async ({ cwd = process.cwd(), diff, diffFilter } =
35
45
  .split('\u0000:')
36
46
  .map(parseGitZOutput)
37
47
  .flatMap(([info, src, dst]) => {
38
- const [, dstMode, , , ,] = info.split(' ')
48
+ const [, dstMode, , , statusWithScore] = info.split(' ')
39
49
 
40
50
  /**
41
51
  * Filter out submodules and symlinks
@@ -45,10 +55,30 @@ export const getStagedFiles = async ({ cwd = process.cwd(), diff, diffFilter } =
45
55
  return []
46
56
  }
47
57
 
58
+ /**
59
+ * @example "M"
60
+ * @example "R86"
61
+ *
62
+ * - A: addition of a file
63
+ * - C: copy of a file into a new one
64
+ * - D: deletion of a file
65
+ * - M: modification of the contents or mode of a file
66
+ * - R: renaming of a file
67
+ * - T: change in the type of the file (regular file, symbolic link or submodule)
68
+ * - U: file is unmerged (you must complete the merge before it can be committed)
69
+ * - X: "unknown" change type (most probably a bug, please report it)
70
+ */
71
+ const status = statusWithScore[0]
72
+
48
73
  /** "dst" exists when moving files, otherwise it's undefined and only "src" exists */
49
74
  const filename = dst ?? src
50
75
 
51
- return [normalizePath(path.resolve(cwd, filename))]
76
+ return [
77
+ {
78
+ filepath: normalizePath(path.resolve(cwd, filename)),
79
+ status,
80
+ },
81
+ ]
52
82
  })
53
83
  } catch {
54
84
  return null
@@ -66,6 +66,10 @@ const handleError = (error, ctx, symbol) => {
66
66
  }
67
67
 
68
68
  export class GitWorkflow {
69
+ /**
70
+ * @param {Object} opts
71
+ * @param {import('./getStagedFiles.js').StagedFile[][]} opts.matchedFileChunks
72
+ */
69
73
  constructor({ allowEmpty, gitConfigDir, topLevelDir, matchedFileChunks, diff, diffFilter }) {
70
74
  this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: topLevelDir })
71
75
  this.deletedFiles = []
@@ -74,6 +78,7 @@ export class GitWorkflow {
74
78
  this.diff = diff
75
79
  this.diffFilter = diffFilter
76
80
  this.allowEmpty = allowEmpty
81
+ /** @type {import('./getStagedFiles.js').StagedFile[][]} */
77
82
  this.matchedFileChunks = matchedFileChunks
78
83
 
79
84
  /**
@@ -185,7 +190,7 @@ export class GitWorkflow {
185
190
  const [index, workingTree] = line
186
191
  return index !== ' ' && workingTree !== ' ' && index !== '?' && workingTree !== '?'
187
192
  })
188
- .map((line) => line.substr(3)) // Remove first three letters (index, workingTree, and a whitespace)
193
+ .map((line) => line.slice(3)) // Remove first three letters (index, workingTree, and a whitespace)
189
194
  .filter(Boolean) // Filter empty string
190
195
  debugLog('Found partially staged files:', partiallyStaged)
191
196
  return partiallyStaged.length ? partiallyStaged : null
@@ -198,13 +203,14 @@ export class GitWorkflow {
198
203
  try {
199
204
  debugLog(task.title)
200
205
 
201
- // Get a list of files with bot staged and unstaged changes.
206
+ // Get a list of files with both staged and unstaged changes.
202
207
  // Unstaged changes to these files should be hidden before the tasks run.
203
208
  this.partiallyStagedFiles = await this.getPartiallyStagedFiles()
204
209
 
205
210
  if (this.partiallyStagedFiles) {
206
211
  ctx.hasPartiallyStagedFiles = true
207
212
  const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
213
+ ctx.unstagedPatch = unstagedPatch
208
214
  const files = processRenames(this.partiallyStagedFiles)
209
215
  await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch, '--', ...files])
210
216
  } else {
@@ -273,7 +279,8 @@ export class GitWorkflow {
273
279
  // These additions per chunk are run "serially" to prevent race conditions.
274
280
  // Git add creates a lockfile in the repo causing concurrent operations to fail.
275
281
  for (const files of this.matchedFileChunks) {
276
- await this.execGit(['add', '--', ...files])
282
+ /** @todo Deleted files cannot be staged because they're... deleted */
283
+ await this.execGit(['add', '--', ...files.map((f) => f.filepath)])
277
284
  }
278
285
 
279
286
  debugLog('Done adding task modifications to index!')
@@ -4,10 +4,17 @@ import debug from 'debug'
4
4
 
5
5
  const debugLog = debug('lint-staged:groupFilesByConfig')
6
6
 
7
+ /**
8
+ * @typedef {import('./getStagedFiles.js').StagedFile} StagedFile
9
+ * @type {(args: { config: {[key: string]: { config: any; files: string[] }}; files: StagedFile[]; singleConfigMode?: boolean }) => Promise<{[key: string]: { config: any; files: StagedFile[] } }>
10
+ */
7
11
  export const groupFilesByConfig = async ({ configs, files, singleConfigMode }) => {
8
12
  debugLog('Grouping %d files by %d configurations', files.length, Object.keys(configs).length)
9
13
 
14
+ /** @type {Set<StagedFile>} */
10
15
  const filesSet = new Set(files)
16
+
17
+ /** @type {{[key: string]: { config: any; files: StagedFile[] } }} */
11
18
  const filesByConfig = {}
12
19
 
13
20
  /** Configs are sorted deepest first by `searchConfigs` */
@@ -22,7 +29,7 @@ export const groupFilesByConfig = async ({ configs, files, singleConfigMode }) =
22
29
 
23
30
  /** Check if file is inside directory of the configuration file */
24
31
  const isInsideDir = (file) => {
25
- const relative = path.relative(dir, file)
32
+ const relative = path.relative(dir, file.filepath)
26
33
  return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
27
34
  }
28
35
 
package/lib/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  NO_CONFIGURATION,
7
7
  PREVENTED_EMPTY_COMMIT,
8
8
  RESTORE_STASH_EXAMPLE,
9
+ UNSTAGED_CHANGES_BACKUP_STASH_LOCATION,
9
10
  } from './messages.js'
10
11
  import { printTaskOutput } from './printTaskOutput.js'
11
12
  import { runAll } from './runAll.js'
@@ -15,6 +16,7 @@ import {
15
16
  ConfigNotFoundError,
16
17
  GetBackupStashError,
17
18
  GitError,
19
+ RestoreUnstagedChangesError,
18
20
  } from './symbols.js'
19
21
  import { validateOptions } from './validateOptions.js'
20
22
  import { getVersion } from './version.js'
@@ -81,7 +83,7 @@ const lintStaged = async (
81
83
  stash = diff === undefined,
82
84
  // Cannot revert to original state without stash
83
85
  revert = stash,
84
- hidePartiallyStaged = stash,
86
+ hidePartiallyStaged = true,
85
87
  verbose = false,
86
88
  } = {},
87
89
  logger = console
@@ -138,6 +140,9 @@ const lintStaged = async (
138
140
  logger.error(NO_CONFIGURATION)
139
141
  } else if (ctx.errors.has(ApplyEmptyCommitError)) {
140
142
  logger.warn(PREVENTED_EMPTY_COMMIT)
143
+ } else if (ctx.errors.has(RestoreUnstagedChangesError)) {
144
+ logger.warn(UNSTAGED_CHANGES_BACKUP_STASH_LOCATION)
145
+ logger.warn(ctx.unstagedPatch)
141
146
  } else if (
142
147
  (ctx.errors.has(GitError) || cleanupSkipped(ctx)) &&
143
148
  !ctx.errors.has(GetBackupStashError)
package/lib/messages.js CHANGED
@@ -31,25 +31,15 @@ export const skippingBackup = (hasInitialCommit, diff) => {
31
31
  const reason =
32
32
  diff !== undefined
33
33
  ? '`--diff` was used'
34
- : hasInitialCommit
35
- ? '`--no-stash` was used'
36
- : 'there’s no initial commit yet'
34
+ : (hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet') +
35
+ '. This might result in data loss'
37
36
 
38
37
  return chalk.yellow(`${warning} Skipping backup because ${reason}.\n`)
39
38
  }
40
39
 
41
- export const skippingHidePartiallyStaged = (stash, diff) => {
42
- const reason =
43
- diff !== undefined
44
- ? '`--diff` was used'
45
- : !stash
46
- ? '`--no-stash` was used'
47
- : '`--no-hide-partially-staged` was used'
48
-
49
- return chalk.yellow(
50
- `${warning} Skipping hiding unstaged changes from partially staged files because ${reason}.\n`
51
- )
52
- }
40
+ export const SKIPPING_HIDE_PARTIALLY_CHANGED = chalk.yellow(
41
+ `${warning} Skipping hiding unstaged changes from partially staged files because \`--no-hide-partially-staged\` was used.\n`
42
+ )
53
43
 
54
44
  export const DEPRECATED_GIT_ADD = chalk.yellow(
55
45
  `${warning} Some of your tasks use \`git add\` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.
@@ -96,3 +86,5 @@ export const failedToParseConfig = (
96
86
  ${error}
97
87
 
98
88
  See https://github.com/okonet/lint-staged#configuration.`
89
+
90
+ export const UNSTAGED_CHANGES_BACKUP_STASH_LOCATION = `Unstaged changes have been kept back in a patch file:`
package/lib/runAll.js CHANGED
@@ -22,8 +22,8 @@ import {
22
22
  NO_TASKS,
23
23
  NOT_GIT_REPO,
24
24
  SKIPPED_GIT_ERROR,
25
+ SKIPPING_HIDE_PARTIALLY_CHANGED,
25
26
  skippingBackup,
26
- skippingHidePartiallyStaged,
27
27
  } from './messages.js'
28
28
  import { normalizePath } from './normalizePath.js'
29
29
  import { resolveGitRepo } from './resolveGitRepo.js'
@@ -42,7 +42,12 @@ import { ConfigNotFoundError, GetStagedFilesError, GitError, GitRepoError } from
42
42
 
43
43
  const debugLog = debug('lint-staged:runAll')
44
44
 
45
- const createError = (ctx) => Object.assign(new Error('lint-staged failed'), { ctx })
45
+ /**
46
+ * @param {ReturnType<typeof getInitialState>} ctx context
47
+ * @param {unknown} cause error cause
48
+ */
49
+ const createError = (ctx, cause) =>
50
+ Object.assign(new Error('lint-staged failed', { cause }), { ctx })
46
51
 
47
52
  /**
48
53
  * Executes all tasks and either resolves or rejects the promise
@@ -82,7 +87,7 @@ export const runAll = async (
82
87
  stash = diff === undefined,
83
88
  // Cannot revert to original state without stash
84
89
  revert = stash,
85
- hidePartiallyStaged = stash,
90
+ hidePartiallyStaged = true,
86
91
  verbose = false,
87
92
  },
88
93
  logger = console
@@ -100,7 +105,7 @@ export const runAll = async (
100
105
  if (!topLevelDir) {
101
106
  if (!quiet) ctx.output.push(NOT_GIT_REPO)
102
107
  ctx.errors.add(GitRepoError)
103
- throw createError(ctx)
108
+ throw createError(ctx, GitRepoError)
104
109
  }
105
110
 
106
111
  // Test whether we have any commits or not.
@@ -118,19 +123,19 @@ export const runAll = async (
118
123
 
119
124
  ctx.shouldHidePartiallyStaged = hidePartiallyStaged
120
125
  if (!ctx.shouldHidePartiallyStaged && !quiet) {
121
- logger.warn(skippingHidePartiallyStaged(hasInitialCommit && stash, diff))
126
+ logger.warn(SKIPPING_HIDE_PARTIALLY_CHANGED)
122
127
  }
123
128
 
124
- const files = await getStagedFiles({ cwd: topLevelDir, diff, diffFilter })
125
- if (!files) {
129
+ const stagedFiles = await getStagedFiles({ cwd: topLevelDir, diff, diffFilter })
130
+ if (!stagedFiles) {
126
131
  if (!quiet) ctx.output.push(FAILED_GET_STAGED_FILES)
127
132
  ctx.errors.add(GetStagedFilesError)
128
133
  throw createError(ctx, GetStagedFilesError)
129
134
  }
130
- debugLog('Loaded list of staged files in git:\n%O', files)
135
+ debugLog('Loaded list of staged files in git:\n%O', stagedFiles)
131
136
 
132
137
  // If there are no files avoid executing any lint-staged logic
133
- if (files.length === 0) {
138
+ if (stagedFiles.length === 0) {
134
139
  if (!quiet) ctx.output.push(NO_STAGED_FILES)
135
140
  return ctx
136
141
  }
@@ -146,7 +151,7 @@ export const runAll = async (
146
151
 
147
152
  const filesByConfig = await groupFilesByConfig({
148
153
  configs: foundConfigs,
149
- files,
154
+ files: stagedFiles,
150
155
  singleConfigMode: configObject || configPath !== undefined,
151
156
  })
152
157
 
@@ -174,6 +179,7 @@ export const runAll = async (
174
179
  const listrTasks = []
175
180
 
176
181
  // Set of all staged files that matched a task glob. Values in a set are unique.
182
+ /** @type {Set<import('./getStagedFiles.js').StagedFile>} */
177
183
  const matchedFiles = new Set()
178
184
 
179
185
  for (const [configPath, { config, files }] of Object.entries(filesByConfig)) {
@@ -195,7 +201,7 @@ export const runAll = async (
195
201
  const chunkListrTasks = await Promise.all(
196
202
  generateTasks({ config, cwd: groupCwd, files, relative }).map((task) =>
197
203
  (isFunctionTask(task.commands)
198
- ? getFunctionTask(task.commands, files)
204
+ ? getFunctionTask(task.commands, task.fileList)
199
205
  : getSpawnedTasks({
200
206
  commands: task.commands,
201
207
  cwd: groupCwd,
@@ -209,9 +215,12 @@ export const runAll = async (
209
215
  // Make sure relative files are normalized to the
210
216
  // group cwd, because other there might be identical
211
217
  // relative filenames in the entire set.
212
- const normalizedFile = path.isAbsolute(file)
218
+ const normalizedFile = path.isAbsolute(file.filepath)
213
219
  ? file
214
- : normalizePath(path.join(groupCwd, file))
220
+ : {
221
+ filepath: normalizePath(path.join(groupCwd, file.filepath)),
222
+ status: file.status,
223
+ }
215
224
 
216
225
  matchedFiles.add(normalizedFile)
217
226
  })
@@ -275,6 +284,7 @@ export const runAll = async (
275
284
  }
276
285
 
277
286
  // Chunk matched files for better Windows compatibility
287
+ /** @type {import('./getStagedFiles.js').StagedFile[][]} */
278
288
  const matchedFileChunks = chunkFiles({
279
289
  // matched files are relative to `cwd`, not `topLevelDir`, when `relative` is used
280
290
  baseDir: cwd,
@@ -292,6 +302,8 @@ export const runAll = async (
292
302
  diffFilter,
293
303
  })
294
304
 
305
+ logger.log('shouldHidePartiallyStagedFiles', shouldHidePartiallyStagedFiles(ctx))
306
+
295
307
  const runner = new Listr(
296
308
  [
297
309
  {
package/lib/state.js CHANGED
@@ -9,15 +9,16 @@ import {
9
9
  } from './symbols.js'
10
10
 
11
11
  export const getInitialState = ({ quiet = false, revert = true } = {}) => ({
12
- hasPartiallyStagedFiles: null,
13
- shouldBackup: null,
14
- shouldRevert: revert,
15
12
  backupHash: null,
16
- shouldHidePartiallyStaged: true,
17
13
  errors: new Set([]),
18
14
  events: new EventEmitter(),
15
+ hasPartiallyStagedFiles: null,
19
16
  output: [],
20
17
  quiet,
18
+ shouldBackup: null,
19
+ shouldHidePartiallyStaged: true,
20
+ shouldRevert: revert,
21
+ unstagedPatch: null,
21
22
  })
22
23
 
23
24
  export const shouldHidePartiallyStagedFiles = (ctx) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "16.1.0",
3
+ "version": "16.1.1",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -63,17 +63,17 @@
63
63
  "@changesets/cli": "2.29.4",
64
64
  "@commitlint/cli": "19.8.1",
65
65
  "@commitlint/config-conventional": "19.8.1",
66
- "@eslint/js": "9.27.0",
66
+ "@eslint/js": "9.29.0",
67
67
  "consolemock": "1.1.0",
68
68
  "cross-env": "7.0.3",
69
- "eslint": "9.27.0",
69
+ "eslint": "9.29.0",
70
70
  "eslint-config-prettier": "10.1.5",
71
- "eslint-plugin-jest": "28.11.0",
72
- "eslint-plugin-n": "17.18.0",
73
- "eslint-plugin-prettier": "5.4.0",
71
+ "eslint-plugin-jest": "28.13.5",
72
+ "eslint-plugin-n": "17.20.0",
73
+ "eslint-plugin-prettier": "5.4.1",
74
74
  "eslint-plugin-simple-import-sort": "12.1.1",
75
75
  "husky": "9.1.7",
76
- "jest": "29.7.0",
76
+ "jest": "30.0.0",
77
77
  "jest-snapshot-serializer-ansi": "2.2.1",
78
78
  "mock-stdin": "1.0.0",
79
79
  "prettier": "3.5.3",