lint-staged 12.3.7 → 12.4.1

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.
@@ -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', 'maximum length of the command-line argument string')
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: getMaxArgLength() / 2,
64
+ maxArgLength: JSON.parse(cmdlineOptions.maxArgLength || null),
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 */,
@@ -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(([rawPattern, commands]) => {
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 { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } from './messages.js'
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 { ApplyEmptyCommitError, GetBackupStashError, GitError } from './symbols.js'
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
- if (ctx.errors.has(ApplyEmptyCommitError)) {
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.`
@@ -5,5 +5,7 @@
5
5
  */
6
6
  export const parseGitZOutput = (input) =>
7
7
  input
8
- .replace(/\u0000$/, '') // eslint-disable-line no-control-regex
9
- .split('\u0000')
8
+ ? input
9
+ .replace(/\u0000$/, '') // eslint-disable-line no-control-regex
10
+ .split('\u0000')
11
+ : []
@@ -56,10 +56,16 @@ const handleOutput = (command, result, ctx, isError = false) => {
56
56
  * checks the context.
57
57
  */
58
58
  const interruptExecutionOnError = (ctx, execaChildProcess) => {
59
+ let loopIntervalId
60
+
59
61
  async function loop() {
60
62
  if (ctx.errors.size > 0) {
61
- const ids = await pidTree(execaChildProcess.pid)
62
- ids.forEach(process.kill)
63
+ clearInterval(loopIntervalId)
64
+
65
+ const childPids = await pidTree(execaChildProcess.pid)
66
+ for (const pid of childPids) {
67
+ process.kill(pid)
68
+ }
63
69
 
64
70
  // The execa process is killed separately in order
65
71
  // to get the `KILLED` status.
@@ -67,7 +73,7 @@ const interruptExecutionOnError = (ctx, execaChildProcess) => {
67
73
  }
68
74
  }
69
75
 
70
- const loopIntervalId = setInterval(loop, ERROR_CHECK_INTERVAL)
76
+ loopIntervalId = setInterval(loop, ERROR_CHECK_INTERVAL)
71
77
 
72
78
  return () => {
73
79
  clearInterval(loopIntervalId)
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 configGroups = await getConfigGroups({ configObject, configPath, cwd, files }, logger)
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
- debugLog('Found %d configs:\n%O', numberOfConfigs, foundConfigs)
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 [configPath, { config, files }] of Object.entries(configGroups)) {
156
- const relativeConfig = normalize(path.relative(cwd, configPath))
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
- `${relativeConfig}${dim(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` +
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 `${relativeConfig}${dim(' — no tasks to run')}`
233
+ return `${configName}${dim(' — no tasks to run')}`
231
234
  }
232
235
  return false
233
236
  },
@@ -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 = (file) => searchPlaces.includes(basename(file))
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 {string} gitDir
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
- * @returns {Promise<{ [key: string]: * }>} found configs with filepath as key, and config as value
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 (gitDir = process.cwd(), logger) => {
28
- /** Get all possible config files known to git */
29
- const cachedFiles = parseGitZOutput(await execGit(EXEC_GIT, { cwd: gitDir })).filter(
30
- filterPossibleConfigFiles
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
- /** Get all possible config files from uncommitted files */
34
- const otherFiles = parseGitZOutput(
35
- await execGit([...EXEC_GIT, '--others', '--exclude-standard'], { cwd: gitDir })
36
- ).filter(filterPossibleConfigFiles)
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
- .map((file) => normalize(file))
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
- possibleConfigFiles
53
- .map((configPath) => loadConfig({ configPath }, logger))
54
- .map((promise) =>
55
- promise.then(({ config, filepath }) => {
56
- if (config) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "12.3.7",
3
+ "version": "12.4.1",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",
@@ -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
- }