lint-staged 16.1.6 → 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 +9 -6
- 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
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,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
|
package/lib/messages.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { inspect } from 'node:util'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import { bold, red, yellow } from './colors.js'
|
|
5
4
|
import { error, info, warning } from './figures.js'
|
|
6
5
|
|
|
7
6
|
export const configurationError = (opt, helpMsg, value) =>
|
|
8
|
-
`${
|
|
7
|
+
`${red(`${error} Validation Error:`)}
|
|
9
8
|
|
|
10
|
-
Invalid value for '${
|
|
9
|
+
Invalid value for '${bold(opt)}': ${bold(inspect(value))}
|
|
11
10
|
|
|
12
11
|
${helpMsg}`
|
|
13
12
|
|
|
14
|
-
export const NOT_GIT_REPO =
|
|
13
|
+
export const NOT_GIT_REPO = red(`${error} Current directory is not a git directory!`)
|
|
15
14
|
|
|
16
|
-
export const FAILED_GET_STAGED_FILES =
|
|
15
|
+
export const FAILED_GET_STAGED_FILES = red(`${error} Failed to get staged files!`)
|
|
17
16
|
|
|
18
17
|
export const incorrectBraces = (before, after) =>
|
|
19
|
-
|
|
18
|
+
yellow(
|
|
20
19
|
`${warning} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\`
|
|
21
20
|
`
|
|
22
21
|
)
|
|
@@ -34,36 +33,36 @@ export const skippingBackup = (hasInitialCommit, diff) => {
|
|
|
34
33
|
: (hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet') +
|
|
35
34
|
'. This might result in data loss'
|
|
36
35
|
|
|
37
|
-
return
|
|
36
|
+
return yellow(`${warning} Skipping backup because ${reason}.\n`)
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
export const SKIPPING_HIDE_PARTIALLY_CHANGED =
|
|
39
|
+
export const SKIPPING_HIDE_PARTIALLY_CHANGED = yellow(
|
|
41
40
|
`${warning} Skipping hiding unstaged changes from partially staged files because \`--no-hide-partially-staged\` was used.\n`
|
|
42
41
|
)
|
|
43
42
|
|
|
44
|
-
export const DEPRECATED_GIT_ADD =
|
|
43
|
+
export const DEPRECATED_GIT_ADD = yellow(
|
|
45
44
|
`${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.
|
|
46
45
|
`
|
|
47
46
|
)
|
|
48
47
|
|
|
49
48
|
export const TASK_ERROR = 'Skipped because of errors from tasks.'
|
|
50
49
|
|
|
50
|
+
export const PREVENTED_TASK_MODIFICATIONS = `\n${error} lint-staged failed because \`--fail-on-changes\` was used.`
|
|
51
|
+
|
|
51
52
|
export const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'
|
|
52
53
|
|
|
53
|
-
export const GIT_ERROR = `\n ${
|
|
54
|
+
export const GIT_ERROR = `\n ${red(`${error} lint-staged failed due to a git error.`)}`
|
|
54
55
|
|
|
55
|
-
export const invalidOption = (name, value, message) => `${
|
|
56
|
-
`${error} Validation Error:`
|
|
57
|
-
)}
|
|
56
|
+
export const invalidOption = (name, value, message) => `${red(`${error} Validation Error:`)}
|
|
58
57
|
|
|
59
|
-
Invalid value for option '${
|
|
58
|
+
Invalid value for option '${bold(name)}': ${bold(value)}
|
|
60
59
|
|
|
61
60
|
${message}
|
|
62
61
|
|
|
63
62
|
See https://github.com/okonet/lint-staged#command-line-flags`
|
|
64
63
|
|
|
65
64
|
export const PREVENTED_EMPTY_COMMIT = `
|
|
66
|
-
${
|
|
65
|
+
${yellow(`${warning} lint-staged prevented an empty git commit.
|
|
67
66
|
Use the --allow-empty option to continue, or check your task configuration`)}
|
|
68
67
|
`
|
|
69
68
|
|
|
@@ -73,15 +72,15 @@ export const RESTORE_STASH_EXAMPLE = `Any lost modifications can be restored fro
|
|
|
73
72
|
stash@{0}: automatic lint-staged backup
|
|
74
73
|
> git stash apply --index stash@{0}`
|
|
75
74
|
|
|
76
|
-
export const CONFIG_STDIN_ERROR =
|
|
75
|
+
export const CONFIG_STDIN_ERROR = red(`${error} Failed to read config from stdin.`)
|
|
77
76
|
|
|
78
77
|
export const failedToLoadConfig = (filepath) =>
|
|
79
|
-
|
|
78
|
+
red(`${error} Failed to read config from file "${filepath}".`)
|
|
80
79
|
|
|
81
80
|
export const failedToParseConfig = (
|
|
82
81
|
filepath,
|
|
83
82
|
error
|
|
84
|
-
) => `${
|
|
83
|
+
) => `${red(`${error} Failed to parse config from file "${filepath}".`)}
|
|
85
84
|
|
|
86
85
|
${error}
|
|
87
86
|
|