UNPKG

4.32 kBJavaScriptView Raw
1/** @typedef {import('./index').Logger} Logger */
2
3import { basename, join } from '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(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
26export const ConfigObjectSymbol = Symbol()
27
28/**
29 * Search all config files from the git repository, preferring those inside `cwd`.
30 *
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
35 * @param {Logger} logger
36 *
37 * @returns {Promise<{ [key: string]: { config: *, files: string[] } }>} found configs with filepath as key, and config as value
38 */
39export 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...')
55
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 ])
70
71 /** Sort possible config files so that deepest is first */
72 const possibleConfigFiles = [...cachedFiles, ...otherFiles]
73 .map((file) => normalize(join(gitDir, file)))
74 .filter(isInsideDirectory(cwd))
75 .sort(sortDeepestParth)
76
77 debugLog('Found possible config files:', possibleConfigFiles)
78
79 /** Create object with key as config file, and value as null */
80 const configs = possibleConfigFiles.reduce(
81 (acc, configPath) => Object.assign(acc, { [configPath]: null }),
82 {}
83 )
84
85 /** Load and validate all configs to the above object */
86 await Promise.all(
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)
92 }
93
94 configs[configPath] = validateConfig(config, filepath, logger)
95 }
96 })
97 )
98 )
99
100 /** Get validated configs from the above object, without any `null` values (not found) */
101 const foundConfigs = Object.entries(configs)
102 .filter(([, value]) => !!value)
103 .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
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
124 return foundConfigs
125}