UNPKG

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