lint-staged 17.0.8 β†’ 17.1.1

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,9 +1,5 @@
1
1
  # πŸš«πŸ’© lint-staged
2
2
 
3
- [![npm version](https://badge.fury.io/js/lint-staged.svg)](https://badge.fury.io/js/lint-staged)
4
-
5
- ---
6
-
7
3
  Run tasks like formatters and linters against staged git files and don't let :poop: slip into your code base!
8
4
 
9
5
  ```bash
@@ -13,26 +9,20 @@ npm install --save-dev lint-staged # requires further setup
13
9
  ```
14
10
  $ git commit
15
11
 
16
- βœ” Backed up original state in git stash (5bda95f)
17
- ❯ Running tasks for staged files...
18
- ❯ packages/frontend/.lintstagedrc.json β€” 1 file
19
- ↓ *.js β€” no files [SKIPPED]
20
- ❯ *.{json,md} β€” 1 file
21
- β Ή prettier --write
22
- ↓ packages/backend/.lintstagedrc.json β€” 2 files
23
- ❯ *.js β€” 2 files
24
- β Ό eslint --fix
25
- ↓ *.{json,md} β€” no files [SKIPPED]
26
- β—Ό Updating Git index again...
27
- β—Ό Cleaning up temporary files...
28
- ```
29
-
30
- <details>
31
- <summary>See asciinema video</summary>
12
+ β‹― Backing up original state…
13
+ βœ” Done backing up original state (1f4c047d)!
14
+ β‹― Running tasks for staged files…
15
+ *.{json,md} β€” 1 file
16
+ β‹― prettier --write
32
17
 
33
- [![asciicast](https://asciinema.org/a/199934.svg)](https://asciinema.org/a/199934)
18
+ βœ” prettier --write
34
19
 
35
- </details>
20
+ βœ” Done running tasks for staged files!
21
+ β‹― Staging changes from tasks…
22
+ βœ” Done staging changes from tasks!
23
+ β‹― Cleaning up temporary files…
24
+ βœ” Done cleaning up temporary files!
25
+ ```
36
26
 
37
27
  > [!Tip]
38
28
  > Do you only want to check staged files for errors, but not edit them automatically?
@@ -154,11 +144,7 @@ Change the working directory _lint-staged_ runs tasks in. Defaults to `process.c
154
144
 
155
145
  #### `--debug`
156
146
 
157
- Run in debug mode. When set, it does the following:
158
-
159
- - log additional information about staged files, commands being executed, location of binaries, etc.
160
- - uses [`verbose` renderer](https://listr2.kilic.dev/renderers/verbose-renderer/) for `listr2`; this causes serial, uncoloured output to the terminal, instead of the default (beautified, dynamic) output.
161
- (the [`verbose` renderer](https://listr2.kilic.dev/renderers/verbose-renderer/) can also be activated by setting the `TERM=dumb` or `NODE_ENV=test` environment variables)
147
+ Log additional information about staged files, commands being executed, location of binaries, etc.
162
148
 
163
149
  #### `--diff`
164
150
 
@@ -178,7 +164,7 @@ By default changes made by tasks are automatically staged and added to the commi
178
164
 
179
165
  #### `--max-arg-length [number]`
180
166
 
181
- 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.
167
+ The list of staged files are appended to configured tasks as arguments, and the resulting string might be too long for the current shell, especially on Windows. _Lint-staged_ tries to avoid this by splitting the list of staged files into chunks resulting in the commands being run multiple times. This behavior affects all tasks, including functions. Use the `--max-arg-length` to override the platform-specific default value if you want to avoid this. Setting `--max-arg-length=Infinity` will disable chunking completely.
182
168
 
183
169
  #### `--no-stash`
184
170
 
@@ -3,14 +3,15 @@
3
3
  import { userInfo } from 'node:os'
4
4
 
5
5
  import { getVersionNumber, parseCliOptions, printHelpText } from '../lib/cli.js'
6
+ import { enableColors } from '../lib/colors.js'
6
7
  import { createDebug, enableDebug } from '../lib/debug.js'
7
8
  import lintStaged from '../lib/index.js'
8
- import { CONFIG_STDIN_ERROR } from '../lib/messages.js'
9
9
  import { readStdin } from '../lib/readStdin.js'
10
10
 
11
+ enableColors(!!process.stdout.hasColors?.())
11
12
  const debugLog = createDebug('lint-staged:bin')
12
13
 
13
- // Do not terminate main Listr process on SIGINT
14
+ // SIGINT handled by an AbortController
14
15
  process.on('SIGINT', () => {})
15
16
 
16
17
  const cliOptions = parseCliOptions(process.argv)
@@ -44,9 +45,8 @@ if (cliOptions.configPath === '-') {
44
45
  debugLog('Reading config from stdin')
45
46
  cliOptions.config = JSON.parse(await readStdin())
46
47
  } catch (error) {
47
- debugLog(CONFIG_STDIN_ERROR, error)
48
- console.error(CONFIG_STDIN_ERROR)
49
- process.exit(1)
48
+ console.error('Failed to read config from stdin!')
49
+ throw error
50
50
  }
51
51
  }
52
52
 
package/lib/chunkFiles.js CHANGED
@@ -46,8 +46,8 @@ export const chunkFiles = ({ files, baseDir, maxArgLength = null, relative = fal
46
46
  }
47
47
  })
48
48
 
49
- if (!maxArgLength) {
50
- debugLog('Skip chunking files because of undefined maxArgLength')
49
+ if (!maxArgLength || maxArgLength === Infinity) {
50
+ debugLog(`Skip chunking files because of maxArgLength is ${maxArgLength}`)
51
51
  return [normalizedFiles] // wrap in an array to return a single chunk
52
52
  }
53
53
 
package/lib/cli.js CHANGED
@@ -91,7 +91,7 @@ const CLI_OPTIONS = [
91
91
  },
92
92
  {
93
93
  flag: 'max-arg-length',
94
- type: 'string', // Parsed with `parseInt()` below
94
+ type: 'string', // Parsed with `Number()` below
95
95
  positional: '[number]',
96
96
  description: 'maximum length of the command-line argument string (default: 0)',
97
97
  },
@@ -128,6 +128,20 @@ const CLI_OPTIONS = [
128
128
  },
129
129
  ]
130
130
 
131
+ /** @type {(value: string |undefined) => number | boolean | null | undefined} */
132
+ const parseBoolOrNumber = (value) => {
133
+ switch (value) {
134
+ case undefined:
135
+ return undefined
136
+ case 'true':
137
+ return true
138
+ case 'false':
139
+ return false
140
+ default:
141
+ return Number(value)
142
+ }
143
+ }
144
+
131
145
  /** @param {string[]} argv */
132
146
  export const parseCliOptions = (argv) => {
133
147
  const options = CLI_OPTIONS.reduce((acc, current) => {
@@ -167,18 +181,9 @@ export const parseCliOptions = (argv) => {
167
181
  values['hide-unstaged'] = false // becomes redundant
168
182
  }
169
183
 
170
- const maxArgLength =
171
- values['max-arg-length'] !== undefined ? parseInt(values['max-arg-length'], 10) : undefined
172
-
173
- if (Number.isNaN(maxArgLength)) {
174
- throw new TypeError(`Option '--mar-arg-length' takes a numeric argument`, {
175
- cause: { '--max-arg-length': values['max-arg-length'] },
176
- })
177
- }
178
-
179
184
  return {
180
185
  allowEmpty: values['allow-empty'] ?? false,
181
- concurrent: values.concurrent === undefined ? true : JSON.parse(values.concurrent),
186
+ concurrent: parseBoolOrNumber(values.concurrent) ?? true,
182
187
  configPath: values.config,
183
188
  continueOnError: !!values['continue-on-error'],
184
189
  cwd: values.cwd,
@@ -190,7 +195,7 @@ export const parseCliOptions = (argv) => {
190
195
  hidePartiallyStaged: values['hide-partially-staged'] ?? true,
191
196
  hideUnstaged: !!values['hide-unstaged'],
192
197
  hideAll: !!values['hide-all'],
193
- maxArgLength,
198
+ maxArgLength: parseBoolOrNumber(values['max-arg-length']),
194
199
  quiet: !!values.quiet,
195
200
  relative: !!values.relative,
196
201
  revert: values.revert ?? true,
package/lib/colors.js CHANGED
@@ -1,11 +1,29 @@
1
- /* eslint-disable n/no-unsupported-features/node-builtins */
2
-
3
1
  import util from 'node:util'
4
2
 
5
- export const SUPPORTS_COLOR = !!process.stdout.hasColors?.()
3
+ export let COLORS_ENABLED = false
4
+
5
+ /** @param {boolean} [enabled] */
6
+ export const enableColors = (enabled) => {
7
+ if (enabled) {
8
+ COLORS_ENABLED = true
9
+ }
10
+ }
11
+
12
+ /**
13
+ * @param {util.InspectColor | readonly util.InspectColor[]} format
14
+ * @returns {(text: string) => string}
15
+ */
16
+ const styleText = (format) => (text) =>
17
+ COLORS_ENABLED ? util.styleText(format, text, { validateStream: false }) : text
18
+
19
+ export const green = styleText('green')
20
+
21
+ export const red = styleText('red')
22
+
23
+ export const yellow = styleText('yellow')
24
+
25
+ export const blue = styleText('blue')
26
+
27
+ export const dim = styleText('dim')
6
28
 
7
- export const red = (text) => (SUPPORTS_COLOR ? util.styleText('red', text) : text)
8
- export const yellow = (text) => (SUPPORTS_COLOR ? util.styleText('yellow', text) : text)
9
- export const blue = (text) => (SUPPORTS_COLOR ? util.styleText('blue', text) : text)
10
- export const dim = (text) => (SUPPORTS_COLOR ? util.styleText('dim', text) : text)
11
- export const bold = (text) => (SUPPORTS_COLOR ? util.styleText('bold', text) : text)
29
+ export const bold = styleText('bold')
package/lib/debug.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { formatWithOptions } from 'node:util'
2
2
 
3
- import { dim, SUPPORTS_COLOR } from './colors.js'
3
+ import { COLORS_ENABLED, dim } from './colors.js'
4
4
 
5
- const format = (...args) => formatWithOptions({ colors: SUPPORTS_COLOR }, ...args)
5
+ const format = (...args) => formatWithOptions({ colors: COLORS_ENABLED }, ...args)
6
6
 
7
7
  let activeLogger
8
8
 
package/lib/figures.js CHANGED
@@ -1,9 +1,13 @@
1
- import { figures } from 'listr2'
1
+ import { blue, dim, green, red, yellow } from './colors.js'
2
2
 
3
- import { blue, red, yellow } from './colors.js'
3
+ export const wip = () => blue('β‹―')
4
4
 
5
- export const info = blue(figures.arrowRight)
5
+ export const done = () => green('βœ”')
6
6
 
7
- export const error = red(figures.cross)
7
+ export const info = () => blue('β†’')
8
8
 
9
- export const warning = yellow(figures.warning)
9
+ export const error = () => red('βœ–')
10
+
11
+ export const warning = () => yellow('⚠')
12
+
13
+ export const cancelled = () => dim('↓')
@@ -1,4 +1,5 @@
1
1
  import { createDebug } from './debug.js'
2
+ import { Signal } from './getAbortController.js'
2
3
  import { createTaskError } from './getSpawnedTask.js'
3
4
 
4
5
  const debugLog = createDebug('lint-staged:getFunctionTasks')
@@ -13,17 +14,25 @@ export const isFunctionTask = (commands) => typeof commands === 'object' && !Arr
13
14
  /**
14
15
  * Handles function configuration and pushes the tasks into the task array
15
16
  *
16
- * @param {object} command
17
- * @param {import('./getStagedFiles.js').StagedFile[]} files
17
+ * @param {object} options
18
+ * @param {AbortController} options.abortController
19
+ * @param {FunctionTask} options.command
20
+ * @param {boolean} options.continueOnError
21
+ * @param {import('./getStagedFiles.js').StagedFile[]} options.files
18
22
  * @throws {Error} If the function configuration is not valid
19
23
  */
20
- export const getFunctionTask = async (command, files) => {
21
- debugLog('Creating Listr tasks for function %o', command)
24
+ export const getFunctionTask = async ({ abortController, command, continueOnError, files }) => {
25
+ debugLog('Creating task for function %o', command)
22
26
 
23
27
  const task = async (ctx) => {
24
28
  try {
25
29
  await command.task(files.map((file) => file.filepath))
26
30
  } catch (e) {
31
+ if (continueOnError !== true) {
32
+ /** Other tasks should be killed */
33
+ abortController.abort(Signal.SIGKILL)
34
+ }
35
+
27
36
  throw createTaskError(command.title, e, ctx)
28
37
  }
29
38
  }
@@ -1,9 +1,9 @@
1
1
  import { parseArgsStringToArgv } from 'string-argv'
2
2
  import { exec } from 'tinyexec'
3
3
 
4
- import { dim, red } from './colors.js'
4
+ import { COLORS_ENABLED, dim, red } from './colors.js'
5
5
  import { createDebug } from './debug.js'
6
- import { error, info } from './figures.js'
6
+ import * as figures from './figures.js'
7
7
  import { Signal } from './getAbortController.js'
8
8
  import { killSubProcesses } from './killSubprocesses.js'
9
9
  import { getInitialState } from './state.js'
@@ -22,7 +22,9 @@ const debugLog = createDebug('lint-staged:getSpawnedTask')
22
22
  */
23
23
  const handleTaskOutput = (command, output, ctx, signal, errorResult) => {
24
24
  if (output) {
25
- const outputTitle = errorResult ? red(`${error} ${command}:`) : `${info} ${command}:`
25
+ const outputTitle = errorResult
26
+ ? red(`${figures.error()} ${command}:`)
27
+ : `${figures.info()} ${command}:`
26
28
  ctx.output.push([...(ctx.quiet ? [] : ['', outputTitle]), output].join('\n'))
27
29
  return
28
30
  }
@@ -32,11 +34,11 @@ const handleTaskOutput = (command, output, ctx, signal, errorResult) => {
32
34
  }
33
35
 
34
36
  if (signal === 'SIGINT') {
35
- ctx.output.push(red(`\n${error} Task interrupted: ${command}`))
37
+ ctx.output.push(red(`\n${figures.error()} Task interrupted: ${command}`))
36
38
  } else if (signal === 'SIGKILL') {
37
- ctx.output.push(red(`\n${error} Task killed: ${command}`))
39
+ ctx.output.push(red(`\n${figures.error()} Task killed: ${command}`))
38
40
  } else if (errorResult) {
39
- ctx.output.push(red(`\n${error} Task failed to spawn: ${command}`), signal)
41
+ ctx.output.push(red(`\n${figures.error()} Task failed to spawn: ${command}`), signal)
40
42
  }
41
43
  }
42
44
 
@@ -59,7 +61,6 @@ export const createTaskError = (command, result, ctx, signal = 'FAILED') => {
59
61
  *
60
62
  * @param {Object} options
61
63
  * @param {AbortController} options.abortController
62
- * @param {boolean} [options.color]
63
64
  * @param {string} options.command β€” Linter task
64
65
  * @param {string} [options.continueOnError]
65
66
  * @param {string} [options.cwd]
@@ -71,7 +72,6 @@ export const createTaskError = (command, result, ctx, signal = 'FAILED') => {
71
72
  */
72
73
  export const getSpawnedTask = ({
73
74
  abortController,
74
- color,
75
75
  command,
76
76
  continueOnError = false,
77
77
  cwd = process.cwd(),
@@ -90,7 +90,7 @@ export const getSpawnedTask = ({
90
90
  // Only use topLevelDir as CWD if we are using the git binary
91
91
  // e.g `npm` should run tasks in the actual CWD
92
92
  cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
93
- env: color ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
93
+ env: COLORS_ENABLED ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
94
94
  },
95
95
  }
96
96
 
@@ -5,11 +5,10 @@ import { configurationError } from './messages.js'
5
5
  const debugLog = createDebug('lint-staged:getSpawnedTasks')
6
6
 
7
7
  /**
8
- * Creates and returns an array of listr tasks which map to the given commands.
8
+ * Creates and returns an array of tasks which map to the given commands.
9
9
  *
10
10
  * @param {object} options
11
11
  * @param {AbortController} options.abortController
12
- * @param {boolean} [options.color]
13
12
  * @param {Array<string|Function>|string|Function} options.commands
14
13
  * @param {string} options.continueOnError
15
14
  * @param {string} options.cwd
@@ -19,7 +18,6 @@ const debugLog = createDebug('lint-staged:getSpawnedTasks')
19
18
  */
20
19
  export const getSpawnedTasks = async ({
21
20
  abortController,
22
- color,
23
21
  commands,
24
22
  continueOnError,
25
23
  cwd,
@@ -27,7 +25,7 @@ export const getSpawnedTasks = async ({
27
25
  topLevelDir,
28
26
  verbose,
29
27
  }) => {
30
- debugLog('Creating Listr tasks for commands %o', commands)
28
+ debugLog('Creating tasks for commands %o', commands)
31
29
  const cmdTasks = []
32
30
 
33
31
  const commandArray = Array.isArray(commands) ? commands : [commands]
@@ -58,7 +56,6 @@ export const getSpawnedTasks = async ({
58
56
 
59
57
  const task = getSpawnedTask({
60
58
  abortController,
61
- color,
62
59
  command,
63
60
  continueOnError,
64
61
  cwd,
@@ -68,7 +65,7 @@ export const getSpawnedTasks = async ({
68
65
  verbose,
69
66
  })
70
67
 
71
- cmdTasks.push({ title: command, command, task })
68
+ cmdTasks.push({ title: command, task })
72
69
  }
73
70
  }
74
71