lint-staged 16.1.5 → 16.2.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/README.md +88 -22
- 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 +25 -19
- package/lib/getSpawnedTasks.js +14 -5
- package/lib/gitWorkflow.js +96 -55
- package/lib/groupFilesByConfig.js +2 -2
- package/lib/index.d.ts +20 -5
- package/lib/index.js +25 -8
- package/lib/loadConfig.js +6 -22
- package/lib/messages.js +18 -19
- package/lib/resolveGitRepo.js +2 -3
- package/lib/runAll.js +43 -23
- package/lib/searchConfigs.js +95 -56
- package/lib/state.js +20 -7
- package/lib/symbols.js +2 -0
- package/lib/validateConfig.js +2 -3
- package/lib/validateOptions.js +2 -3
- package/package.json +19 -25
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
|
-
import
|
|
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
|
}
|
|
@@ -45,19 +45,20 @@ const handleOutput = (command, result, ctx, isError = false) => {
|
|
|
45
45
|
const killSubprocess = async (subprocess) => {
|
|
46
46
|
const childProcess = await subprocess.nodeChildProcess
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
if (childProcess?.pid !== undefined) {
|
|
49
|
+
try {
|
|
50
|
+
for (const childPid of await pidtree(childProcess.pid)) {
|
|
51
|
+
try {
|
|
52
|
+
process.kill(childPid, 'SIGKILL')
|
|
53
|
+
} catch (error) {
|
|
54
|
+
debugLog(`Failed to kill process with pid "%d": %o`, childPid, error)
|
|
55
|
+
}
|
|
55
56
|
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Suppress "No matching pid found" error. This probably means
|
|
59
|
+
// the process already died before executing.
|
|
60
|
+
debugLog(`Failed to list child processes of pid "%d": %o`, childProcess.pid, error)
|
|
56
61
|
}
|
|
57
|
-
} catch (error) {
|
|
58
|
-
// Suppress "No matching pid found" error. This probably means
|
|
59
|
-
// the process already died before executing.
|
|
60
|
-
debugLog(`Failed to kill process with pid "%d": %o`, childProcess.pid, error)
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// The child process is terminated separately in order to get the `KILLED` status.
|
|
@@ -85,7 +86,9 @@ const interruptExecutionOnError = (ctx, subprocess) => {
|
|
|
85
86
|
|
|
86
87
|
return async () => {
|
|
87
88
|
ctx.events.off(TASK_ERROR, errorListener)
|
|
88
|
-
|
|
89
|
+
if (killPromise) {
|
|
90
|
+
await killPromise
|
|
91
|
+
}
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
|
|
@@ -105,13 +108,14 @@ export const makeErr = (command, error, ctx) => {
|
|
|
105
108
|
|
|
106
109
|
handleOutput(command, error, ctx, true)
|
|
107
110
|
const tag = getTag(error)
|
|
108
|
-
return new Error(`${
|
|
111
|
+
return new Error(`${red(command)} ${blackBright(`[${tag}]`)}`)
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/**
|
|
112
115
|
* Returns the task function for the linter.
|
|
113
116
|
*
|
|
114
117
|
* @param {Object} options
|
|
118
|
+
* @param {boolean} [options.color]
|
|
115
119
|
* @param {string} options.command — Linter task
|
|
116
120
|
* @param {string} [options.cwd]
|
|
117
121
|
* @param {String} options.topLevelDir - Current git repo top-level path
|
|
@@ -121,6 +125,7 @@ export const makeErr = (command, error, ctx) => {
|
|
|
121
125
|
* @returns {() => Promise<Array<string>>}
|
|
122
126
|
*/
|
|
123
127
|
export const getSpawnedTask = ({
|
|
128
|
+
color,
|
|
124
129
|
command,
|
|
125
130
|
cwd = process.cwd(),
|
|
126
131
|
files,
|
|
@@ -138,6 +143,7 @@ export const getSpawnedTask = ({
|
|
|
138
143
|
cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
|
|
139
144
|
preferLocal: true,
|
|
140
145
|
stdin: 'ignore',
|
|
146
|
+
env: color ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
|
|
141
147
|
}
|
|
142
148
|
|
|
143
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,14 @@
|
|
|
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
9
|
import {
|
|
10
10
|
ApplyEmptyCommitError,
|
|
11
|
+
ExitCodeError,
|
|
11
12
|
GetBackupStashError,
|
|
12
13
|
GitError,
|
|
13
14
|
HideUnstagedChangesError,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
RestoreUnstagedChangesError,
|
|
17
18
|
} from './symbols.js'
|
|
18
19
|
|
|
19
|
-
const debugLog =
|
|
20
|
+
const debugLog = createDebug('lint-staged:GitWorkflow')
|
|
20
21
|
|
|
21
22
|
const MERGE_HEAD = 'MERGE_HEAD'
|
|
22
23
|
const MERGE_MODE = 'MERGE_MODE'
|
|
@@ -66,21 +67,32 @@ const handleError = (error, ctx, symbol) => {
|
|
|
66
67
|
throw error
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
const calculateSha256 = (input) => crypto.createHash('sha256').update(input, 'utf-8').digest('hex')
|
|
71
|
+
|
|
69
72
|
export class GitWorkflow {
|
|
70
73
|
/**
|
|
71
74
|
* @param {Object} opts
|
|
72
75
|
* @param {import('./getStagedFiles.js').StagedFile[][]} opts.matchedFileChunks
|
|
73
76
|
*/
|
|
74
|
-
constructor({
|
|
77
|
+
constructor({
|
|
78
|
+
allowEmpty,
|
|
79
|
+
diff,
|
|
80
|
+
diffFilter,
|
|
81
|
+
failOnChanges,
|
|
82
|
+
gitConfigDir,
|
|
83
|
+
matchedFileChunks,
|
|
84
|
+
topLevelDir,
|
|
85
|
+
}) {
|
|
75
86
|
this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: topLevelDir })
|
|
87
|
+
this.allowEmpty = allowEmpty
|
|
76
88
|
this.deletedFiles = []
|
|
77
|
-
this.gitConfigDir = gitConfigDir
|
|
78
|
-
this.topLevelDir = topLevelDir
|
|
79
89
|
this.diff = diff
|
|
80
90
|
this.diffFilter = diffFilter
|
|
81
|
-
this.
|
|
91
|
+
this.gitConfigDir = gitConfigDir
|
|
92
|
+
this.failOnChanges = !!failOnChanges
|
|
82
93
|
/** @type {import('./getStagedFiles.js').StagedFile[][]} */
|
|
83
94
|
this.matchedFileChunks = matchedFileChunks
|
|
95
|
+
this.topLevelDir = topLevelDir
|
|
84
96
|
|
|
85
97
|
/**
|
|
86
98
|
* These three files hold state about an ongoing git merge
|
|
@@ -172,7 +184,7 @@ export class GitWorkflow {
|
|
|
172
184
|
* Renames have special treatment, since the single status line includes
|
|
173
185
|
* both the "from" and "to" filenames, where "from" is no longer on disk.
|
|
174
186
|
*/
|
|
175
|
-
async
|
|
187
|
+
async getUnstagedFiles({ onlyPartial = false } = {}) {
|
|
176
188
|
debugLog('Getting partially staged files...')
|
|
177
189
|
const status = await this.execGit(['status', '-z'])
|
|
178
190
|
/**
|
|
@@ -184,69 +196,85 @@ export class GitWorkflow {
|
|
|
184
196
|
* renamed file, the file names are separated by a NUL character
|
|
185
197
|
* (e.g. `to`\0`from`)
|
|
186
198
|
*/
|
|
187
|
-
const
|
|
199
|
+
const unstagedFiles = status
|
|
188
200
|
// eslint-disable-next-line no-control-regex
|
|
189
201
|
.split(/\x00(?=[ AMDRCU?!]{2} |$)/)
|
|
190
202
|
.filter((line) => {
|
|
191
203
|
const [index, workingTree] = line
|
|
192
|
-
|
|
204
|
+
const updatedInIndex = index !== ' ' && index !== '?'
|
|
205
|
+
const updatedInWorkingTree = workingTree !== ' ' && workingTree !== '?'
|
|
206
|
+
|
|
207
|
+
if (onlyPartial) {
|
|
208
|
+
return updatedInIndex && updatedInWorkingTree
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return updatedInWorkingTree
|
|
193
212
|
})
|
|
194
213
|
.map((line) => line.slice(3)) // Remove first three letters (index, workingTree, and a whitespace)
|
|
195
|
-
.filter(Boolean) // Filter empty
|
|
196
|
-
debugLog(
|
|
197
|
-
return
|
|
214
|
+
.filter(Boolean) // Filter empty strings
|
|
215
|
+
debugLog(`Found ${onlyPartial ? 'partially staged' : 'unstaged'} files:`, unstagedFiles)
|
|
216
|
+
return unstagedFiles.length ? unstagedFiles : null
|
|
198
217
|
}
|
|
199
218
|
|
|
200
219
|
/**
|
|
201
|
-
* Create a diff of partially staged files and backup stash if enabled.
|
|
220
|
+
* Create a diff of unstaged or partially staged files and backup stash if enabled.
|
|
202
221
|
*/
|
|
203
222
|
async prepare(ctx, task) {
|
|
204
223
|
try {
|
|
205
224
|
debugLog(task.title)
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
if (ctx.shouldBackup) {
|
|
227
|
+
// When backup is enabled, the revert will clear ongoing merge status.
|
|
228
|
+
await this.backupMergeStatus()
|
|
229
|
+
|
|
230
|
+
// Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
|
|
231
|
+
// - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
|
|
232
|
+
// - git stash can't infer RD or MD states correctly, and will lose the deletion
|
|
233
|
+
this.deletedFiles = await this.getDeletedFiles()
|
|
234
|
+
|
|
235
|
+
// Save stash of all staged files.
|
|
236
|
+
// The `stash create` command creates a dangling commit without removing any files,
|
|
237
|
+
// and `stash store` saves it as an actual stash.
|
|
238
|
+
const stashHash = await this.execGit(['stash', 'create'])
|
|
239
|
+
ctx.backupHash = await this.execGit(['rev-parse', '--short', stashHash])
|
|
240
|
+
await this.execGit([
|
|
241
|
+
'stash',
|
|
242
|
+
'store',
|
|
243
|
+
'--quiet',
|
|
244
|
+
'--message',
|
|
245
|
+
`${STASH} (${ctx.backupHash})`,
|
|
246
|
+
ctx.backupHash,
|
|
247
|
+
])
|
|
248
|
+
|
|
249
|
+
task.title = `Backed up original state in git stash (${ctx.backupHash})`
|
|
250
|
+
debugLog(task.title)
|
|
219
251
|
}
|
|
220
252
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
])
|
|
253
|
+
if (this.failOnChanges) {
|
|
254
|
+
debugLog(
|
|
255
|
+
'Calculating SHA-256 hash of unstaged changes because "--fail-on-changes" was used...'
|
|
256
|
+
)
|
|
257
|
+
const diff = await this.execGit(['diff', '--patch', '--unified=0'])
|
|
258
|
+
this.unstagedDiffSha256 = calculateSha256(diff)
|
|
259
|
+
debugLog('SHA-256 hash of unstaged changes is %S', this.unstagedDiffSha256)
|
|
260
|
+
}
|
|
247
261
|
|
|
248
|
-
|
|
249
|
-
|
|
262
|
+
if (ctx.shouldHideUnstaged || ctx.shouldHidePartiallyStaged) {
|
|
263
|
+
// Unstaged changes to these files should be hidden before the tasks run.
|
|
264
|
+
this.unstagedFiles = await this.getUnstagedFiles({
|
|
265
|
+
onlyPartial: ctx.shouldHideUnstaged ? false : ctx.shouldHidePartiallyStaged,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
if (this.unstagedFiles) {
|
|
269
|
+
ctx.hasFilesToHide = true
|
|
270
|
+
const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
|
|
271
|
+
ctx.unstagedPatch = unstagedPatch
|
|
272
|
+
const files = processRenames(this.unstagedFiles)
|
|
273
|
+
await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch, '--', ...files])
|
|
274
|
+
} else {
|
|
275
|
+
ctx.hasFilesToHide = false
|
|
276
|
+
}
|
|
277
|
+
}
|
|
250
278
|
} catch (error) {
|
|
251
279
|
handleError(error, ctx)
|
|
252
280
|
}
|
|
@@ -257,7 +285,7 @@ export class GitWorkflow {
|
|
|
257
285
|
*/
|
|
258
286
|
async hideUnstagedChanges(ctx) {
|
|
259
287
|
try {
|
|
260
|
-
const files = processRenames(this.
|
|
288
|
+
const files = processRenames(this.unstagedFiles, false)
|
|
261
289
|
await this.execGit(['checkout', '--force', '--', ...files])
|
|
262
290
|
} catch (error) {
|
|
263
291
|
/**
|
|
@@ -273,6 +301,19 @@ export class GitWorkflow {
|
|
|
273
301
|
* In case of a merge-conflict retry with 3-way merge.
|
|
274
302
|
*/
|
|
275
303
|
async applyModifications(ctx) {
|
|
304
|
+
if (this.failOnChanges) {
|
|
305
|
+
debugLog(
|
|
306
|
+
'Calculating SHA-256 hash of changes after tasks because "--fail-on-changes" was used...'
|
|
307
|
+
)
|
|
308
|
+
const diff = await this.execGit(['diff', '--patch', '--unified=0'])
|
|
309
|
+
const diffSha256 = calculateSha256(diff)
|
|
310
|
+
debugLog('SHA-256 hash of changes after tasks is %S', this.diffSha256)
|
|
311
|
+
if (this.unstagedDiffSha256 !== diffSha256) {
|
|
312
|
+
ctx.errors.add(ExitCodeError)
|
|
313
|
+
throw new Error('Tasks modified files and --fail-on-changes was used!')
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
276
317
|
debugLog('Adding task modifications to index...')
|
|
277
318
|
|
|
278
319
|
// `matchedFileChunks` includes staged files that lint-staged originally detected and matched against a task.
|
|
@@ -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
|
@@ -32,6 +32,11 @@ export type Options = {
|
|
|
32
32
|
* Path to single configuration file; disables automatic config file discovery when used
|
|
33
33
|
*/
|
|
34
34
|
configPath?: string
|
|
35
|
+
/**
|
|
36
|
+
* Run all tasks to completion even if one fails
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
continueOnError?: boolean
|
|
35
40
|
/**
|
|
36
41
|
* Working directory to run all tasks in, defaults to current working directory
|
|
37
42
|
*/
|
|
@@ -52,10 +57,25 @@ export type Options = {
|
|
|
52
57
|
* @default "ACMR"
|
|
53
58
|
*/
|
|
54
59
|
diffFilter?: string
|
|
60
|
+
/**
|
|
61
|
+
* Fail with exit code 1 when tasks modify tracked files
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
failOnChanges?: true
|
|
55
65
|
/**
|
|
56
66
|
* Maximum argument string length, by default automatically detected
|
|
57
67
|
*/
|
|
58
68
|
maxArgLength?: number
|
|
69
|
+
/**
|
|
70
|
+
* Whether to hide unstaged changes from partially staged files before running tasks
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
hidePartiallyStaged?: boolean
|
|
74
|
+
/**
|
|
75
|
+
* Whether to hide all unstaged changes before running tasks
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
hideUnstaged?: boolean
|
|
59
79
|
/**
|
|
60
80
|
* Disable lint-staged’s own console output
|
|
61
81
|
* @default false
|
|
@@ -77,11 +97,6 @@ export type Options = {
|
|
|
77
97
|
* @default true
|
|
78
98
|
*/
|
|
79
99
|
stash?: boolean
|
|
80
|
-
/**
|
|
81
|
-
* Whether to hide unstaged changes from partially staged files before running tasks
|
|
82
|
-
* @default true
|
|
83
|
-
*/
|
|
84
|
-
hidePartiallyStaged?: boolean
|
|
85
100
|
/**
|
|
86
101
|
* Show task output even when tasks succeed; by default only failed output is shown
|
|
87
102
|
* @default false
|
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 {Object} [options.color] - Enable ANSI colors 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
|
+
hidePartiallyStaged = true,
|
|
90
|
+
hideUnstaged = false,
|
|
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)
|
package/lib/loadConfig.js
CHANGED
|
@@ -3,20 +3,15 @@
|
|
|
3
3
|
import fs from 'node:fs/promises'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
|
|
6
|
-
import debug from 'debug'
|
|
7
6
|
import YAML from 'yaml'
|
|
8
7
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
CONFIG_NAME,
|
|
12
|
-
PACKAGE_JSON_FILE,
|
|
13
|
-
PACKAGE_YAML_FILES,
|
|
14
|
-
} from './configFiles.js'
|
|
8
|
+
import { CONFIG_NAME, PACKAGE_JSON_FILE, PACKAGE_YAML_FILES } from './configFiles.js'
|
|
9
|
+
import { createDebug } from './debug.js'
|
|
15
10
|
import { dynamicImport } from './dynamicImport.js'
|
|
16
11
|
import { failedToLoadConfig } from './messages.js'
|
|
17
12
|
import { resolveConfig } from './resolveConfig.js'
|
|
18
13
|
|
|
19
|
-
const debugLog =
|
|
14
|
+
const debugLog = createDebug('lint-staged:loadConfig')
|
|
20
15
|
|
|
21
16
|
const jsonParse = (filePath, content) => {
|
|
22
17
|
const isPackageFile = PACKAGE_JSON_FILE.includes(path.basename(filePath))
|
|
@@ -94,22 +89,11 @@ const loadConfigByExt = async (filepath) => {
|
|
|
94
89
|
/**
|
|
95
90
|
* @param {object} options
|
|
96
91
|
* @param {string} [options.configPath] - Explicit path to a config file
|
|
97
|
-
* @param {string} [options.cwd] - Current working directory
|
|
98
92
|
*/
|
|
99
|
-
export const loadConfig = async ({ configPath
|
|
93
|
+
export const loadConfig = async ({ configPath }, logger) => {
|
|
100
94
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (configPath) {
|
|
104
|
-
debugLog('Loading configuration from `%s`...', configPath)
|
|
105
|
-
result = await loadConfigByExt(resolveConfig(configPath))
|
|
106
|
-
} else {
|
|
107
|
-
debugLog('Searching for configuration from `%s`...', cwd)
|
|
108
|
-
const { lilconfig } = await import('lilconfig')
|
|
109
|
-
const explorer = lilconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILE_NAMES, loaders })
|
|
110
|
-
result = await explorer.search(cwd)
|
|
111
|
-
}
|
|
112
|
-
|
|
95
|
+
debugLog('Loading configuration from `%s`...', configPath)
|
|
96
|
+
const result = configPath ? await loadConfigByExt(resolveConfig(configPath)) : undefined
|
|
113
97
|
if (!result) return {}
|
|
114
98
|
|
|
115
99
|
// config is a promise when using the `dynamicImport` loader
|