lint-staged 12.3.8 → 12.4.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/bin/lint-staged.js +2 -19
- package/lib/generateTasks.js +2 -3
- package/lib/groupFilesByConfig.js +59 -0
- package/lib/index.js +35 -4
- package/lib/messages.js +2 -0
- package/lib/parseGitZOutput.js +4 -2
- package/lib/resolveTaskFn.js +4 -2
- package/lib/runAll.js +15 -12
- package/lib/searchConfigs.js +79 -23
- package/package.json +1 -1
- package/lib/getConfigGroups.js +0 -105
package/bin/lint-staged.js
CHANGED
|
@@ -34,6 +34,7 @@ cmdline
|
|
|
34
34
|
.option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
|
|
35
35
|
.option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
|
|
36
36
|
.option('-d, --debug', 'print additional debug information', false)
|
|
37
|
+
.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
|
|
37
38
|
.option('--no-stash', 'disable the backup stash, and do not revert in case of errors', false)
|
|
38
39
|
.option('-q, --quiet', 'disable lint-staged’s own console output', false)
|
|
39
40
|
.option('-r, --relative', 'pass relative filepaths to tasks', false)
|
|
@@ -54,31 +55,13 @@ if (cmdlineOptions.debug) {
|
|
|
54
55
|
const debugLog = debug('lint-staged:bin')
|
|
55
56
|
debugLog('Running `lint-staged@%s`', version)
|
|
56
57
|
|
|
57
|
-
/**
|
|
58
|
-
* Get the maximum length of a command-line argument string based on current platform
|
|
59
|
-
*
|
|
60
|
-
* https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
|
|
61
|
-
* https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
|
|
62
|
-
* https://unix.stackexchange.com/a/120652
|
|
63
|
-
*/
|
|
64
|
-
const getMaxArgLength = () => {
|
|
65
|
-
switch (process.platform) {
|
|
66
|
-
case 'darwin':
|
|
67
|
-
return 262144
|
|
68
|
-
case 'win32':
|
|
69
|
-
return 8191
|
|
70
|
-
default:
|
|
71
|
-
return 131072
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
58
|
const options = {
|
|
76
59
|
allowEmpty: !!cmdlineOptions.allowEmpty,
|
|
77
60
|
concurrent: JSON.parse(cmdlineOptions.concurrent),
|
|
78
61
|
configPath: cmdlineOptions.config,
|
|
79
62
|
cwd: cmdlineOptions.cwd,
|
|
80
63
|
debug: !!cmdlineOptions.debug,
|
|
81
|
-
maxArgLength:
|
|
64
|
+
maxArgLength: cmdlineOptions.maxArgLength || undefined,
|
|
82
65
|
quiet: !!cmdlineOptions.quiet,
|
|
83
66
|
relative: !!cmdlineOptions.relative,
|
|
84
67
|
shell: cmdlineOptions.shell /* Either a boolean or a string pointing to the shell */,
|
package/lib/generateTasks.js
CHANGED
|
@@ -21,9 +21,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f
|
|
|
21
21
|
|
|
22
22
|
const relativeFiles = files.map((file) => normalize(path.relative(cwd, file)))
|
|
23
23
|
|
|
24
|
-
return Object.entries(config).map(([
|
|
25
|
-
let pattern = rawPattern
|
|
26
|
-
|
|
24
|
+
return Object.entries(config).map(([pattern, commands]) => {
|
|
27
25
|
const isParentDirPattern = pattern.startsWith('../')
|
|
28
26
|
|
|
29
27
|
// Only worry about children of the CWD unless the pattern explicitly
|
|
@@ -40,6 +38,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f
|
|
|
40
38
|
// match against filenames in every directory. This makes `*.js`
|
|
41
39
|
// match both `test.js` and `subdirectory/test.js`.
|
|
42
40
|
matchBase: !pattern.includes('/'),
|
|
41
|
+
posixSlashes: true,
|
|
43
42
|
strictBrackets: true,
|
|
44
43
|
})
|
|
45
44
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import debug from 'debug'
|
|
4
|
+
|
|
5
|
+
import { ConfigObjectSymbol } from './searchConfigs.js'
|
|
6
|
+
|
|
7
|
+
const debugLog = debug('lint-staged:groupFilesByConfig')
|
|
8
|
+
|
|
9
|
+
export const groupFilesByConfig = async ({ configs, files }) => {
|
|
10
|
+
debugLog('Grouping %d files by %d configurations', files.length, Object.keys(configs).length)
|
|
11
|
+
|
|
12
|
+
const filesSet = new Set(files)
|
|
13
|
+
const filesByConfig = {}
|
|
14
|
+
|
|
15
|
+
/** Configs are sorted deepest first by `searchConfigs` */
|
|
16
|
+
for (const filepath of Reflect.ownKeys(configs)) {
|
|
17
|
+
const config = configs[filepath]
|
|
18
|
+
|
|
19
|
+
/** When passed an explicit config object via the Node.js API, skip logic */
|
|
20
|
+
if (filepath === ConfigObjectSymbol) {
|
|
21
|
+
filesByConfig[filepath] = { config, files }
|
|
22
|
+
break
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dir = path.normalize(path.dirname(filepath))
|
|
26
|
+
|
|
27
|
+
/** Check if file is inside directory of the configuration file */
|
|
28
|
+
const isInsideDir = (file) => {
|
|
29
|
+
const relative = path.relative(dir, file)
|
|
30
|
+
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** This config should match all files since it has a parent glob */
|
|
34
|
+
const includeAllFiles = Object.keys(config).some((glob) => glob.startsWith('..'))
|
|
35
|
+
|
|
36
|
+
const scopedFiles = new Set(includeAllFiles ? filesSet : undefined)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Without a parent glob, if file is inside the config file's directory,
|
|
40
|
+
* assign it to that configuration.
|
|
41
|
+
*/
|
|
42
|
+
if (!includeAllFiles) {
|
|
43
|
+
filesSet.forEach((file) => {
|
|
44
|
+
if (isInsideDir(file)) {
|
|
45
|
+
scopedFiles.add(file)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Files should only match a single config */
|
|
51
|
+
scopedFiles.forEach((file) => {
|
|
52
|
+
filesSet.delete(file)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
filesByConfig[filepath] = { config, files: Array.from(scopedFiles) }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return filesByConfig
|
|
59
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import debug from 'debug'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PREVENTED_EMPTY_COMMIT,
|
|
5
|
+
GIT_ERROR,
|
|
6
|
+
RESTORE_STASH_EXAMPLE,
|
|
7
|
+
NO_CONFIGURATION,
|
|
8
|
+
} from './messages.js'
|
|
4
9
|
import { printTaskOutput } from './printTaskOutput.js'
|
|
5
10
|
import { runAll } from './runAll.js'
|
|
6
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ApplyEmptyCommitError,
|
|
13
|
+
ConfigNotFoundError,
|
|
14
|
+
GetBackupStashError,
|
|
15
|
+
GitError,
|
|
16
|
+
} from './symbols.js'
|
|
7
17
|
import { validateOptions } from './validateOptions.js'
|
|
8
18
|
|
|
9
19
|
const debugLog = debug('lint-staged')
|
|
10
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Get the maximum length of a command-line argument string based on current platform
|
|
23
|
+
*
|
|
24
|
+
* https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
|
|
25
|
+
* https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
|
|
26
|
+
* https://unix.stackexchange.com/a/120652
|
|
27
|
+
*/
|
|
28
|
+
const getMaxArgLength = () => {
|
|
29
|
+
switch (process.platform) {
|
|
30
|
+
case 'darwin':
|
|
31
|
+
return 262144
|
|
32
|
+
case 'win32':
|
|
33
|
+
return 8191
|
|
34
|
+
default:
|
|
35
|
+
return 131072
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
11
39
|
/**
|
|
12
40
|
* @typedef {(...any) => void} LogFunction
|
|
13
41
|
* @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
|
|
@@ -39,7 +67,7 @@ const lintStaged = async (
|
|
|
39
67
|
configPath,
|
|
40
68
|
cwd,
|
|
41
69
|
debug = false,
|
|
42
|
-
maxArgLength,
|
|
70
|
+
maxArgLength = getMaxArgLength() / 2,
|
|
43
71
|
quiet = false,
|
|
44
72
|
relative = false,
|
|
45
73
|
shell = false,
|
|
@@ -78,7 +106,10 @@ const lintStaged = async (
|
|
|
78
106
|
} catch (runAllError) {
|
|
79
107
|
if (runAllError && runAllError.ctx && runAllError.ctx.errors) {
|
|
80
108
|
const { ctx } = runAllError
|
|
81
|
-
|
|
109
|
+
|
|
110
|
+
if (ctx.errors.has(ConfigNotFoundError)) {
|
|
111
|
+
logger.error(NO_CONFIGURATION)
|
|
112
|
+
} else if (ctx.errors.has(ApplyEmptyCommitError)) {
|
|
82
113
|
logger.warn(PREVENTED_EMPTY_COMMIT)
|
|
83
114
|
} else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
|
|
84
115
|
logger.error(GIT_ERROR)
|
package/lib/messages.js
CHANGED
|
@@ -22,6 +22,8 @@ export const incorrectBraces = (before, after) =>
|
|
|
22
22
|
`
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
+
export const NO_CONFIGURATION = `${error} No valid configuration found.`
|
|
26
|
+
|
|
25
27
|
export const NO_STAGED_FILES = `${info} No staged files found.`
|
|
26
28
|
|
|
27
29
|
export const NO_TASKS = `${info} No staged files match any configured task.`
|
package/lib/parseGitZOutput.js
CHANGED
package/lib/resolveTaskFn.js
CHANGED
|
@@ -62,8 +62,10 @@ const interruptExecutionOnError = (ctx, execaChildProcess) => {
|
|
|
62
62
|
if (ctx.errors.size > 0) {
|
|
63
63
|
clearInterval(loopIntervalId)
|
|
64
64
|
|
|
65
|
-
const
|
|
66
|
-
|
|
65
|
+
const childPids = await pidTree(execaChildProcess.pid)
|
|
66
|
+
for (const pid of childPids) {
|
|
67
|
+
process.kill(pid)
|
|
68
|
+
}
|
|
67
69
|
|
|
68
70
|
// The execa process is killed separately in order
|
|
69
71
|
// to get the `KILLED` status.
|
package/lib/runAll.js
CHANGED
|
@@ -10,10 +10,10 @@ import normalize from 'normalize-path'
|
|
|
10
10
|
import { chunkFiles } from './chunkFiles.js'
|
|
11
11
|
import { execGit } from './execGit.js'
|
|
12
12
|
import { generateTasks } from './generateTasks.js'
|
|
13
|
-
import { getConfigGroups } from './getConfigGroups.js'
|
|
14
13
|
import { getRenderer } from './getRenderer.js'
|
|
15
14
|
import { getStagedFiles } from './getStagedFiles.js'
|
|
16
15
|
import { GitWorkflow } from './gitWorkflow.js'
|
|
16
|
+
import { groupFilesByConfig } from './groupFilesByConfig.js'
|
|
17
17
|
import { makeCmdTasks } from './makeCmdTasks.js'
|
|
18
18
|
import {
|
|
19
19
|
DEPRECATED_GIT_ADD,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
restoreUnstagedChangesSkipped,
|
|
37
37
|
} from './state.js'
|
|
38
38
|
import { GitRepoError, GetStagedFilesError, GitError, ConfigNotFoundError } from './symbols.js'
|
|
39
|
-
import { searchConfigs } from './searchConfigs.js'
|
|
39
|
+
import { ConfigObjectSymbol, searchConfigs } from './searchConfigs.js'
|
|
40
40
|
|
|
41
41
|
const debugLog = debug('lint-staged:runAll')
|
|
42
42
|
|
|
@@ -120,11 +120,8 @@ export const runAll = async (
|
|
|
120
120
|
return ctx
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const hasExplicitConfig = configObject || configPath
|
|
126
|
-
const foundConfigs = hasExplicitConfig ? null : await searchConfigs(gitDir, logger)
|
|
127
|
-
const numberOfConfigs = hasExplicitConfig ? 1 : Object.keys(foundConfigs).length
|
|
123
|
+
const foundConfigs = await searchConfigs({ configObject, configPath, cwd, gitDir }, logger)
|
|
124
|
+
const numberOfConfigs = Reflect.ownKeys(foundConfigs).length
|
|
128
125
|
|
|
129
126
|
// Throw if no configurations were found
|
|
130
127
|
if (numberOfConfigs === 0) {
|
|
@@ -132,7 +129,7 @@ export const runAll = async (
|
|
|
132
129
|
throw createError(ctx, ConfigNotFoundError)
|
|
133
130
|
}
|
|
134
131
|
|
|
135
|
-
|
|
132
|
+
const filesByConfig = await groupFilesByConfig({ configs: foundConfigs, files })
|
|
136
133
|
|
|
137
134
|
const hasMultipleConfigs = numberOfConfigs > 1
|
|
138
135
|
|
|
@@ -152,8 +149,14 @@ export const runAll = async (
|
|
|
152
149
|
// Set of all staged files that matched a task glob. Values in a set are unique.
|
|
153
150
|
const matchedFiles = new Set()
|
|
154
151
|
|
|
155
|
-
for (const
|
|
156
|
-
const
|
|
152
|
+
for (const configPath of Reflect.ownKeys(filesByConfig)) {
|
|
153
|
+
const { config, files } = filesByConfig[configPath]
|
|
154
|
+
|
|
155
|
+
const configName =
|
|
156
|
+
configPath === ConfigObjectSymbol
|
|
157
|
+
? 'Config object'
|
|
158
|
+
: normalize(path.relative(cwd, configPath))
|
|
159
|
+
|
|
157
160
|
const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
|
|
158
161
|
|
|
159
162
|
// Use actual cwd if it's specified, or there's only a single config file.
|
|
@@ -219,7 +222,7 @@ export const runAll = async (
|
|
|
219
222
|
|
|
220
223
|
listrTasks.push({
|
|
221
224
|
title:
|
|
222
|
-
`${
|
|
225
|
+
`${configName}${dim(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` +
|
|
223
226
|
(chunkCount > 1 ? dim(` (chunk ${index + 1}/${chunkCount})...`) : ''),
|
|
224
227
|
task: () => new Listr(chunkListrTasks, { ...listrOptions, concurrent, exitOnError: true }),
|
|
225
228
|
skip: () => {
|
|
@@ -227,7 +230,7 @@ export const runAll = async (
|
|
|
227
230
|
if (ctx.errors.has(GitError)) return SKIPPED_GIT_ERROR
|
|
228
231
|
// Skip chunk when no every task is skipped (due to no matches)
|
|
229
232
|
if (chunkListrTasks.every((task) => task.skip())) {
|
|
230
|
-
return `${
|
|
233
|
+
return `${configName}${dim(' — no tasks to run')}`
|
|
231
234
|
}
|
|
232
235
|
return false
|
|
233
236
|
},
|
package/lib/searchConfigs.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { basename, join } from 'path'
|
|
4
4
|
|
|
5
|
+
import debug from 'debug'
|
|
5
6
|
import normalize from 'normalize-path'
|
|
6
7
|
|
|
7
8
|
import { execGit } from './execGit.js'
|
|
@@ -9,38 +10,72 @@ import { loadConfig, searchPlaces } from './loadConfig.js'
|
|
|
9
10
|
import { parseGitZOutput } from './parseGitZOutput.js'
|
|
10
11
|
import { validateConfig } from './validateConfig.js'
|
|
11
12
|
|
|
13
|
+
const debugLog = debug('lint-staged:searchConfigs')
|
|
14
|
+
|
|
12
15
|
const EXEC_GIT = ['ls-files', '-z', '--full-name']
|
|
13
16
|
|
|
14
|
-
const filterPossibleConfigFiles = (
|
|
17
|
+
const filterPossibleConfigFiles = (files) =>
|
|
18
|
+
files.filter((file) => searchPlaces.includes(basename(file)))
|
|
15
19
|
|
|
16
20
|
const numberOfLevels = (file) => file.split('/').length
|
|
17
21
|
|
|
18
22
|
const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 : 1)
|
|
19
23
|
|
|
24
|
+
const isInsideDirectory = (dir) => (file) => file.startsWith(normalize(dir))
|
|
25
|
+
|
|
26
|
+
export const ConfigObjectSymbol = Symbol()
|
|
27
|
+
|
|
20
28
|
/**
|
|
21
|
-
* Search all config files from the git repository
|
|
29
|
+
* Search all config files from the git repository, preferring those inside `cwd`.
|
|
22
30
|
*
|
|
23
|
-
* @param {
|
|
31
|
+
* @param {object} options
|
|
32
|
+
* @param {Object} [options.configObject] - Explicit config object from the js API
|
|
33
|
+
* @param {string} [options.configPath] - Explicit path to a config file
|
|
34
|
+
* @param {string} [options.cwd] - Current working directory
|
|
24
35
|
* @param {Logger} logger
|
|
25
|
-
*
|
|
36
|
+
*
|
|
37
|
+
* @returns {Promise<{ [key: string]: { config: *, files: string[] } }>} found configs with filepath as key, and config as value
|
|
26
38
|
*/
|
|
27
|
-
export const searchConfigs = async (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
39
|
+
export const searchConfigs = async (
|
|
40
|
+
{ configObject, configPath, cwd = process.cwd(), gitDir = cwd },
|
|
41
|
+
logger
|
|
42
|
+
) => {
|
|
43
|
+
debugLog('Searching for configuration files...')
|
|
44
|
+
|
|
45
|
+
// Return explicit config object from js API
|
|
46
|
+
if (configObject) {
|
|
47
|
+
debugLog('Using single direct configuration object...')
|
|
48
|
+
|
|
49
|
+
return { [ConfigObjectSymbol]: validateConfig(configObject, 'config object', logger) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Use only explicit config path instead of discovering multiple
|
|
53
|
+
if (configPath) {
|
|
54
|
+
debugLog('Using single configuration path...')
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
const { config, filepath } = await loadConfig({ configPath }, logger)
|
|
57
|
+
|
|
58
|
+
if (!config) return {}
|
|
59
|
+
return { [configPath]: validateConfig(config, filepath, logger) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const [cachedFiles, otherFiles] = await Promise.all([
|
|
63
|
+
/** Get all possible config files known to git */
|
|
64
|
+
execGit(EXEC_GIT, { cwd: gitDir }).then(parseGitZOutput).then(filterPossibleConfigFiles),
|
|
65
|
+
/** Get all possible config files from uncommitted files */
|
|
66
|
+
execGit([...EXEC_GIT, '--others', '--exclude-standard'], { cwd: gitDir })
|
|
67
|
+
.then(parseGitZOutput)
|
|
68
|
+
.then(filterPossibleConfigFiles),
|
|
69
|
+
])
|
|
37
70
|
|
|
38
71
|
/** Sort possible config files so that deepest is first */
|
|
39
72
|
const possibleConfigFiles = [...cachedFiles, ...otherFiles]
|
|
40
|
-
.map((file) => join(gitDir, file))
|
|
41
|
-
.
|
|
73
|
+
.map((file) => normalize(join(gitDir, file)))
|
|
74
|
+
.filter(isInsideDirectory(cwd))
|
|
42
75
|
.sort(sortDeepestParth)
|
|
43
76
|
|
|
77
|
+
debugLog('Found possible config files:', possibleConfigFiles)
|
|
78
|
+
|
|
44
79
|
/** Create object with key as config file, and value as null */
|
|
45
80
|
const configs = possibleConfigFiles.reduce(
|
|
46
81
|
(acc, configPath) => Object.assign(acc, { [configPath]: null }),
|
|
@@ -49,15 +84,17 @@ export const searchConfigs = async (gitDir = process.cwd(), logger) => {
|
|
|
49
84
|
|
|
50
85
|
/** Load and validate all configs to the above object */
|
|
51
86
|
await Promise.all(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
configs[filepath] = validateConfig(config, filepath, logger)
|
|
87
|
+
Object.keys(configs).map((configPath) =>
|
|
88
|
+
loadConfig({ configPath }, logger).then(({ config, filepath }) => {
|
|
89
|
+
if (config) {
|
|
90
|
+
if (configPath !== filepath) {
|
|
91
|
+
debugLog('Config file "%s" resolved to "%s"', configPath, filepath)
|
|
58
92
|
}
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
|
|
94
|
+
configs[configPath] = validateConfig(config, filepath, logger)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
)
|
|
61
98
|
)
|
|
62
99
|
|
|
63
100
|
/** Get validated configs from the above object, without any `null` values (not found) */
|
|
@@ -65,5 +102,24 @@ export const searchConfigs = async (gitDir = process.cwd(), logger) => {
|
|
|
65
102
|
.filter(([, value]) => !!value)
|
|
66
103
|
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
|
67
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Try to find a single config from parent directories
|
|
107
|
+
* to match old behavior before monorepo support
|
|
108
|
+
*/
|
|
109
|
+
if (!Object.keys(foundConfigs).length) {
|
|
110
|
+
debugLog('Could not find config files inside "%s"', cwd)
|
|
111
|
+
|
|
112
|
+
const { config, filepath } = await loadConfig({ cwd }, logger)
|
|
113
|
+
if (config) {
|
|
114
|
+
debugLog('Found parent configuration file from "%s"', filepath)
|
|
115
|
+
|
|
116
|
+
foundConfigs[filepath] = validateConfig(config, filepath, logger)
|
|
117
|
+
} else {
|
|
118
|
+
debugLog('Could not find parent configuration files from "%s"', cwd)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
debugLog('Found %d config files', Object.keys(foundConfigs).length)
|
|
123
|
+
|
|
68
124
|
return foundConfigs
|
|
69
125
|
}
|
package/package.json
CHANGED
package/lib/getConfigGroups.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/** @typedef {import('./index').Logger} Logger */
|
|
2
|
-
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
import debug from 'debug'
|
|
6
|
-
import objectInspect from 'object-inspect'
|
|
7
|
-
|
|
8
|
-
import { loadConfig } from './loadConfig.js'
|
|
9
|
-
import { ConfigNotFoundError } from './symbols.js'
|
|
10
|
-
import { validateConfig } from './validateConfig.js'
|
|
11
|
-
|
|
12
|
-
const debugLog = debug('lint-staged:getConfigGroups')
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Return matched files grouped by their configuration.
|
|
16
|
-
*
|
|
17
|
-
* @param {object} options
|
|
18
|
-
* @param {Object} [options.configObject] - Explicit config object from the js API
|
|
19
|
-
* @param {string} [options.configPath] - Explicit path to a config file
|
|
20
|
-
* @param {string} [options.cwd] - Current working directory
|
|
21
|
-
* @param {string} [options.files] - List of staged files
|
|
22
|
-
* @param {Logger} logger
|
|
23
|
-
*/
|
|
24
|
-
export const getConfigGroups = async (
|
|
25
|
-
{ configObject, configPath, cwd, files },
|
|
26
|
-
logger = console
|
|
27
|
-
) => {
|
|
28
|
-
debugLog('Grouping configuration files...')
|
|
29
|
-
|
|
30
|
-
// Return explicit config object from js API
|
|
31
|
-
if (configObject) {
|
|
32
|
-
debugLog('Using single direct configuration object...')
|
|
33
|
-
|
|
34
|
-
const config = validateConfig(configObject, 'config object', logger)
|
|
35
|
-
return { '': { config, files } }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Use only explicit config path instead of discovering multiple
|
|
39
|
-
if (configPath) {
|
|
40
|
-
debugLog('Using single configuration path...')
|
|
41
|
-
|
|
42
|
-
const { config, filepath } = await loadConfig({ configPath }, logger)
|
|
43
|
-
|
|
44
|
-
if (!config) {
|
|
45
|
-
logger.error(`${ConfigNotFoundError.message}.`)
|
|
46
|
-
throw ConfigNotFoundError
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const validatedConfig = validateConfig(config, filepath, logger)
|
|
50
|
-
return { [configPath]: { config: validatedConfig, files } }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
debugLog('Grouping staged files by their directories...')
|
|
54
|
-
|
|
55
|
-
// Group files by their base directory
|
|
56
|
-
const filesByDir = files.reduce((acc, file) => {
|
|
57
|
-
const dir = path.normalize(path.dirname(file))
|
|
58
|
-
|
|
59
|
-
if (dir in acc) {
|
|
60
|
-
acc[dir].push(file)
|
|
61
|
-
} else {
|
|
62
|
-
acc[dir] = [file]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return acc
|
|
66
|
-
}, {})
|
|
67
|
-
|
|
68
|
-
debugLog('Grouped staged files into %d directories:', Object.keys(filesByDir).length)
|
|
69
|
-
debugLog(objectInspect(filesByDir, { indent: 2 }))
|
|
70
|
-
|
|
71
|
-
// Group files by their discovered config
|
|
72
|
-
// { '.lintstagedrc.json': { config: {...}, files: [...] } }
|
|
73
|
-
const configGroups = {}
|
|
74
|
-
|
|
75
|
-
debugLog('Searching config files...')
|
|
76
|
-
|
|
77
|
-
const searchConfig = async (cwd, files = []) => {
|
|
78
|
-
const { config, filepath } = await loadConfig({ cwd }, logger)
|
|
79
|
-
if (!config) {
|
|
80
|
-
debugLog('Found no config from "%s"!', cwd)
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (filepath in configGroups) {
|
|
85
|
-
debugLog('Found existing config "%s" from "%s"!', filepath, cwd)
|
|
86
|
-
// Re-use cached config and skip validation
|
|
87
|
-
configGroups[filepath].files.push(...files)
|
|
88
|
-
} else {
|
|
89
|
-
debugLog('Found new config "%s" from "%s"!', filepath, cwd)
|
|
90
|
-
|
|
91
|
-
const validatedConfig = validateConfig(config, filepath, logger)
|
|
92
|
-
configGroups[filepath] = { config: validatedConfig, files }
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Start by searching from cwd
|
|
97
|
-
await searchConfig(cwd)
|
|
98
|
-
|
|
99
|
-
// Discover configs from the base directory of each file
|
|
100
|
-
await Promise.all(Object.entries(filesByDir).map(([dir, files]) => searchConfig(dir, files)))
|
|
101
|
-
|
|
102
|
-
debugLog('Grouped staged files into %d groups!', Object.keys(configGroups).length)
|
|
103
|
-
|
|
104
|
-
return configGroups
|
|
105
|
-
}
|