lint-staged 12.3.7 → 13.0.2

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
@@ -2,6 +2,10 @@
2
2
 
3
3
  Run linters against staged git files and don't let :poop: slip into your code base!
4
4
 
5
+ ```bash
6
+ npm install --save-dev lint-staged # requires further setup
7
+ ```
8
+
5
9
  ```
6
10
  $ git commit
7
11
 
@@ -44,13 +48,17 @@ This project contains a script that will run arbitrary shell tasks with a list o
44
48
 
45
49
  ## Installation and setup
46
50
 
47
- The fastest way to start using lint-staged is to run the following command in your terminal:
48
-
49
- ```bash
50
- npx mrm@2 lint-staged
51
- ```
51
+ To install _lint-staged_ in the recommended way, you need to:
52
52
 
53
- This command will install and configure [husky](https://github.com/typicode/husky) and lint-staged depending on the code quality tools from your project's `package.json` dependencies, so please make sure you install (`npm install --save-dev`) and configure all code quality tools like [Prettier](https://prettier.io) and [ESLint](https://eslint.org) prior to that.
53
+ 1. Install _lint-staged_ itself:
54
+ - `npm install --save-dev lint-staged`
55
+ 1. Set up the `pre-commit` git hook to run _lint-staged_
56
+ - [Husky](https://github.com/typicode/husky) is a popular choice for configuring git hooks
57
+ - Read more about git hooks [here](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
58
+ 1. Install some linters, like [ESLint](https://eslint.org) or [Prettier](https://prettier.io)
59
+ 1. Configure _lint-staged_ to run linters and other tasks:
60
+ - for example: `{ "*.js": "eslint" }` to run ESLint for all staged JS files
61
+ - See [Configuration](#Configuration) for more info
54
62
 
55
63
  Don't forget to commit changes to `package.json` and `.husky` to share this setup with your team!
56
64
 
@@ -64,6 +72,10 @@ See [Releases](https://github.com/okonet/lint-staged/releases).
64
72
 
65
73
  ### Migration
66
74
 
75
+ #### v13
76
+
77
+ - Since `v13.0.0` _lint-staged_ no longer supports Node.js 12. Please upgrade your Node.js version to at least `14.13.1`, or `16.0.0` onward.
78
+
67
79
  #### v12
68
80
 
69
81
  - Since `v12.0.0` _lint-staged_ is a pure ESM module, so make sure your Node.js version is at least `12.20.0`, `14.13.1`, or `16.0.0`. Read more about ESM modules from the official [Node.js Documentation site here](https://nodejs.org/api/esm.html#introduction).
@@ -92,6 +104,10 @@ Options:
92
104
  -c, --config [path] path to configuration file, or - to read from stdin
93
105
  --cwd [path] run all tasks in specific directory, instead of the current
94
106
  -d, --debug print additional debug information (default: false)
107
+ --diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies
108
+ "--no-stash".
109
+ --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
110
+ --max-arg-length [number] maximum length of the command-line argument string (default: 0)
95
111
  --no-stash disable the backup stash, and do not revert in case of errors
96
112
  -q, --quiet disable lint-staged’s own console output (default: false)
97
113
  -r, --relative pass relative filepaths to tasks (default: false)
@@ -102,7 +118,7 @@ Options:
102
118
  ```
103
119
 
104
120
  - **`--allow-empty`**: By default, when linter tasks undo all staged changes, lint-staged will exit with an error and abort the commit. Use this flag to allow creating empty git commits.
