lint-staged 15.1.0 → 15.2.0

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
@@ -117,7 +117,9 @@ Options:
117
117
  "--no-stash".
118
118
  --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
119
119
  --max-arg-length [number] maximum length of the command-line argument string (default: 0)
120
- --no-stash disable the backup stash, and do not revert in case of errors
120
+ --no-stash disable the backup stash, and do not revert in case of errors. Implies
121
+ "--no-hide-partially-staged".
122
+ --no-hide-partially-staged disable hiding unstaged changes from partially staged files
121
123
  -q, --quiet disable lint-staged’s own console output (default: false)
122
124
  -r, --relative pass relative filepaths to tasks (default: false)
123
125
  -x, --shell [path] skip parsing of tasks for better shell support (default: false)
@@ -140,7 +142,8 @@ Options:
140
142
  - **`--diff`**: By default linters are filtered against all files staged in git, generated from `git diff --staged`. This option allows you to override the `--staged` flag with arbitrary revisions. For example to get a list of changed files between two branches, use `--diff="branch1...branch2"`. You can also read more from about [git diff](https://git-scm.com/docs/git-diff) and [gitrevisions](https://git-scm.com/docs/gitrevisions). This option also implies `--no-stash`.
141
143
  - **`--diff-filter`**: By default only files that are _added_, _copied_, _modified_, or _renamed_ are included. Use this flag to override the default `ACMR` value with something else: _added_ (`A`), _copied_ (`C`), _deleted_ (`D`), _modified_ (`M`), _renamed_ (`R`), _type changed_ (`T`), _unmerged_ (`U`), _unknown_ (`X`), or _pairing broken_ (`B`). See also the `git diff` docs for [--diff-filter](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203).
142
144
  - **`--max-arg-length`**: long commands (a lot of files) are automatically split into multiple chunks when it detects the current shell cannot handle them. Use this flag to override the maximum length of the generated command string.
143
- - **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. Can be re-enabled with `--stash`.
145
+ - **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. Can be re-enabled with `--stash`. This option also implies `--no-hide-partially-staged`.
146
+ - **`--no-hide-partially-staged`**: By default, unstaged changes from partially staged files will be hidden. This option will disable this behavior and include all unstaged changes in partially staged files. Can be re-enabled with `--hide-partially-staged`
144
147
  - **`--quiet`**: Supress all CLI output, except from tasks.
145
148
  - **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
146
149
  - **`--shell`**: By default linter commands will be parsed for speed and security. This has the side-effect that regular shell scripts might not work as expected. You can skip parsing of commands with this option. To use a specific shell, use a path like `--shell "/bin/bash"`.
@@ -644,7 +647,7 @@ module.exports = {
644
647
 
645
648
  ## Frequently Asked Questions
646
649
 
647
- ### The output of commit hook looks weird (no colors, duplicate lines, …)
650
+ ### The output of commit hook looks weird (no colors, duplicate lines, verbose output on Windows, …)
648
651
 
649
652
  <details>
650
653
  <summary>Click to expand</summary>
@@ -660,6 +663,21 @@ https://raw.githubusercontent.com/git/git/master/Documentation/RelNotes/2.37.0.t
660
663
  > hook, which was noticed post release. This is getting corrected.
661
664
  > (merge [a082345372](https://github.com/git/git/commit/a082345372) ab/hooks-regression-fix later to maint).
662
665
 
666
+ If updating Git doesn't help, you can try to manually redirect the output in your Git hook; for example:
667
+
668
+ ```shell
669
+ # .husky/pre-commit
670
+
671
+ #!/usr/bin/env sh
672
+ . "$(dirname -- "$0")/_/husky.sh"
673
+
674
+ if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then exec >/dev/tty 2>&1; fi
675
+
676
+ npx lint-staged
677
+ ```
678
+
679
+ Source: https://github.com/typicode/husky/issues/968#issuecomment-1176848345
680
+
663
681
  </details>
664
682
 
665
683
  ### Can I use `lint-staged` via node?
@@ -67,7 +67,27 @@ cli
67
67
  .addOption(
68
68
  new Option(
69
69
  '--no-stash',
70
- 'disable the backup stash, and do not revert in case of errors'
70
+ 'disable the backup stash, and do not revert in case of errors. Implies "--no-hide-partially-staged".'
71
+ )
72
+ .default(false)
73
+ .implies({ hidePartiallyStaged: false })
74
+ )
75
+
76
+ /**
77
+ * We don't want to show the `--hide-partially-staged` flag because it's on by default, and only show the
78
+ * negatable flag `--no-hide-partially-staged` in stead. There seems to be a bug in Commander.js where
79
+ * configuring only the latter won't actually set the default value.
80
+ */
81
+ cli
82
+ .addOption(
83
+ new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
84
+ .default(true)
85
+ .hideHelp()
86
+ )
87
+ .addOption(
88
+ new Option(
89
+ '--no-hide-partially-staged',
90
+ 'disable hiding unstaged changes from partially staged files'
71
91
  ).default(false)
72
92
  )
73
93
 
@@ -104,6 +124,7 @@ const options = {
104
124
  relative: !!cliOptions.relative,
105
125
  shell: cliOptions.shell /* Either a boolean or a string pointing to the shell */,
106
126
  stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
127
+ hidePartiallyStaged: !!cliOptions.hidePartiallyStaged, // commander inverts `no-<x>` flags to `!x`
107
128
  verbose: !!cliOptions.verbose,
108
129
  }
109
130
 
@@ -0,0 +1,24 @@
1
+ export const CONFIG_NAME = 'lint-staged'
2
+
3
+ export const PACKAGE_JSON_FILE = 'package.json'
4
+
5
+ export const PACKAGE_YAML_FILES = ['package.yaml', 'package.yml']
6
+
7
+ /**
8
+ * The list of files `lint-staged` will read configuration
9
+ * from, in the declared order.
10
+ */
11
+ export const CONFIG_FILE_NAMES = [
12
+ PACKAGE_JSON_FILE,
13
+ ...PACKAGE_YAML_FILES,
14
+ '.lintstagedrc',
15
+ '.lintstagedrc.json',
16
+ '.lintstagedrc.yaml',
17
+ '.lintstagedrc.yml',
18
+ '.lintstagedrc.mjs',
19
+ '.lintstagedrc.js',
20
+ '.lintstagedrc.cjs',
21
+ 'lint-staged.config.mjs',
22
+ 'lint-staged.config.js',
23
+ 'lint-staged.config.cjs',
24
+ ]
package/lib/index.js CHANGED
@@ -77,6 +77,7 @@ const lintStaged = async (
77
77
  shell = false,
78
78
  // Stashing should be disabled by default when the `diff` option is used
79
79
  stash = diff === undefined,
80
+ hidePartiallyStaged = stash,
80
81
  verbose = false,
81
82
  } = {},
82
83
  logger = console
@@ -101,6 +102,7 @@ const lintStaged = async (
101
102
  relative,
102
103
  shell,
103
104
  stash,
105
+ hidePartiallyStaged,
104
106
  verbose,
105
107
  }
106
108
 
package/lib/loadConfig.js CHANGED
@@ -1,44 +1,27 @@
1
1
  /** @typedef {import('./index').Logger} Logger */
2
2
 
3
+ import fs from 'node:fs/promises'
3
4
  import path from 'node:path'
4
5
 
5
6
  import debug from 'debug'
6
- import { lilconfig } from 'lilconfig'
7
7
  import YAML from 'yaml'
8
8
 
9
9
  import { dynamicImport } from './dynamicImport.js'
10
10
  import { resolveConfig } from './resolveConfig.js'
11
+ import {
12
+ CONFIG_FILE_NAMES,
13
+ CONFIG_NAME,
14
+ PACKAGE_JSON_FILE,
15
+ PACKAGE_YAML_FILES,
16
+ } from './configFiles.js'
11
17
 
12
18
  const debugLog = debug('lint-staged:loadConfig')
13
19
 
14
- const CONFIG_NAME = 'lint-staged'
15
-
16
- const PACKAGE_JSON_FILE = 'package.json'
17
-
18
- const PACKAGE_YAML_FILES = ['package.yaml', 'package.yml']
19
-
20
- /**
21
- * The list of files `lint-staged` will read configuration
22
- * from, in the declared order.
23
- */
24
- export const searchPlaces = [
25
- PACKAGE_JSON_FILE,
26
- ...PACKAGE_YAML_FILES,
27
- '.lintstagedrc',
28
- '.lintstagedrc.json',
29
- '.lintstagedrc.yaml',
30
- '.lintstagedrc.yml',
31
- '.lintstagedrc.mjs',
32
- '.lintstagedrc.js',
33
- '.lintstagedrc.cjs',
34
- 'lint-staged.config.mjs',
35
- 'lint-staged.config.js',
36
- 'lint-staged.config.cjs',
37
- ]
38
-
39
20
  const jsonParse = (filePath, content) => {
21
+ const isPackageFile = PACKAGE_JSON_FILE.includes(path.basename(filePath))
40
22
  try {
41
- return JSON.parse(content)
23
+ const json = JSON.parse(content)
24
+ return isPackageFile ? json[CONFIG_NAME] : json
42
25
  } catch (error) {
43
26
  if (path.basename(filePath) === PACKAGE_JSON_FILE) {
44
27
  debugLog('Ignoring invalid package file `%s` with content:\n%s', filePath, content)
@@ -64,6 +47,8 @@ const yamlParse = (filePath, content) => {
64
47
  }
65
48
  }
66
49
 
50
+ const NO_EXT = 'noExt'
51
+
67
52
  /**
68
53
  * `lilconfig` doesn't support yaml files by default,
69
54
  * so we add custom loaders for those. Files without
@@ -77,10 +62,31 @@ const loaders = {
77
62
  '.cjs': dynamicImport,
78
63
  '.yaml': yamlParse,
79
64
  '.yml': yamlParse,
80
- noExt: yamlParse,
65
+ [NO_EXT]: yamlParse,
81
66
  }
82
67
 
83
- const explorer = lilconfig(CONFIG_NAME, { searchPlaces, loaders })
68
+ const readFile = async (filepath) => {
69
+ const absolutePath = path.resolve(filepath)
70
+ const file = await fs.readFile(absolutePath)
71
+ return await file.toString()
72
+ }
73
+
74
+ const loadConfigByExt = async (filepath) => {
75
+ filepath = path.resolve(filepath)
76
+ const ext = path.extname(filepath) || NO_EXT
77
+ const loader = loaders[ext]
78
+
79
+ /**
80
+ * No need to read file contents when loader only takes in the filepath argument
81
+ * and reads itself; this is for `lilconfig` compatibility
82
+ */
83
+ const content = loader.length > 1 ? await readFile(filepath) : undefined
84
+
85
+ return {
86
+ config: await loader(filepath, content),
87
+ filepath,
88
+ }
89
+ }
84
90
 
85
91
  /**
86
92
  * @param {object} options
@@ -89,20 +95,22 @@ const explorer = lilconfig(CONFIG_NAME, { searchPlaces, loaders })
89
95
  */
90
96
  export const loadConfig = async ({ configPath, cwd }, logger) => {
91
97
  try {
98
+ let result
99
+
92
100
  if (configPath) {
93
101
  debugLog('Loading configuration from `%s`...', configPath)
102
+ result = await loadConfigByExt(resolveConfig(configPath))
94
103
  } else {
95
104
  debugLog('Searching for configuration from `%s`...', cwd)
105
+ const { lilconfig } = await import('lilconfig')
106
+ const explorer = lilconfig(CONFIG_NAME, { searchPlaces: CONFIG_FILE_NAMES, loaders })
107
+ result = await explorer.search(cwd)
96
108
  }
97
109
 
98
- const result = await (configPath
99
- ? explorer.load(resolveConfig(configPath))
100
- : explorer.search(cwd))
101
-
102
110
  if (!result) return {}
103
111
 
104
112
  // config is a promise when using the `dynamicImport` loader
105
- const config = await result.config
113
+ const config = (await result.config) ?? null
106
114
  const filepath = result.filepath
107
115
 
108
116
  debugLog('Successfully loaded config from `%s`:\n%O', filepath, config)
package/lib/messages.js CHANGED
@@ -32,12 +32,25 @@ export const skippingBackup = (hasInitialCommit, diff) => {
32
32
  diff !== undefined
33
33
  ? '`--diff` was used'
34
34
  : hasInitialCommit
35
- ? '`--no-stash` was used'
36
- : 'there’s no initial commit yet'
35
+ ? '`--no-stash` was used'
36
+ : 'there’s no initial commit yet'
37
37
 
38
38
  return chalk.yellow(`${warning} Skipping backup because ${reason}.\n`)
39
39
  }
40
40
 
41
+ export const skippingHidePartiallyStaged = (stash, diff) => {
42
+ const reason =
43
+ diff !== undefined
44
+ ? '`--diff` was used'
45
+ : !stash
46
+ ? '`--no-stash` was used'
47
+ : '`--no-hide-partially-staged` was used'
48
+
49
+ return chalk.yellow(
50
+ `${warning} Skipping hiding unstaged changes from partially staged files because ${reason}.\n`
51
+ )
52
+ }
53
+
41
54
  export const DEPRECATED_GIT_ADD = chalk.yellow(
42
55
  `${warning} Some of your tasks use \`git add\` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.
43
56
  `
@@ -15,10 +15,19 @@ const debugLog = debug('lint-staged:resolveGitRepo')
15
15
  */
16
16
  const resolveGitConfigDir = async (gitDir) => {
17
17
  // Get the real path in case it's a symlink
18
- const defaultDir = normalizePath(await fs.realpath(path.join(gitDir, '.git')))
18
+ const defaultDir = path.join(gitDir, '.git')
19
19
  const stats = await fs.lstat(defaultDir)
20
+
20
21
  // If .git is a directory, use it
21
- if (stats.isDirectory()) return defaultDir
22
+ if (stats.isDirectory()) {
23
+ return defaultDir
24
+ }
25
+
26
+ // If .git is a symlink, return the real location
27
+ if (stats.isSymbolicLink()) {
28
+ return await fs.realpath(gitDir)
29
+ }
30
+
22
31
  // Otherwise .git is a file containing path to real location
23
32
  const file = (await readFile(defaultDir)).toString()
24
33
  return path.resolve(gitDir, file.replace(/^gitdir: /, '')).trim()
@@ -33,10 +42,10 @@ export const determineGitDir = (cwd, relativeDir) => {
33
42
  }
34
43
  if (relativeDir) {
35
44
  // the current working dir is inside the git top-level directory
36
- return normalizePath(cwd.substring(0, cwd.lastIndexOf(relativeDir)))
45
+ return cwd.substring(0, cwd.lastIndexOf(relativeDir))
37
46
  } else {
38
47
  // the current working dir is the top-level git directory
39
- return normalizePath(cwd)
48
+ return cwd
40
49
  }
41
50
  }
42
51
 
@@ -56,7 +65,7 @@ export const resolveGitRepo = async (cwd = process.cwd()) => {
56
65
  // read the path of the current directory relative to the top-level directory
57
66
  // don't read the toplevel directly, it will lead to an posix conform path on non posix systems (cygwin)
58
67
  const gitRel = normalizePath(await execGit(['rev-parse', '--show-prefix'], { cwd }))
59
- const gitDir = determineGitDir(normalizePath(cwd), gitRel)
68
+ const gitDir = normalizePath(determineGitDir(normalizePath(cwd), gitRel))
60
69
  const gitConfigDir = normalizePath(await resolveGitConfigDir(gitDir))
61
70
 
62
71
  debugLog('Resolved git directory to be `%s`', gitDir)
package/lib/runAll.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  NO_TASKS,
23
23
  SKIPPED_GIT_ERROR,
24
24
  skippingBackup,
25
+ skippingHidePartiallyStaged,
25
26
  } from './messages.js'
26
27
  import { normalizePath } from './normalizePath.js'
27
28
  import { resolveGitRepo } from './resolveGitRepo.js'
@@ -30,7 +31,7 @@ import {
30
31
  cleanupEnabled,
31
32
  cleanupSkipped,
32
33
  getInitialState,
33
- hasPartiallyStagedFiles,
34
+ shouldHidePartiallyStagedFiles,
34
35
  restoreOriginalStateEnabled,
35
36
  restoreOriginalStateSkipped,
36
37
  restoreUnstagedChangesSkipped,
@@ -79,6 +80,7 @@ export const runAll = async (
79
80
  shell = false,
80
81
  // Stashing should be disabled by default when the `diff` option is used
81
82
  stash = diff === undefined,
83
+ hidePartiallyStaged = stash,
82
84
  verbose = false,
83
85
  },
84
86
  logger = console
@@ -112,6 +114,11 @@ export const runAll = async (
112
114
  logger.warn(skippingBackup(hasInitialCommit, diff))
113
115
  }
114
116
 
117
+ ctx.shouldHidePartiallyStaged = hidePartiallyStaged
118
+ if (!ctx.shouldHidePartiallyStaged && !quiet) {
119
+ logger.warn(skippingHidePartiallyStaged(hasInitialCommit && stash, diff))
120
+ }
121
+
115
122
  const files = await getStagedFiles({ cwd: gitDir, diff, diffFilter })
116
123
  if (!files) {
117
124
  if (!quiet) ctx.output.push(FAILED_GET_STAGED_FILES)
@@ -280,7 +287,7 @@ export const runAll = async (
280
287
  {
281
288
  title: 'Hiding unstaged changes to partially staged files...',
282
289
  task: (ctx) => git.hideUnstagedChanges(ctx),
283
- enabled: hasPartiallyStagedFiles,
290
+ enabled: shouldHidePartiallyStagedFiles,
284
291
  },
285
292
  {
286
293
  title: `Running tasks for ${diff ? 'changed' : 'staged'} files...`,
@@ -295,7 +302,7 @@ export const runAll = async (
295
302
  {
296
303
  title: 'Restoring unstaged changes to partially staged files...',
297
304
  task: (ctx) => git.restoreUnstagedChanges(ctx),
298
- enabled: hasPartiallyStagedFiles,
305
+ enabled: shouldHidePartiallyStagedFiles,
299
306
  skip: restoreUnstagedChangesSkipped,
300
307
  },
301
308
  {
@@ -5,17 +5,18 @@ import path from 'node:path'
5
5
  import debug from 'debug'
6
6
 
7
7
  import { execGit } from './execGit.js'
8
- import { loadConfig, searchPlaces } from './loadConfig.js'
8
+ import { loadConfig } from './loadConfig.js'
9
9
  import { normalizePath } from './normalizePath.js'
10
10
  import { parseGitZOutput } from './parseGitZOutput.js'
11
11
  import { validateConfig } from './validateConfig.js'
12
+ import { CONFIG_FILE_NAMES } from './configFiles.js'
12
13
 
13
14
  const debugLog = debug('lint-staged:searchConfigs')
14
15
 
15
16
  const EXEC_GIT = ['ls-files', '-z', '--full-name']
16
17
 
17
18
  const filterPossibleConfigFiles = (files) =>
18
- files.filter((file) => searchPlaces.includes(path.basename(file)))
19
+ files.filter((file) => CONFIG_FILE_NAMES.includes(path.basename(file)))
19
20
 
20
21
  const numberOfLevels = (file) => file.split('/').length
21
22
 
package/lib/state.js CHANGED
@@ -12,13 +12,15 @@ import {
12
12
  export const getInitialState = ({ quiet = false } = {}) => ({
13
13
  hasPartiallyStagedFiles: null,
14
14
  shouldBackup: null,
15
+ shouldHidePartiallyStaged: true,
15
16
  errors: new Set([]),
16
17
  events: new EventEmitter(),
17
18
  output: [],
18
19
  quiet,
19
20
  })
20
21
 
21
- export const hasPartiallyStagedFiles = (ctx) => ctx.hasPartiallyStagedFiles
22
+ export const shouldHidePartiallyStagedFiles = (ctx) =>
23
+ ctx.hasPartiallyStagedFiles && ctx.shouldHidePartiallyStaged
22
24
 
23
25
  export const applyModificationsSkipped = (ctx) => {
24
26
  // Always apply back unstaged modifications when skipping backup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "15.1.0",
3
+ "version": "15.2.0",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",
@@ -29,8 +29,8 @@
29
29
  ],
30
30
  "scripts": {
31
31
  "lint": "eslint .",
32
- "test": "jest --coverage",
33
- "test:watch": "jest --watch",
32
+ "test": "cross-env NODE_OPTIONS=--experimental-vm-modules npx jest --coverage",
33
+ "test:watch": "npm run test -- --watch",
34
34
  "version": "npx changeset version",
35
35
  "postversion": "npm i --package-lock-only && git commit -am \"chore(changeset): release\"",
36
36
  "tag": "npx changeset tag"
@@ -40,25 +40,21 @@
40
40
  "commander": "11.1.0",
41
41
  "debug": "4.3.4",
42
42
  "execa": "8.0.1",
43
- "lilconfig": "2.1.0",
44
- "listr2": "7.0.2",
43
+ "lilconfig": "3.0.0",
44
+ "listr2": "8.0.0",
45
45
  "micromatch": "4.0.5",
46
46
  "pidtree": "0.6.0",
47
47
  "string-argv": "0.3.2",
48
48
  "yaml": "2.3.4"
49
49
  },
50
50
  "devDependencies": {
51
- "@babel/core": "7.23.3",
52
- "@babel/eslint-parser": "7.23.3",
53
- "@babel/preset-env": "7.23.3",
54
- "@changesets/changelog-github": "0.4.8",
55
- "@changesets/cli": "2.26.2",
56
- "@commitlint/cli": "18.4.0",
57
- "@commitlint/config-conventional": "18.4.0",
58
- "babel-jest": "29.7.0",
59
- "babel-plugin-transform-imports": "2.0.0",
51
+ "@changesets/changelog-github": "0.5.0",
52
+ "@changesets/cli": "2.27.1",
53
+ "@commitlint/cli": "18.4.3",
54
+ "@commitlint/config-conventional": "18.4.3",
60
55
  "consolemock": "1.1.0",
61
- "eslint": "8.53.0",
56
+ "cross-env": "7.0.3",
57
+ "eslint": "8.55.0",
62
58
  "eslint-config-prettier": "9.0.0",
63
59
  "eslint-plugin-import": "2.29.0",
64
60
  "eslint-plugin-node": "11.1.0",
@@ -67,7 +63,7 @@
67
63
  "jest": "29.7.0",
68
64
  "jest-snapshot-serializer-ansi": "2.1.0",
69
65
  "mock-stdin": "1.0.0",
70
- "prettier": "3.0.3"
66
+ "prettier": "3.1.0"
71
67
  },
72
68
  "keywords": [
73
69
  "lint",