lint-staged 16.1.6 → 16.2.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.
@@ -1,16 +1,16 @@
1
- import chalk from 'chalk'
2
- import debug from 'debug'
3
1
  import spawn from 'nano-spawn'
4
2
  import pidtree from 'pidtree'
5
3
  import { parseArgsStringToArgv } from 'string-argv'
6
4
 
5
+ import { blackBright, red } from './colors.js'
6
+ import { createDebug } from './debug.js'
7
7
  import { error, info } from './figures.js'
8
8
  import { getInitialState } from './state.js'
9
9
  import { TaskError } from './symbols.js'
10
10
 
11
11
  const TASK_ERROR = 'lint-staged:taskError'
12
12
 
13
- const debugLog = debug('lint-staged:getSpawnedTask')
13
+ const debugLog = createDebug('lint-staged:getSpawnedTask')
14
14
 
15
15
  /** @type {(error: import('nano-spawn').SubprocessError) => string} */
16
16
  const getTag = (error) => {
@@ -27,13 +27,13 @@ const getTag = (error) => {
27
27
  */
28
28
  const handleOutput = (command, result, ctx, isError = false) => {
29
29
  if (result.output) {
30
- const outputTitle = isError ? chalk.redBright(`${error} ${command}:`) : `${info} ${command}:`
30
+ const outputTitle = isError ? red(`${error} ${command}:`) : `${info} ${command}:`
31
31
  const output = [...(ctx.quiet ? [] : ['', outputTitle]), result.output]
32
32
  ctx.output.push(output.join('\n'))
33
33
  } else if (isError) {
34
34
  // Show generic error when task had no output
35
35
  const tag = getTag(result)
36
- const message = chalk.redBright(`\n${error} ${command} failed without output (${tag}).`)
36
+ const message = red(`\n${error} ${command} failed without output (${tag}).`)
37
37
  if (!ctx.quiet) ctx.output.push(message)
38
38
  }
39
39
  }
@@ -108,13 +108,14 @@ export const makeErr = (command, error, ctx) => {
108
108
 
109
109
  handleOutput(command, error, ctx, true)
110
110
  const tag = getTag(error)
111
- return new Error(`${chalk.redBright(command)} ${chalk.dim(`[${tag}]`)}`)
111
+ return new Error(`${red(command)} ${blackBright(`[${tag}]`)}`)
112
112
  }
113
113
 
114
114
  /**
115
115
  * Returns the task function for the linter.
116
116
  *
117
117
  * @param {Object} options
118
+ * @param {boolean} [options.color]
118
119
  * @param {string} options.command — Linter task
119
120
  * @param {string} [options.cwd]
120
121
  * @param {String} options.topLevelDir - Current git repo top-level path
@@ -124,6 +125,7 @@ export const makeErr = (command, error, ctx) => {
124
125
  * @returns {() => Promise<Array<string>>}
125
126
  */
126
127
  export const getSpawnedTask = ({
128
+ color,
127
129
  command,
128
130
  cwd = process.cwd(),
129
131
  files,
@@ -141,6 +143,7 @@ export const getSpawnedTask = ({
141
143
  cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
142
144
  preferLocal: true,
143
145
  stdin: 'ignore',
146
+ env: color ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
144
147
  }
145
148
 
146
149
  debugLog('Spawn options:', spawnOptions)
@@ -1,21 +1,21 @@
1
- import debug from 'debug'
2
-
1
+ import { createDebug } from './debug.js'
3
2
  import { getSpawnedTask } from './getSpawnedTask.js'
4
3
  import { configurationError } from './messages.js'
5
4
 
6
- const debugLog = debug('lint-staged:getSpawnedTasks')
5
+ const debugLog = createDebug('lint-staged:getSpawnedTasks')
7
6
 
8
7
  /**
9
8
  * Creates and returns an array of listr tasks which map to the given commands.
10
9
  *
11
10
  * @param {object} options
11
+ * @param {boolean} [options.color]
12
12
  * @param {Array<string|Function>|string|Function} options.commands
13
13
  * @param {string} options.cwd
14
14
  * @param {import('./getStagedFiles.js').StagedFile[]} options.files
15
15
  * @param {string} options.topLevelDir
16
16
  * @param {Boolean} verbose
17
17
  */
18
- export const getSpawnedTasks = async ({ commands, cwd, files, topLevelDir, verbose }) => {
18
+ export const getSpawnedTasks = async ({ color, commands, cwd, files, topLevelDir, verbose }) => {
19
19
  debugLog('Creating Listr tasks for commands %o', commands)
20
20
  const cmdTasks = []
21
21
 
@@ -45,7 +45,16 @@ export const getSpawnedTasks = async ({ commands, cwd, files, topLevelDir, verbo
45
45
  )
46
46
  }
47
47
 
48
- const task = getSpawnedTask({ command, cwd, files: filepaths, topLevelDir, isFn, verbose })
48
+ const task = getSpawnedTask({
49
+ color,
50
+ command,
51
+ cwd,
52
+ files: filepaths,
53
+ topLevelDir,
54
+ isFn,
55
+ verbose,
56
+ })
57
+
49
58
  cmdTasks.push({ title: command, command, task })
50
59
  }
51
60
  }
@@ -1,13 +1,15 @@
1
+ import crypto from 'node:crypto'
1
2
  import fs from 'node:fs/promises'
2
3
  import path from 'node:path'
3
4
 
4
- import debug from 'debug'
5
-
5
+ import { createDebug } from './debug.js'
6
6
  import { execGit } from './execGit.js'
7
7
  import { readFile, unlink, writeFile } from './file.js'
8
8
  import { getDiffCommand } from './getDiffCommand.js'
9
+ import { parseGitZOutput } from './parseGitZOutput.js'
9
10
  import {
10
11
  ApplyEmptyCommitError,
12
+ ExitCodeError,
11
13
  GetBackupStashError,
12
14
  GitError,
13
15
  HideUnstagedChangesError,
@@ -16,7 +18,7 @@ import {
16
18
  RestoreUnstagedChangesError,
17
19
  } from './symbols.js'
18
20
 
19
- const debugLog = debug('lint-staged:GitWorkflow')
21
+ const debugLog = createDebug('lint-staged:GitWorkflow')
20
22
 
21
23
  const MERGE_HEAD = 'MERGE_HEAD'
22
24
  const MERGE_MODE = 'MERGE_MODE'
@@ -66,21 +68,38 @@ const handleError = (error, ctx, symbol) => {
66
68
  throw error
67
69
  }
68
70
 
71
+ const calculateSha256 = (input) => crypto.createHash('sha256').update(input, 'utf-8').digest('hex')
72
+
73
+ /**
74
+ * The lines are wrapped in double quotes
75
+ * @returns {string[]}
76
+ */
77
+ const cleanGitStashOutput = (lines) => lines.map((line) => line.replace(/^"(.*)"$/, '$1'))
78
+
69
79
  export class GitWorkflow {
70
80
  /**
71
81
  * @param {Object} opts
72
82
  * @param {import('./getStagedFiles.js').StagedFile[][]} opts.matchedFileChunks
73
83
  */
74
- constructor({ allowEmpty, gitConfigDir, topLevelDir, matchedFileChunks, diff, diffFilter }) {
84
+ constructor({
85
+ allowEmpty,
86
+ diff,
87
+ diffFilter,
88
+ failOnChanges,
89
+ gitConfigDir,
90
+ matchedFileChunks,
91
+ topLevelDir,
92
+ }) {
75
93
  this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: topLevelDir })
94
+ this.allowEmpty = allowEmpty
76
95
  this.deletedFiles = []
77
- this.gitConfigDir = gitConfigDir
78
- this.topLevelDir = topLevelDir
79
96
  this.diff = diff
80
97
  this.diffFilter = diffFilter
81
- this.allowEmpty = allowEmpty
98
+ this.gitConfigDir = gitConfigDir
99
+ this.failOnChanges = !!failOnChanges
82
100
  /** @type {import('./getStagedFiles.js').StagedFile[][]} */
83
101
  this.matchedFileChunks = matchedFileChunks
102
+ this.topLevelDir = topLevelDir
84
103
 
85
104
  /**
86
105
  * These three files hold state about an ongoing git merge
@@ -103,11 +122,12 @@ export class GitWorkflow {
103
122
  * Get name of backup stash
104
123
  */
105
124
  async getBackupStash(ctx) {
106
- const stashes = await this.execGit(['stash', 'list'])
125
+ /** Print stash list with short hash and subject */
126
+ const stashes = await this.execGit(['stash', 'list', '--format="%h %s"', '-z'])
127
+ .then(parseGitZOutput)
128
+ .then(cleanGitStashOutput)
107
129
 
108
- const index = stashes
109
- .split('\n')
110
- .findIndex((line) => line.includes(STASH) && line.includes(ctx.backupHash))
130
+ const index = stashes.findIndex((line) => line.startsWith(ctx.backupHash))
111
131
 
112
132
  if (index === -1) {
113
133
  ctx.errors.add(GetBackupStashError)
@@ -172,7 +192,7 @@ export class GitWorkflow {
172
192
  * Renames have special treatment, since the single status line includes
173
193
  * both the "from" and "to" filenames, where "from" is no longer on disk.
174
194
  */
175
- async getPartiallyStagedFiles() {
195
+ async getUnstagedFiles({ onlyPartial = false } = {}) {
176
196
  debugLog('Getting partially staged files...')
177
197
  const status = await this.execGit(['status', '-z'])
178
198
  /**
@@ -184,80 +204,96 @@ export class GitWorkflow {
184
204
  * renamed file, the file names are separated by a NUL character
185
205
  * (e.g. `to`\0`from`)
186
206
  */
187
- const partiallyStaged = status
207
+ const unstagedFiles = status
188
208
  // eslint-disable-next-line no-control-regex
189
209
  .split(/\x00(?=[ AMDRCU?!]{2} |$)/)
190
210
  .filter((line) => {
191
211
  const [index, workingTree] = line
192
- return index !== ' ' && workingTree !== ' ' && index !== '?' && workingTree !== '?'
212
+ const updatedInIndex = index !== ' ' && index !== '?'
213
+ const updatedInWorkingTree = workingTree !== ' ' && workingTree !== '?'
214
+
215
+ if (onlyPartial) {
216
+ return updatedInIndex && updatedInWorkingTree
217
+ }
218
+
219
+ return updatedInWorkingTree
193
220
  })
194
221
  .map((line) => line.slice(3)) // Remove first three letters (index, workingTree, and a whitespace)
195
- .filter(Boolean) // Filter empty string
196
- debugLog('Found partially staged files:', partiallyStaged)
197
- return partiallyStaged.length ? partiallyStaged : null
222
+ .filter(Boolean) // Filter empty strings
223
+ debugLog(`Found ${onlyPartial ? 'partially staged' : 'unstaged'} files:`, unstagedFiles)
224
+ return unstagedFiles.length ? unstagedFiles : null
198
225
  }
199
226
 
200
227
  /**
201
- * Create a diff of partially staged files and backup stash if enabled.
228
+ * Create a diff of unstaged or partially staged files and backup stash if enabled.
202
229
  */
203
230
  async prepare(ctx, task) {
204
231
  try {
205
232
  debugLog(task.title)
206
233
 
207
- // Get a list of files with both staged and unstaged changes.
208
- // Unstaged changes to these files should be hidden before the tasks run.
209
- this.partiallyStagedFiles = await this.getPartiallyStagedFiles()
234
+ if (ctx.shouldBackup) {
235
+ // When backup is enabled, the revert will clear ongoing merge status.
236
+ await this.backupMergeStatus()
237
+
238
+ // Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
239
+ // - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
240
+ // - git stash can't infer RD or MD states correctly, and will lose the deletion
241
+ this.deletedFiles = await this.getDeletedFiles()
242
+ }
210
243
 
211
- if (this.partiallyStagedFiles) {
212
- ctx.hasPartiallyStagedFiles = true
244
+ if (ctx.shouldHideUnstaged) {
245
+ this.unstagedFiles = await this.getUnstagedFiles({ onlyPartial: false })
246
+ ctx.hasFilesToHide = !!this.unstagedFiles
247
+ } else if (ctx.shouldHidePartiallyStaged) {
248
+ this.unstagedFiles = await this.getUnstagedFiles({ onlyPartial: true })
249
+ ctx.hasFilesToHide = !!this.unstagedFiles
250
+ }
251
+
252
+ if (this.unstagedFiles) {
213
253
  const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
214
254
  ctx.unstagedPatch = unstagedPatch
215
- const files = processRenames(this.partiallyStagedFiles)
255
+ const files = processRenames(this.unstagedFiles)
216
256
  await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch, '--', ...files])
217
- } else {
218
- ctx.hasPartiallyStagedFiles = false
219
257
  }
220
258
 
221
- /**
222
- * If backup stash should be skipped, no need to continue
223
- */
224
- if (!ctx.shouldBackup) return
225
-
226
- // When backup is enabled, the revert will clear ongoing merge status.
227
- await this.backupMergeStatus()
228
-
229
- // Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
230
- // - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
231
- // - git stash can't infer RD or MD states correctly, and will lose the deletion
232
- this.deletedFiles = await this.getDeletedFiles()
233
-
234
- // Save stash of all staged files.
235
- // The `stash create` command creates a dangling commit without removing any files,
236
- // and `stash store` saves it as an actual stash.
237
- const stashHash = await this.execGit(['stash', 'create'])
238
- ctx.backupHash = await this.execGit(['rev-parse', '--short', stashHash])
239
- await this.execGit([
240
- 'stash',
241
- 'store',
242
- '--quiet',
243
- '--message',
244
- `${STASH} (${ctx.backupHash})`,
245
- ctx.backupHash,
246
- ])
259
+ if (ctx.shouldBackup) {
260
+ if (ctx.shouldHideUnstaged) {
261
+ /** Save stash of all changes, clearing the working tree but keeping staged files as-is */
262
+ await this.execGit(['stash', 'push', '--keep-index', '--message', STASH])
263
+ /** Print stash list with short hash and subject */
264
+ const stashes = await this.execGit(['stash', 'list', '--format="%h %s"', '-z'])
265
+ .then(parseGitZOutput)
266
+ .then(cleanGitStashOutput)
267
+
268
+ /** The stash line starts with the short hash, so we split from space and choose the first part */
269
+ ctx.backupHash = stashes.find((line) => line.includes(STASH))?.split(' ')[0]
270
+ } else {
271
+ /** Save stash of all changes, keeping all files as-is */
272
+ const stashHash = await this.execGit(['stash', 'create'])
273
+ ctx.backupHash = await this.execGit(['rev-parse', '--short', stashHash])
274
+ await this.execGit(['stash', 'store', '--quiet', '--message', STASH, ctx.backupHash])
275
+ }
276
+
277
+ task.title = `Backed up original state in git stash (${ctx.backupHash})`
278
+ debugLog(task.title)
279
+ }
247
280
 
248
- task.title = `Backed up original state in git stash (${ctx.backupHash})`
249
- debugLog(task.title)
281
+ if (this.failOnChanges) {
282
+ debugLog(
283
+ 'Calculating SHA-256 hash of unstaged changes because "--fail-on-changes" was used...'
284
+ )
285
+ const diff = await this.execGit(['diff', '--patch', '--unified=0'])
286
+ this.unstagedDiffSha256 = calculateSha256(diff)
287
+ debugLog('SHA-256 hash of unstaged changes is %S', this.unstagedDiffSha256)
288
+ }
250
289
  } catch (error) {
251
290
  handleError(error, ctx)
252
291
  }
253
292
  }
254
293
 
255
- /**
256
- * Remove unstaged changes to all partially staged files, to avoid tasks from seeing them
257
- */
258
- async hideUnstagedChanges(ctx) {
294
+ async hidePartiallyStagedChanges(ctx) {
259
295
  try {
260
- const files = processRenames(this.partiallyStagedFiles, false)
296
+ const files = processRenames(this.unstagedFiles, false)
261
297
  await this.execGit(['checkout', '--force', '--', ...files])
262
298
  } catch (error) {
263
299
  /**
@@ -273,6 +309,19 @@ export class GitWorkflow {
273
309
  * In case of a merge-conflict retry with 3-way merge.
274
310
  */
275
311
  async applyModifications(ctx) {
312
+ if (this.failOnChanges) {
313
+ debugLog(
314
+ 'Calculating SHA-256 hash of changes after tasks because "--fail-on-changes" was used...'
315
+ )
316
+ const diff = await this.execGit(['diff', '--patch', '--unified=0'])
317
+ const diffSha256 = calculateSha256(diff)
318
+ debugLog('SHA-256 hash of changes after tasks is %S', this.diffSha256)
319
+ if (this.unstagedDiffSha256 !== diffSha256) {
320
+ ctx.errors.add(ExitCodeError)
321
+ throw new Error('Tasks modified files and --fail-on-changes was used!')
322
+ }
323
+ }
324
+
276
325
  debugLog('Adding task modifications to index...')
277
326
 
278
327
  // `matchedFileChunks` includes staged files that lint-staged originally detected and matched against a task.
@@ -327,8 +376,8 @@ export class GitWorkflow {
327
376
  debugLog('Error while restoring changes:')
328
377
  debugLog(applyError)
329
378
  debugLog('Retrying with 3-way merge')
379
+ // Retry with a 3-way merge if normal apply fails
330
380
  try {
331
- // Retry with a 3-way merge if normal apply fails
332
381
  await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', unstagedPatch])
333
382
  } catch (threeWayApplyError) {
334
383
  debugLog('Error while restoring unstaged changes using 3-way merge:')
@@ -1,8 +1,8 @@
1
1
  import path from 'node:path'
2
2
 
3
- import debug from 'debug'
3
+ import { createDebug } from './debug.js'
4
4
 
5
- const debugLog = debug('lint-staged:groupFilesByConfig')
5
+ const debugLog = createDebug('lint-staged:groupFilesByConfig')
6
6
 
7
7
  /**
8
8
  * @typedef {import('./getStagedFiles.js').StagedFile} StagedFile
package/lib/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- type SyncGenerateTask = (stagedFileNames: string[]) => string | string[]
1
+ type SyncGenerateTask = (stagedFileNames: readonly string[]) => string | string[]
2
2
 
3
- type AsyncGenerateTask = (stagedFileNames: string[]) => Promise<string | string[]>
3
+ type AsyncGenerateTask = (stagedFileNames: readonly string[]) => Promise<string | string[]>
4
4
 
5
5
  type GenerateTask = SyncGenerateTask | AsyncGenerateTask
6
6
 
7
7
  type TaskFunction = {
8
8
  title: string
9
- task: (stagedFileNames: string[]) => void | Promise<void>
9
+ task: (stagedFileNames: readonly string[]) => void | Promise<void>
10
10
  }
11
11
 
12
12
  export type Configuration =
@@ -19,6 +19,11 @@ export type Options = {
19
19
  * @default false
20
20
  */
21
21
  allowEmpty?: boolean
22
+ /**
23
+ * Enable or disable ANSI color codes in output. By default value is auto-detected
24
+ * and controlled by `FORCE_COLOR` or `NO_COLOR` env variables.
25
+ */
26
+ color?: boolean
22
27
  /**
23
28
  * The number of tasks to run concurrently, or `false` to run tasks serially
24
29
  * @default true
@@ -32,6 +37,11 @@ export type Options = {
32
37
  * Path to single configuration file; disables automatic config file discovery when used
33
38
  */
34
39
  configPath?: string
40
+ /**
41
+ * Run all tasks to completion even if one fails
42
+ * @default false
43
+ */
44
+ continueOnError?: boolean
35
45
  /**
36
46
  * Working directory to run all tasks in, defaults to current working directory
37
47
  */
@@ -52,10 +62,25 @@ export type Options = {
52
62
  * @default "ACMR"
53
63
  */
54
64
  diffFilter?: string
65
+ /**
66
+ * Fail with exit code 1 when tasks modify tracked files
67
+ * @default false
68
+ */
69
+ failOnChanges?: boolean
55
70
  /**
56
71
  * Maximum argument string length, by default automatically detected
57
72
  */
58
73
  maxArgLength?: number
74
+ /**
75
+ * Whether to hide unstaged changes from partially staged files before running tasks
76
+ * @default true
77
+ */
78
+ hidePartiallyStaged?: boolean
79
+ /**
80
+ * Whether to hide all unstaged changes before running tasks
81
+ * @default false
82
+ */
83
+ hideUnstaged?: boolean
59
84
  /**
60
85
  * Disable lint-staged’s own console output
61
86
  * @default false
@@ -77,11 +102,6 @@ export type Options = {
77
102
  * @default true
78
103
  */
79
104
  stash?: boolean
80
- /**
81
- * Whether to hide unstaged changes from partially staged files before running tasks
82
- * @default true
83
- */
84
- hidePartiallyStaged?: boolean
85
105
  /**
86
106
  * Show task output even when tasks succeed; by default only failed output is shown
87
107
  * @default false
@@ -89,12 +109,13 @@ export type Options = {
89
109
  verbose?: boolean
90
110
  }
91
111
 
92
- type LogFunction = (...params: any) => void
112
+ type LogFunction = typeof console.log
93
113
 
94
114
  type Logger = {
95
115
  log: LogFunction
96
116
  warn: LogFunction
97
117
  error: LogFunction
118
+ debug: LogFunction
98
119
  }
99
120
 
100
121
  /**
package/lib/index.js CHANGED
@@ -1,10 +1,11 @@
1
- import debugLib from 'debug'
2
-
1
+ import { SUPPORTS_COLOR } from './colors.js'
2
+ import { createDebug, enableDebug } from './debug.js'
3
3
  import { execGit } from './execGit.js'
4
4
  import {
5
5
  GIT_ERROR,
6
6
  NO_CONFIGURATION,
7
7
  PREVENTED_EMPTY_COMMIT,
8
+ PREVENTED_TASK_MODIFICATIONS,
8
9
  RESTORE_STASH_EXAMPLE,
9
10
  UNSTAGED_CHANGES_BACKUP_STASH_LOCATION,
10
11
  } from './messages.js'
@@ -14,6 +15,7 @@ import { cleanupSkipped } from './state.js'
14
15
  import {
15
16
  ApplyEmptyCommitError,
16
17
  ConfigNotFoundError,
18
+ ExitCodeError,
17
19
  GetBackupStashError,
18
20
  GitError,
19
21
  RestoreUnstagedChangesError,
@@ -21,7 +23,7 @@ import {
21
23
  import { validateOptions } from './validateOptions.js'
22
24
  import { getVersion } from './version.js'
23
25
 
24
- const debugLog = debugLib('lint-staged')
26
+ const debugLog = createDebug('lint-staged')
25
27
 
26
28
  /**
27
29
  * Get the maximum length of a command-line argument string based on current platform
@@ -49,13 +51,18 @@ const getMaxArgLength = () => {
49
51
  *
50
52
  * @param {object} options
51
53
  * @param {Object} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
54
+ * @param {boolean} [options.color] - Enable or disable ANSI color codes in output.
52
55
  * @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
53
56
  * @param {object} [options.config] - Object with configuration for programmatic API
54
57
  * @param {string} [options.configPath] - Path to configuration file
58
+ * @param {boolean} [options.continueOnError] - Run all tasks to completion even if one fails
55
59
  * @param {Object} [options.cwd] - Current working directory
56
60
  * @param {boolean} [options.debug] - Enable debug mode
57
61
  * @param {string} [options.diff] - Override the default "--staged" flag of "git diff" to get list of files
58
62
  * @param {string} [options.diffFilter] - Override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
63
+ * @param {boolean} [options.failOnChanges] - Fail with exit code 1 when tasks modify tracked files
64
+ * @param {boolean} [options.hidePartiallyStaged] - Whether to hide unstaged changes from partially staged files before running tasks
65
+ * @param {boolean} [options.hideUnstaged] - Whether to hide all unstaged changes before running tasks
59
66
  * @param {number} [options.maxArgLength] - Maximum argument string length
60
67
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
61
68
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
@@ -69,28 +76,32 @@ const getMaxArgLength = () => {
69
76
  const lintStaged = async (
70
77
  {
71
78
  allowEmpty = false,
79
+ color = SUPPORTS_COLOR,
72
80
  concurrent = true,
73
81
  config: configObject,
74
82
  configPath,
83
+ continueOnError = false,
75
84
  cwd,
76
85
  debug = false,
77
86
  diff,
78
87
  diffFilter,
88
+ failOnChanges = false,
89
+ hideUnstaged = false,
90
+ hidePartiallyStaged = !hideUnstaged,
79
91
  maxArgLength = getMaxArgLength() / 2,
80
92
  quiet = false,
81
93
  relative = false,
82
94
  // Stashing should be disabled by default when the `diff` option is used
83
95
  stash = diff === undefined,
84
- // Cannot revert to original state without stash
85
- revert = stash,
86
- hidePartiallyStaged = true,
96
+ // Default to false when using failOnChanges; cannot revert to original state without stash
97
+ revert = !failOnChanges && !!stash,
87
98
  verbose = false,
88
99
  } = {},
89
100
  logger = console
90
101
  ) => {
91
102
  // Seemingly enable debug twice (also done in bin), so that it also works when using the Node.js API
92
103
  if (debug) {
93
- debugLib.enable('lint-staged*')
104
+ enableDebug(logger)
94
105
 
95
106
  debugLog(
96
107
  'Running `lint-staged@%s` on Node.js %s (%s)',
@@ -105,19 +116,23 @@ const lintStaged = async (
105
116
 
106
117
  const options = {
107
118
  allowEmpty,
119
+ color,
108
120
  concurrent,
109
121
  configObject,
110
122
  configPath,
123
+ continueOnError,
111
124
  cwd,
112
125
  debug,
113
126
  diff,
114
127
  diffFilter,
128
+ failOnChanges,
129
+ hidePartiallyStaged,
130
+ hideUnstaged,
115
131
  maxArgLength,
116
132
  quiet,
117
133
  relative,
118
134
  revert,
119
135
  stash,
120
- hidePartiallyStaged,
121
136
  verbose,
122
137
  }
123
138
 
@@ -140,6 +155,8 @@ const lintStaged = async (
140
155
  logger.error(NO_CONFIGURATION)
141
156
  } else if (ctx.errors.has(ApplyEmptyCommitError)) {
142
157
  logger.warn(PREVENTED_EMPTY_COMMIT)
158
+ } else if (ctx.errors.has(ExitCodeError)) {
159
+ logger.warn(PREVENTED_TASK_MODIFICATIONS)
143
160
  } else if (ctx.errors.has(RestoreUnstagedChangesError)) {
144
161
  logger.warn(UNSTAGED_CHANGES_BACKUP_STASH_LOCATION)
145
162
  logger.warn(ctx.unstagedPatch)