lint-staged 11.0.0 → 11.1.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 +7 -29
- package/bin/lint-staged.js +2 -2
- package/lib/generateTasks.js +24 -26
- package/lib/index.js +81 -91
- package/lib/makeCmdTasks.js +2 -2
- package/lib/messages.js +33 -7
- package/lib/runAll.js +2 -1
- package/lib/symbols.js +4 -0
- package/lib/validateBraces.js +71 -0
- package/lib/validateConfig.js +71 -56
- package/lib/validateOptions.js +31 -0
- package/package.json +1 -2
- package/lib/formatConfig.js +0 -7
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ This project contains a script that will run arbitrary shell tasks with a list o
|
|
|
25
25
|
The fastest way to start using lint-staged is to run the following command in your terminal:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npx mrm lint-staged
|
|
28
|
+
npx mrm@2 lint-staged
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
This command will install and configure [husky](https://github.com/typicode/husky) and lint-staged depending on the code quality tools from your project's `package.json` dependencies, so please make sure you install (`npm install --save-dev`) and configure all code quality tools like [Prettier](https://prettier.io) and [ESLint](https://eslint.org) prior to that.
|
|
@@ -71,7 +71,7 @@ Options:
|
|
|
71
71
|
tasks serially (default: true)
|
|
72
72
|
-q, --quiet disable lint-staged’s own console output (default: false)
|
|
73
73
|
-r, --relative pass relative filepaths to tasks (default: false)
|
|
74
|
-
-x, --shell
|
|
74
|
+
-x, --shell [path] skip parsing of tasks for better shell support (default:
|
|
75
75
|
false)
|
|
76
76
|
-v, --verbose show task output even when tasks succeed; by default only
|
|
77
77
|
failed output is shown (default: false)
|
|
@@ -90,7 +90,7 @@ Options:
|
|
|
90
90
|
- **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit.
|
|
91
91
|
- **`--quiet`**: Supress all CLI output, except from tasks.
|
|
92
92
|
- **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
|
|
93
|
-
- **`--shell`**: By default linter commands will be parsed for speed and security. This has the side-effect that regular shell scripts might not work as expected. You can skip parsing of commands with this option.
|
|
93
|
+
- **`--shell`**: By default linter commands will be parsed for speed and security. This has the side-effect that regular shell scripts might not work as expected. You can skip parsing of commands with this option. To use a specific shell, use a path like `--shell "/bin/bash"`.
|
|
94
94
|
- **`--verbose`**: Show task output even when tasks succeed. By default only failed output is shown.
|
|
95
95
|
|
|
96
96
|
## Configuration
|
|
@@ -445,36 +445,14 @@ For example, here is `jest` running on all `.js` files with the `NODE_ENV` varia
|
|
|
445
445
|
|
|
446
446
|
</details>
|
|
447
447
|
|
|
448
|
-
### Automatically fix code style with `prettier` for any format
|
|
448
|
+
### Automatically fix code style with `prettier` for any format Prettier supports
|
|
449
449
|
|
|
450
450
|
<details>
|
|
451
451
|
<summary>Click to expand</summary>
|
|
452
452
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
$ npm i --save-dev micromatch
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
```js
|
|
460
|
-
// lint-staged.config.js
|
|
461
|
-
const micromatch = require('micromatch')
|
|
462
|
-
const prettier = require('prettier')
|
|
463
|
-
|
|
464
|
-
const prettierSupportedExtensions = prettier
|
|
465
|
-
.getSupportInfo()
|
|
466
|
-
.languages.map(({ extensions }) => extensions)
|
|
467
|
-
.flat()
|
|
468
|
-
const addQuotes = (a) => `"${a}"`
|
|
469
|
-
|
|
470
|
-
module.exports = (allStagedFiles) => {
|
|
471
|
-
const prettierFiles = micromatch(
|
|
472
|
-
allStagedFiles,
|
|
473
|
-
prettierSupportedExtensions.map((extension) => `**/*${extension}`)
|
|
474
|
-
)
|
|
475
|
-
return prettierFiles.length > 0
|
|
476
|
-
? [`prettier --write ${prettierFiles.map(addQuotes).join(' ')}`]
|
|
477
|
-
: []
|
|
453
|
+
```json
|
|
454
|
+
{
|
|
455
|
+
"*": "prettier --ignore-unknown --write"
|
|
478
456
|
}
|
|
479
457
|
```
|
|
480
458
|
|
package/bin/lint-staged.js
CHANGED
|
@@ -42,7 +42,7 @@ cmdline
|
|
|
42
42
|
)
|
|
43
43
|
.option('-q, --quiet', 'disable lint-staged’s own console output', false)
|
|
44
44
|
.option('-r, --relative', 'pass relative filepaths to tasks', false)
|
|
45
|
-
.option('-x, --shell', 'skip parsing of tasks for better shell support', false)
|
|
45
|
+
.option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
|
|
46
46
|
.option(
|
|
47
47
|
'-v, --verbose',
|
|
48
48
|
'show task output even when tasks succeed; by default only failed output is shown',
|
|
@@ -85,7 +85,7 @@ const options = {
|
|
|
85
85
|
stash: !!cmdlineOptions.stash, // commander inverts `no-<x>` flags to `!x`
|
|
86
86
|
quiet: !!cmdlineOptions.quiet,
|
|
87
87
|
relative: !!cmdlineOptions.relative,
|
|
88
|
-
shell:
|
|
88
|
+
shell: cmdlineOptions.shell /* Either a boolean or a string pointing to the shell */,
|
|
89
89
|
verbose: !!cmdlineOptions.verbose,
|
|
90
90
|
}
|
|
91
91
|
|
package/lib/generateTasks.js
CHANGED
|
@@ -16,39 +16,35 @@ const debug = require('debug')('lint-staged:gen-tasks')
|
|
|
16
16
|
* @param {boolean} [options.files] - Staged filepaths
|
|
17
17
|
* @param {boolean} [options.relative] - Whether filepaths to should be relative to gitDir
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
config,
|
|
21
|
-
cwd = process.cwd(),
|
|
22
|
-
gitDir,
|
|
23
|
-
files,
|
|
24
|
-
relative = false,
|
|
25
|
-
}) {
|
|
19
|
+
const generateTasks = ({ config, cwd = process.cwd(), gitDir, files, relative = false }) => {
|
|
26
20
|
debug('Generating linter tasks')
|
|
27
21
|
|
|
28
22
|
const absoluteFiles = files.map((file) => normalize(path.resolve(gitDir, file)))
|
|
29
23
|
const relativeFiles = absoluteFiles.map((file) => normalize(path.relative(cwd, file)))
|
|
30
24
|
|
|
31
|
-
return Object.entries(config).map(([
|
|
25
|
+
return Object.entries(config).map(([rawPattern, commands]) => {
|
|
26
|
+
let pattern = rawPattern
|
|
27
|
+
|
|
32
28
|
const isParentDirPattern = pattern.startsWith('../')
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
30
|
+
// Only worry about children of the CWD unless the pattern explicitly
|
|
31
|
+
// specifies that it concerns a parent directory.
|
|
32
|
+
const filteredFiles = relativeFiles.filter((file) => {
|
|
33
|
+
if (isParentDirPattern) return true
|
|
34
|
+
return !file.startsWith('..') && !path.isAbsolute(file)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const matches = micromatch(filteredFiles, pattern, {
|
|
38
|
+
cwd,
|
|
39
|
+
dot: true,
|
|
40
|
+
// If the pattern doesn't look like a path, enable `matchBase` to
|
|
41
|
+
// match against filenames in every directory. This makes `*.js`
|
|
42
|
+
// match both `test.js` and `subdirectory/test.js`.
|
|
43
|
+
matchBase: !pattern.includes('/'),
|
|
44
|
+
strictBrackets: true,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const fileList = matches.map((file) => normalize(relative ? file : path.resolve(cwd, file)))
|
|
52
48
|
|
|
53
49
|
const task = { pattern, commands, fileList }
|
|
54
50
|
debug('Generated task: \n%O', task)
|
|
@@ -56,3 +52,5 @@ module.exports = function generateTasks({
|
|
|
56
52
|
return task
|
|
57
53
|
})
|
|
58
54
|
}
|
|
55
|
+
|
|
56
|
+
module.exports = generateTasks
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const dedent = require('dedent')
|
|
4
3
|
const { cosmiconfig } = require('cosmiconfig')
|
|
5
4
|
const debugLog = require('debug')('lint-staged')
|
|
6
5
|
const stringifyObject = require('stringify-object')
|
|
@@ -8,13 +7,16 @@ const stringifyObject = require('stringify-object')
|
|
|
8
7
|
const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./messages')
|
|
9
8
|
const printTaskOutput = require('./printTaskOutput')
|
|
10
9
|
const runAll = require('./runAll')
|
|
11
|
-
const {
|
|
12
|
-
|
|
10
|
+
const {
|
|
11
|
+
ApplyEmptyCommitError,
|
|
12
|
+
ConfigNotFoundError,
|
|
13
|
+
GetBackupStashError,
|
|
14
|
+
GitError,
|
|
15
|
+
} = require('./symbols')
|
|
13
16
|
const validateConfig = require('./validateConfig')
|
|
17
|
+
const validateOptions = require('./validateOptions')
|
|
14
18
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
function resolveConfig(configPath) {
|
|
19
|
+
const resolveConfig = (configPath) => {
|
|
18
20
|
try {
|
|
19
21
|
return require.resolve(configPath)
|
|
20
22
|
} catch {
|
|
@@ -22,7 +24,7 @@ function resolveConfig(configPath) {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
const loadConfig = (configPath) => {
|
|
26
28
|
const explorer = cosmiconfig('lint-staged', {
|
|
27
29
|
searchPlaces: [
|
|
28
30
|
'package.json',
|
|
@@ -56,14 +58,14 @@ function loadConfig(configPath) {
|
|
|
56
58
|
* @param {number} [options.maxArgLength] - Maximum argument string length
|
|
57
59
|
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
|
|
58
60
|
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
|
|
59
|
-
* @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
|
|
61
|
+
* @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
|
|
60
62
|
* @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
|
|
61
63
|
* @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
|
|
62
64
|
* @param {Logger} [logger]
|
|
63
65
|
*
|
|
64
66
|
* @returns {Promise<boolean>} Promise of whether the linting passed or failed
|
|
65
67
|
*/
|
|
66
|
-
|
|
68
|
+
const lintStaged = async (
|
|
67
69
|
{
|
|
68
70
|
allowEmpty = false,
|
|
69
71
|
concurrent = true,
|
|
@@ -79,92 +81,80 @@ module.exports = async function lintStaged(
|
|
|
79
81
|
verbose = false,
|
|
80
82
|
} = {},
|
|
81
83
|
logger = console
|
|
82
|
-
) {
|
|
83
|
-
|
|
84
|
-
debugLog('Loading config using `cosmiconfig`')
|
|
85
|
-
|
|
86
|
-
const resolved = configObject
|
|
87
|
-
? { config: configObject, filepath: '(input)' }
|
|
88
|
-
: await loadConfig(configPath)
|
|
89
|
-
if (resolved == null) throw errConfigNotFound
|
|
90
|
-
|
|
91
|
-
debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
|
|
92
|
-
// resolved.config is the parsed configuration object
|
|
93
|
-
// resolved.filepath is the path to the config file that was found
|
|
94
|
-
const formattedConfig = formatConfig(resolved.config)
|
|
95
|
-
const config = validateConfig(formattedConfig)
|
|
96
|
-
if (debug) {
|
|
97
|
-
// Log using logger to be able to test through `consolemock`.
|
|
98
|
-
logger.log('Running lint-staged with the following config:')
|
|
99
|
-
logger.log(stringifyObject(config, { indent: ' ' }))
|
|
100
|
-
} else {
|
|
101
|
-
// We might not be in debug mode but `DEBUG=lint-staged*` could have
|
|
102
|
-
// been set.
|
|
103
|
-
debugLog('lint-staged config:\n%O', config)
|
|
104
|
-
}
|
|
84
|
+
) => {
|
|
85
|
+
await validateOptions({ shell }, logger)
|
|
105
86
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
stash,
|
|
123
|
-
verbose,
|
|
124
|
-
},
|
|
125
|
-
logger
|
|
126
|
-
)
|
|
127
|
-
debugLog('Tasks were executed successfully!')
|
|
128
|
-
printTaskOutput(ctx, logger)
|
|
129
|
-
return true
|
|
130
|
-
} catch (runAllError) {
|
|
131
|
-
if (runAllError && runAllError.ctx && runAllError.ctx.errors) {
|
|
132
|
-
const { ctx } = runAllError
|
|
133
|
-
if (ctx.errors.has(ApplyEmptyCommitError)) {
|
|
134
|
-
logger.warn(PREVENTED_EMPTY_COMMIT)
|
|
135
|
-
} else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
|
|
136
|
-
logger.error(GIT_ERROR)
|
|
137
|
-
if (ctx.shouldBackup) {
|
|
138
|
-
// No sense to show this if the backup stash itself is missing.
|
|
139
|
-
logger.error(RESTORE_STASH_EXAMPLE)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
87
|
+
debugLog('Loading config using `cosmiconfig`')
|
|
88
|
+
|
|
89
|
+
const resolved = configObject
|
|
90
|
+
? { config: configObject, filepath: '(input)' }
|
|
91
|
+
: await loadConfig(configPath)
|
|
92
|
+
|
|
93
|
+
if (resolved == null) {
|
|
94
|
+
logger.error(`${ConfigNotFoundError.message}.`)
|
|
95
|
+
throw ConfigNotFoundError
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
|
|
99
|
+
|
|
100
|
+
// resolved.config is the parsed configuration object
|
|
101
|
+
// resolved.filepath is the path to the config file that was found
|
|
102
|
+
const config = validateConfig(resolved.config, logger)
|
|
142
103
|
|
|
143
|
-
|
|
144
|
-
|
|
104
|
+
if (debug) {
|
|
105
|
+
// Log using logger to be able to test through `consolemock`.
|
|
106
|
+
logger.log('Running lint-staged with the following config:')
|
|
107
|
+
logger.log(stringifyObject(config, { indent: ' ' }))
|
|
108
|
+
} else {
|
|
109
|
+
// We might not be in debug mode but `DEBUG=lint-staged*` could have
|
|
110
|
+
// been set.
|
|
111
|
+
debugLog('lint-staged config:\n%O', config)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
|
|
115
|
+
debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
|
|
116
|
+
delete process.env.GIT_LITERAL_PATHSPECS
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const ctx = await runAll(
|
|
120
|
+
{
|
|
121
|
+
allowEmpty,
|
|
122
|
+
concurrent,
|
|
123
|
+
config,
|
|
124
|
+
cwd,
|
|
125
|
+
debug,
|
|
126
|
+
maxArgLength,
|
|
127
|
+
quiet,
|
|
128
|
+
relative,
|
|
129
|
+
shell,
|
|
130
|
+
stash,
|
|
131
|
+
verbose,
|
|
132
|
+
},
|
|
133
|
+
logger
|
|
134
|
+
)
|
|
135
|
+
debugLog('Tasks were executed successfully!')
|
|
136
|
+
printTaskOutput(ctx, logger)
|
|
137
|
+
return true
|
|
138
|
+
} catch (runAllError) {
|
|
139
|
+
if (runAllError && runAllError.ctx && runAllError.ctx.errors) {
|
|
140
|
+
const { ctx } = runAllError
|
|
141
|
+
if (ctx.errors.has(ApplyEmptyCommitError)) {
|
|
142
|
+
logger.warn(PREVENTED_EMPTY_COMMIT)
|
|
143
|
+
} else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
|
|
144
|
+
logger.error(GIT_ERROR)
|
|
145
|
+
if (ctx.shouldBackup) {
|
|
146
|
+
// No sense to show this if the backup stash itself is missing.
|
|
147
|
+
logger.error(RESTORE_STASH_EXAMPLE)
|
|
148
|
+
}
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
} catch (lintStagedError) {
|
|
151
|
-
if (lintStagedError === errConfigNotFound) {
|
|
152
|
-
logger.error(`${lintStagedError.message}.`)
|
|
153
|
-
} else {
|
|
154
|
-
// It was probably a parsing error
|
|
155
|
-
logger.error(dedent`
|
|
156
|
-
Could not parse lint-staged config.
|
|
157
|
-
|
|
158
|
-
${lintStagedError}
|
|
159
|
-
`)
|
|
151
|
+
printTaskOutput(ctx, logger)
|
|
152
|
+
return false
|
|
160
153
|
}
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
Please make sure you have created it correctly.
|
|
165
|
-
See https://github.com/okonet/lint-staged#configuration.
|
|
166
|
-
`)
|
|
167
|
-
|
|
168
|
-
throw lintStagedError
|
|
154
|
+
|
|
155
|
+
// Probably a compilation error in the config js file. Pass it up to the outer error handler for logging.
|
|
156
|
+
throw runAllError
|
|
169
157
|
}
|
|
170
158
|
}
|
|
159
|
+
|
|
160
|
+
module.exports = lintStaged
|
package/lib/makeCmdTasks.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
const cliTruncate = require('cli-truncate')
|
|
4
4
|
const debug = require('debug')('lint-staged:make-cmd-tasks')
|
|
5
5
|
|
|
6
|
+
const { configurationError } = require('./messages')
|
|
6
7
|
const resolveTaskFn = require('./resolveTaskFn')
|
|
7
|
-
const { createError } = require('./validateConfig')
|
|
8
8
|
|
|
9
9
|
const STDOUT_COLUMNS_DEFAULT = 80
|
|
10
10
|
|
|
@@ -51,7 +51,7 @@ const makeCmdTasks = async ({ commands, files, gitDir, renderer, shell, verbose
|
|
|
51
51
|
// Do the validation here instead of `validateConfig` to skip evaluating the function multiple times
|
|
52
52
|
if (isFn && typeof command !== 'string') {
|
|
53
53
|
throw new Error(
|
|
54
|
-
|
|
54
|
+
configurationError(
|
|
55
55
|
'[Function]',
|
|
56
56
|
'Function task should return a string or an array of strings',
|
|
57
57
|
resolved
|
package/lib/messages.js
CHANGED
|
@@ -2,11 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk')
|
|
4
4
|
const { error, info, warning } = require('log-symbols')
|
|
5
|
+
const format = require('stringify-object')
|
|
6
|
+
|
|
7
|
+
const configurationError = (opt, helpMsg, value) =>
|
|
8
|
+
`${chalk.redBright(`${error} Validation Error:`)}
|
|
9
|
+
|
|
10
|
+
Invalid value for '${chalk.bold(opt)}': ${chalk.bold(
|
|
11
|
+
format(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY })
|
|
12
|
+
)}
|
|
13
|
+
|
|
14
|
+
${helpMsg}`
|
|
5
15
|
|
|
6
16
|
const NOT_GIT_REPO = chalk.redBright(`${error} Current directory is not a git directory!`)
|
|
7
17
|
|
|
8
18
|
const FAILED_GET_STAGED_FILES = chalk.redBright(`${error} Failed to get staged files!`)
|
|
9
19
|
|
|
20
|
+
const incorrectBraces = (before, after) => `${warning} ${chalk.yellow(
|
|
21
|
+
`Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\``
|
|
22
|
+
)}
|
|
23
|
+
`
|
|
24
|
+
|
|
10
25
|
const NO_STAGED_FILES = `${info} No staged files found.`
|
|
11
26
|
|
|
12
27
|
const NO_TASKS = `${info} No staged files match any configured task.`
|
|
@@ -27,6 +42,14 @@ const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'
|
|
|
27
42
|
|
|
28
43
|
const GIT_ERROR = `\n ${error} ${chalk.red(`lint-staged failed due to a git error.`)}`
|
|
29
44
|
|
|
45
|
+
const invalidOption = (name, value, message) => `${chalk.redBright(`${error} Validation Error:`)}
|
|
46
|
+
|
|
47
|
+
Invalid value for option '${chalk.bold(name)}': ${chalk.bold(value)}
|
|
48
|
+
|
|
49
|
+
${message}
|
|
50
|
+
|
|
51
|
+
See https://github.com/okonet/lint-staged#command-line-flags`
|
|
52
|
+
|
|
30
53
|
const PREVENTED_EMPTY_COMMIT = `
|
|
31
54
|
${warning} ${chalk.yellow(`lint-staged prevented an empty git commit.
|
|
32
55
|
Use the --allow-empty option to continue, or check your task configuration`)}
|
|
@@ -42,16 +65,19 @@ const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored from a g
|
|
|
42
65
|
const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'
|
|
43
66
|
|
|
44
67
|
module.exports = {
|
|
45
|
-
|
|
68
|
+
CONFIG_STDIN_ERROR,
|
|
69
|
+
configurationError,
|
|
70
|
+
DEPRECATED_GIT_ADD,
|
|
46
71
|
FAILED_GET_STAGED_FILES,
|
|
72
|
+
GIT_ERROR,
|
|
73
|
+
incorrectBraces,
|
|
74
|
+
invalidOption,
|
|
47
75
|
NO_STAGED_FILES,
|
|
48
76
|
NO_TASKS,
|
|
49
|
-
|
|
50
|
-
DEPRECATED_GIT_ADD,
|
|
51
|
-
TASK_ERROR,
|
|
52
|
-
SKIPPED_GIT_ERROR,
|
|
53
|
-
GIT_ERROR,
|
|
77
|
+
NOT_GIT_REPO,
|
|
54
78
|
PREVENTED_EMPTY_COMMIT,
|
|
55
79
|
RESTORE_STASH_EXAMPLE,
|
|
56
|
-
|
|
80
|
+
SKIPPED_GIT_ERROR,
|
|
81
|
+
skippingBackup,
|
|
82
|
+
TASK_ERROR,
|
|
57
83
|
}
|
package/lib/runAll.js
CHANGED
|
@@ -147,7 +147,8 @@ const runAll = async (
|
|
|
147
147
|
matchedFiles.add(file)
|
|
148
148
|
})
|
|
149
149
|
|
|
150
|
-
hasDeprecatedGitAdd =
|
|
150
|
+
hasDeprecatedGitAdd =
|
|
151
|
+
hasDeprecatedGitAdd || subTasks.some((subTask) => subTask.command === 'git add')
|
|
151
152
|
|
|
152
153
|
chunkListrTasks.push({
|
|
153
154
|
title: `Running tasks for ${task.pattern}`,
|
package/lib/symbols.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const ApplyEmptyCommitError = Symbol('ApplyEmptyCommitError')
|
|
4
|
+
const ConfigNotFoundError = new Error('Config could not be found')
|
|
4
5
|
const GetBackupStashError = Symbol('GetBackupStashError')
|
|
5
6
|
const GetStagedFilesError = Symbol('GetStagedFilesError')
|
|
6
7
|
const GitError = Symbol('GitError')
|
|
7
8
|
const GitRepoError = Symbol('GitRepoError')
|
|
8
9
|
const HideUnstagedChangesError = Symbol('HideUnstagedChangesError')
|
|
10
|
+
const InvalidOptionsError = new Error('Invalid Options')
|
|
9
11
|
const RestoreMergeStatusError = Symbol('RestoreMergeStatusError')
|
|
10
12
|
const RestoreOriginalStateError = Symbol('RestoreOriginalStateError')
|
|
11
13
|
const RestoreUnstagedChangesError = Symbol('RestoreUnstagedChangesError')
|
|
@@ -13,10 +15,12 @@ const TaskError = Symbol('TaskError')
|
|
|
13
15
|
|
|
14
16
|
module.exports = {
|
|
15
17
|
ApplyEmptyCommitError,
|
|
18
|
+
ConfigNotFoundError,
|
|
16
19
|
GetBackupStashError,
|
|
17
20
|
GetStagedFilesError,
|
|
18
21
|
GitError,
|
|
19
22
|
GitRepoError,
|
|
23
|
+
InvalidOptionsError,
|
|
20
24
|
HideUnstagedChangesError,
|
|
21
25
|
RestoreMergeStatusError,
|
|
22
26
|
RestoreOriginalStateError,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const { incorrectBraces } = require('./messages')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A correctly-formed brace expansion must contain unquoted opening and closing braces,
|
|
5
|
+
* and at least one unquoted comma or a valid sequence expression.
|
|
6
|
+
* Any incorrectly formed brace expansion is left unchanged.
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html
|
|
9
|
+
*
|
|
10
|
+
* Lint-staged uses `micromatch` for brace expansion, and its behavior is to treat
|
|
11
|
+
* invalid brace expansions as literal strings, which means they (typically) do not match
|
|
12
|
+
* anything.
|
|
13
|
+
*
|
|
14
|
+
* This RegExp tries to match most cases of invalid brace expansions, so that they can be
|
|
15
|
+
* detected, warned about, and re-formatted by removing the braces and thus hopefully
|
|
16
|
+
* matching the files as intended by the user. The only real fix is to remove the incorrect
|
|
17
|
+
* braces from user configuration, but this is left to the user (after seeing the warning).
|
|
18
|
+
*
|
|
19
|
+
* @example <caption>Globs with brace expansions</caption>
|
|
20
|
+
* - *.{js,tx} // expanded as *.js, *.ts
|
|
21
|
+
* - *.{{j,t}s,css} // expanded as *.js, *.ts, *.css
|
|
22
|
+
* - file_{1..10}.css // expanded as file_1.css, file_2.css, …, file_10.css
|
|
23
|
+
*
|
|
24
|
+
* @example <caption>Globs with incorrect brace expansions</caption>
|
|
25
|
+
* - *.{js} // should just be *.js
|
|
26
|
+
* - *.{js,{ts}} // should just be *.{js,ts}
|
|
27
|
+
* - *.\{js\} // escaped braces, so they're treated literally
|
|
28
|
+
* - *.${js} // dollar-sign inhibits expansion, so treated literally
|
|
29
|
+
* - *.{js\,ts} // the comma is escaped, so treated literally
|
|
30
|
+
*/
|
|
31
|
+
const BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} pattern
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
const withoutIncorrectBraces = (pattern) => {
|
|
38
|
+
let output = `${pattern}`
|
|
39
|
+
let match = null
|
|
40
|
+
|
|
41
|
+
while ((match = BRACES_REGEXP.exec(pattern))) {
|
|
42
|
+
const fullMatch = match[0]
|
|
43
|
+
const withoutBraces = fullMatch.replace(/{/, '').replace(/}/, '')
|
|
44
|
+
output = output.replace(fullMatch, withoutBraces)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return output
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate and remove incorrect brace expansions from glob pattern.
|
|
52
|
+
* For example `*.{js}` is incorrect because it doesn't contain a `,` or `..`,
|
|
53
|
+
* and will be reformatted as `*.js`.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} pattern the glob pattern
|
|
56
|
+
* @param {*} logger
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
const validateBraces = (pattern, logger) => {
|
|
60
|
+
const fixedPattern = withoutIncorrectBraces(pattern)
|
|
61
|
+
|
|
62
|
+
if (fixedPattern !== pattern) {
|
|
63
|
+
logger.warn(incorrectBraces(pattern, fixedPattern))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return fixedPattern
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = validateBraces
|
|
70
|
+
|
|
71
|
+
module.exports.BRACES_REGEXP = BRACES_REGEXP
|
package/lib/validateConfig.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
5
|
-
const chalk = require('chalk')
|
|
6
|
-
const format = require('stringify-object')
|
|
7
|
-
|
|
8
5
|
const debug = require('debug')('lint-staged:cfg')
|
|
9
6
|
|
|
7
|
+
const { configurationError } = require('./messages')
|
|
8
|
+
const validateBraces = require('./validateBraces')
|
|
9
|
+
|
|
10
10
|
const TEST_DEPRECATED_KEYS = new Map([
|
|
11
11
|
['concurrent', (key) => typeof key === 'boolean'],
|
|
12
12
|
['chunkSize', (key) => typeof key === 'number'],
|
|
@@ -18,76 +18,91 @@ const TEST_DEPRECATED_KEYS = new Map([
|
|
|
18
18
|
['relative', (key) => typeof key === 'boolean'],
|
|
19
19
|
])
|
|
20
20
|
|
|
21
|
-
const formatError = (helpMsg) => `● Validation Error:
|
|
22
|
-
|
|
23
|
-
${helpMsg}
|
|
24
|
-
|
|
25
|
-
Please refer to https://github.com/okonet/lint-staged#configuration for more information...`
|
|
26
|
-
|
|
27
|
-
const createError = (opt, helpMsg, value) =>
|
|
28
|
-
formatError(`Invalid value for '${chalk.bold(opt)}'.
|
|
29
|
-
|
|
30
|
-
${helpMsg}.
|
|
31
|
-
|
|
32
|
-
Configured value is: ${chalk.bold(
|
|
33
|
-
format(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY })
|
|
34
|
-
)}`)
|
|
35
|
-
|
|
36
21
|
/**
|
|
37
22
|
* Runs config validation. Throws error if the config is not valid.
|
|
38
23
|
* @param config {Object}
|
|
39
24
|
* @returns config {Object}
|
|
40
25
|
*/
|
|
41
|
-
|
|
26
|
+
const validateConfig = (config, logger) => {
|
|
42
27
|
debug('Validating config')
|
|
43
28
|
|
|
44
|
-
|
|
29
|
+
if (!config || (typeof config !== 'object' && typeof config !== 'function')) {
|
|
30
|
+
throw new Error('Configuration should be an object or a function!')
|
|
31
|
+
}
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Function configurations receive all staged files as their argument.
|
|
35
|
+
* They are not further validated here to make sure the function gets
|
|
36
|
+
* evaluated only once.
|
|
37
|
+
*
|
|
38
|
+
* @see makeCmdTasks
|
|
39
|
+
*/
|
|
40
|
+
if (typeof config === 'function') {
|
|
41
|
+
return { '*': config }
|
|
42
|
+
}
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
if (Object.entries(config).length === 0) {
|
|
45
|
+
throw new Error('Configuration should not be empty!')
|
|
46
|
+
}
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
if (TEST_DEPRECATED_KEYS.has(pattern)) {
|
|
57
|
-
const testFn = TEST_DEPRECATED_KEYS.get(pattern)
|
|
58
|
-
if (testFn(task)) {
|
|
59
|
-
errors.push(
|
|
60
|
-
createError(
|
|
61
|
-
pattern,
|
|
62
|
-
'Advanced configuration has been deprecated. For more info, please visit: https://github.com/okonet/lint-staged',
|
|
63
|
-
task
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
48
|
+
const errors = []
|
|
68
49
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Create a new validated config because the keys (patterns) might change.
|
|
52
|
+
* Since the Object.reduce method already loops through each entry in the config,
|
|
53
|
+
* it can be used for validating the values at the same time.
|
|
54
|
+
*/
|
|
55
|
+
const validatedConfig = Object.entries(config).reduce((collection, [pattern, task]) => {
|
|
56
|
+
/** Versions < 9 had more complex configuration options that are no longer supported. */
|
|
57
|
+
if (TEST_DEPRECATED_KEYS.has(pattern)) {
|
|
58
|
+
const testFn = TEST_DEPRECATED_KEYS.get(pattern)
|
|
59
|
+
if (testFn(task)) {
|
|
75
60
|
errors.push(
|
|
76
|
-
|
|
77
|
-
pattern,
|
|
78
|
-
'Should be a string, a function, or an array of strings and functions',
|
|
79
|
-
task
|
|
80
|
-
)
|
|
61
|
+
configurationError(pattern, 'Advanced configuration has been deprecated.', task)
|
|
81
62
|
)
|
|
82
63
|
}
|
|
83
|
-
|
|
84
|
-
|
|
64
|
+
|
|
65
|
+
/** Return early for deprecated keys to skip validating their (deprecated) values */
|
|
66
|
+
return collection
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
(!Array.isArray(task) ||
|
|
71
|
+
task.some((item) => typeof item !== 'string' && typeof item !== 'function')) &&
|
|
72
|
+
typeof task !== 'string' &&
|
|
73
|
+
typeof task !== 'function'
|
|
74
|
+
) {
|
|
75
|
+
errors.push(
|
|
76
|
+
configurationError(
|
|
77
|
+
pattern,
|
|
78
|
+
'Should be a string, a function, or an array of strings and functions.',
|
|
79
|
+
task
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A typical configuration error is using invalid brace expansion, like `*.{js}`.
|
|
86
|
+
* These are automatically fixed and warned about.
|
|
87
|
+
*/
|
|
88
|
+
const fixedPattern = validateBraces(pattern, logger)
|
|
89
|
+
|
|
90
|
+
return { ...collection, [fixedPattern]: task }
|
|
91
|
+
}, {})
|
|
85
92
|
|
|
86
93
|
if (errors.length) {
|
|
87
|
-
|
|
94
|
+
const message = errors.join('\n\n')
|
|
95
|
+
|
|
96
|
+
logger.error(`Could not parse lint-staged config.
|
|
97
|
+
|
|
98
|
+
${message}
|
|
99
|
+
|
|
100
|
+
See https://github.com/okonet/lint-staged#configuration.`)
|
|
101
|
+
|
|
102
|
+
throw new Error(message)
|
|
88
103
|
}
|
|
89
104
|
|
|
90
|
-
return
|
|
105
|
+
return validatedConfig
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
module.exports
|
|
108
|
+
module.exports = validateConfig
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { promises: fs, constants } = require('fs')
|
|
2
|
+
|
|
3
|
+
const { invalidOption } = require('./messages')
|
|
4
|
+
const { InvalidOptionsError } = require('./symbols')
|
|
5
|
+
|
|
6
|
+
const debug = require('debug')('lint-staged:options')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate lint-staged options, either from the Node.js API or the command line flags.
|
|
10
|
+
* @param {*} options
|
|
11
|
+
* @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
|
|
12
|
+
*
|
|
13
|
+
* @throws {InvalidOptionsError}
|
|
14
|
+
*/
|
|
15
|
+
const validateOptions = async (options = {}, logger) => {
|
|
16
|
+
debug('Validating options...')
|
|
17
|
+
|
|
18
|
+
/** Ensure the passed shell option is executable */
|
|
19
|
+
if (typeof options.shell === 'string') {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(options.shell, constants.X_OK)
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.error(invalidOption('shell', options.shell, error.message))
|
|
24
|
+
throw InvalidOptionsError
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
debug('Validated options!')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = validateOptions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lint-staged",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.1.2",
|
|
4
4
|
"description": "Lint files staged by git",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "https://github.com/okonet/lint-staged",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"commander": "^7.2.0",
|
|
33
33
|
"cosmiconfig": "^7.0.0",
|
|
34
34
|
"debug": "^4.3.1",
|
|
35
|
-
"dedent": "^0.7.0",
|
|
36
35
|
"enquirer": "^2.3.6",
|
|
37
36
|
"execa": "^5.0.0",
|
|
38
37
|
"listr2": "^3.8.2",
|