lint-staged 16.4.0 → 17.0.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/MIGRATION.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## v17
2
+
3
+ #### Node.js v20 is no longer supported.
4
+
5
+ The oldest supported Node.js version is now 22.22.1, which is the latest active v22 LTS version at the time of release. You can — of course — update to v24 or v25, or later.
6
+
7
+ #### The `yaml` dependency is now optional
8
+
9
+ The dependency `yaml` is now marked as optional and probably won't be installed by default. If you're using a YAML configuration file you should install the package separately:
10
+
11
+ ```shell
12
+ npm install --development yaml
13
+ ```
14
+
15
+ If you're using `.lintstagedrc` as the config file name (without a file extension), it will be treated as a YAML file. If the content is JSON, consider renaming it to `.lintstagedrc.json` to avoid needing to install `yaml`.
16
+
17
+ #### Git version needs to be at least 2.32.0
18
+
19
+ _Lint-staged_ now tries to verify the installed Git version is at least `2.32.0`, released in 2021. If you're using an even older Git version, you need to [upgrade](https://git-scm.com/install/mac) it before running _lint-staged_!
20
+
1
21
  ## v16
2
22
 
3
23
  #### Updated Node.js version requirement
package/README.md CHANGED
@@ -23,7 +23,7 @@ $ git commit
23
23
  ❯ *.js — 2 files
24
24
  ⠼ eslint --fix
25
25
  ↓ *.{json,md} — no files [SKIPPED]
26
- Applying modifications from tasks...
26
+ Updating Git index again...
27
27
  ◼ Cleaning up temporary files...
28
28
  ```
29
29
 
@@ -104,34 +104,32 @@ For breaking changes, see [MIGRATION.md](./MIGRATION.md).
104
104
  ❯ npx lint-staged --help
105
105
  Usage: lint-staged [options]
106
106
 
107
- Options:
108
- -V, --version output the version number
109
- --allow-empty allow empty commits when tasks revert all staged changes (default: false)
110
- -p, --concurrent <number|boolean> the number of tasks to run concurrently, or false for serial (default: true)
111
- -c, --config [path] path to configuration file, or - to read from stdin
112
- --cwd [path] run all tasks in specific directory, instead of the current
113
- -d, --debug print additional debug information (default: false)
114
- --diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies
115
- "--no-stash".
116
- --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
117
- --continue-on-error run all tasks to completion even if one fails (default: false)
118
- --fail-on-changes fail with exit code 1 when tasks modify tracked files (default: false)
119
- --max-arg-length [number] maximum length of the command-line argument string (default: 0)
120
- --no-revert do not revert to original state in case of errors.
121
- --no-stash disable the backup stash. Implies "--no-revert".
122
- --no-hide-partially-staged disable hiding unstaged changes from partially staged files
123
- --hide-unstaged hide all unstaged changes, instead of just partially staged (default: false)
124
- -q, --quiet disable lint-staged’s own console output (default: false)
125
- -r, --relative pass relative filepaths to tasks (default: false)
126
- -v, --verbose show task output even when tasks succeed; by default only failed output is shown
127
- (default: false)
128
- -h, --help display help for command
107
+ -h, --help display this help message
108
+ -V, --version display the current version number
109
+ --allow-empty allow empty commits when tasks revert all staged changes (default: false)
110
+ -p, --concurrent <number|boolean> the number of tasks to run concurrently, or false for serial (default: true)
111
+ -c, --config [path] path to configuration file, or - to read from stdin
112
+ --continue-on-error run all tasks to completion even if one fails (default: false)
113
+ --cwd [path] run all tasks in specific directory, instead of the current
114
+ -d, --debug print additional debug information (default: false)
115
+ --diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".
116
+ --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
117
+ --fail-on-changes fail with exit code 1 when tasks modify tracked files (default: false)
118
+ --no-hide-partially-staged hide unstaged changes from partially staged files (default: true)
119
+ --hide-unstaged hide all unstaged changes, instead of just partially staged (default: false)
120
+ --hide-all hide all unstaged changes and untracked files (default: false)
121
+ --max-arg-length [number] maximum length of the command-line argument string (default: 0)
122
+ -q, --quiet disable lint-staged's own console output (default: false)
123
+ -r, --relative pass relative filepaths to tasks (default: false)
124
+ --no-revert revert to original state in case of errors (default: true)
125
+ --no-stash enable the backup stash (default: true)
126
+ -v, --verbose show task output even when tasks succeed; by default only failed output is shown (default: false)
129
127
 
130
128
  Any lost modifications can be restored from a git stash:
131
129
 
132
130
  > git stash list --format="%h %s"
133
- h0a0s0h0 On main: lint-staged automatic backup
134
- > git apply --index h0a0s0h0
131
+ <git-hash> On main: lint-staged automatic backup
132
+ > git apply --index <git-hash>
135
133
  ```
