@depup/lint-staged 16.3.2-depup.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/LICENSE +21 -0
- package/MIGRATION.md +72 -0
- package/README.md +1120 -0
- package/bin/lint-staged.js +175 -0
- package/lib/chunkFiles.js +65 -0
- package/lib/colors.js +106 -0
- package/lib/configFiles.js +30 -0
- package/lib/debug.js +27 -0
- package/lib/execGit.js +37 -0
- package/lib/figures.js +9 -0
- package/lib/file.js +53 -0
- package/lib/generateTasks.js +69 -0
- package/lib/getAbortController.js +18 -0
- package/lib/getDiffCommand.js +19 -0
- package/lib/getFunctionTask.js +37 -0
- package/lib/getRenderer.js +63 -0
- package/lib/getSpawnedTask.js +160 -0
- package/lib/getSpawnedTasks.js +76 -0
- package/lib/getStagedFiles.js +86 -0
- package/lib/gitWorkflow.js +434 -0
- package/lib/groupFilesByConfig.js +62 -0
- package/lib/index.d.ts +125 -0
- package/lib/index.js +184 -0
- package/lib/killSubprocesses.js +42 -0
- package/lib/loadConfig.js +102 -0
- package/lib/messages.js +91 -0
- package/lib/normalizePath.js +50 -0
- package/lib/parseGitZOutput.js +11 -0
- package/lib/printTaskOutput.js +12 -0
- package/lib/readStdin.js +19 -0
- package/lib/resolveConfig.js +15 -0
- package/lib/resolveGitRepo.js +65 -0
- package/lib/runAll.js +372 -0
- package/lib/searchConfigs.js +186 -0
- package/lib/state.js +102 -0
- package/lib/symbols.js +29 -0
- package/lib/validateBraces.js +95 -0
- package/lib/validateConfig.js +108 -0
- package/lib/validateOptions.js +33 -0
- package/lib/version.js +6 -0
- package/package.json +93 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { userInfo } from 'node:os'
|
|
4
|
+
|
|
5
|
+
import { Option, program } from 'commander'
|
|
6
|
+
|
|
7
|
+
import { createDebug, enableDebug } from '../lib/debug.js'
|
|
8
|
+
import lintStaged from '../lib/index.js'
|
|
9
|
+
import { CONFIG_STDIN_ERROR, restoreStashExample } from '../lib/messages.js'
|
|
10
|
+
import { readStdin } from '../lib/readStdin.js'
|
|
11
|
+
import { getVersion } from '../lib/version.js'
|
|
12
|
+
|
|
13
|
+
const debugLog = createDebug('lint-staged:bin')
|
|
14
|
+
|
|
15
|
+
// Do not terminate main Listr process on SIGINT
|
|
16
|
+
process.on('SIGINT', () => {})
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.version(await getVersion())
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This shouldn't be necessary for lint-staged, but add migration step just in case
|
|
23
|
+
* to preserve old behavior of "commander".
|
|
24
|
+
*
|
|
25
|
+
* @todo remove this in the major version
|
|
26
|
+
* @see https://github.com/tj/commander.js/releases/tag/v13.0.0
|
|
27
|
+
* */
|
|
28
|
+
.allowExcessArguments()
|
|
29
|
+
|
|
30
|
+
.addOption(
|
|
31
|
+
new Option('--allow-empty', 'allow empty commits when tasks revert all staged changes').default(
|
|
32
|
+
false
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
.addOption(
|
|
36
|
+
new Option(
|
|
37
|
+
'-p, --concurrent <number|boolean>',
|
|
38
|
+
'the number of tasks to run concurrently, or false for serial'
|
|
39
|
+
).default(true)
|
|
40
|
+
)
|
|
41
|
+
.addOption(
|
|
42
|
+
new Option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
|
|
43
|
+
)
|
|
44
|
+
.addOption(
|
|
45
|
+
new Option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
|
|
46
|
+
)
|
|
47
|
+
.addOption(new Option('-d, --debug', 'print additional debug information').default(false))
|
|
48
|
+
.addOption(
|
|
49
|
+
new Option(
|
|
50
|
+
'--diff [string]',
|
|
51
|
+
'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".'
|
|
52
|
+
).implies({ stash: false })
|
|
53
|
+
)
|
|
54
|
+
.addOption(
|
|
55
|
+
new Option(
|
|
56
|
+
'--diff-filter [string]',
|
|
57
|
+
'override the default "--diff-filter=ACMR" flag of "git diff" to get list of files'
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
.addOption(
|
|
61
|
+
new Option('--continue-on-error', 'run all tasks to completion even if one fails').default(
|
|
62
|
+
false
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
.addOption(
|
|
66
|
+
new Option('--fail-on-changes', 'fail with exit code 1 when tasks modify tracked files')
|
|
67
|
+
.default(false)
|
|
68
|
+
.implies({ revert: false })
|
|
69
|
+
)
|
|
70
|
+
.addOption(
|
|
71
|
+
new Option(
|
|
72
|
+
'--max-arg-length [number]',
|
|
73
|
+
'maximum length of the command-line argument string'
|
|
74
|
+
).default(0)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* We don't want to show the `--revert` flag because it's on by default, and only show the
|
|
79
|
+
* negatable flag `--no-rever` instead. There seems to be a bug in Commander.js where
|
|
80
|
+
* configuring only the latter won't actually set the default value.
|
|
81
|
+
*/
|
|
82
|
+
.addOption(
|
|
83
|
+
new Option('--revert', 'revert to original state in case of errors').default(true).hideHelp()
|
|
84
|
+
)
|
|
85
|
+
.addOption(
|
|
86
|
+
new Option('--no-revert', 'do not revert to original state in case of errors.').default(false)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
.addOption(new Option('--stash', 'enable the backup stash').default(true).hideHelp())
|
|
90
|
+
.addOption(
|
|
91
|
+
new Option('--no-stash', 'disable the backup stash. Implies "--no-revert".')
|
|
92
|
+
.default(false)
|
|
93
|
+
.implies({ revert: false })
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
.addOption(
|
|
97
|
+
new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
|
|
98
|
+
.default(true)
|
|
99
|
+
.hideHelp()
|
|
100
|
+
)
|
|
101
|
+
.addOption(
|
|
102
|
+
new Option(
|
|
103
|
+
'--no-hide-partially-staged',
|
|
104
|
+
'disable hiding unstaged changes from partially staged files'
|
|
105
|
+
).default(false)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
.addOption(
|
|
109
|
+
new Option('--hide-unstaged', 'hide all unstaged changes, instead of just partially staged')
|
|
110
|
+
.default(false)
|
|
111
|
+
.implies({ hidePartiallyStaged: false })
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
.addOption(new Option('-q, --quiet', 'disable lint-staged’s own console output').default(false))
|
|
115
|
+
.addOption(new Option('-r, --relative', 'pass relative filepaths to tasks').default(false))
|
|
116
|
+
.addOption(
|
|
117
|
+
new Option(
|
|
118
|
+
'-v, --verbose',
|
|
119
|
+
'show task output even when tasks succeed; by default only failed output is shown'
|
|
120
|
+
).default(false)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
.addHelpText('afterAll', '\n' + restoreStashExample())
|
|
124
|
+
|
|
125
|
+
const cliOptions = program.parse(process.argv).opts()
|
|
126
|
+
|
|
127
|
+
if (cliOptions.debug) {
|
|
128
|
+
enableDebug()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const options = {
|
|
132
|
+
allowEmpty: !!cliOptions.allowEmpty,
|
|
133
|
+
concurrent: JSON.parse(cliOptions.concurrent),
|
|
134
|
+
configPath: cliOptions.config,
|
|
135
|
+
continueOnError: !!cliOptions.continueOnError,
|
|
136
|
+
cwd: cliOptions.cwd,
|
|
137
|
+
debug: !!cliOptions.debug,
|
|
138
|
+
diff: cliOptions.diff,
|
|
139
|
+
diffFilter: cliOptions.diffFilter,
|
|
140
|
+
failOnChanges: !!cliOptions.failOnChanges,
|
|
141
|
+
hidePartiallyStaged: !!cliOptions.hidePartiallyStaged, // commander inverts `no-<x>` flags to `!x`
|
|
142
|
+
hideUnstaged: !!cliOptions.hideUnstaged,
|
|
143
|
+
maxArgLength: cliOptions.maxArgLength || undefined,
|
|
144
|
+
quiet: !!cliOptions.quiet,
|
|
145
|
+
relative: !!cliOptions.relative,
|
|
146
|
+
revert: !!cliOptions.revert, // commander inverts `no-<x>` flags to `!x`
|
|
147
|
+
stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
|
|
148
|
+
verbose: !!cliOptions.verbose,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const { shell } = userInfo()
|
|
153
|
+
debugLog('Using shell: %s', shell)
|
|
154
|
+
} catch {
|
|
155
|
+
debugLog('Could not determine current shell')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
debugLog('Options parsed from command-line: %o', options)
|
|
159
|
+
|
|
160
|
+
if (options.configPath === '-') {
|
|
161
|
+
delete options.configPath
|
|
162
|
+
try {
|
|
163
|
+
debugLog('Reading config from stdin')
|
|
164
|
+
options.config = JSON.parse(await readStdin())
|
|
165
|
+
} catch (error) {
|
|
166
|
+
debugLog(CONFIG_STDIN_ERROR, error)
|
|
167
|
+
console.error(CONFIG_STDIN_ERROR)
|
|
168
|
+
process.exit(1)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const passed = await lintStaged(options)
|
|
173
|
+
if (!passed) {
|
|
174
|
+
process.exitCode = 1
|
|
175
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { createDebug } from './debug.js'
|
|
4
|
+
import { normalizePath } from './normalizePath.js'
|
|
5
|
+
|
|
6
|
+
const debugLog = createDebug('lint-staged:chunkFiles')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Chunk array into sub-arrays
|
|
10
|
+
* @param {Array} arr
|
|
11
|
+
* @param {Number} chunkCount
|
|
12
|
+
* @returns {Array<Array>}
|
|
13
|
+
*/
|
|
14
|
+
const chunkArray = (arr, chunkCount) => {
|
|
15
|
+
if (chunkCount === 1) return [arr]
|
|
16
|
+
const chunked = []
|
|
17
|
+
let position = 0
|
|
18
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
19
|
+
const chunkLength = Math.ceil((arr.length - position) / (chunkCount - i))
|
|
20
|
+
chunked.push([])
|
|
21
|
+
chunked[i] = arr.slice(position, chunkLength + position)
|
|
22
|
+
position += chunkLength
|
|
23
|
+
}
|
|
24
|
+
return chunked
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Chunk files into sub-arrays based on the length of the resulting argument string
|
|
29
|
+
*
|
|
30
|
+
* @typedef {import('./getStagedFiles.js').StagedFile[]} StagedFile
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} opts
|
|
33
|
+
* @param {Array<StagedFile>} opts.files
|
|
34
|
+
* @param {String} [opts.baseDir] The optional base directory to resolve relative paths.
|
|
35
|
+
* @param {number} [opts.maxArgLength] the maximum argument string length
|
|
36
|
+
* @param {Boolean} [opts.relative] whether files are relative to `topLevelDir` or should be resolved as absolute
|
|
37
|
+
* @returns {Array<Array<StagedFile>>}
|
|
38
|
+
*/
|
|
39
|
+
export const chunkFiles = ({ files, baseDir, maxArgLength = null, relative = false }) => {
|
|
40
|
+
const normalizedFiles = files.map((file) => {
|
|
41
|
+
return {
|
|
42
|
+
filepath: normalizePath(
|
|
43
|
+
relative || !baseDir ? file.filepath : path.resolve(baseDir, file.filepath)
|
|
44
|
+
),
|
|
45
|
+
status: file.status,
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (!maxArgLength) {
|
|
50
|
+
debugLog('Skip chunking files because of undefined maxArgLength')
|
|
51
|
+
return [normalizedFiles] // wrap in an array to return a single chunk
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Calculate total character length of all filepaths, with added spaces in between */
|
|
55
|
+
const fileListLength =
|
|
56
|
+
normalizedFiles.reduce((sum, file) => sum + file.filepath.length, 0) +
|
|
57
|
+
Math.max(normalizedFiles.length - 1, 0)
|
|
58
|
+
|
|
59
|
+
debugLog(
|
|
60
|
+
`Resolved an argument string length of ${fileListLength} characters from ${normalizedFiles.length} files`
|
|
61
|
+
)
|
|
62
|
+
const chunkCount = Math.min(Math.ceil(fileListLength / maxArgLength), normalizedFiles.length)
|
|
63
|
+
debugLog(`Creating ${chunkCount} chunks for maxArgLength of ${maxArgLength}`)
|
|
64
|
+
return chunkArray(normalizedFiles, chunkCount)
|
|
65
|
+
}
|
package/lib/colors.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import nodeTty from 'node:tty'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @example NO_COLOR
|
|
5
|
+
* @example NO_COLOR=1
|
|
6
|
+
* @example NO_COLOR=true
|
|
7
|
+
*/
|
|
8
|
+
const TRUTHRY_ENV_VAR_VALUES = ['', '1', 'true']
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @example FORCE_COLOR=0
|
|
12
|
+
* @example FORCE_COLOR=false
|
|
13
|
+
*/
|
|
14
|
+
const FALSY_ENV_VAR_VALUES = ['0', 'false']
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @returns `true` if ANSI colors are supported
|
|
18
|
+
*
|
|
19
|
+
* @param {NodeJS.Process} [p]
|
|
20
|
+
* @param {boolean} [isTty]
|
|
21
|
+
*/
|
|
22
|
+
export const supportsAnsiColors = (p = process, isTty = nodeTty.isatty(1)) => {
|
|
23
|
+
const noColor = p?.env?.NO_COLOR?.toLowerCase()
|
|
24
|
+
if (TRUTHRY_ENV_VAR_VALUES.includes(noColor)) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const forceColor = p?.env?.FORCE_COLOR?.toLowerCase()
|
|
29
|
+
if (TRUTHRY_ENV_VAR_VALUES.includes(forceColor)) {
|
|
30
|
+
return true
|
|
31
|
+
} else if (FALSY_ENV_VAR_VALUES.includes(forceColor)) {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const forceTty = p?.env?.FORCE_TTY
|
|
36
|
+
if (TRUTHRY_ENV_VAR_VALUES.includes(forceTty)) {
|
|
37
|
+
return true
|
|
38
|
+
} else if (FALSY_ENV_VAR_VALUES.includes(forceTty)) {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isTty) {
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Assume CI supports color
|
|
48
|
+
* @see {@link https://github.com/alexeyraspopov/picocolors/blob/0e7c4af2de299dd7bc5916f2bddd151fa2f66740/picocolors.js#L4}
|
|
49
|
+
* @see {@link https://github.com/tinylibs/tinyrainbow/blob/071034bf2eafa28d91ef0ba48a3837420d81a40a/src/index.ts#L91}
|
|
50
|
+
*/
|
|
51
|
+
if (TRUTHRY_ENV_VAR_VALUES.includes(p?.env?.CI)) {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (p?.env?.TERM && p.env.TERM === 'dumb') {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Assume Windows supports color
|
|
61
|
+
* @see {@link https://github.com/alexeyraspopov/picocolors/blob/0e7c4af2de299dd7bc5916f2bddd151fa2f66740/picocolors.js#L4}
|
|
62
|
+
* @see {@link https://github.com/tinylibs/tinyrainbow/blob/071034bf2eafa28d91ef0ba48a3837420d81a40a/src/index.ts#L89}
|
|
63
|
+
*/
|
|
64
|
+
if (p?.platform === 'win32') {
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @deprecated replace this with Node.js builtin after minimum supported version is >=20.18.0
|
|
73
|
+
* @example util.styleText('red', 'test') !== 'text'
|
|
74
|
+
*/
|
|
75
|
+
export const SUPPORTS_COLOR = supportsAnsiColors()
|
|
76
|
+
|
|
77
|
+
const ANSI_RESET = '\u001B[0m'
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @callback WrapAnsi
|
|
81
|
+
* @param {string} text
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated replace this with Node.js builtin after minimum supported version is >=20.18.0
|
|
86
|
+
* @example (format) => (text) => util.styleText(format, text)
|
|
87
|
+
*
|
|
88
|
+
* @param {string} code
|
|
89
|
+
* @param {boolean} [supported]
|
|
90
|
+
* @returns {WrapAnsi}
|
|
91
|
+
*
|
|
92
|
+
*/
|
|
93
|
+
export const wrapAnsiColor = (code, supported = SUPPORTS_COLOR) => {
|
|
94
|
+
if (supported) {
|
|
95
|
+
return (text) => code + text + ANSI_RESET
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (text) => text
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const red = wrapAnsiColor('\u001B[0;31m')
|
|
102
|
+
export const green = wrapAnsiColor('\u001B[0;32m')
|
|
103
|
+
export const yellow = wrapAnsiColor('\u001B[0;33m')
|
|
104
|
+
export const blue = wrapAnsiColor('\u001B[0;34m')
|
|
105
|
+
export const blackBright = wrapAnsiColor('\u001B[0;90m')
|
|
106
|
+
export const bold = wrapAnsiColor('\u001b[1m')
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const CONFIG_NAME = 'lint-staged'
|
|
2
|
+
|
|
3
|
+
export const PACKAGE_JSON_FILE = 'package.json'
|
|
4
|
+
|
|
5
|
+
export const PACKAGE_YAML_FILES = ['package.yaml', 'package.yml']
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The list of files `lint-staged` will read configuration
|
|
9
|
+
* from, in the declared order.
|
|
10
|
+
*/
|
|
11
|
+
export const CONFIG_FILE_NAMES = [
|
|
12
|
+
PACKAGE_JSON_FILE,
|
|
13
|
+
...PACKAGE_YAML_FILES,
|
|
14
|
+
'.lintstagedrc',
|
|
15
|
+
'.lintstagedrc.json',
|
|
16
|
+
'.lintstagedrc.yaml',
|
|
17
|
+
'.lintstagedrc.yml',
|
|
18
|
+
'.lintstagedrc.mjs',
|
|
19
|
+
'.lintstagedrc.mts',
|
|
20
|
+
'.lintstagedrc.js',
|
|
21
|
+
'.lintstagedrc.ts',
|
|
22
|
+
'.lintstagedrc.cjs',
|
|
23
|
+
'.lintstagedrc.cts',
|
|
24
|
+
'lint-staged.config.mjs',
|
|
25
|
+
'lint-staged.config.mts',
|
|
26
|
+
'lint-staged.config.js',
|
|
27
|
+
'lint-staged.config.ts',
|
|
28
|
+
'lint-staged.config.cjs',
|
|
29
|
+
'lint-staged.config.cts',
|
|
30
|
+
]
|
package/lib/debug.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { formatWithOptions } from 'node:util'
|
|
2
|
+
|
|
3
|
+
import { blackBright, SUPPORTS_COLOR } from './colors.js'
|
|
4
|
+
|
|
5
|
+
const format = (...args) => formatWithOptions({ colors: SUPPORTS_COLOR }, ...args)
|
|
6
|
+
|
|
7
|
+
let activeLogger
|
|
8
|
+
|
|
9
|
+
export const enableDebug = (logger = console) => {
|
|
10
|
+
if (!activeLogger) {
|
|
11
|
+
activeLogger = logger
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @param {string} name */
|
|
16
|
+
export const createDebug = (name) => {
|
|
17
|
+
let previous = process.hrtime.bigint()
|
|
18
|
+
|
|
19
|
+
return (...args) => {
|
|
20
|
+
if (!activeLogger) return
|
|
21
|
+
|
|
22
|
+
const now = process.hrtime.bigint()
|
|
23
|
+
const ms = (now - previous) / 1_000_000n
|
|
24
|
+
previous = now
|
|
25
|
+
activeLogger.debug(blackBright(name + ': ') + format(...args) + blackBright(` +${ms}ms`))
|
|
26
|
+
}
|
|
27
|
+
}
|
package/lib/execGit.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { exec } from 'tinyexec'
|
|
2
|
+
|
|
3
|
+
import { createDebug } from './debug.js'
|
|
4
|
+
|
|
5
|
+
const debugLog = createDebug('lint-staged:execGit')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Explicitly never recurse commands into submodules, overriding local/global configuration.
|
|
9
|
+
* @see https://git-scm.com/docs/git-config#Documentation/git-config.txt-submodulerecurse
|
|
10
|
+
*/
|
|
11
|
+
const NO_SUBMODULE_RECURSE = ['-c', 'submodule.recurse=false']
|
|
12
|
+
|
|
13
|
+
// exported for tests
|
|
14
|
+
export const GIT_GLOBAL_OPTIONS = [...NO_SUBMODULE_RECURSE]
|
|
15
|
+
|
|
16
|
+
/** @type {(cmd: string[], options?: { cwd?: string }) => Promise<string>} */
|
|
17
|
+
export const execGit = async (cmd, options) => {
|
|
18
|
+
debugLog('Running git command:', cmd)
|
|
19
|
+
const result = exec('git', [...NO_SUBMODULE_RECURSE, ...cmd], {
|
|
20
|
+
nodeOptions: {
|
|
21
|
+
cwd: options?.cwd,
|
|
22
|
+
stdio: ['ignore'],
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
let output = ''
|
|
27
|
+
for await (const line of result) {
|
|
28
|
+
output += line + '\n'
|
|
29
|
+
}
|
|
30
|
+
output = output.trimEnd()
|
|
31
|
+
|
|
32
|
+
if (result.exitCode > 0) {
|
|
33
|
+
throw new Error(output, { cause: result })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return output
|
|
37
|
+
}
|
package/lib/figures.js
ADDED
package/lib/file.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
import { createDebug } from './debug.js'
|
|
4
|
+
|
|
5
|
+
const debugLog = createDebug('lint-staged:file')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Read contents of a file to buffer
|
|
9
|
+
* @param {String} filename
|
|
10
|
+
* @param {Boolean} [ignoreENOENT=true] — Whether to throw if the file doesn't exist
|
|
11
|
+
* @returns {Promise<Buffer>}
|
|
12
|
+
*/
|
|
13
|
+
export const readFile = async (filename, ignoreENOENT = true) => {
|
|
14
|
+
debugLog('Reading file `%s`', filename)
|
|
15
|
+
try {
|
|
16
|
+
return await fs.readFile(filename)
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (ignoreENOENT && error.code === 'ENOENT') {
|
|
19
|
+
debugLog("File `%s` doesn't exist, ignoring...", filename)
|
|
20
|
+
return null // no-op file doesn't exist
|
|
21
|
+
} else {
|
|
22
|
+
throw error
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Remove a file
|
|
29
|
+
* @param {String} filename
|
|
30
|
+
* @param {Boolean} [ignoreENOENT=true] — Whether to throw if the file doesn't exist
|
|
31
|
+
*/
|
|
32
|
+
export const unlink = async (filename, ignoreENOENT = true) => {
|
|
33
|
+
debugLog('Removing file `%s`', filename)
|
|
34
|
+
try {
|
|
35
|
+
await fs.unlink(filename)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (ignoreENOENT && error.code === 'ENOENT') {
|
|
38
|
+
debugLog("File `%s` doesn't exist, ignoring...", filename)
|
|
39
|
+
} else {
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Write buffer to file
|
|
47
|
+
* @param {String} filename
|
|
48
|
+
* @param {Buffer} buffer
|
|
49
|
+
*/
|
|
50
|
+
export const writeFile = async (filename, buffer) => {
|
|
51
|
+
debugLog('Writing file `%s`', filename)
|
|
52
|
+
await fs.writeFile(filename, buffer)
|
|
53
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import micromatch from 'micromatch'
|
|
4
|
+
|
|
5
|
+
import { createDebug } from './debug.js'
|
|
6
|
+
import { normalizePath } from './normalizePath.js'
|
|
7
|
+
|
|
8
|
+
const debugLog = createDebug('lint-staged:generateTasks')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generates all task commands, and filelist
|
|
12
|
+
*
|
|
13
|
+
* @param {object} options
|
|
14
|
+
* @param {Object} [options.config] - Task configuration
|
|
15
|
+
* @param {Object} [options.cwd] - Current working directory
|
|
16
|
+
* @param {import('./getStagedFiles.js').StagedFile[]} [options.files] - Staged filepaths
|
|
17
|
+
* @param {boolean} [options.relative] - Whether filepaths to should be relative to cwd
|
|
18
|
+
*/
|
|
19
|
+
export const generateTasks = ({ config, cwd = process.cwd(), files, relative = false }) => {
|
|
20
|
+
debugLog('Generating linter tasks')
|
|
21
|
+
|
|
22
|
+
/** @type {StagedFile[]} */
|
|
23
|
+
const relativeFiles = files.map((file) => ({
|
|
24
|
+
filepath: normalizePath(path.relative(cwd, file.filepath)),
|
|
25
|
+
status: file.status,
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
return Object.entries(config).map(([pattern, commands]) => {
|
|
29
|
+
const isParentDirPattern = pattern.startsWith('../')
|
|
30
|
+
|
|
31
|
+
// Only worry about children of the CWD unless the pattern explicitly
|
|
32
|
+
// specifies that it concerns a parent directory.
|
|
33
|
+
const filteredFiles = relativeFiles.filter((file) => {
|
|
34
|
+
if (isParentDirPattern) return true
|
|
35
|
+
return !file.filepath.startsWith('..') && !path.isAbsolute(file.filepath)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const matches = micromatch(
|
|
39
|
+
filteredFiles.map((file) => file.filepath),
|
|
40
|
+
pattern,
|
|
41
|
+
{
|
|
42
|
+
cwd,
|
|
43
|
+
dot: true,
|
|
44
|
+
// If the pattern doesn't look like a path, enable `matchBase` to
|
|
45
|
+
// match against filenames in every directory. This makes `*.js`
|
|
46
|
+
// match both `test.js` and `subdirectory/test.js`.
|
|
47
|
+
matchBase: !pattern.includes('/'),
|
|
48
|
+
posixSlashes: true,
|
|
49
|
+
strictBrackets: true,
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const fileList = filteredFiles.flatMap((file) =>
|
|
54
|
+
matches.includes(file.filepath)
|
|
55
|
+
? [
|
|
56
|
+
{
|
|
57
|
+
filepath: normalizePath(relative ? file.filepath : path.resolve(cwd, file.filepath)),
|
|
58
|
+
status: file.status,
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
: []
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const task = { pattern, commands, fileList }
|
|
65
|
+
debugLog('Generated task: \n%O', task)
|
|
66
|
+
|
|
67
|
+
return task
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const Signal = {
|
|
2
|
+
SIGINT: 'SIGINT',
|
|
3
|
+
SIGKILL: 'SIGKILL',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get an AbortController used to cancel running tasks on failure/interruption.
|
|
8
|
+
* @returns AbortController
|
|
9
|
+
*/
|
|
10
|
+
export const getAbortController = (nodeProcess = process) => {
|
|
11
|
+
const abortController = new AbortController()
|
|
12
|
+
|
|
13
|
+
nodeProcess.on(Signal.SIGINT, () => {
|
|
14
|
+
abortController.abort(Signal.SIGINT)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return abortController
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @type {(diff?: string, diffFilter?: string) => string[]} */
|
|
2
|
+
export const getDiffCommand = (diff, diffFilter) => {
|
|
3
|
+
/**
|
|
4
|
+
* Docs for --diff-filter option:
|
|
5
|
+
* @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
|
|
6
|
+
*/
|
|
7
|
+
const diffFilterArg = diffFilter !== undefined ? diffFilter.trim() : 'ACMR'
|
|
8
|
+
|
|
9
|
+
/** Use `--diff branch1...branch2` or `--diff="branch1 branch2", or fall back to default staged files */
|
|
10
|
+
const diffArgs = diff !== undefined ? diff.trim().split(' ') : ['--staged']
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Docs for -z option:
|
|
14
|
+
* @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
|
|
15
|
+
*/
|
|
16
|
+
const diffCommand = ['diff', `--diff-filter=${diffFilterArg}`, ...diffArgs]
|
|
17
|
+
|
|
18
|
+
return diffCommand
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createDebug } from './debug.js'
|
|
2
|
+
import { createTaskError } from './getSpawnedTask.js'
|
|
3
|
+
|
|
4
|
+
const debugLog = createDebug('lint-staged:getFunctionTasks')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {{ title: string; task: Function }} FunctionTask
|
|
8
|
+
* @type {(commands: FunctionTask|Array<string|Function>|string|Function) => boolean}
|
|
9
|
+
* @returns `true` if command is a function task
|
|
10
|
+
*/
|
|
11
|
+
export const isFunctionTask = (commands) => typeof commands === 'object' && !Array.isArray(commands)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handles function configuration and pushes the tasks into the task array
|
|
15
|
+
*
|
|
16
|
+
* @param {object} command
|
|
17
|
+
* @param {import('./getStagedFiles.js').StagedFile[]} files
|
|
18
|
+
* @throws {Error} If the function configuration is not valid
|
|
19
|
+
*/
|
|
20
|
+
export const getFunctionTask = async (command, files) => {
|
|
21
|
+
debugLog('Creating Listr tasks for function %o', command)
|
|
22
|
+
|
|
23
|
+
const task = async (ctx) => {
|
|
24
|
+
try {
|
|
25
|
+
await command.task(files.map((file) => file.filepath))
|
|
26
|
+
} catch (e) {
|
|
27
|
+
throw createTaskError(command.title, e, ctx)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
title: command.title,
|
|
34
|
+
task,
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
}
|