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.
- package/README.md +91 -25
- package/bin/lint-staged.js +84 -67
- package/lib/chunkFiles.js +2 -3
- package/lib/colors.js +106 -0
- package/lib/debug.js +27 -0
- package/lib/execGit.js +3 -2
- package/lib/figures.js +5 -4
- package/lib/file.js +2 -2
- package/lib/generateTasks.js +2 -2
- package/lib/getFunctionTask.js +2 -3
- package/lib/getRenderer.js +7 -8
- package/lib/getSpawnedTask.js +9 -6
- package/lib/getSpawnedTasks.js +14 -5
- package/lib/gitWorkflow.js +109 -60
- package/lib/groupFilesByConfig.js +2 -2
- package/lib/index.d.ts +30 -9
- package/lib/index.js +25 -8
- package/lib/loadConfig.js +30 -53
- package/lib/messages.js +21 -22
- package/lib/resolveGitRepo.js +2 -3
- package/lib/runAll.js +45 -24
- package/lib/searchConfigs.js +107 -60
- package/lib/state.js +22 -6
- package/lib/symbols.js +2 -0
- package/lib/validateConfig.js +2 -3
- package/lib/validateOptions.js +2 -3
- package/package.json +15 -21
- package/lib/dynamicImport.js +0 -3
package/lib/getSpawnedTask.js
CHANGED
|
@@ -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 =
|
|
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 ?
|
|
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 =
|
|
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(`${
|
|
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)
|
package/lib/getSpawnedTasks.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import
|
|
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 =
|
|
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({
|
|
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
|
}
|
package/lib/gitWorkflow.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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({
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
196
|
-
debugLog(
|
|
197
|
-
return
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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 (
|
|
212
|
-
|
|
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.
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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.
|
|
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
|
|
3
|
+
import { createDebug } from './debug.js'
|
|
4
4
|
|
|
5
|
-
const debugLog =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
//
|
|
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
|
-
|
|
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)
|