lint-staged 12.3.2 → 12.3.5

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.
@@ -99,13 +99,6 @@ export const getConfigGroups = async (
99
99
  // Discover configs from the base directory of each file
100
100
  await Promise.all(Object.entries(filesByDir).map(([dir, files]) => searchConfig(dir, files)))
101
101
 
102
- // Throw if no configurations were found
103
- if (Object.keys(configGroups).length === 0) {
104
- debugLog('Found no config groups!')
105
- logger.error(`${ConfigNotFoundError.message}.`)
106
- throw ConfigNotFoundError
107
- }
108
-
109
102
  debugLog('Grouped staged files into %d groups!', Object.keys(configGroups).length)
110
103
 
111
104
  return configGroups
@@ -3,6 +3,7 @@ import path from 'path'
3
3
  import normalize from 'normalize-path'
4
4
 
5
5
  import { execGit } from './execGit.js'
6
+ import { parseGitZOutput } from './parseGitZOutput.js'
6
7
 
7
8
  export const getStagedFiles = async ({ cwd = process.cwd() } = {}) => {
8
9
  try {
@@ -14,15 +15,7 @@ export const getStagedFiles = async ({ cwd = process.cwd() } = {}) => {
14
15
 
15
16
  if (!lines) return []
16
17
 
17
- // With `-z`, git prints `fileA\u0000fileB\u0000fileC\u0000` so we need to
18
- // remove the last occurrence of `\u0000` before splitting
19
- return (
20
- lines
21
- // eslint-disable-next-line no-control-regex
22
- .replace(/\u0000$/, '')
23
- .split('\u0000')
24
- .map((file) => normalize(path.resolve(cwd, file)))
25
- )
18
+ return parseGitZOutput(lines).map((file) => normalize(path.resolve(cwd, file)))
26
19
  } catch {
27
20
  return null
28
21
  }
package/lib/loadConfig.js CHANGED
@@ -13,7 +13,7 @@ const debugLog = debug('lint-staged:loadConfig')
13
13
  * The list of files `lint-staged` will read configuration
14
14
  * from, in the declared order.
15
15
  */
16
- const searchPlaces = [
16
+ export const searchPlaces = [
17
17
  'package.json',
18
18
  '.lintstagedrc',
19
19
  '.lintstagedrc.json',
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Return array of strings split from the output of `git <something> -z`.
3
+ * With `-z`, git prints `fileA\u0000fileB\u0000fileC\u0000` so we need to
4
+ * remove the last occurrence of `\u0000` before splitting
5
+ */
6
+ export const parseGitZOutput = (input) =>
7
+ input
8
+ .replace(/\u0000$/, '') // eslint-disable-line no-control-regex
9
+ .split('\u0000')
package/lib/runAll.js CHANGED
@@ -35,7 +35,8 @@ import {
35
35
  restoreOriginalStateSkipped,
36
36
  restoreUnstagedChangesSkipped,
37
37
  } from './state.js'
38
- import { GitRepoError, GetStagedFilesError, GitError } from './symbols.js'
38
+ import { GitRepoError, GetStagedFilesError, GitError, ConfigNotFoundError } from './symbols.js'
39
+ import { searchConfigs } from './searchConfigs.js'
39
40
 
40
41
  const debugLog = debug('lint-staged:runAll')
41
42
 
@@ -80,7 +81,8 @@ export const runAll = async (
80
81
  debugLog('Running all linter scripts...')
81
82
 
82
83
  // Resolve relative CWD option
83
- cwd = cwd ? path.resolve(cwd) : process.cwd()
84
+ const hasExplicitCwd = !!cwd
85
+ cwd = hasExplicitCwd ? path.resolve(cwd) : process.cwd()
84
86
  debugLog('Using working directory `%s`', cwd)
85
87
 
86
88
  const ctx = getInitialState({ quiet })
@@ -120,6 +122,20 @@ export const runAll = async (
120
122
 
121
123
  const configGroups = await getConfigGroups({ configObject, configPath, cwd, files }, logger)
122
124
 
125
+ const hasExplicitConfig = configObject || configPath
126
+ const foundConfigs = hasExplicitConfig ? null : await searchConfigs(gitDir, logger)
127
+ const numberOfConfigs = hasExplicitConfig ? 1 : Object.keys(foundConfigs).length
128
+
129
+ // Throw if no configurations were found
130
+ if (numberOfConfigs === 0) {
131
+ ctx.errors.add(ConfigNotFoundError)
132
+ throw createError(ctx, ConfigNotFoundError)
133
+ }
134
+
135
+ debugLog('Found %d configs:\n%O', numberOfConfigs, foundConfigs)
136
+
137
+ const hasMultipleConfigs = numberOfConfigs > 1
138
+
123
139
  // lint-staged 10 will automatically add modifications to index
124
140
  // Warn user when their command includes `git add`
125
141
  let hasDeprecatedGitAdd = false
@@ -138,21 +154,25 @@ export const runAll = async (
138
154
  const matchedFiles = new Set()
139
155
 
140
156
  for (const [configPath, { config, files }] of Object.entries(configGroups)) {
157
+ const relativeConfig = normalize(path.relative(cwd, configPath))
141
158
  const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
142
159
 
160
+ // Use actual cwd if it's specified, or there's only a single config file.
161
+ // Otherwise use the directory of the config file for each config group,
162
+ // to make sure tasks are separated from each other.
163
+ const groupCwd = hasMultipleConfigs && !hasExplicitCwd ? path.dirname(configPath) : cwd
164
+
143
165
  const chunkCount = stagedFileChunks.length
144
166
  if (chunkCount > 1) {
145
167
  debugLog('Chunked staged files from `%s` into %d part', configPath, chunkCount)
146
168
  }
147
169
 
148
170
  for (const [index, files] of stagedFileChunks.entries()) {
149
- const relativeConfig = normalize(path.relative(cwd, configPath))
150
-
151
171
  const chunkListrTasks = await Promise.all(
152
- generateTasks({ config, cwd, files, relative }).map((task) =>
172
+ generateTasks({ config, cwd: groupCwd, files, relative }).map((task) =>
153
173
  makeCmdTasks({
154
174
  commands: task.commands,
155
- cwd,
175
+ cwd: groupCwd,
156
176
  files: task.fileList,
157
177
  gitDir,
158
178
  renderer: listrOptions.renderer,
@@ -161,7 +181,14 @@ export const runAll = async (
161
181
  }).then((subTasks) => {
162
182
  // Add files from task to match set
163
183
  task.fileList.forEach((file) => {
164
- matchedFiles.add(file)
184
+ // Make sure relative files are normalized to the
185
+ // group cwd, because other there might be identical
186
+ // relative filenames in the entire set.
187
+ const normalizedFile = path.isAbsolute(file)
188
+ ? file
189
+ : normalize(path.join(groupCwd, file))
190
+
191
+ matchedFiles.add(normalizedFile)
165
192
  })
166
193
 
167
194
  hasDeprecatedGitAdd =
@@ -0,0 +1,69 @@
1
+ /** @typedef {import('./index').Logger} Logger */
2
+
3
+ import { basename, join } from 'path'
4
+
5
+ import normalize from 'normalize-path'
6
+
7
+ import { execGit } from './execGit.js'
8
+ import { loadConfig, searchPlaces } from './loadConfig.js'
9
+ import { parseGitZOutput } from './parseGitZOutput.js'
10
+ import { validateConfig } from './validateConfig.js'
11
+
12
+ const EXEC_GIT = ['ls-files', '-z', '--full-name']
13
+
14
+ const filterPossibleConfigFiles = (file) => searchPlaces.includes(basename(file))
15
+
16
+ const numberOfLevels = (file) => file.split('/').length
17
+
18
+ const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 : 1)
19
+
20
+ /**
21
+ * Search all config files from the git repository
22
+ *
23
+ * @param {string} gitDir
24
+ * @param {Logger} logger
25
+ * @returns {Promise<{ [key: string]: * }>} found configs with filepath as key, and config as value
26
+ */
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
+ )
32
+
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)
37
+
38
+ /** Sort possible config files so that deepest is first */
39
+ const possibleConfigFiles = [...cachedFiles, ...otherFiles]
40
+ .map((file) => join(gitDir, file))
41
+ .map((file) => normalize(file))
42
+ .sort(sortDeepestParth)
43
+
44
+ /** Create object with key as config file, and value as null */
45
+ const configs = possibleConfigFiles.reduce(
46
+ (acc, configPath) => Object.assign(acc, { [configPath]: null }),
47
+ {}
48
+ )
49
+
50
+ /** Load and validate all configs to the above object */
51
+ 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)
58
+ }
59
+ })
60
+ )
61
+ )
62
+
63
+ /** Get validated configs from the above object, without any `null` values (not found) */
64
+ const foundConfigs = Object.entries(configs)
65
+ .filter(([, value]) => !!value)
66
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
67
+
68
+ return foundConfigs
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "12.3.2",
3
+ "version": "12.3.5",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",
@@ -18,7 +18,10 @@
18
18
  },
19
19
  "type": "module",
20
20
  "bin": "./bin/lint-staged.js",
21
- "exports": "./lib/index.js",
21
+ "exports": {
22
+ ".": "./lib/index.js",
23
+ "./package.json": "./package.json"
24
+ },
22
25
  "files": [
23
26
  "bin",
24
27
  "lib"