136
134
 
137
135
  #### `--allow-empty`
@@ -152,7 +150,7 @@ Manually specify a path to a config file or npm package name. Note: when used, l
152
150
 
153
151
  #### `--cwd [path]`
154
152
 
155
- By default tasks run in the current working directory. Use the `--cwd some/directory` to override this. The path can be absolute or relative to the current working directory.
153
+ Change the working directory _lint-staged_ runs tasks in. Defaults to `process.cwd()` and the value can be absolute or relative to the default value.
156
154
 
157
155
  #### `--debug`
158
156
 
@@ -192,7 +190,11 @@ By default, unstaged changes from partially staged files will be hidden and appl
192
190
 
193
191
  #### `--hide-unstaged`
194
192
 
195
- Use this option to hide all unstaged changes to tracked files before running tasks. The changes will be applied back after running the tasks. Note that the combination of flags `--hide-unstaged --no-hide-partially-staged` isn't meaningful and behaves the same as just `--hide-unstaged`.
193
+ Use this option to hide all unstaged changes in tracked files, instead of just those which are also partially staged, before running tasks. The changes will be applied back after running the tasks.
194
+
195
+ #### `--hide-all`
196
+
197
+ Add new option `--hide-all` for hiding all unstaged changes and untracked files, before running tasks. This makes it easier to run tools like [Knip](https://knip.dev) which check for unused code. Untracked files are included in the backup stash and restored automatically after running.
196
198
 
197
199
  #### `--quiet`
198
200
 
@@ -229,7 +231,7 @@ _Lint-staged_ can be configured in many ways:
229
231
 
230
232
  Configuration should be an object where each value is a **command** to run and its key is a glob pattern to use for this command. This package uses [picomatch](https://github.com/micromatch/picomatch) for glob patterns. JavaScript files can also export advanced configuration as a function. See [Using JS configuration files](#using-js-configuration-files) for more info.
231
233
 
232
- You can also place multiple configuration files in different directories inside a project. For a given staged file, the closest configuration file will always be used. See ["How to use `lint-staged` in a multi-package monorepo?"](#how-to-use-lint-staged-in-a-multi-package-monorepo) for more info and an example.
234
+ You can also place multiple configuration files in different directories inside a project. For a given staged file, the closest configuration file will always be used and tasks will by default run in the directory of the config (for example, the directory of a specific package in a monorepo). See ["How to use `lint-staged` in a multi-package monorepo?"](#how-to-use-lint-staged-in-a-multi-package-monorepo) for more info and an example.
233
235
 
234
236
  #### `package.json` example:
235
237
 
@@ -901,11 +903,11 @@ _Thanks to [this comment](https://youtrack.jetbrains.com/issue/IDEA-135454#comme
901
903
  <details>
902
904
  <summary>Click to expand</summary>
903
905
 
904
- Install _lint-staged_ on the monorepo root level, and add separate configuration files in each package. When running, _lint-staged_ will always use the configuration closest to a staged file, so having separate configuration files makes sure tasks do not "leak" into other packages.
906
+ Install _lint-staged_ on the monorepo root level and add separate configuration files in each package. _Lint-staged_ will find each config file and match staged files to the closest config. The directory of each config file will be used as the working directory for those tasks, unless `--cwd` option is used. This is almost the same as running multiple processes of _lint-staged_ in parallel for each config, but multiple parallel locking Git operations are avoided.
905
907
 
906
908
  For example, in a monorepo with `packages/frontend/.lintstagedrc.json` and `packages/backend/.lintstagedrc.json`, a staged file inside `packages/frontend/` will only match that configuration, and not the one in `packages/backend/`.
907
909
 
908
- **Note**: _lint-staged_ discovers the closest configuration to each staged file, even if that configuration doesn't include any matching globs. Given these example configurations:
910
+ **Note**: _lint-staged_ does not merge config files, so if the closest config file to a staged file doesn't match it, the file will be ignored. For example:
909
911
 
910
912
  ```js
911
913
  // ./.lintstagedrc.json
@@ -928,7 +930,9 @@ export default {
928
930
  }
929
931
  ```
930
932
 
931
- To support backwards-compatibility, monorepo features require multiple _lint-staged_ configuration files present in the git repo. If you still want to run _lint-staged_ in only one of the packages in a monorepo, you can use the `--cwd` option (for example, `lint-staged --cwd packages/frontend`).
933
+ **Note**: If you want to run _lint-staged_ in only one package inside a monorepo, you can simply use the `--cwd` option (for example `lint-staged --cwd packages/frontend`).
934
+
935
+ **Note**: It is possible to run all tasks in the monorepo root by explicitly configuring `lint-staged --cwd="."`.
932
936
 
933
937
  </details>
934
938
 
@@ -2,152 +2,33 @@
2
2
 
3
3
  import { userInfo } from 'node:os'
4
4
 
5
- import { Option, program } from 'commander'
6
-
5
+ import { getVersionNumber, parseCliOptions, printHelpText } from '../lib/cli.js'
7
6
  import { createDebug, enableDebug } from '../lib/debug.js'
8
7
  import lintStaged from '../lib/index.js'
9
- import { CONFIG_STDIN_ERROR, restoreStashExample } from '../lib/messages.js'
8
+ import { CONFIG_STDIN_ERROR } from '../lib/messages.js'
10
9
  import { readStdin } from '../lib/readStdin.js'
11
- import { getVersion } from '../lib/version.js'
12
10
 
13
11
  const debugLog = createDebug('lint-staged:bin')
14
12
 
15
13
  // Do not terminate main Listr process on SIGINT
16
14
  process.on('SIGINT', () => {})
17
15
 
18
- program
19
- .version(await getVersion())
20
-
21
- /**
22
- * This shouldn't be necessary for lint-staged, but add migration step just in case
23
- * to preserve old behavior of "commander".
24
- *
25
- * @todo remove this in the major version
26
- * @see https://github.com/tj/commander.js/releases/tag/v13.0.0
27
- * */
28
- .allowExcessArguments()
29
-
30
- .addOption(
31
- new Option('--allow-empty', 'allow empty commits when tasks revert all staged changes').default(
32
- false
33
- )
34
- )
35
- .addOption(
36
- new Option(
37
- '-p, --concurrent <number|boolean>',
38
- 'the number of tasks to run concurrently, or false for serial'
39
- ).default(true)
40
- )
41
- .addOption(
42
- new Option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
43
- )
44
- .addOption(
45
- new Option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
46
- )
47
- .addOption(new Option('-d, --debug', 'print additional debug information').default(false))
48
- .addOption(
49
- new Option(
50
- '--diff [string]',
51
- 'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".'
52
- ).implies({ stash: false })
53
- )
54
- .addOption(
55
- new Option(
56
- '--diff-filter [string]',
57
- 'override the default "--diff-filter=ACMR" flag of "git diff" to get list of files'
58
- )
59
- )
60
- .addOption(
61
- new Option('--continue-on-error', 'run all tasks to completion even if one fails').default(
62
- false
63
- )
64
- )
65
- .addOption(
66
- new Option('--fail-on-changes', 'fail with exit code 1 when tasks modify tracked files')
67
- .default(false)
68
- .implies({ revert: false })
69
- )
70
- .addOption(
71
- new Option(
72
- '--max-arg-length [number]',
73
- 'maximum length of the command-line argument string'
74
- ).default(0)
75
- )
76
-
77
- /**
78
- * We don't want to show the `--revert` flag because it's on by default, and only show the
79
- * negatable flag `--no-rever` instead. There seems to be a bug in Commander.js where
80
- * configuring only the latter won't actually set the default value.
81
- */
82
- .addOption(
83
- new Option('--revert', 'revert to original state in case of errors').default(true).hideHelp()
84
- )
85
- .addOption(
86
- new Option('--no-revert', 'do not revert to original state in case of errors.').default(false)
87
- )
88
-
89
- .addOption(new Option('--stash', 'enable the backup stash').default(true).hideHelp())
90
- .addOption(
91
- new Option('--no-stash', 'disable the backup stash. Implies "--no-revert".')
92
- .default(false)
93
- .implies({ revert: false })
94
- )
95
-
96
- .addOption(
97
- new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
98
- .default(true)
99
- .hideHelp()
100
- )
101
- .addOption(
102
- new Option(
103
- '--no-hide-partially-staged',
104
- 'disable hiding unstaged changes from partially staged files'
105
- ).default(false)
106
- )
16
+ const cliOptions = parseCliOptions(process.argv)
107
17
 
108
- .addOption(
109
- new Option('--hide-unstaged', 'hide all unstaged changes, instead of just partially staged')
110
- .default(false)
111
- .implies({ hidePartiallyStaged: false })
112
- )
113
-
114
- .addOption(new Option('-q, --quiet', 'disable lint-staged’s own console output').default(false))
115
- .addOption(new Option('-r, --relative', 'pass relative filepaths to tasks').default(false))
116
- .addOption(
117
- new Option(
118
- '-v, --verbose',
119
- 'show task output even when tasks succeed; by default only failed output is shown'
120
- ).default(false)
121
- )
122
-
123
- .addHelpText('afterAll', '\n' + restoreStashExample())
18
+ if (cliOptions.version) {
19
+ console.log(await getVersionNumber())
20
+ process.exit(0)
21
+ }
124
22
 
125
- const cliOptions = program.parse(process.argv).opts()
23
+ if (cliOptions.help) {
24
+ console.log(await printHelpText())
25
+ process.exit(0)
26
+ }
126
27
 
127
28
  if (cliOptions.debug) {
128
29
  enableDebug()
129
30
  }
130
31
 
131
- const options = {
132
- allowEmpty: !!cliOptions.allowEmpty,
133
- concurrent: JSON.parse(cliOptions.concurrent),
134
- configPath: cliOptions.config,
135
- continueOnError: !!cliOptions.continueOnError,
136
- cwd: cliOptions.cwd,
137
- debug: !!cliOptions.debug,
138
- diff: cliOptions.diff,
139
- diffFilter: cliOptions.diffFilter,
140
- failOnChanges: !!cliOptions.failOnChanges,
141
- hidePartiallyStaged: !!cliOptions.hidePartiallyStaged, // commander inverts `no-<x>` flags to `!x`
142
- hideUnstaged: !!cliOptions.hideUnstaged,
143
- maxArgLength: cliOptions.maxArgLength || undefined,
144
- quiet: !!cliOptions.quiet,
145
- relative: !!cliOptions.relative,
146
- revert: !!cliOptions.revert, // commander inverts `no-<x>` flags to `!x`
147
- stash: !!cliOptions.stash, // commander inverts `no-<x>` flags to `!x`
148
- verbose: !!cliOptions.verbose,
149
- }
150
-
151
32
  try {
152
33
  const { shell } = userInfo()
153
34
  debugLog('Using shell: %s', shell)
@@ -155,13 +36,13 @@ try {
155
36
  debugLog('Could not determine current shell')
156
37
  }
157
38
 
158
- debugLog('Options parsed from command-line: %o', options)
39
+ debugLog('Options parsed from command-line: %o', cliOptions)
159
40
 
160
- if (options.configPath === '-') {
161
- delete options.configPath
41
+ if (cliOptions.configPath === '-') {
42
+ delete cliOptions.configPath
162
43
  try {
163
44
  debugLog('Reading config from stdin')
164
- options.config = JSON.parse(await readStdin())
45
+ cliOptions.config = JSON.parse(await readStdin())
165
46
  } catch (error) {
166
47
  debugLog(CONFIG_STDIN_ERROR, error)
167
48
  console.error(CONFIG_STDIN_ERROR)
@@ -169,7 +50,7 @@ if (options.configPath === '-') {
169
50
  }
170
51
  }
171
52
 
172
- const passed = await lintStaged(options)
53
+ const passed = await lintStaged(cliOptions)
173
54
  if (!passed) {
174
55
  process.exitCode = 1
175
56
  }
@@ -0,0 +1,48 @@
1
+ /** @see {@link https://github.com/git/git/blob/master/Documentation/RelNotes/2.32.0.adoc} */
2
+ export const MIN_GIT_VERSION = '2.32.0'
3
+
4
+ /**
5
+ * @param {string} gitVersionOutput the Git version command output
6
+ * @returns {string} the Git version number in format `<major>.<minor>.<patch>`
7
+ */
8
+ const extractGitVersionNumber = (gitVersionOutput) => {
9
+ const match = gitVersionOutput.match(/git version\s+(\d+\.\d+\.\d+)/i)
10
+ return match?.[1]
11
+ }
12
+
13
+ /**
14
+ * @param {string} version the version number in format `<major>.<minor>.<patch>`
15
+ * @returns {[string, string, string]} the version number parsed as integers `[major, minor, patch]`
16
+ */
17
+ const parseSemver = (version) => {
18
+ const match = /(\d+)\.(\d+)\.(\d+)/.exec(version)
19
+ return match?.slice(1, 4).map(Number)
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @param {string} actual the actual version number in format `<major>.<minor>.<patch>`
25
+ * @param {string} expected the expected version number in format `<major>.<minor>.<patch>`
26
+ * @returns {boolean} `true` when the actual version number is at least the expected version number
27
+ */
28
+ export const satisfiesVersion = (actual, expected) => {
29
+ const a = parseSemver(actual)
30
+ const e = parseSemver(expected)
31
+ if (a[0] !== e[0]) return a[0] > e[0]
32
+ if (a[1] !== e[1]) return a[1] > e[1]
33
+ return a[2] >= e[2]
34
+ }
35
+
36
+ /**
37
+ *
38
+ * @param {string} gitVersionOutput the Git version command output
39
+ * @returns {boolean} `true` when the Git version number is supported
40
+ */
41
+ export const assertGitVersion = (gitVersionOutput) => {
42
+ try {
43
+ const version = extractGitVersionNumber(gitVersionOutput)
44
+ return satisfiesVersion(version, MIN_GIT_VERSION)
45
+ } catch {
46
+ return false
47
+ }
48
+ }
package/lib/cli.js ADDED
@@ -0,0 +1,242 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { parseArgs } from 'node:util'
5
+
6
+ import { restoreStashExample } from './messages.js'
7
+
8
+ const CLI_OPTIONS = [
9
+ {
10
+ short: 'h',
11
+ flag: 'help',
12
+ type: 'boolean',
13
+ description: 'display this help message',
14
+ },
15
+ {
16
+ short: 'V',
17
+ flag: 'version',
18
+ type: 'boolean',
19
+ description: 'display the current version number',
20
+ },
21
+ {
22
+ flag: 'allow-empty',
23
+ type: 'boolean',
24
+ description: 'allow empty commits when tasks revert all staged changes (default: false)',
25
+ },
26
+ {
27
+ short: 'p',
28
+ flag: 'concurrent',
29
+ positional: '<number|boolean>',
30
+ type: 'string',
31
+ description: 'the number of tasks to run concurrently, or false for serial (default: true)',
32
+ },
33
+ {
34
+ short: 'c',
35
+ flag: 'config',
36
+ positional: '[path]',
37
+ type: 'string',
38
+ description: 'path to configuration file, or - to read from stdin',
39
+ },
40
+ {
41
+ flag: 'continue-on-error',
42
+ type: 'boolean',
43
+ description: 'run all tasks to completion even if one fails (default: false)',
44
+ },
45
+ {
46
+ flag: 'cwd',
47
+ positional: '[path]',
48
+ type: 'string',
49
+ description: 'run all tasks in specific directory, instead of the current',
50
+ },
51
+ {
52
+ short: 'd',
53
+ flag: 'debug',
54
+ type: 'boolean',
55
+ description: 'print additional debug information (default: false)',
56
+ },
57
+ {
58
+ flag: 'diff',
59
+ positional: '[string]',
60
+ type: 'string',
61
+ description:
62
+ 'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".',
63
+ },
64
+ {
65
+ flag: 'diff-filter',
66
+ positional: '[string]',
67
+ type: 'string',
68
+ description:
69
+ 'override the default "--diff-filter=ACMR" flag of "git diff" to get list of files',
70
+ },
71
+ {
72
+ flag: 'fail-on-changes',
73
+ type: 'boolean',
74
+ description: 'fail with exit code 1 when tasks modify tracked files (default: false)',
75
+ },
76
+ {
77
+ negative: true,
78
+ flag: 'hide-partially-staged',
79
+ type: 'boolean',
80
+ description: 'hide unstaged changes from partially staged files (default: true)',
81
+ },
82
+ {
83
+ flag: 'hide-unstaged',
84
+ type: 'boolean',
85
+ description: 'hide all unstaged changes, instead of just partially staged (default: false)',
86
+ },
87
+ {
88
+ flag: 'hide-all',
89
+ type: 'boolean',
90
+ description: 'hide all unstaged changes and untracked files (default: false)',
91
+ },
92
+ {
93
+ flag: 'max-arg-length',
94
+ type: 'string', // Parsed with `parseInt()` below
95
+ positional: '[number]',
96
+ description: 'maximum length of the command-line argument string (default: 0)',
97
+ },
98
+ {
99
+ short: 'q',
100
+ flag: 'quiet',
101
+ type: 'boolean',
102
+ description: "disable lint-staged's own console output (default: false)",
103
+ },
104
+ {
105
+ short: 'r',
106
+ flag: 'relative',
107
+ type: 'boolean',
108
+ description: 'pass relative filepaths to tasks (default: false)',
109
+ },
110
+ {
111
+ negative: true,
112
+ flag: 'revert',
113
+ type: 'boolean',
114
+ description: 'revert to original state in case of errors (default: true)',
115
+ },
116
+ {
117
+ negative: true,
118
+ flag: 'stash',
119
+ type: 'boolean',
120
+ description: 'enable the backup stash (default: true)',
121
+ },
122
+ {
123
+ short: 'v',
124
+ flag: 'verbose',
125
+ type: 'boolean',
126
+ description:
127
+ 'show task output even when tasks succeed; by default only failed output is shown (default: false)',
128
+ },
129
+ ]
130
+
131
+ /** @param {string[]} argv */
132
+ export const parseCliOptions = (argv) => {
133
+ const options = CLI_OPTIONS.reduce((acc, current) => {
134
+ acc[current.flag] = { type: current.type }
135
+ if (current.short) acc[current.flag].short = current.short
136
+ return acc
137
+ }, {})
138
+
139
+ const { values } = parseArgs({
140
+ args: argv,
141
+ allowNegative: true,
142
+ allowPositionals: true,
143
+ options,
144
+ })
145
+
146
+ if (values.diff !== undefined && values.stash === undefined) {
147
+ /** Disable stashing by default when diffing specific value */
148
+ values.stash = false
149
+ }
150
+
151
+ if (values['fail-on-changes'] && values.revert === undefined) {
152
+ /** When using --fail-on-changes, default to not reverting on errors */
153
+ values.revert = false
154
+ }
155
+
156
+ if (values.stash === false && values.revert === undefined) {
157
+ /** Can't revert when using --no-stash */
158
+ values.revert = false
159
+ }
160
+
161
+ if (values['hide-unstaged'] === true) {
162
+ values['hide-partially-staged'] = false // becomes redundant
163
+ }
164
+
165
+ if (values['hide-all'] === true) {
166
+ values['hide-partially-staged'] = false // becomes redundant
167
+ values['hide-unstaged'] = false // becomes redundant
168
+ }
169
+
170
+ return {
171
+ allowEmpty: values['allow-empty'] ?? false,
172
+ concurrent: values.concurrent === undefined ? true : JSON.parse(values.concurrent),
173
+ configPath: values.config,
174
+ continueOnError: !!values['continue-on-error'],
175
+ cwd: values.cwd,
176
+ debug: !!values.debug,
177
+ diff: values.diff,
178
+ diffFilter: values['diff-filter'],
179
+ failOnChanges: !!values['fail-on-changes'],
180
+ help: !!values.help,
181
+ hidePartiallyStaged: values['hide-partially-staged'] ?? true,
182
+ hideUnstaged: !!values['hide-unstaged'],
183
+ hideAll: !!values['hide-all'],
184
+ maxArgLength: parseInt(values['max-arg-length'], 10),
185
+ quiet: !!values.quiet,
186
+ relative: !!values.relative,
187
+ revert: values.revert ?? true,
188
+ stash: values.stash ?? true,
189
+ verbose: !!values.verbose,
190
+ version: !!values.version,
191
+ }
192
+ }
193
+
194
+ export const getVersionNumber = async () => {
195
+ const dirname = path.dirname(fileURLToPath(import.meta.url))
196
+ const packageJsonFile = await readFile(path.join(dirname, '../package.json'), 'utf-8')
197
+ /** @type {import('../package.json')} */
198
+ const packageJson = JSON.parse(packageJsonFile)
199
+
200
+ return packageJson.version
201
+ }
202
+
203
+ const helpOptions = CLI_OPTIONS.map((option) => {
204
+ if (option.negative) {
205
+ /** @example `--no-stash` */
206
+ return [`--no-${option.flag}`, option.description]
207
+ }
208
+
209
+ /**
210
+ * @example `-V, --version
211
+ * or
212
+ * @example `--allow-empty`
213
+ */
214
+ let arg = option.short ? `-${option.short}, --${option.flag}` : `--${option.flag}`
215
+
216
+ /** @example `--cwd [path]` */
217
+ if (option.positional) arg += ` ${option.positional}`
218
+
219
+ return [arg, option.description]
220
+ })
221
+
222
+ const createWrap = (width) => {
223
+ const regExp = new RegExp(`.{1,${width}}(\\s|$)`, 'g')
224
+ return (text) => text.match(regExp)?.map((s) => s.trimEnd())
225
+ }
226
+
227
+ export const printHelpText = async (width = process.stdout.columns ?? 80) => {
228
+ const output = ['Usage: lint-staged [options]', '']
229
+
230
+ const col1Width = Math.max(...helpOptions.map(([arg]) => arg.length)) + 2
231
+ const wrap = createWrap(width - col1Width)
232
+
233
+ for (const [arg, description] of helpOptions) {
234
+ const lines = wrap(description)
235
+ const pad = ' '.repeat(col1Width)
236
+ output.push(arg.padEnd(col1Width) + lines[0], ...lines.slice(1).map((line) => pad + line))
237
+ }
238
+
239
+ output.push('', restoreStashExample())
240
+
241
+ return output.join('\n')
242
+ }