lint-staged 17.0.7 → 17.1.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 +14 -24
- package/bin/lint-staged.js +5 -5
- package/lib/colors.js +26 -8
- package/lib/debug.js +2 -2
- package/lib/execGit.js +3 -17
- package/lib/figures.js +9 -5
- package/lib/getFunctionTask.js +13 -4
- package/lib/getSpawnedTask.js +9 -9
- package/lib/getSpawnedTasks.js +3 -6
- package/lib/gitWorkflow.js +190 -113
- package/lib/index.js +14 -12
- package/lib/messages.js +36 -29
- package/lib/parseGitZOutput.js +1 -1
- package/lib/printTaskOutput.js +2 -2
- package/lib/runAll.js +103 -103
- package/lib/runParallelTasks.js +112 -0
- package/lib/state.js +14 -9
- package/lib/validateOptions.js +9 -0
- package/package.json +20 -25
- package/lib/getRenderer.js +0 -63
package/lib/gitWorkflow.js
CHANGED
|
@@ -2,12 +2,15 @@ import crypto from 'node:crypto'
|
|
|
2
2
|
import fs from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
|
|
5
|
+
import { dim } from './colors.js'
|
|
5
6
|
import { createDebug } from './debug.js'
|
|
6
7
|
import { execGit } from './execGit.js'
|
|
8
|
+
import * as figures from './figures.js'
|
|
7
9
|
import { readFile, unlink, writeFile } from './file.js'
|
|
8
10
|
import { getDiffCommand } from './getDiffCommand.js'
|
|
9
11
|
import { normalizePath } from './normalizePath.js'
|
|
10
12
|
import { parseGitZOutput } from './parseGitZOutput.js'
|
|
13
|
+
import { runParallelTasks } from './runParallelTasks.js'
|
|
11
14
|
import {
|
|
12
15
|
ApplyEmptyCommitError,
|
|
13
16
|
FailOnChangesError,
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
RestoreMergeStatusError,
|
|
18
21
|
RestoreOriginalStateError,
|
|
19
22
|
RestoreUnstagedChangesError,
|
|
23
|
+
TaskError,
|
|
20
24
|
} from './symbols.js'
|
|
21
25
|
|
|
22
26
|
const debugLog = createDebug('lint-staged:GitWorkflow')
|
|
@@ -27,7 +31,7 @@ const MERGE_MSG = 'MERGE_MSG'
|
|
|
27
31
|
|
|
28
32
|
// In git status machine output, renames are presented as `to`NUL`from`
|
|
29
33
|
// When diffing, both need to be taken into account, but in some cases on the `to`.
|
|
30
|
-
//
|
|
34
|
+
// oxlint-disable-next-line no-control-regex
|
|
31
35
|
const RENAME = /\x00/
|
|
32
36
|
|
|
33
37
|
/**
|
|
@@ -63,12 +67,6 @@ const GIT_DIFF_ARGS = [
|
|
|
63
67
|
]
|
|
64
68
|
const GIT_APPLY_ARGS = ['-v', '--whitespace=nowarn', '--recount', '--unidiff-zero']
|
|
65
69
|
|
|
66
|
-
const handleError = (error, ctx, symbol) => {
|
|
67
|
-
ctx.errors.add(GitError)
|
|
68
|
-
if (symbol) ctx.errors.add(symbol)
|
|
69
|
-
throw error
|
|
70
|
-
}
|
|
71
|
-
|
|
72
70
|
const calculateSha256 = (input) => crypto.createHash('sha256').update(input, 'utf-8').digest('hex')
|
|
73
71
|
|
|
74
72
|
/**
|
|
@@ -87,6 +85,7 @@ export class GitWorkflow {
|
|
|
87
85
|
diffFilter,
|
|
88
86
|
failOnChanges,
|
|
89
87
|
gitConfigDir,
|
|
88
|
+
logger,
|
|
90
89
|
matchedFileChunks,
|
|
91
90
|
topLevelDir,
|
|
92
91
|
}) {
|
|
@@ -96,6 +95,7 @@ export class GitWorkflow {
|
|
|
96
95
|
this.diffFilter = diffFilter
|
|
97
96
|
this.gitConfigDir = gitConfigDir
|
|
98
97
|
this.failOnChanges = !!failOnChanges
|
|
98
|
+
this.logger = logger
|
|
99
99
|
/** @type {import('./getStagedFiles.js').StagedFile[][]} */
|
|
100
100
|
this.matchedFileChunks = matchedFileChunks
|
|
101
101
|
this.topLevelDir = topLevelDir
|
|
@@ -130,6 +130,7 @@ export class GitWorkflow {
|
|
|
130
130
|
|
|
131
131
|
if (index === -1) {
|
|
132
132
|
ctx.errors.add(GetBackupStashError)
|
|
133
|
+
this.logger.error(`${figures.error()} lint-staged automatic backup is missing!`)
|
|
133
134
|
throw new Error('lint-staged automatic backup is missing!')
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -162,13 +163,13 @@ export class GitWorkflow {
|
|
|
162
163
|
])
|
|
163
164
|
debugLog('Done restoring merge state!')
|
|
164
165
|
} catch (error) {
|
|
166
|
+
ctx.errors.add(GitError)
|
|
167
|
+
ctx.errors.add(RestoreMergeStatusError)
|
|
168
|
+
|
|
165
169
|
debugLog('Failed restoring merge state with error:')
|
|
166
170
|
debugLog(error)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
ctx,
|
|
170
|
-
RestoreMergeStatusError
|
|
171
|
-
)
|
|
171
|
+
|
|
172
|
+
throw error
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
|
|
@@ -190,7 +191,7 @@ export class GitWorkflow {
|
|
|
190
191
|
* (e.g. `to`\0`from`)
|
|
191
192
|
*/
|
|
192
193
|
const unstagedFiles = status
|
|
193
|
-
//
|
|
194
|
+
// oxlint-disable-next-line no-control-regex
|
|
194
195
|
.split(/\x00(?=[ AMDRCU?!]{2} |$)/)
|
|
195
196
|
.filter((line) => {
|
|
196
197
|
const [index, workingTree] = line
|
|
@@ -212,10 +213,14 @@ export class GitWorkflow {
|
|
|
212
213
|
/**
|
|
213
214
|
* Create a diff of unstaged or partially staged files and backup stash if enabled.
|
|
214
215
|
*/
|
|
215
|
-
async prepare(ctx
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
async prepare(ctx) {
|
|
217
|
+
this.logger.log(
|
|
218
|
+
dim(
|
|
219
|
+
`${figures.wip()} ${ctx.shouldBackup ? 'Backing up original state…' : 'Preparing lint-staged…'}`
|
|
220
|
+
)
|
|
221
|
+
)
|
|
218
222
|
|
|
223
|
+
try {
|
|
219
224
|
if (ctx.shouldBackup) {
|
|
220
225
|
// When backup is enabled, the revert will clear ongoing merge status.
|
|
221
226
|
await this.backupMergeStatus()
|
|
@@ -249,120 +254,183 @@ export class GitWorkflow {
|
|
|
249
254
|
|
|
250
255
|
/** The stash line starts with the short hash, so we split from space and choose the first part */
|
|
251
256
|
ctx.backupHash = stashes.find((line) => line.includes(STASH))?.split(' ')[0]
|
|
257
|
+
|
|
258
|
+
await this.restoreMergeStatus(ctx)
|
|
252
259
|
} else {
|
|
253
260
|
/** Save stash of all changes, keeping all files as-is */
|
|
254
261
|
const stashHash = await this.execGit(['stash', 'create'])
|
|
255
262
|
ctx.backupHash = await this.execGit(['rev-parse', '--short', stashHash])
|
|
256
263
|
await this.execGit(['stash', 'store', '--quiet', '--message', STASH, ctx.backupHash])
|
|
257
264
|
}
|
|
258
|
-
|
|
259
|
-
task.title = `Backed up original state in git stash (${ctx.backupHash})`
|
|
260
|
-
debugLog(task.title)
|
|
261
265
|
}
|
|
266
|
+
|
|
267
|
+
this.logger.log(
|
|
268
|
+
`${figures.done()} ${
|
|
269
|
+
ctx.shouldBackup
|
|
270
|
+
? `Done backing up original state (${ctx.backupHash})!`
|
|
271
|
+
: 'Done preparing lint-staged!'
|
|
272
|
+
}`
|
|
273
|
+
)
|
|
262
274
|
} catch (error) {
|
|
263
|
-
|
|
275
|
+
ctx.errors.add(GitError)
|
|
276
|
+
|
|
277
|
+
const errorMessage = ctx.shouldBackup
|
|
278
|
+
? 'Failed to back up original state!'
|
|
279
|
+
: 'Failed to prepare lint-staged!'
|
|
280
|
+
|
|
281
|
+
this.logger.error(`${figures.error()} ${errorMessage}`)
|
|
282
|
+
debugLog(error)
|
|
264
283
|
}
|
|
265
284
|
}
|
|
266
285
|
|
|
267
286
|
async hidePartiallyStagedChanges(ctx) {
|
|
287
|
+
this.logger.log(dim(`${figures.wip()} Hiding unstaged changes to partially staged files…`))
|
|
288
|
+
|
|
268
289
|
try {
|
|
269
290
|
const files = processRenames(this.unstagedFiles, false)
|
|
270
291
|
await this.execGit(['restore', '--worktree', '--', ...files])
|
|
292
|
+
this.logger.log(`${figures.done()} Done hiding unstaged changes to partially staged files!`)
|
|
271
293
|
} catch (error) {
|
|
272
294
|
/**
|
|
273
295
|
* `git checkout --force` doesn't throw errors, so it shouldn't be possible to get here.
|
|
274
|
-
* If this does fail, the handleError method will set ctx.gitError and lint-staged will fail.
|
|
275
296
|
*/
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
297
|
+
ctx.errors.add(GitError)
|
|
298
|
+
ctx.errors.add(HideUnstagedChangesError)
|
|
279
299
|
|
|
280
|
-
|
|
281
|
-
if (ctx.shouldFailOnChanges) {
|
|
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
|
-
ctx.unstagedDiffSha256 = calculateSha256(diff)
|
|
287
|
-
debugLog('SHA-256 hash of unstaged changes is %s', ctx.unstagedDiffSha256)
|
|
288
|
-
}
|
|
300
|
+
const errorMessage = `${figures.error()} Failed to hude unstaged changes to partially staged files!`
|
|
289
301
|
|
|
290
|
-
|
|
302
|
+
this.logger.error(errorMessage)
|
|
303
|
+
debugLog(error)
|
|
304
|
+
}
|
|
291
305
|
}
|
|
292
306
|
|
|
293
|
-
|
|
294
|
-
|
|
307
|
+
async runTasks(ctx, tasks, { abortController, concurrent }) {
|
|
308
|
+
this.logger.log(
|
|
309
|
+
dim(`${figures.wip()} Running tasks for ${this.diff ? 'changed' : 'staged'} files…`)
|
|
310
|
+
)
|
|
311
|
+
|
|
295
312
|
if (ctx.shouldFailOnChanges) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
313
|
+
try {
|
|
314
|
+
debugLog(
|
|
315
|
+
'Calculating SHA-256 hash of unstaged changes because "--fail-on-changes" was used...'
|
|
316
|
+
)
|
|
317
|
+
const diff = await this.execGit(['diff', '--patch', '--unified=0'])
|
|
318
|
+
ctx.unstagedDiffSha256 = calculateSha256(diff)
|
|
319
|
+
debugLog('SHA-256 hash of unstaged changes is %s', ctx.unstagedDiffSha256)
|
|
320
|
+
} catch (error) {
|
|
321
|
+
ctx.errors.add(GitError)
|
|
322
|
+
this.logger.error('Failed to calculate SHA-256 hash of unstaged changes!')
|
|
323
|
+
debugLog(error)
|
|
324
|
+
return
|
|
305
325
|
}
|
|
306
326
|
}
|
|
307
327
|
|
|
308
|
-
|
|
309
|
-
const activeIndexFile = process.env.GIT_INDEX_FILE
|
|
310
|
-
? normalizePath(process.env.GIT_INDEX_FILE)
|
|
311
|
-
: process.env.GIT_INDEX_FILE
|
|
312
|
-
|
|
313
|
-
/** Needs to be run serially because of locking Git operation */
|
|
314
|
-
for (const files of this.matchedFileChunks) {
|
|
315
|
-
const accessCheckedFiles = await Promise.allSettled(
|
|
316
|
-
files.map(async (f) => {
|
|
317
|
-
if (f.status === 'D') {
|
|
318
|
-
await fs.access(f.filepath)
|
|
319
|
-
return f.filepath // File is no longer deleted and can be added
|
|
320
|
-
} else {
|
|
321
|
-
return f.filepath
|
|
322
|
-
}
|
|
323
|
-
})
|
|
324
|
-
)
|
|
328
|
+
const failureMessage = `${figures.error()} Failed to run tasks for ${this.diff ? 'changed' : 'staged'} files!`
|
|
325
329
|
|
|
326
|
-
|
|
327
|
-
|
|
330
|
+
try {
|
|
331
|
+
await runParallelTasks(ctx, tasks, { abortController, concurrent, logger: this.logger })
|
|
332
|
+
|
|
333
|
+
this.logger.log(
|
|
334
|
+
ctx.errors.has(TaskError)
|
|
335
|
+
? failureMessage
|
|
336
|
+
: `${figures.done()} Done running tasks for ${this.diff ? 'changed' : 'staged'} files!`
|
|
328
337
|
)
|
|
338
|
+
} catch (error) {
|
|
339
|
+
ctx.errors.add(TaskError)
|
|
340
|
+
this.logger.error(failureMessage)
|
|
341
|
+
debugLog(error)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
329
344
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
345
|
+
/** Update Git index again for the originally staged files to stage task modifications. */
|
|
346
|
+
async updateIndex(ctx) {
|
|
347
|
+
this.logger.log(dim(`${figures.wip()} Staging changes from tasks…`))
|
|
333
348
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
349
|
+
try {
|
|
350
|
+
if (ctx.shouldFailOnChanges) {
|
|
351
|
+
debugLog(
|
|
352
|
+
'Calculating SHA-256 hash of changes after tasks because "--fail-on-changes" was used...'
|
|
337
353
|
)
|
|
354
|
+
const diff = await this.execGit(['diff', '--patch', '--unified=0'])
|
|
355
|
+
const diffSha256 = calculateSha256(diff)
|
|
356
|
+
debugLog('SHA-256 hash of changes after tasks is %s', diffSha256)
|
|
357
|
+
|
|
358
|
+
if (ctx.unstagedDiffSha256 !== diffSha256) {
|
|
359
|
+
ctx.errors.add(FailOnChangesError)
|
|
360
|
+
this.logger.error(
|
|
361
|
+
`${figures.error()} Tasks modified files and --fail-on-changes was used!`
|
|
362
|
+
)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
}
|
|
338
366
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
367
|
+
// This looks confusing but the intent is to only normalize truthy values
|
|
368
|
+
const activeIndexFile = process.env.GIT_INDEX_FILE
|
|
369
|
+
? normalizePath(process.env.GIT_INDEX_FILE)
|
|
370
|
+
: process.env.GIT_INDEX_FILE
|
|
371
|
+
|
|
372
|
+
/** Needs to be run serially because of locking Git operation */
|
|
373
|
+
for (const files of this.matchedFileChunks) {
|
|
374
|
+
const accessCheckedFiles = await Promise.allSettled(
|
|
375
|
+
files.map(async (f) => {
|
|
376
|
+
if (f.status === 'D') {
|
|
377
|
+
await fs.access(f.filepath)
|
|
378
|
+
return f.filepath // File is no longer deleted and can be added
|
|
379
|
+
} else {
|
|
380
|
+
return f.filepath
|
|
381
|
+
}
|
|
350
382
|
})
|
|
351
|
-
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
const addableFiles = accessCheckedFiles.flatMap((r) =>
|
|
386
|
+
r.status === 'fulfilled' ? [r.value] : []
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
debugLog('Updating active Git index: %s', activeIndexFile)
|
|
390
|
+
await this.execGit(['add', '--', ...addableFiles])
|
|
391
|
+
debugLog('Done updating Git index: %s', activeIndexFile)
|
|
392
|
+
|
|
393
|
+
if (activeIndexFile?.endsWith('.lock')) {
|
|
394
|
+
const defaultIndexLock = normalizePath(
|
|
395
|
+
await this.execGit(['rev-parse', '--path-format=absolute', '--git-path', 'index.lock'])
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* If the active index file is a non-default lockfile, we are committing with a pathspec
|
|
400
|
+
* without having explicitly run `git add`. In this case we need to also update the
|
|
401
|
+
* default index, otherwise there will be leftover diff after committing
|
|
402
|
+
*/
|
|
403
|
+
if (activeIndexFile !== defaultIndexLock) {
|
|
404
|
+
debugLog('Updating default Git index again: %s', defaultIndexLock)
|
|
405
|
+
await this.execGit(['add', '--', ...addableFiles], {
|
|
406
|
+
env: {
|
|
407
|
+
GIT_INDEX_FILE: defaultIndexLock,
|
|
408
|
+
},
|
|
409
|
+
})
|
|
410
|
+
debugLog('Done updating default Git index lock: %s', defaultIndexLock)
|
|
411
|
+
}
|
|
352
412
|
}
|
|
353
413
|
}
|
|
354
|
-
}
|
|
355
414
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
415
|
+
const stagedFilesAfterAdd = await this.execGit([
|
|
416
|
+
...getDiffCommand(this.diff, this.diffFilter),
|
|
417
|
+
'--name-only',
|
|
418
|
+
'-z',
|
|
419
|
+
])
|
|
361
420
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
421
|
+
if (!stagedFilesAfterAdd && !this.allowEmpty) {
|
|
422
|
+
// Tasks reverted all staged changes and the commit would be empty
|
|
423
|
+
// Stop commit unless `--allow-empty` was used
|
|
424
|
+
ctx.errors.add(ApplyEmptyCommitError)
|
|
425
|
+
this.logger.error(`${figures.error()} Prevented an empty git commit!`)
|
|
426
|
+
} else {
|
|
427
|
+
this.logger.log(`${figures.done()} Done staging changes from tasks!`)
|
|
428
|
+
}
|
|
429
|
+
} catch (error) {
|
|
430
|
+
ctx.errors.add(GitError)
|
|
431
|
+
|
|
432
|
+
this.logger.log(`${figures.error()} Failed to stage changes from tasks!`)
|
|
433
|
+
debugLog(error)
|
|
366
434
|
}
|
|
367
435
|
}
|
|
368
436
|
|
|
@@ -372,8 +440,10 @@ export class GitWorkflow {
|
|
|
372
440
|
* 3-way merge usually fixes this, and in case it doesn't we should just give up and throw.
|
|
373
441
|
*/
|
|
374
442
|
async restoreUnstagedChanges(ctx) {
|
|
375
|
-
|
|
443
|
+
this.logger.log(dim(`${figures.wip()} Restoring unstaged changes…`))
|
|
444
|
+
|
|
376
445
|
const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
|
|
446
|
+
|
|
377
447
|
try {
|
|
378
448
|
await this.execGit(['apply', ...GIT_APPLY_ARGS, unstagedPatch])
|
|
379
449
|
} catch (applyError) {
|
|
@@ -384,20 +454,19 @@ export class GitWorkflow {
|
|
|
384
454
|
try {
|
|
385
455
|
await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', unstagedPatch])
|
|
386
456
|
} catch (threeWayApplyError) {
|
|
387
|
-
|
|
457
|
+
ctx.errors.add(GitError)
|
|
458
|
+
ctx.errors.add(RestoreUnstagedChangesError)
|
|
459
|
+
|
|
460
|
+
this.logger.error(`${figures.error()} Failed to restore unstaged changes!`)
|
|
388
461
|
debugLog(threeWayApplyError)
|
|
389
|
-
handleError(
|
|
390
|
-
new Error('Unstaged changes could not be restored due to a merge conflict!'),
|
|
391
|
-
ctx,
|
|
392
|
-
RestoreUnstagedChangesError
|
|
393
|
-
)
|
|
394
462
|
}
|
|
395
463
|
}
|
|
396
464
|
}
|
|
397
465
|
|
|
398
466
|
async restoreUntrackedFiles(ctx) {
|
|
467
|
+
this.logger.log(dim(`${figures.wip()} Restoring untracked files…`))
|
|
468
|
+
|
|
399
469
|
try {
|
|
400
|
-
debugLog('Restoring untracked files...')
|
|
401
470
|
const backupStash = await this.getBackupStash(ctx)
|
|
402
471
|
const untrackedFiles = await this.execGit([
|
|
403
472
|
'stash',
|
|
@@ -417,17 +486,17 @@ export class GitWorkflow {
|
|
|
417
486
|
'--',
|
|
418
487
|
...untrackedFiles.map(normalizePath),
|
|
419
488
|
])
|
|
489
|
+
|
|
490
|
+
this.logger.log(`${figures.done()} ${'Done restoring untracked files!'}`)
|
|
420
491
|
} else {
|
|
421
|
-
|
|
492
|
+
this.logger.log(`${figures.cancelled()} ${dim('No untracked files to restore!')}`)
|
|
422
493
|
}
|
|
423
494
|
} catch (restoreUntrackedFilesError) {
|
|
424
|
-
|
|
495
|
+
ctx.errors.add(GitError)
|
|
496
|
+
ctx.errors.add(RestoreUnstagedChangesError)
|
|
497
|
+
|
|
498
|
+
this.logger.error(`${figures.error()} ${'Failed to restore untracked files!'}`)
|
|
425
499
|
debugLog(restoreUntrackedFilesError)
|
|
426
|
-
handleError(
|
|
427
|
-
new Error('Untracked files could not be restored!'),
|
|
428
|
-
ctx,
|
|
429
|
-
RestoreUnstagedChangesError
|
|
430
|
-
)
|
|
431
500
|
}
|
|
432
501
|
}
|
|
433
502
|
|
|
@@ -435,20 +504,25 @@ export class GitWorkflow {
|
|
|
435
504
|
* Restore original HEAD state in case of errors
|
|
436
505
|
*/
|
|
437
506
|
async restoreOriginalState(ctx) {
|
|
507
|
+
this.logger.log(dim(`${figures.wip()} Reverting to original state because of errors…`))
|
|
508
|
+
|
|
438
509
|
try {
|
|
439
510
|
debugLog('Restoring original state...')
|
|
440
511
|
await this.execGit(['reset', '--hard', 'HEAD'])
|
|
441
512
|
await this.execGit(['stash', 'apply', '--quiet', '--index', await this.getBackupStash(ctx)])
|
|
442
513
|
|
|
443
|
-
// Restore meta information about ongoing git merge
|
|
444
514
|
await this.restoreMergeStatus(ctx)
|
|
445
515
|
|
|
446
516
|
// Clean out patch
|
|
447
517
|
await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
|
|
448
518
|
|
|
449
|
-
|
|
519
|
+
this.logger.log(`${figures.done()} Done reverting to original state!`)
|
|
450
520
|
} catch (error) {
|
|
451
|
-
|
|
521
|
+
ctx.errors.add(GitError)
|
|
522
|
+
ctx.errors.add(RestoreOriginalStateError)
|
|
523
|
+
|
|
524
|
+
this.logger.error(`${figures.error()} Failed to revert to original state!`)
|
|
525
|
+
debugLog(error)
|
|
452
526
|
}
|
|
453
527
|
}
|
|
454
528
|
|
|
@@ -456,12 +530,15 @@ export class GitWorkflow {
|
|
|
456
530
|
* Drop the created stashes after everything has run
|
|
457
531
|
*/
|
|
458
532
|
async cleanup(ctx) {
|
|
533
|
+
this.logger.log(dim(`${figures.wip()} Cleaning up temporary files…`))
|
|
534
|
+
|
|
459
535
|
try {
|
|
460
|
-
debugLog('Dropping backup stash...')
|
|
461
536
|
await this.execGit(['stash', 'drop', '--quiet', await this.getBackupStash(ctx)])
|
|
462
|
-
|
|
537
|
+
this.logger.log(`${figures.done()} Done cleaning up temporary files!`)
|
|
463
538
|
} catch (error) {
|
|
464
|
-
|
|
539
|
+
ctx.errors.add(GitError)
|
|
540
|
+
this.logger.error(`${figures.error()} Failed to clean up temporary files!`)
|
|
541
|
+
debugLog(error)
|
|
465
542
|
}
|
|
466
543
|
}
|
|
467
544
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { assertGitVersion, MIN_GIT_VERSION } from './assertGitVersion.js'
|
|
2
|
-
import {
|
|
2
|
+
import { enableColors } from './colors.js'
|
|
3
3
|
import { createDebug, enableDebug } from './debug.js'
|
|
4
4
|
import { execGit } from './execGit.js'
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
gitError,
|
|
7
7
|
minGitVersionRequired,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
noConfiguration,
|
|
9
|
+
preventedEmptyCommit,
|
|
10
|
+
preventedTaskModifications,
|
|
11
11
|
restoreStashExample,
|
|
12
|
-
|
|
12
|
+
unstagedChangesBackupStashLocation,
|
|
13
13
|
} from './messages.js'
|
|
14
14
|
import { printTaskOutput } from './printTaskOutput.js'
|
|
15
15
|
import { runAll } from './runAll.js'
|
|
@@ -78,7 +78,7 @@ const getMaxArgLength = () => {
|
|
|
78
78
|
const lintStaged = async (
|
|
79
79
|
{
|
|
80
80
|
allowEmpty = false,
|
|
81
|
-
color =
|
|
81
|
+
color = !!process.stdout.hasColors?.(),
|
|
82
82
|
concurrent = true,
|
|
83
83
|
config: configObject,
|
|
84
84
|
configPath,
|
|
@@ -102,6 +102,8 @@ const lintStaged = async (
|
|
|
102
102
|
} = {},
|
|
103
103
|
logger = console
|
|
104
104
|
) => {
|
|
105
|
+
enableColors(color)
|
|
106
|
+
|
|
105
107
|
// Seemingly enable debug twice (also done in bin), so that it also works when using the Node.js API
|
|
106
108
|
if (debug) {
|
|
107
109
|
enableDebug(logger)
|
|
@@ -160,20 +162,20 @@ const lintStaged = async (
|
|
|
160
162
|
const { ctx } = runAllError
|
|
161
163
|
|
|
162
164
|
if (ctx.errors.has(ConfigNotFoundError)) {
|
|
163
|
-
logger.error(
|
|
165
|
+
logger.error(noConfiguration())
|
|
164
166
|
} else if (ctx.errors.has(ApplyEmptyCommitError)) {
|
|
165
|
-
logger.warn(
|
|
167
|
+
logger.warn(preventedEmptyCommit())
|
|
166
168
|
} else if (ctx.errors.has(FailOnChangesError)) {
|
|
167
|
-
logger.warn(
|
|
169
|
+
logger.warn(preventedTaskModifications() + '\n')
|
|
168
170
|
logger.warn(restoreStashExample(ctx.backupHash))
|
|
169
171
|
} else if (ctx.errors.has(RestoreUnstagedChangesError)) {
|
|
170
|
-
logger.warn(
|
|
172
|
+
logger.warn(unstagedChangesBackupStashLocation())
|
|
171
173
|
logger.warn(ctx.unstagedPatch)
|
|
172
174
|
} else if (
|
|
173
175
|
(ctx.errors.has(GitError) || cleanupSkipped(ctx)) &&
|
|
174
176
|
!ctx.errors.has(GetBackupStashError)
|
|
175
177
|
) {
|
|
176
|
-
logger.error(
|
|
178
|
+
logger.error(gitError())
|
|
177
179
|
if (ctx.shouldBackup) {
|
|
178
180
|
// No sense to show this if the backup stash itself is missing.
|
|
179
181
|
logger.error(restoreStashExample(ctx.backupHash) + '\n')
|
package/lib/messages.js
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { inspect } from 'node:util'
|
|
2
2
|
|
|
3
3
|
import { bold, red, yellow } from './colors.js'
|
|
4
|
-
import
|
|
4
|
+
import * as figures from './figures.js'
|
|
5
5
|
|
|
6
6
|
export const configurationError = (opt, helpMsg, value) =>
|
|
7
|
-
`${red(`${error} Validation Error:`)}
|
|
7
|
+
`${red(`${figures.error()} Validation Error:`)}
|
|
8
8
|
|
|
9
9
|
Invalid value for '${bold(opt)}': ${bold(inspect(value))}
|
|
10
10
|
|
|
11
11
|
${helpMsg}`
|
|
12
12
|
|
|
13
|
-
export const
|
|
13
|
+
export const notGitRepo = () => red(`${figures.error()} Current directory is not a git directory!`)
|
|
14
14
|
|
|
15
|
-
export const
|
|
15
|
+
export const failedGetStagedFiles = () => red(`${figures.error()} Failed to get staged files!`)
|
|
16
16
|
|
|
17
17
|
export const incorrectBraces = (before, after) =>
|
|
18
18
|
yellow(
|
|
19
|
-
`${warning} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\`
|
|
19
|
+
`${figures.warning()} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\`
|
|
20
20
|
`
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
-
export const
|
|
23
|
+
export const noConfiguration = () =>
|
|
24
|
+
`${figures.error()} lint-staged could not find any valid configuration.`
|
|
24
25
|
|
|
25
|
-
export const
|
|
26
|
+
export const noStagedFiles = () => `${figures.info()} lint-staged could not find any staged files.`
|
|
26
27
|
|
|
27
|
-
export const
|
|
28
|
+
export const noTasks = () =>
|
|
29
|
+
`${figures.info()} lint-staged could not find any staged files matching configured tasks.`
|
|
28
30
|
|
|
29
31
|
export const skippingBackup = (hasInitialCommit, diff) => {
|
|
30
32
|
const reason =
|
|
@@ -33,27 +35,33 @@ export const skippingBackup = (hasInitialCommit, diff) => {
|
|
|
33
35
|
: (hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet') +
|
|
34
36
|
'. This might result in data loss'
|
|
35
37
|
|
|
36
|
-
return yellow(`${warning} Skipping backup because ${reason}.\n`)
|
|
38
|
+
return yellow(`${figures.warning()} Skipping backup because ${reason}.\n`)
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
-
)
|
|
41
|
+
export const skippingHidePartiallyChanged = () =>
|
|
42
|
+
yellow(
|
|
43
|
+
`${figures.warning()} Skipping hiding unstaged changes from partially staged files because \`--no-hide-partially-staged\` was used.\n`
|
|
44
|
+
)
|
|
42
45
|
|
|
43
|
-
export const
|
|
44
|
-
|
|
46
|
+
export const deprecatedGitAdd = () =>
|
|
47
|
+
yellow(
|
|
48
|
+
`${figures.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.
|
|
45
49
|
`
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
export const TASK_ERROR = 'Skipped because of errors from tasks.'
|
|
50
|
+
)
|
|
49
51
|
|
|
50
|
-
export const
|
|
52
|
+
export const taskError = () => 'Skipped because of errors from tasks.'
|
|
51
53
|
|
|
52
|
-
export const
|
|
54
|
+
export const preventedTaskModifications = () =>
|
|
55
|
+
`\n${figures.error()} lint-staged failed because \`--fail-on-changes\` was used.`
|
|
53
56
|
|
|
54
|
-
export const
|
|
57
|
+
export const gitError = () =>
|
|
58
|
+
`\n ${red(`${figures.error()} lint-staged failed due to a git error.`)}`
|
|
55
59
|
|
|
56
|
-
export const invalidOption = (
|
|
60
|
+
export const invalidOption = (
|
|
61
|
+
name,
|
|
62
|
+
value,
|
|
63
|
+
message
|
|
64
|
+
) => `${red(`${figures.error()} Validation Error:`)}
|
|
57
65
|
|
|
58
66
|
Invalid value for option '${bold(name)}': ${bold(value)}
|
|
59
67
|
|
|
@@ -61,8 +69,8 @@ export const invalidOption = (name, value, message) => `${red(`${error} Validati
|
|
|
61
69
|
|
|
62
70
|
See https://github.com/lint-staged/lint-staged#command-line-flags`
|
|
63
71
|
|
|
64
|
-
export const
|
|
65
|
-
${yellow(`${warning} lint-staged prevented an empty git commit.
|
|
72
|
+
export const preventedEmptyCommit = () => `
|
|
73
|
+
${yellow(`${figures.warning()} lint-staged prevented an empty git commit.
|
|
66
74
|
Use the --allow-empty option to continue, or check your task configuration`)}
|
|
67
75
|
`
|
|
68
76
|
|
|
@@ -75,23 +83,22 @@ export const restoreStashExample = (
|
|
|
75
83
|
> git apply --index ${hash}
|
|
76
84
|
`
|
|
77
85
|
|
|
78
|
-
export const CONFIG_STDIN_ERROR = red(`${error} Failed to read config from stdin.`)
|
|
79
|
-
|
|
80
86
|
export const failedToLoadConfig = (filepath) =>
|
|
81
|
-
red(`${error} Failed to read config from file "${filepath}".`)
|
|
87
|
+
red(`${figures.error()} Failed to read config from file "${filepath}".`)
|
|
82
88
|
|
|
83
89
|
export const failedToParseConfig = (
|
|
84
90
|
filepath,
|
|
85
91
|
error
|
|
86
|
-
) => `${red(`${error} Failed to parse config from file "${filepath}".`)}
|
|
92
|
+
) => `${red(`${figures.error()} Failed to parse config from file "${filepath}".`)}
|
|
87
93
|
|
|
88
94
|
${error}
|
|
89
95
|
|
|
90
96
|
See https://github.com/lint-staged/lint-staged#configuration.`
|
|
91
97
|
|
|
92
|
-
export const
|
|
98
|
+
export const unstagedChangesBackupStashLocation = () =>
|
|
99
|
+
'Unstaged changes have been kept back in a patch file:'
|
|
93
100
|
|
|
94
101
|
export const minGitVersionRequired = (expected) =>
|
|
95
|
-
red(`${error} lint-staged requires at least Git version ${bold(expected)}.
|
|
102
|
+
red(`${figures.error()} lint-staged requires at least Git version ${bold(expected)}.
|
|
96
103
|
|
|
97
104
|
Please update Git: https://git-scm.com/downloads`)
|