lint-staged 16.2.0 → 16.2.2
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 +3 -3
- package/bin/lint-staged.js +3 -3
- package/lib/gitWorkflow.js +49 -41
- package/lib/index.d.ts +11 -5
- package/lib/index.js +8 -7
- package/lib/loadConfig.js +26 -33
- package/lib/messages.js +6 -4
- package/lib/runAll.js +9 -8
- package/lib/searchConfigs.js +33 -25
- package/lib/state.js +23 -9
- package/lib/symbols.js +1 -1
- package/package.json +10 -10
- package/lib/dynamicImport.js +0 -3
package/README.md
CHANGED
|
@@ -127,9 +127,9 @@ Options:
|
|
|
127
127
|
|
|
128
128
|
Any lost modifications can be restored from a git stash:
|
|
129
129
|
|
|
130
|
-
> git stash list
|
|
131
|
-
|
|
132
|
-
> git
|
|
130
|
+
> git stash list --format="%h %s"
|
|
131
|
+
h0a0s0h0 On main: lint-staged automatic backup
|
|
132
|
+
> git apply --index h0a0s0h0
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
#### `--allow-empty`
|
package/bin/lint-staged.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Option, program } from 'commander'
|
|
|
6
6
|
|
|
7
7
|
import { createDebug, enableDebug } from '../lib/debug.js'
|
|
8
8
|
import lintStaged from '../lib/index.js'
|
|
9
|
-
import { CONFIG_STDIN_ERROR,
|
|
9
|
+
import { CONFIG_STDIN_ERROR, restoreStashExample } from '../lib/messages.js'
|
|
10
10
|
import { readStdin } from '../lib/readStdin.js'
|
|
11
11
|
import { getVersion } from '../lib/version.js'
|
|
12
12
|
|
|
@@ -108,7 +108,7 @@ program
|
|
|
108
108
|
.addOption(
|
|
109
109
|
new Option('--hide-unstaged', 'hide all unstaged changes, instead of just partially staged')
|
|
110
110
|
.default(false)
|
|
111
|
-
.implies({ hidePartiallyStaged:
|
|
111
|
+
.implies({ hidePartiallyStaged: false })
|
|
112
112
|
)
|
|
113
113
|
|
|
114
114
|
.addOption(new Option('-q, --quiet', 'disable lint-staged’s own console output').default(false))
|
|
@@ -120,7 +120,7 @@ program
|
|
|
120
120
|
).default(false)
|
|
121
121
|
)
|
|
122
122
|
|
|
123
|
-
.addHelpText('afterAll', '\n' +
|
|
123
|
+
.addHelpText('afterAll', '\n' + restoreStashExample())
|
|
124
124
|
|
|
125
125
|
const cliOptions = program.parse(process.argv).opts()
|
|
126
126
|
|
package/lib/gitWorkflow.js
CHANGED
|
@@ -6,9 +6,10 @@ 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,
|
|
11
|
-
|
|
12
|
+
FailOnChangesError,
|
|
12
13
|
GetBackupStashError,
|
|
13
14
|
GitError,
|
|
14
15
|
HideUnstagedChangesError,
|
|
@@ -69,6 +70,12 @@ const handleError = (error, ctx, symbol) => {
|
|
|
69
70
|
|
|
70
71
|
const calculateSha256 = (input) => crypto.createHash('sha256').update(input, 'utf-8').digest('hex')
|
|
71
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
|
+
|
|
72
79
|
export class GitWorkflow {
|
|
73
80
|
/**
|
|
74
81
|
* @param {Object} opts
|
|
@@ -115,11 +122,12 @@ export class GitWorkflow {
|
|
|
115
122
|
* Get name of backup stash
|
|
116
123
|
*/
|
|
117
124
|
async getBackupStash(ctx) {
|
|
118
|
-
|
|
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)
|
|
119
129
|
|
|
120
|
-
const index = stashes
|
|
121
|
-
.split('\n')
|
|
122
|
-
.findIndex((line) => line.includes(STASH) && line.includes(ctx.backupHash))
|
|
130
|
+
const index = stashes.findIndex((line) => line.startsWith(ctx.backupHash))
|
|
123
131
|
|
|
124
132
|
if (index === -1) {
|
|
125
133
|
ctx.errors.add(GetBackupStashError)
|
|
@@ -231,20 +239,40 @@ export class GitWorkflow {
|
|
|
231
239
|
// - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
|
|
232
240
|
// - git stash can't infer RD or MD states correctly, and will lose the deletion
|
|
233
241
|
this.deletedFiles = await this.getDeletedFiles()
|
|
242
|
+
}
|
|
234
243
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
])
|
|
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) {
|
|
253
|
+
const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
|
|
254
|
+
ctx.unstagedPatch = unstagedPatch
|
|
255
|
+
const files = processRenames(this.unstagedFiles)
|
|
256
|
+
await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch, '--', ...files])
|
|
257
|
+
}
|
|
258
|
+
|
|
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
|
+
}
|
|
248
276
|
|
|
249
277
|
task.title = `Backed up original state in git stash (${ctx.backupHash})`
|
|
250
278
|
debugLog(task.title)
|
|
@@ -258,32 +286,12 @@ export class GitWorkflow {
|
|
|
258
286
|
this.unstagedDiffSha256 = calculateSha256(diff)
|
|
259
287
|
debugLog('SHA-256 hash of unstaged changes is %S', this.unstagedDiffSha256)
|
|
260
288
|
}
|
|
261
|
-
|
|
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
|
-
}
|
|
278
289
|
} catch (error) {
|
|
279
290
|
handleError(error, ctx)
|
|
280
291
|
}
|
|
281
292
|
}
|
|
282
293
|
|
|
283
|
-
|
|
284
|
-
* Remove unstaged changes to all partially staged files, to avoid tasks from seeing them
|
|
285
|
-
*/
|
|
286
|
-
async hideUnstagedChanges(ctx) {
|
|
294
|
+
async hidePartiallyStagedChanges(ctx) {
|
|
287
295
|
try {
|
|
288
296
|
const files = processRenames(this.unstagedFiles, false)
|
|
289
297
|
await this.execGit(['checkout', '--force', '--', ...files])
|
|
@@ -309,7 +317,7 @@ export class GitWorkflow {
|
|
|
309
317
|
const diffSha256 = calculateSha256(diff)
|
|
310
318
|
debugLog('SHA-256 hash of changes after tasks is %S', this.diffSha256)
|
|
311
319
|
if (this.unstagedDiffSha256 !== diffSha256) {
|
|
312
|
-
ctx.errors.add(
|
|
320
|
+
ctx.errors.add(FailOnChangesError)
|
|
313
321
|
throw new Error('Tasks modified files and --fail-on-changes was used!')
|
|
314
322
|
}
|
|
315
323
|
}
|
|
@@ -368,8 +376,8 @@ export class GitWorkflow {
|
|
|
368
376
|
debugLog('Error while restoring changes:')
|
|
369
377
|
debugLog(applyError)
|
|
370
378
|
debugLog('Retrying with 3-way merge')
|
|
379
|
+
// Retry with a 3-way merge if normal apply fails
|
|
371
380
|
try {
|
|
372
|
-
// Retry with a 3-way merge if normal apply fails
|
|
373
381
|
await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', unstagedPatch])
|
|
374
382
|
} catch (threeWayApplyError) {
|
|
375
383
|
debugLog('Error while restoring unstaged changes using 3-way merge:')
|
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
|
|
@@ -61,7 +66,7 @@ export type Options = {
|
|
|
61
66
|
* Fail with exit code 1 when tasks modify tracked files
|
|
62
67
|
* @default false
|
|
63
68
|
*/
|
|
64
|
-
failOnChanges?:
|
|
69
|
+
failOnChanges?: boolean
|
|
65
70
|
/**
|
|
66
71
|
* Maximum argument string length, by default automatically detected
|
|
67
72
|
*/
|
|
@@ -104,12 +109,13 @@ export type Options = {
|
|
|
104
109
|
verbose?: boolean
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
type LogFunction =
|
|
112
|
+
type LogFunction = typeof console.log
|
|
108
113
|
|
|
109
114
|
type Logger = {
|
|
110
115
|
log: LogFunction
|
|
111
116
|
warn: LogFunction
|
|
112
117
|
error: LogFunction
|
|
118
|
+
debug: LogFunction
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
/**
|
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
NO_CONFIGURATION,
|
|
7
7
|
PREVENTED_EMPTY_COMMIT,
|
|
8
8
|
PREVENTED_TASK_MODIFICATIONS,
|
|
9
|
-
|
|
9
|
+
restoreStashExample,
|
|
10
10
|
UNSTAGED_CHANGES_BACKUP_STASH_LOCATION,
|
|
11
11
|
} from './messages.js'
|
|
12
12
|
import { printTaskOutput } from './printTaskOutput.js'
|
|
@@ -15,7 +15,7 @@ import { cleanupSkipped } from './state.js'
|
|
|
15
15
|
import {
|
|
16
16
|
ApplyEmptyCommitError,
|
|
17
17
|
ConfigNotFoundError,
|
|
18
|
-
|
|
18
|
+
FailOnChangesError,
|
|
19
19
|
GetBackupStashError,
|
|
20
20
|
GitError,
|
|
21
21
|
RestoreUnstagedChangesError,
|
|
@@ -51,7 +51,7 @@ const getMaxArgLength = () => {
|
|
|
51
51
|
*
|
|
52
52
|
* @param {object} options
|
|
53
53
|
* @param {Object} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
|
|
54
|
-
* @param {
|
|
54
|
+
* @param {boolean} [options.color] - Enable or disable ANSI color codes in output.
|
|
55
55
|
* @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
|
|
56
56
|
* @param {object} [options.config] - Object with configuration for programmatic API
|
|
57
57
|
* @param {string} [options.configPath] - Path to configuration file
|
|
@@ -86,8 +86,8 @@ const lintStaged = async (
|
|
|
86
86
|
diff,
|
|
87
87
|
diffFilter,
|
|
88
88
|
failOnChanges = false,
|
|
89
|
-
hidePartiallyStaged = true,
|
|
90
89
|
hideUnstaged = false,
|
|
90
|
+
hidePartiallyStaged = !hideUnstaged,
|
|
91
91
|
maxArgLength = getMaxArgLength() / 2,
|
|
92
92
|
quiet = false,
|
|
93
93
|
relative = false,
|
|
@@ -155,8 +155,9 @@ const lintStaged = async (
|
|
|
155
155
|
logger.error(NO_CONFIGURATION)
|
|
156
156
|
} else if (ctx.errors.has(ApplyEmptyCommitError)) {
|
|
157
157
|
logger.warn(PREVENTED_EMPTY_COMMIT)
|
|
158
|
-
} else if (ctx.errors.has(
|
|
159
|
-
logger.warn(PREVENTED_TASK_MODIFICATIONS)
|
|
158
|
+
} else if (ctx.errors.has(FailOnChangesError)) {
|
|
159
|
+
logger.warn(PREVENTED_TASK_MODIFICATIONS + '\n')
|
|
160
|
+
logger.warn(restoreStashExample(ctx.backupHash))
|
|
160
161
|
} else if (ctx.errors.has(RestoreUnstagedChangesError)) {
|
|
161
162
|
logger.warn(UNSTAGED_CHANGES_BACKUP_STASH_LOCATION)
|
|
162
163
|
logger.warn(ctx.unstagedPatch)
|
|
@@ -167,7 +168,7 @@ const lintStaged = async (
|
|
|
167
168
|
logger.error(GIT_ERROR)
|
|
168
169
|
if (ctx.shouldBackup) {
|
|
169
170
|
// No sense to show this if the backup stash itself is missing.
|
|
170
|
-
logger.error(
|
|
171
|
+
logger.error(restoreStashExample(ctx.backupHash) + '\n')
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
package/lib/loadConfig.js
CHANGED
|
@@ -2,25 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs/promises'
|
|
4
4
|
import path from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
5
6
|
|
|
6
7
|
import YAML from 'yaml'
|
|
7
8
|
|
|
8
9
|
import { CONFIG_NAME, PACKAGE_JSON_FILE, PACKAGE_YAML_FILES } from './configFiles.js'
|
|
9
10
|
import { createDebug } from './debug.js'
|
|
10
|
-
import { dynamicImport } from './dynamicImport.js'
|
|
11
11
|
import { failedToLoadConfig } from './messages.js'
|
|
12
12
|
import { resolveConfig } from './resolveConfig.js'
|
|
13
13
|
|
|
14
14
|
const debugLog = createDebug('lint-staged:loadConfig')
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
16
|
+
const readFile = async (filename) => fs.readFile(path.resolve(filename), 'utf-8')
|
|
17
|
+
|
|
18
|
+
const jsonParse = async (filename) => {
|
|
19
|
+
const isPackageFile = PACKAGE_JSON_FILE.includes(path.basename(filename))
|
|
18
20
|
try {
|
|
21
|
+
const content = await readFile(filename)
|
|
19
22
|
const json = JSON.parse(content)
|
|
20
23
|
return isPackageFile ? json[CONFIG_NAME] : json
|
|
21
24
|
} catch (error) {
|
|
22
|
-
if (path.basename(
|
|
23
|
-
debugLog('Ignoring invalid
|
|
25
|
+
if (path.basename(filename) === PACKAGE_JSON_FILE) {
|
|
26
|
+
debugLog('Ignoring invalid JSON file %s', filename)
|
|
24
27
|
return undefined
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -28,14 +31,15 @@ const jsonParse = (filePath, content) => {
|
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
const yamlParse = (
|
|
32
|
-
const isPackageFile = PACKAGE_YAML_FILES.includes(path.basename(
|
|
34
|
+
const yamlParse = async (filename) => {
|
|
35
|
+
const isPackageFile = PACKAGE_YAML_FILES.includes(path.basename(filename))
|
|
33
36
|
try {
|
|
37
|
+
const content = await readFile(filename)
|
|
34
38
|
const yaml = YAML.parse(content)
|
|
35
39
|
return isPackageFile ? yaml[CONFIG_NAME] : yaml
|
|
36
40
|
} catch (error) {
|
|
37
41
|
if (isPackageFile) {
|
|
38
|
-
debugLog('Ignoring invalid
|
|
42
|
+
debugLog('Ignoring invalid YAML file %s', filename)
|
|
39
43
|
return undefined
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -43,6 +47,8 @@ const yamlParse = (filePath, content) => {
|
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
export const dynamicImport = (path) => import(pathToFileURL(path)).then((module) => module.default)
|
|
51
|
+
|
|
46
52
|
const NO_EXT = 'noExt'
|
|
47
53
|
|
|
48
54
|
/**
|
|
@@ -64,43 +70,30 @@ const loaders = {
|
|
|
64
70
|
'.yml': yamlParse,
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
return fs.readFile(absolutePath, 'utf-8')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const loadConfigByExt = async (filepath) => {
|
|
73
|
-
filepath = path.resolve(filepath)
|
|
73
|
+
const loadConfigByExt = async (filename) => {
|
|
74
|
+
const filepath = path.resolve(filename)
|
|
74
75
|
const ext = path.extname(filepath) || NO_EXT
|
|
75
76
|
const loader = loaders[ext]
|
|
77
|
+
const config = await loader(filepath)
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
* No need to read file contents when loader only takes in the filepath argument
|
|
79
|
-
* and reads itself; this is for `lilconfig` compatibility
|
|
80
|
-
*/
|
|
81
|
-
const content = loader.length > 1 ? await readFile(filepath) : undefined
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
config: await loader(filepath, content),
|
|
85
|
-
filepath,
|
|
86
|
-
}
|
|
79
|
+
return { config, filepath }
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
* @param {string} [options.configPath] - Explicit path to a config file
|
|
92
|
-
*/
|
|
93
|
-
export const loadConfig = async ({ configPath }, logger) => {
|
|
82
|
+
/** @param {string} configPath */
|
|
83
|
+
export const loadConfig = async (configPath, logger) => {
|
|
94
84
|
try {
|
|
95
85
|
debugLog('Loading configuration from `%s`...', configPath)
|
|
96
|
-
const result =
|
|
97
|
-
if (!result) return {}
|
|
86
|
+
const result = await loadConfigByExt(resolveConfig(configPath))
|
|
98
87
|
|
|
99
88
|
// config is a promise when using the `dynamicImport` loader
|
|
100
89
|
const config = (await result.config) ?? null
|
|
101
90
|
const filepath = result.filepath
|
|
102
91
|
|
|
103
|
-
|
|
92
|
+
if (config) {
|
|
93
|
+
debugLog('Successfully loaded config from `%s`:\n%O', filepath, config)
|
|
94
|
+
} else {
|
|
95
|
+
debugLog('Found no config in %s', filepath)
|
|
96
|
+
}
|
|
104
97
|
|
|
105
98
|
return { config, filepath }
|
|
106
99
|
} catch (error) {
|
package/lib/messages.js
CHANGED
|
@@ -66,11 +66,13 @@ export const PREVENTED_EMPTY_COMMIT = `
|
|
|
66
66
|
Use the --allow-empty option to continue, or check your task configuration`)}
|
|
67
67
|
`
|
|
68
68
|
|
|
69
|
-
export const
|
|
69
|
+
export const restoreStashExample = (
|
|
70
|
+
hash = 'h0a0s0h0'
|
|
71
|
+
) => `Any lost modifications can be restored from a git stash:
|
|
70
72
|
|
|
71
|
-
> git stash list
|
|
72
|
-
|
|
73
|
-
> git
|
|
73
|
+
> git stash list --format="%h %s"
|
|
74
|
+
${hash} On main: lint-staged automatic backup
|
|
75
|
+
> git apply --index ${hash}`
|
|
74
76
|
|
|
75
77
|
export const CONFIG_STDIN_ERROR = red(`${error} Failed to read config from stdin.`)
|
|
76
78
|
|
package/lib/runAll.js
CHANGED
|
@@ -36,7 +36,8 @@ import {
|
|
|
36
36
|
restoreOriginalStateEnabled,
|
|
37
37
|
restoreOriginalStateSkipped,
|
|
38
38
|
restoreUnstagedChangesSkipped,
|
|
39
|
-
|
|
39
|
+
shouldHidePartiallyStagedFiles,
|
|
40
|
+
shouldRestoreUnstagedChanges,
|
|
40
41
|
} from './state.js'
|
|
41
42
|
import { ConfigNotFoundError, GetStagedFilesError, GitError, GitRepoError } from './symbols.js'
|
|
42
43
|
|
|
@@ -54,7 +55,7 @@ const createError = (ctx, cause) =>
|
|
|
54
55
|
*
|
|
55
56
|
* @param {object} options
|
|
56
57
|
* @param {boolean} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
|
|
57
|
-
* @param {
|
|
58
|
+
* @param {boolean} [options.color] - Enable or disable ANSI color codes in output.
|
|
58
59
|
* @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
|
|
59
60
|
* @param {Object} [options.configObject] - Explicit config object from the js API
|
|
60
61
|
* @param {string} [options.configPath] - Explicit path to a config file
|
|
@@ -88,8 +89,8 @@ export const runAll = async (
|
|
|
88
89
|
diff,
|
|
89
90
|
diffFilter,
|
|
90
91
|
failOnChanges = false,
|
|
91
|
-
hidePartiallyStaged = true,
|
|
92
92
|
hideUnstaged = false,
|
|
93
|
+
hidePartiallyStaged = !hideUnstaged,
|
|
93
94
|
maxArgLength,
|
|
94
95
|
quiet = false,
|
|
95
96
|
relative = false,
|
|
@@ -135,7 +136,7 @@ export const runAll = async (
|
|
|
135
136
|
logger.warn(skippingBackup(hasInitialCommit, diff))
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
if (!ctx.shouldHidePartiallyStaged && !quiet) {
|
|
139
|
+
if (!ctx.shouldHidePartiallyStaged && !ctx.shouldHideUnstaged && !quiet) {
|
|
139
140
|
logger.warn(SKIPPING_HIDE_PARTIALLY_CHANGED)
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -330,8 +331,8 @@ export const runAll = async (
|
|
|
330
331
|
},
|
|
331
332
|
{
|
|
332
333
|
title: 'Hiding unstaged changes to partially staged files...',
|
|
333
|
-
task: (ctx) => git.
|
|
334
|
-
enabled:
|
|
334
|
+
task: (ctx) => git.hidePartiallyStagedChanges(ctx),
|
|
335
|
+
enabled: shouldHidePartiallyStagedFiles,
|
|
335
336
|
},
|
|
336
337
|
{
|
|
337
338
|
title: `Running tasks for ${diff ? 'changed' : 'staged'} files...`,
|
|
@@ -344,9 +345,9 @@ export const runAll = async (
|
|
|
344
345
|
skip: applyModificationsSkipped,
|
|
345
346
|
},
|
|
346
347
|
{
|
|
347
|
-
title: 'Restoring unstaged changes
|
|
348
|
+
title: 'Restoring unstaged changes...',
|
|
348
349
|
task: (ctx) => git.restoreUnstagedChanges(ctx),
|
|
349
|
-
enabled:
|
|
350
|
+
enabled: shouldRestoreUnstagedChanges,
|
|
350
351
|
skip: restoreUnstagedChangesSkipped,
|
|
351
352
|
},
|
|
352
353
|
{
|
package/lib/searchConfigs.js
CHANGED
|
@@ -19,6 +19,8 @@ const CONFIG_PATHSPEC = CONFIG_FILE_NAMES.map((f) => `:(glob)**/${f}`)
|
|
|
19
19
|
|
|
20
20
|
const numberOfLevels = (file) => file.split('/').length
|
|
21
21
|
|
|
22
|
+
const sortAlphabetically = (a, b) => a.localeCompare(b)
|
|
23
|
+
|
|
22
24
|
const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 : 1)
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -43,22 +45,20 @@ const listConfigFilesFromGit = async ({ cwd, topLevelDir }) =>
|
|
|
43
45
|
)
|
|
44
46
|
.then(parseGitZOutput)
|
|
45
47
|
.then((lines) => {
|
|
46
|
-
const possibleConfigFiles = lines
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
48
|
+
const possibleConfigFiles = lines.flatMap((line) => {
|
|
49
|
+
/**
|
|
50
|
+
* Leave out lines starting with "S " to ignore not-checked-out files in a sparse repo.
|
|
51
|
+
* The "S" status means a tracked file that is "skip-worktree"
|
|
52
|
+
* @see https://git-scm.com/docs/git-ls-files#Documentation/git-ls-files.txt--t
|
|
53
|
+
*/
|
|
54
|
+
if (line.startsWith('S ')) {
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.sort(sortDeepestParth)
|
|
58
|
+
const relativePath = line.replace(/^[HSMRCK?U] /, '')
|
|
59
|
+
const absolutePath = normalizePath(path.join(topLevelDir, relativePath))
|
|
60
|
+
return [absolutePath]
|
|
61
|
+
})
|
|
62
62
|
|
|
63
63
|
debugLog('Found possible config files from git:', possibleConfigFiles)
|
|
64
64
|
|
|
@@ -91,7 +91,7 @@ export const listConfigFilesFromFs = async ({ cwd }) => {
|
|
|
91
91
|
|
|
92
92
|
if (possibleConfigFiles.length > 0) {
|
|
93
93
|
debugLog('Found possible config files from filesystem:', possibleConfigFiles)
|
|
94
|
-
return possibleConfigFiles
|
|
94
|
+
return possibleConfigFiles
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const parentDir = path.dirname(cwd)
|
|
@@ -131,27 +131,35 @@ export const searchConfigs = async (
|
|
|
131
131
|
if (configPath) {
|
|
132
132
|
debugLog('Using single configuration path...')
|
|
133
133
|
|
|
134
|
-
const { config, filepath } = await loadConfig(
|
|
134
|
+
const { config, filepath } = await loadConfig(configPath, logger)
|
|
135
135
|
|
|
136
136
|
if (!config) return {}
|
|
137
137
|
return { [configPath]: validateConfig(config, filepath, logger) }
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const possibleConfigFiles = new Set()
|
|
141
|
+
|
|
142
|
+
const addToSet = (files) => {
|
|
143
|
+
files.forEach((f) => {
|
|
144
|
+
possibleConfigFiles.add(f)
|
|
145
|
+
})
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
await Promise.all([
|
|
149
|
+
listConfigFilesFromGit({ cwd, topLevelDir }).then(addToSet),
|
|
150
|
+
listConfigFilesFromFs({ cwd }).then(addToSet),
|
|
151
|
+
])
|
|
152
|
+
|
|
145
153
|
/** Create object with key as config file, and value as null */
|
|
146
|
-
const configs =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
const configs = Array.from(possibleConfigFiles)
|
|
155
|
+
.sort(sortAlphabetically)
|
|
156
|
+
.sort(sortDeepestParth)
|
|
157
|
+
.reduce((acc, configPath) => Object.assign(acc, { [configPath]: null }), {})
|
|
150
158
|
|
|
151
159
|
/** Load and validate all configs to the above object */
|
|
152
160
|
await Promise.all(
|
|
153
161
|
Object.keys(configs).map((configPath) =>
|
|
154
|
-
loadConfig(
|
|
162
|
+
loadConfig(configPath, logger).then(({ config, filepath }) => {
|
|
155
163
|
if (config) {
|
|
156
164
|
if (configPath !== filepath) {
|
|
157
165
|
debugLog('Config file "%s" resolved to "%s"', configPath, filepath)
|
package/lib/state.js
CHANGED
|
@@ -2,7 +2,7 @@ import EventEmitter from 'events'
|
|
|
2
2
|
|
|
3
3
|
import { GIT_ERROR, TASK_ERROR } from './messages.js'
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
FailOnChangesError,
|
|
6
6
|
GitError,
|
|
7
7
|
RestoreOriginalStateError,
|
|
8
8
|
RestoreUnstagedChangesError,
|
|
@@ -10,14 +10,15 @@ import {
|
|
|
10
10
|
} from './symbols.js'
|
|
11
11
|
|
|
12
12
|
export const getInitialState = ({
|
|
13
|
-
hidePartiallyStaged = true,
|
|
14
13
|
hideUnstaged = false,
|
|
14
|
+
hidePartiallyStaged = !hideUnstaged,
|
|
15
15
|
quiet = false,
|
|
16
16
|
revert = true,
|
|
17
17
|
} = {}) => ({
|
|
18
18
|
backupHash: null,
|
|
19
19
|
errors: new Set([]),
|
|
20
20
|
events: new EventEmitter(),
|
|
21
|
+
shouldFailOnChanges: null,
|
|
21
22
|
hasFilesToHide: null,
|
|
22
23
|
output: [],
|
|
23
24
|
quiet,
|
|
@@ -28,16 +29,21 @@ export const getInitialState = ({
|
|
|
28
29
|
unstagedPatch: null,
|
|
29
30
|
})
|
|
30
31
|
|
|
31
|
-
export const
|
|
32
|
+
export const shouldHidePartiallyStagedFiles = (ctx) =>
|
|
33
|
+
ctx.shouldHidePartiallyStaged && ctx.hasFilesToHide
|
|
34
|
+
|
|
35
|
+
export const shouldRestoreUnstagedChanges = (ctx) =>
|
|
32
36
|
(ctx.shouldHideUnstaged || ctx.shouldHidePartiallyStaged) && ctx.hasFilesToHide
|
|
33
37
|
|
|
34
38
|
export const applyModificationsSkipped = (ctx) => {
|
|
35
39
|
// Always apply back unstaged modifications when skipping revert or backup
|
|
36
40
|
if (!ctx.shouldRevert || !ctx.shouldBackup) return false
|
|
41
|
+
|
|
37
42
|
// Should be skipped in case of git errors
|
|
38
43
|
if (ctx.errors.has(GitError)) {
|
|
39
44
|
return GIT_ERROR
|
|
40
45
|
}
|
|
46
|
+
|
|
41
47
|
// Should be skipped when tasks fail
|
|
42
48
|
if (ctx.errors.has(TaskError)) {
|
|
43
49
|
return TASK_ERROR
|
|
@@ -50,6 +56,13 @@ export const restoreUnstagedChangesSkipped = (ctx) => {
|
|
|
50
56
|
return GIT_ERROR
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
// When complete reverting to original state is skipped,
|
|
60
|
+
// we can still restore unstaged changes to make it easier
|
|
61
|
+
// to do manually.
|
|
62
|
+
if (!ctx.shouldRevert) {
|
|
63
|
+
false
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
// Should be skipped when tasks fail
|
|
54
67
|
if (ctx.errors.has(TaskError)) {
|
|
55
68
|
return TASK_ERROR
|
|
@@ -59,17 +72,13 @@ export const restoreUnstagedChangesSkipped = (ctx) => {
|
|
|
59
72
|
export const restoreOriginalStateEnabled = (ctx) =>
|
|
60
73
|
!!ctx.shouldRevert &&
|
|
61
74
|
!!ctx.shouldBackup &&
|
|
62
|
-
(ctx.errors.has(
|
|
75
|
+
(ctx.errors.has(FailOnChangesError) ||
|
|
63
76
|
ctx.errors.has(TaskError) ||
|
|
64
77
|
ctx.errors.has(RestoreUnstagedChangesError))
|
|
65
78
|
|
|
66
79
|
export const restoreOriginalStateSkipped = (ctx) => {
|
|
67
80
|
// Should be skipped in case of unknown git errors
|
|
68
|
-
if (
|
|
69
|
-
ctx.errors.has(GitError) &&
|
|
70
|
-
!ctx.errors.has(RestoreUnstagedChangesError) &&
|
|
71
|
-
!ctx.errors.has(ExitCodeError)
|
|
72
|
-
) {
|
|
81
|
+
if (ctx.errors.has(GitError) && !ctx.errors.has(RestoreUnstagedChangesError)) {
|
|
73
82
|
return GIT_ERROR
|
|
74
83
|
}
|
|
75
84
|
}
|
|
@@ -77,6 +86,11 @@ export const restoreOriginalStateSkipped = (ctx) => {
|
|
|
77
86
|
export const cleanupEnabled = (ctx) => ctx.shouldBackup
|
|
78
87
|
|
|
79
88
|
export const cleanupSkipped = (ctx) => {
|
|
89
|
+
// "--fail-on-changes" was used, so we shouldn't drop the backup stash
|
|
90
|
+
if (ctx.errors.has(FailOnChangesError) && !ctx.shouldRevert) {
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
// Should be skipped in case of unknown git errors
|
|
81
95
|
if (restoreOriginalStateSkipped(ctx)) {
|
|
82
96
|
return GIT_ERROR
|
package/lib/symbols.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lint-staged",
|
|
3
|
-
"version": "16.2.
|
|
3
|
+
"version": "16.2.2",
|
|
4
4
|
"description": "Lint files staged by git",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,25 +48,25 @@
|
|
|
48
48
|
"tag": "npx changeset tag"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"commander": "14.0.1",
|
|
52
|
-
"listr2": "9.0.4",
|
|
53
|
-
"micromatch": "4.0.8",
|
|
54
|
-
"nano-spawn": "1.0.3",
|
|
55
|
-
"pidtree": "0.6.0",
|
|
56
|
-
"string-argv": "0.3.2",
|
|
57
|
-
"yaml": "2.8.1"
|
|
51
|
+
"commander": "^14.0.1",
|
|
52
|
+
"listr2": "^9.0.4",
|
|
53
|
+
"micromatch": "^4.0.8",
|
|
54
|
+
"nano-spawn": "^1.0.3",
|
|
55
|
+
"pidtree": "^0.6.0",
|
|
56
|
+
"string-argv": "^0.3.2",
|
|
57
|
+
"yaml": "^2.8.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@changesets/changelog-github": "0.5.1",
|
|
61
61
|
"@changesets/cli": "2.29.7",
|
|
62
62
|
"@commitlint/cli": "19.8.1",
|
|
63
63
|
"@commitlint/config-conventional": "19.8.1",
|
|
64
|
-
"@eslint/js": "9.
|
|
64
|
+
"@eslint/js": "9.36.0",
|
|
65
65
|
"@vitest/coverage-v8": "3.2.4",
|
|
66
66
|
"@vitest/eslint-plugin": "1.3.12",
|
|
67
67
|
"consolemock": "1.1.0",
|
|
68
68
|
"cross-env": "10.0.0",
|
|
69
|
-
"eslint": "9.
|
|
69
|
+
"eslint": "9.36.0",
|
|
70
70
|
"eslint-config-prettier": "10.1.8",
|
|
71
71
|
"eslint-plugin-n": "17.23.1",
|
|
72
72
|
"eslint-plugin-prettier": "5.5.4",
|
package/lib/dynamicImport.js
DELETED