lint-staged 9.2.5 β†’ 9.4.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,30 @@
2
2
 
3
3
  Run linters against staged git files and don't let :poop: slip into your code base!
4
4
 
5
+ ---
6
+
7
+ ## 🚧 Help test `lint-staged@next`!
8
+
9
+ Version 10 of `lint-staged` is coming with changes that help it run faster on large git repositories and prevent loss of data during errors. Please help test the `next` version and report any inconsistencies in our [GitHub Issues](https://github.com/okonet/lint-staged/issues):
10
+
11
+ **Using npm**
12
+
13
+ npm install --save-dev lint-staged@next
14
+
15
+ **Using yarn**
16
+
17
+ yarn add -D lint-staged@next
18
+
19
+ ### Notable changes
20
+
21
+ - A git stash is created before running any tasks, so in case of errors any lost changes can be restored easily (and automatically unless lint-staged itself crashes)
22
+ - Instead of write-tree/read-tree, `lint-staged@next` uses git stashes to hide unstaged changes while running tasks against staged files
23
+ - This results in a performance increase of up to 45x on very large repositories
24
+ - The behaviour of committing modifications during tasks (eg. `prettier --write && git add`) is different. The current version creates a diff of these modifications, and applies it against the original state, silently ignoring any errors. The `next` version leaves modifications of staged files as-is, and then restores all hidden unstaged changes as patch. If applying the patch fails due to a merge conflict (because tasks have modified the same lines), a 3-way merge will be retried. If this also fails, the entire commit will fail and the original state will be restored.
25
+ - **TL;DR** the `next` version will never skip committing any changes by tasks (due to a merge conflict), but might fail in very complex situations where unstaged changes cannot be restored cleanly. If this happens to you, we are very interested in a repeatable test scenario.
26
+
27
+ ---
28
+
5
29
  [![asciicast](https://asciinema.org/a/199934.svg)](https://asciinema.org/a/199934)
6
30
 
7
31
  ## Why
@@ -109,7 +133,7 @@ Linter commands work on a subset of all staged files, defined by a _glob pattern
109
133
  * **`"!(*test).js"`**. will match all JS files, except those ending in `test.js`, so `foo.js` but not `foo.test.js`
110
134
  * If the glob pattern does contain a slash (`/`), it will match for paths as well:
111
135
  * **`"/*.js"`** will match all JS files in the git repo root, so `/test.js` but not `/foo/bar/test.js`
112
- * **`"foo/**/\*.js"`** will match all JS files inside the`/foo`directory, so`/foo/bar/test.js`but not`/test.js`
136
+ * **`"foo/**/*.js"`** will match all JS files inside the`/foo`directory, so`/foo/bar/test.js`but not`/test.js`
113
137
 
114
138
  When matching, `lint-staged` will do the following
115
139
 
@@ -400,6 +424,20 @@ const success = await lintStaged({
400
424
  })
401
425
  ```
402
426
 
427
+ You can also pass config directly with `config` option:
428
+
429
+
430
+ ```js
431
+ const success = await lintStaged({
432
+ config: {
433
+ '*.js': 'eslint --fix'
434
+ },
435
+ shell: false,
436
+ quiet: false,
437
+ debug: false
438
+ })
439
+ ```
440
+
403
441
  ### Using with JetBrains IDEs _(WebStorm, PyCharm, IntelliJ IDEA, RubyMine, etc.)_
404
442
 
405
443
  _**Update**_: The latest version of JetBrains IDEs now support running hooks as you would expect.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "9.2.5",
3
+ "version": "9.4.2",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/okonet/lint-staged",
package/src/index.js CHANGED
@@ -43,6 +43,7 @@ function loadConfig(configPath) {
43
43
  *
44
44
  * @param {object} options
45
45
  * @param {string} [options.configPath] - Path to configuration file
46
+ * @param {object} [options.config] - Object with configuration for programmatic API
46
47
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
47
48
  * @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
48
49
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
@@ -52,12 +53,12 @@ function loadConfig(configPath) {
52
53
  * @returns {Promise<boolean>} Promise of whether the linting passed or failed
53
54
  */
54
55
  module.exports = function lintStaged(
55
- { configPath, relative = false, shell = false, quiet = false, debug = false } = {},
56
+ { configPath, config, relative = false, shell = false, quiet = false, debug = false } = {},
56
57
  logger = console
57
58
  ) {
58
59
  debugLog('Loading config using `cosmiconfig`')
59
60
 
60
- return loadConfig(configPath)
61
+ return (config ? Promise.resolve({ config, filepath: '(input)' }) : loadConfig(configPath))
61
62
  .then(result => {
62
63
  if (result == null) throw errConfigNotFound
63
64
 
@@ -8,27 +8,40 @@ const debug = require('debug')('lint-staged:make-cmd-tasks')
8
8
  * Creates and returns an array of listr tasks which map to the given commands.
9
9
  *
10
10
  * @param {object} options
11
- * @param {Array<string|Function>|string|Function} [options.commands]
12
- * @param {string} [options.gitDir]
13
- * @param {Array<string>} [options.pathsToLint]
11
+ * @param {Array<string|Function>|string|Function} options.commands
12
+ * @param {Array<string>} options.files
13
+ * @param {string} options.gitDir
14
14
  * @param {Boolean} shell
15
15
  */
16
- module.exports = async function makeCmdTasks({ commands, gitDir, pathsToLint, shell }) {
16
+ module.exports = async function makeCmdTasks({ commands, files, gitDir, shell }) {
17
17
  debug('Creating listr tasks for commands %o', commands)
18
18
  const commandsArray = Array.isArray(commands) ? commands : [commands]
19
19
 
20
20
  return commandsArray.reduce((tasks, command) => {
21
- // linter function may return array of commands that already include `pathsToLit`
21
+ // command function may return array of commands that already include `stagedFiles`
22
22
  const isFn = typeof command === 'function'
23
- const resolved = isFn ? command(pathsToLint) : command
24
- const linters = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array linter as array
23
+ const resolved = isFn ? command(files) : command
24
+ const commands = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array command as array
25
25
 
26
- linters.forEach(linter => {
27
- const task = {
28
- title: linter,
29
- task: resolveTaskFn({ gitDir, isFn, linter, pathsToLint, shell })
26
+ // Function command should not be used as the task title as-is
27
+ // because the resolved string it might be very long
28
+ // Create a matching command array with [file] in place of file names
29
+ let mockCommands
30
+ if (isFn) {
31
+ const mockFileList = Array(files.length).fill('[file]')
32
+ const resolved = command(mockFileList)
33
+ mockCommands = Array.isArray(resolved) ? resolved : [resolved]
34
+ }
35
+
36
+ commands.forEach((command, i) => {
37
+ let title = isFn ? '[Function]' : command
38
+ if (isFn && mockCommands[i]) {
39
+ // If command is a function, use the matching mock command as title,
40
+ // but since might include multiple [file] arguments, shorten to one
41
+ title = mockCommands[i].replace(/\[file\].*\[file\]/, '[file]')
30
42
  }
31
43
 
44
+ const task = { title, task: resolveTaskFn({ gitDir, isFn, command, files, shell }) }
32
45
  tasks.push(task)
33
46
  })
34
47
 
@@ -77,27 +77,20 @@ function makeErr(linter, result, context = {}) {
77
77
  * if the OS is Windows.
78
78
  *
79
79
  * @param {Object} options
80
- * @param {String} [options.gitDir] - Current git repo path
81
- * @param {Boolean} [options.isFn] - Whether the linter task is a function
82
- * @param {string} [options.linter] β€” Linter task
83
- * @param {Array<string>} [options.pathsToLint] β€” Filepaths to run the linter task against
80
+ * @param {string} options.command β€” Linter task
81
+ * @param {String} options.gitDir - Current git repo path
82
+ * @param {Boolean} options.isFn - Whether the linter task is a function
83
+ * @param {Array<string>} options.pathsToLint β€” Filepaths to run the linter task against
84
84
  * @param {Boolean} [options.relative] β€” Whether the filepaths should be relative
85
85
  * @param {Boolean} [options.shell] β€” Whether to skip parsing linter task for better shell support
86
86
  * @returns {function(): Promise<Array<string>>}
87
87
  */
88
- module.exports = function resolveTaskFn({
89
- gitDir,
90
- isFn,
91
- linter,
92
- pathsToLint,
93
- relative,
94
- shell = false
95
- }) {
88
+ module.exports = function resolveTaskFn({ command, files, gitDir, isFn, relative, shell = false }) {
96
89
  const execaOptions = { preferLocal: true, reject: false, shell }
97
90
 
98
91
  if (relative) {
99
92
  execaOptions.cwd = process.cwd()
100
- } else if (/^git(\.exe)?/i.test(linter) && gitDir !== process.cwd()) {
93
+ } else if (/^git(\.exe)?/i.test(command) && gitDir !== process.cwd()) {
101
94
  // Only use gitDir as CWD if we are using the git binary
102
95
  // e.g `npm` should run tasks in the actual CWD
103
96
  execaOptions.cwd = gitDir
@@ -109,20 +102,20 @@ module.exports = function resolveTaskFn({
109
102
  if (shell) {
110
103
  execaOptions.shell = true
111
104
  // If `shell`, passed command shouldn't be parsed
112
- // If `linter` is a function, command already includes `pathsToLint`.
113
- cmd = isFn ? linter : `${linter} ${pathsToLint.join(' ')}`
105
+ // If `linter` is a function, command already includes `files`.
106
+ cmd = isFn ? command : `${command} ${files.join(' ')}`
114
107
  } else {
115
- const [parsedCmd, ...parsedArgs] = stringArgv.parseArgsStringToArgv(linter)
108
+ const [parsedCmd, ...parsedArgs] = stringArgv.parseArgsStringToArgv(command)
116
109
  cmd = parsedCmd
117
- args = isFn ? parsedArgs : parsedArgs.concat(pathsToLint)
110
+ args = isFn ? parsedArgs : parsedArgs.concat(files)
118
111
  }
119
112
 
120
113
  return ctx =>
121
114
  execLinter(cmd, args, execaOptions).then(result => {
122
115
  if (result.failed || result.killed || result.signal != null) {
123
- throw makeErr(linter, result, ctx)
116
+ throw makeErr(command, result, ctx)
124
117
  }
125
118
 
126
- return successMsg(linter)
119
+ return successMsg(command)
127
120
  })
128
121
  }
package/src/runAll.js CHANGED
@@ -73,7 +73,7 @@ https://github.com/okonet/lint-staged#using-js-functions-to-customize-linter-com
73
73
  title: `Running tasks for ${task.pattern}`,
74
74
  task: async () =>
75
75
  new Listr(
76
- await makeCmdTasks({ commands: task.commands, gitDir, shell, pathsToLint: task.fileList }),
76
+ await makeCmdTasks({ commands: task.commands, files: task.fileList, gitDir, shell }),
77
77
  {
78
78
  // In sub-tasks we don't want to run concurrently
79
79
  // and we want to abort on errors