UNPKG

4.26 kBJavaScriptView Raw
1/** @typedef {import('./index').Logger} Logger */
2
3import path from 'node:path'
4
5import debug from 'debug'
6import normalize from 'normalize-path'
7
8import { execGit } from './execGit.js'
9import { loadConfig, searchPlaces } from './loadConfig.js'
10import { parseGitZOutput } from './parseGitZOutput.js'
11import { validateConfig } from './validateConfig.js'
12
13const debugLog = debug('lint-staged:searchConfigs')
14
15const EXEC_GIT = ['ls-files', '-z', '--full-name']
16
17const filterPossibleConfigFiles = (files) =>
18 files.filter((file) => searchPlaces.includes(path.basename(file)))
19
20const numberOfLevels = (file) => file.split('/').length
21
22const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 : 1)
23
24const isInsideDirectory = (dir) => (file) => file.startsWith(normalize(dir))
25
26/**
27 * Search all config files from the git repository, preferring those inside `cwd`.
28 *
29 * @param {object} options
30 * @param {Object} [options.configObject] - Explicit config object from the js API
31 * @param {string} [options.configPath] - Explicit path to a config file
32 * @param {string} [options.cwd] - Current working directory
33 * @param {Logger} logger
34 *
35 * @returns {Promise<{ [key: string]: { config: *, files: string[] } }>} found configs with filepath as key, and config as value
36 */
37export const searchConfigs = async (
38 { configObject, configPath, cwd = process.cwd(), gitDir = cwd },
39 logger
40) => {
41 debugLog('Searching for configuration files...')
42
43 // Return explicit config object from js API
44 if (configObject) {
45 debugLog('Using single direct configuration object...')
46
47 return { '': validateConfig(configObject, 'config object', logger) }
48 }
49
50 // Use only explicit config path instead of discovering multiple
51 if (configPath) {
52 debugLog('Using single configuration path...')
53
54 const { config, filepath } = await loadConfig({ configPath }, logger)
55
56 if (!config) return {}
57 return { [configPath]: validateConfig(config, filepath, logger) }
58 }
59
60 const [cachedFiles, otherFiles] = await Promise.all([
61 /** Get all possible config files known to git */
62 execGit(EXEC_GIT, { cwd: gitDir }).then(parseGitZOutput).then(filterPossibleConfigFiles),
63 /** Get all possible config files from uncommitted files */
64 execGit([...EXEC_GIT, '--others', '--exclude-standard'], { cwd: gitDir })
65 .then(parseGitZOutput)
66 .then(filterPossibleConfigFiles),
67 ])
68
69 /** Sort possible config files so that deepest is first */
70 const possibleConfigFiles = [...cachedFiles, ...otherFiles]
71 .map((file) => normalize(path.join(gitDir, file)))
72 .filter(isInsideDirectory(cwd))
73 .sort(sortDeepestParth)
74
75 debugLog('Found possible config files:', possibleConfigFiles)
76
77 /** Create object with key as config file, and value as null */
78 const configs = possibleConfigFiles.reduce(
79 (acc, configPath) => Object.assign(acc, { [configPath]: null }),
80 {}
81 )
82
83 /** Load and validate all configs to the above object */
84 await Promise.all(
85 Object.keys(configs).map((configPath) =>
86 loadConfig({ configPath }, logger).then(({ config, filepath }) => {
87 if (config) {
88 if (configPath !== filepath) {
89 debugLog('Config file "%s" resolved to "%s"', configPath, filepath)
90 }
91
92 configs[configPath] = validateConfig(config, filepath, logger)
93 }
94 })
95 )
96 )
97
98 /** Get validated configs from the above object, without any `null` values (not found) */
99 const foundConfigs = Object.entries(configs)
100 .filter(([, value]) => !!value)
101 .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
102
103 /**
104 * Try to find a single config from parent directories
105 * to match old behavior before monorepo support
106 */
107 if (!Object.keys(foundConfigs).length) {
108 debugLog('Could not find config files inside "%s"', cwd)
109
110 const { config, filepath } = await loadConfig({ cwd }, logger)
111 if (config) {
112 debugLog('Found parent configuration file from "%s"', filepath)
113
114 foundConfigs[filepath] = validateConfig(config, filepath, logger)
115 } else {
116 debugLog('Could not find parent configuration files from "%s"', cwd)
117 }
118 }
119
120 debugLog('Found %d config files', Object.keys(foundConfigs).length)
121
122 return foundConfigs
123}