lint-staged 12.3.3 → 12.3.6

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.
package/README.md CHANGED
@@ -713,6 +713,17 @@ Example repo: [sudo-suhas/lint-staged-django-react-demo](https://github.com/sudo
713
713
 
714
714
  </details>
715
715
 
716
+ ### Can I use `lint-staged` with `ng lint`
717
+
718
+ <details>
719
+ <summary>Click to expand</summary>
720
+
721
+ You should not use `ng lint` through _lint-staged_, because it's designed to lint an entire project. Instead, you can add `ng lint` to your git pre-commit hook the same way as you would run lint-staged.
722
+
723
+ See issue [!951](https://github.com/okonet/lint-staged/issues/951) for more details and possible workarounds.
724
+
725
+ </details>
726
+
716
727
  ### How can I ignore files from `.eslintignore`?
717
728
 
718
729
  <details>
@@ -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')
@@ -2,14 +2,17 @@ import { redBright, dim } from 'colorette'
2
2
  import execa from 'execa'
3
3
  import debug from 'debug'
4
4
  import { parseArgsStringToArgv } from 'string-argv'
5
+ import pidTree from 'pidtree'
5
6
 
6
7
  import { error, info } from './figures.js'
7
8
  import { getInitialState } from './state.js'
8
9
  import { TaskError } from './symbols.js'
9
10
 
11
+ const ERROR_CHECK_INTERVAL = 200
12
+
10
13
  const debugLog = debug('lint-staged:resolveTaskFn')
11
14
 
12
- const getTag = ({ code, killed, signal }) => signal || (killed && 'KILLED') || code || 'FAILED'
15
+ const getTag = ({ code, killed, signal }) => (killed && 'KILLED') || signal || code || 'FAILED'
13
16
 
14
17
  /**
15
18
  * Handle task console output.
@@ -43,6 +46,34 @@ const handleOutput = (command, result, ctx, isError = false) => {
43
46
  }
44
47
  }
45
48
 
49
+ /**
50
+ * Interrupts the execution of the execa process that we spawned if
51
+ * another task adds an error to the context.
52
+ *
53
+ * @param {Object} ctx
54
+ * @param {execa.ExecaChildProcess<string>} execaChildProcess
55
+ * @returns {function(): void} Function that clears the interval that
56
+ * checks the context.
57
+ */
58
+ const interruptExecutionOnError = (ctx, execaChildProcess) => {
59
+ async function loop() {
60
+ if (ctx.errors.size > 0) {
61
+ const ids = await pidTree(execaChildProcess.pid)
62
+ ids.forEach(process.kill)
63
+
64
+ // The execa process is killed separately in order
65
+ // to get the `KILLED` status.
66
+ execaChildProcess.kill()
67
+ }
68
+ }
69
+
70
+ const loopIntervalId = setInterval(loop, ERROR_CHECK_INTERVAL)
71
+
72
+ return () => {
73
+ clearInterval(loopIntervalId)
74
+ }
75
+ }
76
+
46
77
  /**
47
78
  * Create a error output dependding on process result.
48
79
  *
@@ -101,9 +132,13 @@ export const resolveTaskFn = ({
101
132
  debugLog('execaOptions:', execaOptions)
102
133
 
103
134
  return async (ctx = getInitialState()) => {
104
- const result = await (shell
135
+ const execaChildProcess = shell
105
136
  ? execa.command(isFn ? command : `${command} ${files.join(' ')}`, execaOptions)
106
- : execa(cmd, isFn ? args : args.concat(files), execaOptions))
137
+ : execa(cmd, isFn ? args : args.concat(files), execaOptions)
138
+
139
+ const quitInterruptCheck = interruptExecutionOnError(ctx, execaChildProcess)
140
+ const result = await execaChildProcess
141
+ quitInterruptCheck()
107
142
 
108
143
  if (result.failed || result.killed || result.signal != null) {
109
144
  throw makeErr(command, result, ctx)
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
 
@@ -121,7 +122,19 @@ export const runAll = async (
121
122
 
122
123
  const configGroups = await getConfigGroups({ configObject, configPath, cwd, files }, logger)
123
124
 
124
- const hasMultipleConfigs = Object.keys(configGroups).length > 1
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
125
138
 
126
139
  // lint-staged 10 will automatically add modifications to index
127
140
  // Warn user when their command includes `git add`
@@ -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.3",
3
+ "version": "12.3.6",
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"
@@ -39,6 +42,7 @@
39
42
  "micromatch": "^4.0.4",
40
43
  "normalize-path": "^3.0.0",
41
44
  "object-inspect": "^1.12.0",
45
+ "pidtree": "^0.5.0",
42
46
  "string-argv": "^0.3.1",
43
47
  "supports-color": "^9.2.1",
44
48
  "yaml": "^1.10.2"