lint-staged 15.2.7 → 15.2.8

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
@@ -1,4 +1,8 @@
1
- # 🚫💩 lint-staged [![Test & Release](https://github.com/okonet/lint-staged/actions/workflows/push.yml/badge.svg)](https://github.com/okonet/lint-staged/actions/workflows/push.yml) [![Publish](https://github.com/okonet/lint-staged/actions/workflows/tag.yml/badge.svg)](https://github.com/okonet/lint-staged/actions/workflows/tag.yml) [![npm version](https://badge.fury.io/js/lint-staged.svg)](https://badge.fury.io/js/lint-staged) [![Codecov](https://codecov.io/gh/okonet/lint-staged/branch/master/graph/badge.svg)](https://codecov.io/gh/okonet/lint-staged)
1
+ # 🚫💩 lint-staged
2
+
3
+ [![npm version](https://badge.fury.io/js/lint-staged.svg)](https://badge.fury.io/js/lint-staged)
4
+
5
+ ---
2
6
 
3
7
  Run linters against staged git files and don't let :poop: slip into your code base!
4
8
 
@@ -236,17 +240,9 @@ Another example in which tasks make edits to files and globs match multiple file
236
240
 
237
241
  ```json
238
242
  {
239
- "*.css": [
240
- "stylelint --fix",
241
- "prettier --write"
242
- ],
243
- "*.{js,jsx}": [
244
- "eslint --fix",
245
- "prettier --write"
246
- ],
247
- "!(*.css|*.js|*.jsx)": [
248
- "prettier --write"
249
- ]
243
+ "*.css": ["stylelint --fix", "prettier --write"],
244
+ "*.{js,jsx}": ["eslint --fix", "prettier --write"],
245
+ "!(*.css|*.js|*.jsx)": ["prettier --write"]
250
246
  }
251
247
  ```
252
248
 
@@ -489,8 +485,7 @@ All examples assume you've already set up lint-staged in the `package.json` file
489
485
  In `.husky/pre-commit`
490
486
 
491
487
  ```shell
492
- #!/usr/bin/env sh
493
- . "$(dirname "$0")/_/husky.sh"
488
+ # .husky/pre-commit
494
489
 
495
490
  npx lint-staged
496
491
  ```
@@ -696,9 +691,6 @@ If updating Git doesn't help, you can try to manually redirect the output in you
696
691
  ```shell
697
692
  # .husky/pre-commit
698
693
 
699
- #!/usr/bin/env sh
700
- . "$(dirname -- "$0")/_/husky.sh"
701
-
702
694
  if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then exec >/dev/tty 2>&1; fi
703
695
 
704
696
  npx lint-staged
@@ -128,7 +128,7 @@ const options = {
128
128
  verbose: !!cliOptions.verbose,
129
129
  }
130
130
 
131
- debugLog('Options parsed from command-line:', options)
131
+ debugLog('Options parsed from command-line: %o', options)
132
132
 
133
133
  if (options.configPath === '-') {
134
134
  delete options.configPath
package/lib/chunkFiles.js CHANGED
@@ -31,7 +31,7 @@ const chunkArray = (arr, chunkCount) => {
31
31
  * @param {Array<String>} opts.files
32
32
  * @param {String} [opts.baseDir] The optional base directory to resolve relative paths.
33
33
  * @param {number} [opts.maxArgLength] the maximum argument string length
34
- * @param {Boolean} [opts.relative] whether files are relative to `gitDir` or should be resolved as absolute
34
+ * @param {Boolean} [opts.relative] whether files are relative to `topLevelDir` or should be resolved as absolute
35
35
  * @returns {Array<Array<String>>}
36
36
  */
37
37
  export const chunkFiles = ({ files, baseDir, maxArgLength = null, relative = false }) => {
@@ -13,9 +13,8 @@ const debugLog = debug('lint-staged:generateTasks')
13
13
  * @param {object} options
14
14
  * @param {Object} [options.config] - Task configuration
15
15
  * @param {Object} [options.cwd] - Current working directory
16
- * @param {boolean} [options.gitDir] - Git root directory
17
16
  * @param {boolean} [options.files] - Staged filepaths
18
- * @param {boolean} [options.relative] - Whether filepaths to should be relative to gitDir
17
+ * @param {boolean} [options.relative] - Whether filepaths to should be relative to cwd
19
18
  */
20
19
  export const generateTasks = ({ config, cwd = process.cwd(), files, relative = false }) => {
21
20
  debugLog('Generating linter tasks')
@@ -66,11 +66,11 @@ const handleError = (error, ctx, symbol) => {
66
66
  }
67
67
 
68
68
  export class GitWorkflow {
69
- constructor({ allowEmpty, gitConfigDir, gitDir, matchedFileChunks, diff, diffFilter }) {
70
- this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: gitDir })
69
+ constructor({ allowEmpty, gitConfigDir, topLevelDir, matchedFileChunks, diff, diffFilter }) {
70
+ this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: topLevelDir })
71
71
  this.deletedFiles = []
72
72
  this.gitConfigDir = gitConfigDir
73
- this.gitDir = gitDir
73
+ this.topLevelDir = topLevelDir
74
74
  this.diff = diff
75
75
  this.diffFilter = diffFilter
76
76
  this.allowEmpty = allowEmpty
@@ -116,7 +116,7 @@ export class GitWorkflow {
116
116
  const deletedFiles = lsFiles
117
117
  .split('\n')
118
118
  .filter(Boolean)
119
- .map((file) => path.resolve(this.gitDir, file))
119
+ .map((file) => path.resolve(this.topLevelDir, file))
120
120
  debugLog('Found deleted files:', deletedFiles)
121
121
  return deletedFiles
122
122
  }
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import debug from 'debug'
2
2
 
3
+ import { execGit } from './execGit.js'
3
4
  import {
4
5
  PREVENTED_EMPTY_COMMIT,
5
6
  GIT_ERROR,
@@ -82,11 +83,8 @@ const lintStaged = async (
82
83
  } = {},
83
84
  logger = console
84
85
  ) => {
85
- await validateOptions({ cwd, shell }, logger)
86
-
87
- // Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
88
- debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
89
- delete process.env.GIT_LITERAL_PATHSPECS
86
+ const gitVersion = await execGit(['version', '--build-options'], { cwd })
87
+ debugLog('%s', gitVersion)
90
88
 
91
89
  const options = {
92
90
  allowEmpty,
@@ -106,6 +104,12 @@ const lintStaged = async (
106
104
  verbose,
107
105
  }
108
106
 
107
+ await validateOptions(options, logger)
108
+
109
+ // Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
110
+ debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
111
+ delete process.env.GIT_LITERAL_PATHSPECS
112
+
109
113
  try {
110
114
  const ctx = await runAll(options, logger)
111
115
  debugLog('Tasks were executed successfully!')
@@ -12,11 +12,11 @@ const debugLog = debug('lint-staged:makeCmdTasks')
12
12
  * @param {Array<string|Function>|string|Function} options.commands
13
13
  * @param {string} options.cwd
14
14
  * @param {Array<string>} options.files
15
- * @param {string} options.gitDir
15
+ * @param {string} options.topLevelDir
16
16
  * @param {Boolean} shell
17
17
  * @param {Boolean} verbose
18
18
  */
19
- export const makeCmdTasks = async ({ commands, cwd, files, gitDir, shell, verbose }) => {
19
+ export const makeCmdTasks = async ({ commands, cwd, files, topLevelDir, shell, verbose }) => {
20
20
  debugLog('Creating listr tasks for commands %o', commands)
21
21
  const commandArray = Array.isArray(commands) ? commands : [commands]
22
22
  const cmdTasks = []
@@ -43,7 +43,7 @@ export const makeCmdTasks = async ({ commands, cwd, files, gitDir, shell, verbos
43
43
  )
44
44
  }
45
45
 
46
- const task = resolveTaskFn({ command, cwd, files, gitDir, isFn, shell, verbose })
46
+ const task = resolveTaskFn({ command, cwd, files, topLevelDir, isFn, shell, verbose })
47
47
  cmdTasks.push({ title: command, command, task })
48
48
  }
49
49
  }
@@ -1,3 +1,5 @@
1
+ import path from 'node:path'
2
+
1
3
  import debug from 'debug'
2
4
 
3
5
  import { execGit } from './execGit.js'
@@ -6,8 +8,38 @@ import { normalizePath } from './normalizePath.js'
6
8
  const debugLog = debug('lint-staged:resolveGitRepo')
7
9
 
8
10
  /**
9
- * Resolve git directory and possible submodule paths
11
+ * Resolve .git directory relative to repo top-level directory
12
+ *
13
+ * @example ".git"
10
14
  */
15
+ const resolveRelativeGitDir = async (cwd = process.cwd()) => {
16
+ /**
17
+ * Absolute repo top-level directory
18
+ *
19
+ * @example <caption>Git on macOS</caption>
20
+ * "/Users/iiro/Documents/git/lint-staged"
21
+ *
22
+ * @example <caption>Git for Windows</caption>
23
+ * "C:\Users\iiro\Documents\git\lint-staged"
24
+ *
25
+ * @example <caption>Git installed with MSYS2, this doesn't work when used as CWD with Node.js child_process</caption>
26
+ * "/c/Users/iiro/Documents/git/lint-staged"
27
+ */
28
+ const topLevelPromise = execGit(['rev-parse', '--show-toplevel'], { cwd })
29
+
30
+ /**
31
+ * Absolute .git directory, similar to top-level
32
+ *
33
+ * @example "/Users/iiro/Documents/git/lint-staged/.git"
34
+ */
35
+ const absoluteGitDirPromise = execGit(['rev-parse', '--absolute-git-dir'], { cwd })
36
+
37
+ const [topLevel, absoluteGitDir] = await Promise.all([topLevelPromise, absoluteGitDirPromise])
38
+
39
+ return path.relative(topLevel, absoluteGitDir)
40
+ }
41
+
42
+ /** Resolve git directory and possible submodule paths */
11
43
  export const resolveGitRepo = async (cwd = process.cwd()) => {
12
44
  try {
13
45
  debugLog('Resolving git repo from `%s`', cwd)
@@ -18,15 +50,17 @@ export const resolveGitRepo = async (cwd = process.cwd()) => {
18
50
  debugLog('Unset GIT_WORK_TREE (was `%s`)', process.env.GIT_WORK_TREE)
19
51
  delete process.env.GIT_WORK_TREE
20
52
 
21
- const gitDir = normalizePath(await execGit(['rev-parse', '--show-toplevel'], { cwd }))
22
- debugLog('Resolved git directory to be `%s`', gitDir)
53
+ const relativeTopLevelDir = await execGit(['rev-parse', '--show-cdup'], { cwd })
54
+ const topLevelDir = normalizePath(path.join(cwd, relativeTopLevelDir))
55
+ debugLog('Resolved git repository top-level directory to be `%s`', topLevelDir)
23
56
 
24
- const gitConfigDir = normalizePath(await execGit(['rev-parse', '--absolute-git-dir'], { cwd }))
57
+ const relativeGitConfigDir = await resolveRelativeGitDir(cwd)
58
+ const gitConfigDir = normalizePath(path.join(topLevelDir, relativeGitConfigDir))
25
59
  debugLog('Resolved git config directory to be `%s`', gitConfigDir)
26
60
 
27
- return { gitDir, gitConfigDir }
61
+ return { topLevelDir, gitConfigDir }
28
62
  } catch (error) {
29
63
  debugLog('Failed to resolve git repo with error:', error)
30
- return { error, gitDir: null, gitConfigDir: null }
64
+ return { error, topLevelDir: null, gitConfigDir: null }
31
65
  }
32
66
  }
@@ -125,7 +125,7 @@ const makeErr = (command, result, ctx) => {
125
125
  * @param {Object} options
126
126
  * @param {string} options.command — Linter task
127
127
  * @param {string} [options.cwd]
128
- * @param {String} options.gitDir - Current git repo path
128
+ * @param {String} options.topLevelDir - Current git repo top-level path
129
129
  * @param {Boolean} options.isFn - Whether the linter task is a function
130
130
  * @param {Array<string>} options.files — Filepaths to run the linter task against
131
131
  * @param {Boolean} [options.shell] — Whether to skip parsing linter task for better shell support
@@ -136,7 +136,7 @@ export const resolveTaskFn = ({
136
136
  command,
137
137
  cwd = process.cwd(),
138
138
  files,
139
- gitDir,
139
+ topLevelDir,
140
140
  isFn,
141
141
  shell = false,
142
142
  verbose = false,
@@ -146,9 +146,9 @@ export const resolveTaskFn = ({
146
146
  debugLog('args:', args)
147
147
 
148
148
  const execaOptions = {
149
- // Only use gitDir as CWD if we are using the git binary
149
+ // Only use topLevelDir as CWD if we are using the git binary
150
150
  // e.g `npm` should run tasks in the actual CWD
151
- cwd: /^git(\.exe)?/i.test(cmd) ? gitDir : cwd,
151
+ cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
152
152
  preferLocal: true,
153
153
  reject: false,
154
154
  shell,
package/lib/runAll.js CHANGED
@@ -94,8 +94,8 @@ export const runAll = async (
94
94
 
95
95
  const ctx = getInitialState({ quiet })
96
96
 
97
- const { gitDir, gitConfigDir } = await resolveGitRepo(cwd)
98
- if (!gitDir) {
97
+ const { topLevelDir, gitConfigDir } = await resolveGitRepo(cwd)
98
+ if (!topLevelDir) {
99
99
  if (!quiet) ctx.output.push(NOT_GIT_REPO)
100
100
  ctx.errors.add(GitRepoError)
101
101
  throw createError(ctx)
@@ -103,7 +103,7 @@ export const runAll = async (
103
103
 
104
104
  // Test whether we have any commits or not.
105
105
  // Stashing must be disabled with no initial commit.
106
- const hasInitialCommit = await execGit(['log', '-1'], { cwd: gitDir })
106
+ const hasInitialCommit = await execGit(['log', '-1'], { cwd: topLevelDir })
107
107
  .then(() => true)
108
108
  .catch(() => false)
109
109
 
@@ -119,7 +119,7 @@ export const runAll = async (
119
119
  logger.warn(skippingHidePartiallyStaged(hasInitialCommit && stash, diff))
120
120
  }
121
121
 
122
- const files = await getStagedFiles({ cwd: gitDir, diff, diffFilter })
122
+ const files = await getStagedFiles({ cwd: topLevelDir, diff, diffFilter })
123
123
  if (!files) {
124
124
  if (!quiet) ctx.output.push(FAILED_GET_STAGED_FILES)
125
125
  ctx.errors.add(GetStagedFilesError)
@@ -133,7 +133,7 @@ export const runAll = async (
133
133
  return ctx
134
134
  }
135
135
 
136
- const foundConfigs = await searchConfigs({ configObject, configPath, cwd, gitDir }, logger)
136
+ const foundConfigs = await searchConfigs({ configObject, configPath, cwd, topLevelDir }, logger)
137
137
  const numberOfConfigs = Object.keys(foundConfigs).length
138
138
 
139
139
  // Throw if no configurations were found
@@ -169,7 +169,7 @@ export const runAll = async (
169
169
  for (const [configPath, { config, files }] of Object.entries(filesByConfig)) {
170
170
  const configName = configPath ? normalizePath(path.relative(cwd, configPath)) : 'Config object'
171
171
 
172
- const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
172
+ const stagedFileChunks = chunkFiles({ baseDir: topLevelDir, files, maxArgLength, relative })
173
173
 
174
174
  // Use actual cwd if it's specified, or there's only a single config file.
175
175
  // Otherwise use the directory of the config file for each config group,
@@ -188,7 +188,7 @@ export const runAll = async (
188
188
  commands: task.commands,
189
189
  cwd: groupCwd,
190
190
  files: task.fileList,
191
- gitDir,
191
+ topLevelDir,
192
192
  shell,
193
193
  verbose,
194
194
  }).then((subTasks) => {
@@ -262,7 +262,7 @@ export const runAll = async (
262
262
 
263
263
  // Chunk matched files for better Windows compatibility
264
264
  const matchedFileChunks = chunkFiles({
265
- // matched files are relative to `cwd`, not `gitDir`, when `relative` is used
265
+ // matched files are relative to `cwd`, not `topLevelDir`, when `relative` is used
266
266
  baseDir: cwd,
267
267
  files: Array.from(matchedFiles),
268
268
  maxArgLength,
@@ -272,7 +272,7 @@ export const runAll = async (
272
272
  const git = new GitWorkflow({
273
273
  allowEmpty,
274
274
  gitConfigDir,
275
- gitDir,
275
+ topLevelDir,
276
276
  matchedFileChunks,
277
277
  diff,
278
278
  diffFilter,
@@ -35,7 +35,7 @@ const isInsideDirectory = (dir) => (file) => file.startsWith(normalizePath(dir))
35
35
  * @returns {Promise<{ [key: string]: { config: *, files: string[] } }>} found configs with filepath as key, and config as value
36
36
  */
37
37
  export const searchConfigs = async (
38
- { configObject, configPath, cwd = process.cwd(), gitDir = cwd },
38
+ { configObject, configPath, cwd = process.cwd(), topLevelDir = cwd },
39
39
  logger
40
40
  ) => {
41
41
  debugLog('Searching for configuration files...')
@@ -59,9 +59,11 @@ export const searchConfigs = async (
59
59
 
60
60
  const [cachedFilesWithStatus, otherFilesWithStatus] = await Promise.all([
61
61
  /** Get all possible config files known to git */
62
- execGit(EXEC_GIT, { cwd: gitDir }).then(parseGitZOutput),
62
+ execGit(EXEC_GIT, { cwd: topLevelDir }).then(parseGitZOutput),
63
63
  /** Get all possible config files from uncommitted files */
64
- execGit([...EXEC_GIT, '--others', '--exclude-standard'], { cwd: gitDir }).then(parseGitZOutput),
64
+ execGit([...EXEC_GIT, '--others', '--exclude-standard'], { cwd: topLevelDir }).then(
65
+ parseGitZOutput
66
+ ),
65
67
  ])
66
68
 
67
69
  /** Sort possible config files so that deepest is first */
@@ -74,7 +76,7 @@ export const searchConfigs = async (
74
76
  */ (line) => (line.startsWith('S ') ? [] : [line.replace(/^[HSMRCK?U] /, '')])
75
77
  )
76
78
  .filter(filterPossibleConfigFiles)
77
- .map((file) => normalizePath(path.join(gitDir, file)))
79
+ .map((file) => normalizePath(path.join(topLevelDir, file)))
78
80
  .filter(isInsideDirectory(cwd))
79
81
  .sort(sortDeepestParth)
80
82
 
@@ -26,6 +26,7 @@ export const validateOptions = async (options = {}, logger) => {
26
26
  const resolved = path.resolve(options.cwd)
27
27
  await fs.access(resolved, constants.F_OK)
28
28
  } catch (error) {
29
+ debugLog('Failed to validate options: %o', options)
29
30
  logger.error(invalidOption('cwd', options.cwd, error.message))
30
31
  throw InvalidOptionsError
31
32
  }
@@ -36,10 +37,11 @@ export const validateOptions = async (options = {}, logger) => {
36
37
  try {
37
38
  await fs.access(options.shell, constants.X_OK)
38
39
  } catch (error) {
40
+ debugLog('Failed to validate options: %o', options)
39
41
  logger.error(invalidOption('shell', options.shell, error.message))
40
42
  throw InvalidOptionsError
41
43
  }
42
44
  }
43
45
 
44
- debugLog('Validated options!')
46
+ debugLog('Validated options: %o', options)
45
47
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "15.2.7",
3
+ "version": "15.2.8",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
- "repository": "https://github.com/okonet/lint-staged",
6
+ "repository": "https://github.com/lint-staged/lint-staged",
7
7
  "author": "Andrey Okonetchnikov <andrey@okonet.ru>",
8
8
  "maintainers": [
9
9
  "Lufty Wiranda <lufty.wiranda@gmail.com>",
@@ -38,18 +38,18 @@
38
38
  "dependencies": {
39
39
  "chalk": "~5.3.0",
40
40
  "commander": "~12.1.0",
41
- "debug": "~4.3.4",
41
+ "debug": "~4.3.6",
42
42
  "execa": "~8.0.1",
43
- "lilconfig": "~3.1.1",
44
- "listr2": "~8.2.1",
43
+ "lilconfig": "~3.1.2",
44
+ "listr2": "~8.2.4",
45
45
  "micromatch": "~4.0.7",
46
46
  "pidtree": "~0.6.0",
47
47
  "string-argv": "~0.3.2",
48
- "yaml": "~2.4.2"
48
+ "yaml": "~2.5.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@changesets/changelog-github": "0.5.0",
52
- "@changesets/cli": "2.27.3",
52
+ "@changesets/cli": "2.27.7",
53
53
  "@commitlint/cli": "19.3.0",
54
54
  "@commitlint/config-conventional": "19.2.2",
55
55
  "consolemock": "1.1.0",
@@ -58,12 +58,12 @@
58
58
  "eslint-config-prettier": "9.1.0",
59
59
  "eslint-plugin-import": "2.29.1",
60
60
  "eslint-plugin-node": "11.1.0",
61
- "eslint-plugin-prettier": "5.1.3",
62
- "husky": "9.0.11",
61
+ "eslint-plugin-prettier": "5.2.1",
62
+ "husky": "9.1.4",
63
63
  "jest": "29.7.0",
64
64
  "jest-snapshot-serializer-ansi": "2.1.0",
65
65
  "mock-stdin": "1.0.0",
66
- "prettier": "3.2.5"
66
+ "prettier": "3.3.3"
67
67
  },
68
68
  "keywords": [
69
69
  "lint",