105
- - **`--concurrent [number|boolean]`**: Controls the concurrency of tasks being run by lint-staged. **NOTE**: This does NOT affect the concurrency of subtasks (they will always be run sequentially). Possible values are:
121
+ - **`--concurrent [number|boolean]`**: Controls the [concurrency of tasks](#task-concurrency) being run by lint-staged. **NOTE**: This does NOT affect the concurrency of subtasks (they will always be run sequentially). Possible values are:
106
122
  - `false`: Run all tasks serially
107
123
  - `true` (default) : _Infinite_ concurrency. Runs as many tasks in parallel as possible.
108
124
  - `{number}`: Run the specified number of tasks in parallel, where `1` is equivalent to `false`.
@@ -111,6 +127,9 @@ Options:
111
127
  - **`--debug`**: Run in debug mode. When set, it does the following:
112
128
  - uses [debug](https://github.com/visionmedia/debug) internally to log additional information about staged files, commands being executed, location of binaries, etc. Debug logs, which are automatically enabled by passing the flag, can also be enabled by setting the environment variable `$DEBUG` to `lint-staged*`.
113
129
  - uses [`verbose` renderer](https://github.com/SamVerschueren/listr-verbose-renderer) for `listr`; this causes serial, uncoloured output to the terminal, instead of the default (beautified, dynamic) output.
130
+ - **`--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).
131
+ - **`--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).
132
+ - **`--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.
114
133
  - **`--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.
115
134
  - **`--quiet`**: Supress all CLI output, except from tasks.
116
135
  - **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`.
@@ -119,7 +138,7 @@ Options:
119
138
 
120
139
  ## Configuration
121
140
 
122
- Starting with v3.1 you can now use different ways of configuring lint-staged:
141
+ _Lint-staged_ can be configured in many ways:
123
142
 
124
143
  - `lint-staged` object in your `package.json`
125
144
  - `.lintstagedrc` file in JSON or YML format, or you can be explicit with the file extension:
@@ -162,6 +181,37 @@ So, considering you did `git add file1.ext file2.ext`, lint-staged will run the
162
181
 
163
182
  `your-cmd file1.ext file2.ext`
164
183
 
184
+ ### Task concurrency
185
+
186
+ By default _lint-staged_ will run configured tasks concurrently. This means that for every glob, all the commands will be started at the same time. With the following config, both `eslint` and `prettier` will run at the same time:
187
+
188
+ ```json
189
+ {
190
+ "*.ts": "eslint",
191
+ "*.md": "prettier --list-different"
192
+ }
193
+ ```
194
+
195
+ This is typically not a problem since the globs do not overlap, and the commands do not make changes to the files, but only report possible errors (aborting the git commit). If you want to run multiple commands for the same set of files, you can use the array syntax to make sure commands are run in order. In the following example, `prettier` will run for both globs, and in addition `eslint` will run for `*.ts` files _after_ it. Both sets of commands (for each glob) are still started at the same time (but do not overlap).
196
+
197
+ ```json
198
+ {
199
+ "*.ts": ["prettier --list-different", "eslint"],
200
+ "*.md": "prettier --list-different"
201
+ }
202
+ ```
203
+
204
+ Pay extra attention when the configured globs overlap, and tasks make edits to files. For example, in this configuration `prettier` and `eslint` might try to make changes to the same `*.ts` file at the same time, causing a _race condition_:
205
+
206
+ ```json
207
+ {
208
+ "*": "prettier --write",
209
+ "*.ts": "eslint --fix"
210
+ }
211
+ ```
212
+
213
+ If necessary, you can limit the concurrency using `--concurrent <number>` or disable it entirely with `--concurrent false`.
214
+
165
215
  ## Filtering files
166
216
 
167
217
  Linter commands work on a subset of all staged files, defined by a _glob pattern_. lint-staged uses [micromatch](https://github.com/micromatch/micromatch) for matching files with the following rules:
@@ -594,9 +644,9 @@ const success = await lintStaged({
594
644
  maxArgLength: null,
595
645
  quiet: false,
596
646
  relative: false,
597
- shell: false
647
+ shell: false,
598
648
  stash: true,
599
- verbose: false
649
+ verbose: false,
600
650
  })
601
651
  ```
602
652
 
@@ -713,6 +763,30 @@ Example repo: [sudo-suhas/lint-staged-django-react-demo](https://github.com/sudo
713
763
 
714
764
  </details>
715
765
 
766
+ ### Can I run `lint-staged` in CI, or when there are no staged files?
767
+
768
+ <details>
769
+ <summary>Click to expand</summary>
770
+
771
+ Lint-staged will by default run against files staged in git, and should be run during the git pre-commit hook, for example. It's also possible to override this default behaviour and run against files in a specific diff, for example
772
+ all changed files between two different branches. If you want to run _lint-staged_ in the CI, maybe you can set it up to compare the branch in a _Pull Request_/_Merge Request_ to the target branch.
773
+
774
+ Try out the `git diff` command until you are satisfied with the result, for example:
775
+
776
+ ```
777
+ git diff --diff-filter=ACMR --name-only master...my-branch
778
+ ```
779
+
780
+ This will print a list of _added_, _changed_, _modified_, and _renamed_ files between `master` and `my-branch`.
781
+
782
+ You can then run lint-staged against the same files with:
783
+
784
+ ```
785
+ npx lint-staged --diff="master...my-branch"
786
+ ```
787
+
788
+ </details>
789
+
716
790
  ### Can I use `lint-staged` with `ng lint`
717
791
 
718
792
  <details>
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'fs'
4
- import path from 'path'
5
- import { fileURLToPath } from 'url'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
6
 
7
- import cmdline from 'commander'
7
+ import { isColorSupported } from 'colorette'
8
+ import { Option, program } from 'commander'
8
9
  import debug from 'debug'
9
- import supportsColor from 'supports-color'
10
10
 
11
11
  import lintStaged from '../lib/index.js'
12
12
  import { CONFIG_STDIN_ERROR } from '../lib/messages.js'
13
13
 
14
14
  // Force colors for packages that depend on https://www.npmjs.com/package/supports-color
15
- if (supportsColor.stdout) {
16
- process.env.FORCE_COLOR = supportsColor.stdout.level.toString()
15
+ if (isColorSupported) {
16
+ process.env.FORCE_COLOR = '1'
17
17
  }
18
18
 
19
19
  // Do not terminate main Listr process on SIGINT
@@ -23,67 +23,87 @@ const packageJsonPath = path.join(fileURLToPath(import.meta.url), '../../package
23
23
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath))
24
24
  const version = packageJson.version
25
25
 
26
- cmdline
27
- .version(version)
28
- .option('--allow-empty', 'allow empty commits when tasks revert all staged changes', false)
29
- .option(
30
- '-p, --concurrent <number|boolean>',
31
- 'the number of tasks to run concurrently, or false for serial',
32
- true
33
- )
34
- .option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
35
- .option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
36
- .option('-d, --debug', 'print additional debug information', false)
37
- .option('--no-stash', 'disable the backup stash, and do not revert in case of errors', false)
38
- .option('-q, --quiet', 'disable lint-staged’s own console output', false)
39
- .option('-r, --relative', 'pass relative filepaths to tasks', false)
40
- .option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
41
- .option(
42
- '-v, --verbose',
43
- 'show task output even when tasks succeed; by default only failed output is shown',
44
- false
45
- )
46
- .parse(process.argv)
26
+ const debugLog = debug('lint-staged:bin')
27
+ debugLog('Running `lint-staged@%s`', version)
47
28
 
48
- const cmdlineOptions = cmdline.opts()
29
+ const cli = program.version(version)
49
30
 
50
- if (cmdlineOptions.debug) {
51
- debug.enable('lint-staged*')
52
- }
31
+ cli.option('--allow-empty', 'allow empty commits when tasks revert all staged changes', false)
53
32
 
54
- const debugLog = debug('lint-staged:bin')
55
- debugLog('Running `lint-staged@%s`', version)
33
+ cli.option(
34
+ '-p, --concurrent <number|boolean>',
35
+ 'the number of tasks to run concurrently, or false for serial',
36
+ true
37
+ )
38
+
39
+ cli.option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
40
+
41
+ cli.option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
42
+
43
+ cli.option('-d, --debug', 'print additional debug information', false)
44
+
45
+ cli.option(
46
+ '--diff [string]',
47
+ 'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".'
48
+ )
49
+
50
+ cli.option(
51
+ '--diff-filter [string]',
52
+ 'override the default "--diff-filter=ACMR" flag of "git diff" to get list of files'
53
+ )
54
+
55
+ cli.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
56
56
 
57
57
  /**
58
- * Get the maximum length of a command-line argument string based on current platform
59
- *
60
- * https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
61
- * https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
62
- * https://unix.stackexchange.com/a/120652
58
+ * We don't want to show the `--stash` flag because it's on by default, and only show the
59
+ * negatable flag `--no-stash` in stead. There seems to be a bug in Commander.js where
60
+ * configuring only the latter won't actually set the default value.
63
61
  */
64
- const getMaxArgLength = () => {
65
- switch (process.platform) {
66
- case 'darwin':
67
- return 262144
68
- case 'win32':
69
- return 8191
70
- default:
71
- return 131072
72
- }
62
+ cli
63
+ .addOption(
64
+ new Option('--stash', 'enable the backup stash, and revert in case of errors')
65
+ .default(true)
66
+ .hideHelp()
67
+ )
68
+ .addOption(
69
+ new Option(
70
+ '--no-stash',
71
+ 'disable the backup stash, and do not revert in case of errors'
72
+ ).default(false)
73
+ )
74
+
75
+ cli.option('-q, --quiet', 'disable lint-staged’s own console output', false)
76
+
77
+ cli.option('-r, --relative', 'pass relative filepaths to tasks', false)
78
+
79
+ cli.option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
80
+
81
+ cli.option(
82
+ '-v, --verbose',
83
+ 'show task output even when tasks succeed; by default only failed output is shown',
84
+ false
85
+ )
86
+
87
+ const cliOptions = cli.parse(process.argv).opts()
88
+
89
+ if (cliOptions.debug) {
90
+ debug.enable('lint-staged*')
73
91
  }
74
92
 
75
93
  const options = {
76
- allowEmpty: !!cmdlineOptions.allowEmpty,
77
- concurrent: JSON.parse(cmdlineOptions.concurrent),
78
- configPath: cmdlineOptions.config,
79
- cwd: cmdlineOptions.cwd,
80
- debug: !!cmdlineOptions.debug,
81
- maxArgLength: getMaxArgLength() / 2,
82
- quiet: !!cmdlineOptions.quiet,
83
- relative: !!cmdlineOptions.relative,
84
- shell: cmdlineOptions.shell /* Either a boolean or a string pointing to the shell */,
85
- stash: !!cmdlineOptions.stash, // commander inverts `no-<x>` flags to `!x`
86
- verbose: !!cmdlineOptions.verbose,
94
+ allowEmpty: !!cliOptions.allowEmpty,
95
+ concurrent: JSON.parse(cliOptions.concurrent),
96
+ configPath: cliOptions.config,
97
+ cwd: cliOptions.cwd,
98
+ debug: !!cliOptions.debug,
99
+ diff: cliOptions.diff,
100
+ diffFilter: cliOptions.diffFilter,
101
+ maxArgLength: cliOptions.maxArgLength || undefined,
102
+ quiet: !!cliOptions.quiet,
103
+ relative: !!cliOptions.relative,
104
+ shell: cliOptions.shell /* Either a boolean or a string pointing to the shell */,
105
+ stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
106
+ verbose: !!cliOptions.verbose,
87
107
  }
88
108
 
89
109
  debugLog('Options parsed from command-line:', options)
package/lib/chunkFiles.js CHANGED
@@ -1,4 +1,4 @@
1
- import path from 'path'
1
+ import path from 'node:path'
2
2
 
3
3
  import debug from 'debug'
4
4
  import normalize from 'normalize-path'
@@ -1,3 +1,3 @@
1
- import { pathToFileURL } from 'url'
1
+ import { pathToFileURL } from 'node:url'
2
2
 
3
3
  export const dynamicImport = (path) => import(pathToFileURL(path)).then((module) => module.default)
package/lib/execGit.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import debug from 'debug'
2
- import execa from 'execa'
2
+ import { execa } from 'execa'
3
3
 
4
4
  const debugLog = debug('lint-staged:execGit')
5
5
 
package/lib/file.js CHANGED
@@ -1,4 +1,4 @@
1
- import { promises as fs } from 'fs'
1
+ import fs from 'node:fs/promises'
2
2
 
3
3
  import debug from 'debug'
4
4
 
@@ -1,4 +1,4 @@
1
- import path from 'path'
1
+ import path from 'node:path'
2
2
 
3
3
  import debug from 'debug'
4
4
  import micromatch from 'micromatch'
@@ -21,9 +21,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f
21
21
 
22
22
  const relativeFiles = files.map((file) => normalize(path.relative(cwd, file)))
23
23
 
24
- return Object.entries(config).map(([rawPattern, commands]) => {
25
- let pattern = rawPattern
26
-
24
+ return Object.entries(config).map(([pattern, commands]) => {
27
25
  const isParentDirPattern = pattern.startsWith('../')
28
26
 
29
27
  // Only worry about children of the CWD unless the pattern explicitly
@@ -40,6 +38,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f
40
38
  // match against filenames in every directory. This makes `*.js`
41
39
  // match both `test.js` and `subdirectory/test.js`.
42
40
  matchBase: !pattern.includes('/'),
41
+ posixSlashes: true,
43
42
  strictBrackets: true,
44
43
  })
45
44
 
@@ -0,0 +1,18 @@
1
+ export function getDiffCommand(diff, diffFilter) {
2
+ /**
3
+ * Docs for --diff-filter option:
4
+ * @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
5
+ */
6
+ const diffFilterArg = diffFilter !== undefined ? diffFilter.trim() : 'ACMR'
7
+
8
+ /** Use `--diff branch1...branch2` or `--diff="branch1 branch2", or fall back to default staged files */
9
+ const diffArgs = diff !== undefined ? diff.trim().split(' ') : ['--staged']
10
+
11
+ /**
12
+ * Docs for -z option:
13
+ * @see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
14
+ */
15
+ const diffCommand = ['diff', '--name-only', '-z', `--diff-filter=${diffFilterArg}`, ...diffArgs]
16
+
17
+ return diffCommand
18
+ }
@@ -1,18 +1,14 @@
1
- import path from 'path'
1
+ import path from 'node:path'
2
2
 
3
3
  import normalize from 'normalize-path'
4
4
 
5
5
  import { execGit } from './execGit.js'
6
+ import { getDiffCommand } from './getDiffCommand.js'
6
7
  import { parseGitZOutput } from './parseGitZOutput.js'
7
8
 
8
- export const getStagedFiles = async ({ cwd = process.cwd() } = {}) => {
9
+ export const getStagedFiles = async ({ cwd = process.cwd(), diff, diffFilter } = {}) => {
9
10
  try {
10
- // Docs for --diff-filter option: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
11
- // Docs for -z option: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
12
- const lines = await execGit(['diff', '--staged', '--diff-filter=ACMR', '--name-only', '-z'], {
13
- cwd,
14
- })
15
-
11
+ const lines = await execGit(getDiffCommand(diff, diffFilter), { cwd })
16
12
  if (!lines) return []
17
13
 
18
14
  return parseGitZOutput(lines).map((file) => normalize(path.resolve(cwd, file)))
@@ -1,9 +1,10 @@
1
- import path from 'path'
1
+ import path from 'node:path'
2
2
 
3
3
  import debug from 'debug'
4
4
 
5
5
  import { execGit } from './execGit.js'
6
6
  import { readFile, unlink, writeFile } from './file.js'
7
+ import { getDiffCommand } from './getDiffCommand.js'
7
8
  import {
8
9
  GitError,
9
10
  RestoreOriginalStateError,
@@ -65,12 +66,13 @@ const handleError = (error, ctx, symbol) => {
65
66
  }
66
67
 
67
68
  export class GitWorkflow {
68
- constructor({ allowEmpty, gitConfigDir, gitDir, matchedFileChunks }) {
69
+ constructor({ allowEmpty, gitConfigDir, gitDir, matchedFileChunks, diff, diffFilter }) {
69
70
  this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: gitDir })
70
71
  this.deletedFiles = []
71
72
  this.gitConfigDir = gitConfigDir
72
73
  this.gitDir = gitDir
73
- this.unstagedDiff = null
74
+ this.diff = diff
75
+ this.diffFilter = diffFilter
74
76
  this.allowEmpty = allowEmpty
75
77
  this.matchedFileChunks = matchedFileChunks
76
78
 
@@ -262,7 +264,7 @@ export class GitWorkflow {
262
264
 
263
265
  debugLog('Done adding task modifications to index!')
264
266
 
265
- const stagedFilesAfterAdd = await this.execGit(['diff', '--name-only', '--cached'])
267
+ const stagedFilesAfterAdd = await this.execGit(getDiffCommand(this.diff, this.diffFilter))
266
268
  if (!stagedFilesAfterAdd && !this.allowEmpty) {
267
269
  // Tasks reverted all staged changes and the commit would be empty
268
270
  // Throw error to stop commit unless `--allow-empty` was used
@@ -0,0 +1,55 @@
1
+ import path from 'node:path'
2
+
3
+ import debug from 'debug'
4
+
5
+ const debugLog = debug('lint-staged:groupFilesByConfig')
6
+
7
+ export const groupFilesByConfig = async ({ configs, files, singleConfigMode }) => {
8
+ debugLog('Grouping %d files by %d configurations', files.length, Object.keys(configs).length)
9
+
10
+ const filesSet = new Set(files)
11
+ const filesByConfig = {}
12
+
13
+ /** Configs are sorted deepest first by `searchConfigs` */
14
+ for (const [filepath, config] of Object.entries(configs)) {
15
+ /** When passed an explicit config object via the Node.js API‚ or an explicit path, skip logic */
16
+ if (singleConfigMode) {
17
+ filesByConfig[filepath] = { config, files }
18
+ break
19
+ }
20
+
21
+ const dir = path.normalize(path.dirname(filepath))
22
+
23
+ /** Check if file is inside directory of the configuration file */
24
+ const isInsideDir = (file) => {
25
+ const relative = path.relative(dir, file)
26
+ return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
27
+ }
28
+
29
+ /** This config should match all files since it has a parent glob */
30
+ const includeAllFiles = Object.keys(config).some((glob) => glob.startsWith('..'))
31
+
32
+ const scopedFiles = new Set(includeAllFiles ? filesSet : undefined)
33
+
34
+ /**
35
+ * Without a parent glob, if file is inside the config file's directory,
36
+ * assign it to that configuration.
37
+ */
38
+ if (!includeAllFiles) {
39
+ filesSet.forEach((file) => {
40
+ if (isInsideDir(file)) {
41
+ scopedFiles.add(file)
42
+ }
43
+ })
44
+ }
45
+
46
+ /** Files should only match a single config */
47
+ scopedFiles.forEach((file) => {
48
+ filesSet.delete(file)
49
+ })
50
+
51
+ filesByConfig[filepath] = { config, files: Array.from(scopedFiles) }
52
+ }
53
+
54
+ return filesByConfig
55
+ }
package/lib/index.js CHANGED
@@ -1,13 +1,41 @@
1
1
  import debug from 'debug'
2
2
 
3
- import { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } from './messages.js'
3
+ import {
4
+ PREVENTED_EMPTY_COMMIT,
5
+ GIT_ERROR,
6
+ RESTORE_STASH_EXAMPLE,
7
+ NO_CONFIGURATION,
8
+ } from './messages.js'
4
9
  import { printTaskOutput } from './printTaskOutput.js'
5
10
  import { runAll } from './runAll.js'
6
- import { ApplyEmptyCommitError, GetBackupStashError, GitError } from './symbols.js'
11
+ import {
12
+ ApplyEmptyCommitError,
13
+ ConfigNotFoundError,
14
+ GetBackupStashError,
15
+ GitError,
16
+ } from './symbols.js'
7
17
  import { validateOptions } from './validateOptions.js'
8
18
 
9
19
  const debugLog = debug('lint-staged')
10
20
 
21
+ /**
22
+ * Get the maximum length of a command-line argument string based on current platform
23
+ *
24
+ * https://serverfault.com/questions/69430/what-is-the-maximum-length-of-a-command-line-in-mac-os-x
25
+ * https://support.microsoft.com/en-us/help/830473/command-prompt-cmd-exe-command-line-string-limitation
26
+ * https://unix.stackexchange.com/a/120652
27
+ */
28
+ const getMaxArgLength = () => {
29
+ switch (process.platform) {
30
+ case 'darwin':
31
+ return 262144
32
+ case 'win32':
33
+ return 8191
34
+ default:
35
+ return 131072
36
+ }
37
+ }
38
+
11
39
  /**
12
40
  * @typedef {(...any) => void} LogFunction
13
41
  * @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
@@ -21,6 +49,8 @@ const debugLog = debug('lint-staged')
21
49
  * @param {string} [options.configPath] - Path to configuration file
22
50
  * @param {Object} [options.cwd] - Current working directory
23
51
  * @param {boolean} [options.debug] - Enable debug mode
52
+ * @param {string} [options.diff] - Override the default "--staged" flag of "git diff" to get list of files
53
+ * @param {string} [options.diffFilter] - Override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
24
54
  * @param {number} [options.maxArgLength] - Maximum argument string length
25
55
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
26
56
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
@@ -39,7 +69,9 @@ const lintStaged = async (
39
69
  configPath,
40
70
  cwd,
41
71
  debug = false,
42
- maxArgLength,
72
+ diff,
73
+ diffFilter,
74
+ maxArgLength = getMaxArgLength() / 2,
43
75
  quiet = false,
44
76
  relative = false,
45
77
  shell = false,
@@ -54,31 +86,35 @@ const lintStaged = async (
54
86
  debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
55
87
  delete process.env.GIT_LITERAL_PATHSPECS
56
88
 
89
+ const options = {
90
+ allowEmpty,
91
+ concurrent,
92
+ configObject,
93
+ configPath,
94
+ cwd,
95
+ debug,
96
+ diff,
97
+ diffFilter,
98
+ maxArgLength,
99
+ quiet,
100
+ relative,
101
+ shell,
102
+ stash,
103
+ verbose,
104
+ }
105
+
57
106
  try {
58
- const ctx = await runAll(
59
- {
60
- allowEmpty,
61
- concurrent,
62
- configObject,
63
- configPath,
64
- cwd,
65
- debug,
66
- maxArgLength,
67
- quiet,
68
- relative,
69
- shell,
70
- stash,
71
- verbose,
72
- },
73
- logger
74
- )
107
+ const ctx = await runAll(options, logger)
75
108
  debugLog('Tasks were executed successfully!')
76
109
  printTaskOutput(ctx, logger)
77
110
  return true
78
111
  } catch (runAllError) {
79
- if (runAllError && runAllError.ctx && runAllError.ctx.errors) {
112
+ if (runAllError?.ctx?.errors) {
80
113
  const { ctx } = runAllError
81
- if (ctx.errors.has(ApplyEmptyCommitError)) {
114
+
115
+ if (ctx.errors.has(ConfigNotFoundError)) {
116
+ logger.error(NO_CONFIGURATION)
117
+ } else if (ctx.errors.has(ApplyEmptyCommitError)) {
82
118
  logger.warn(PREVENTED_EMPTY_COMMIT)
83
119
  } else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
84
120
  logger.error(GIT_ERROR)
package/lib/messages.js CHANGED
@@ -22,12 +22,20 @@ export const incorrectBraces = (before, after) =>
22
22
  `
23
23
  )
24
24
 
25
+ export const NO_CONFIGURATION = `${error} No valid configuration found.`
26
+
25
27
  export const NO_STAGED_FILES = `${info} No staged files found.`
26
28
 
27
29
  export const NO_TASKS = `${info} No staged files match any configured task.`
28
30
 
29
- export const skippingBackup = (hasInitialCommit) => {
30
- const reason = hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet'
31
+ export const skippingBackup = (hasInitialCommit, diff) => {
32
+ const reason =
33
+ diff !== undefined
34
+ ? '`--diff` was used'
35
+ : hasInitialCommit
36
+ ? '`--no-stash` was used'
37
+ : 'there’s no initial commit yet'
38
+
31
39
  return yellow(`${warning} Skipping backup because ${reason}.\n`)
32
40
  }
33
41