lint-staged 15.2.11 → 15.3.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/README.md CHANGED
@@ -13,7 +13,7 @@ npm install --save-dev lint-staged # requires further setup
13
13
  ```
14
14
  $ git commit
15
15
 
16
- Preparing lint-staged...
16
+ Backed up original state in git stash (5bda95f)
17
17
  ❯ Running tasks for staged files...
18
18
  ❯ packages/frontend/.lintstagedrc.json — 1 file
19
19
  ↓ *.js — no files [SKIPPED]
@@ -70,6 +70,9 @@ Now change a few files, `git add` or `git add --patch` some of them to your comm
70
70
 
71
71
  See [examples](#examples) and [configuration](#configuration) for more information.
72
72
 
73
+ > [!CAUTION]
74
+ > _Lint-staged_ runs `git` operations affecting the files in your repository. By default _lint-staged_ creates a `git stash` as a backup of the original state before running any configured tasks to help prevent data loss.
75
+
73
76
  ## Changelog
74
77
 
75
78
  See [Releases](https://github.com/okonet/lint-staged/releases).
@@ -117,9 +120,10 @@ Options:
117
120
  -c, --config [path] path to configuration file, or - to read from stdin
118
121
  --cwd [path] run all tasks in specific directory, instead of the current
119
122
  -d, --debug print additional debug information (default: false)
120
- --diff [string] override the default "--staged" flag of "git diff" to get list of files. Implies
121
- "--no-stash".
122
- --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of files
123
+ --diff [string] override the default "--staged" flag of "git diff" to get list of files.
124
+ Implies "--no-stash".
125
+ --diff-filter [string] override the default "--diff-filter=ACMR" flag of "git diff" to get list of
126
+ files
123
127
  --max-arg-length [number] maximum length of the command-line argument string (default: 0)
124
128
  --no-stash disable the backup stash, and do not revert in case of errors. Implies
125
129
  "--no-hide-partially-staged".
@@ -127,9 +131,15 @@ Options:
127
131
  -q, --quiet disable lint-staged’s own console output (default: false)
128
132
  -r, --relative pass relative filepaths to tasks (default: false)
129
133
  -x, --shell [path] skip parsing of tasks for better shell support (default: false)
130
- -v, --verbose show task output even when tasks succeed; by default only failed output is shown
131
- (default: false)
134
+ -v, --verbose show task output even when tasks succeed; by default only failed output is
135
+ shown (default: false)
132
136
  -h, --help display help for command
137
+
138
+ Any lost modifications can be restored from a git stash:
139
+
140
+ > git stash list
141
+ stash@{0}: automatic lint-staged backup
142
+ > git stash apply --index stash@{0}
133
143
  ```
134
144
 
135
145
  - **`--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.
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'node:fs/promises'
3
+ import { userInfo } from 'node:os'
4
4
 
5
5
  import { supportsColor } from 'chalk'
6
6
  import { Option, program } from 'commander'
7
7
  import debug from 'debug'
8
8
 
9
9
  import lintStaged from '../lib/index.js'
10
- import { CONFIG_STDIN_ERROR } from '../lib/messages.js'
10
+ import { CONFIG_STDIN_ERROR, RESTORE_STASH_EXAMPLE } from '../lib/messages.js'
11
11
  import { readStdin } from '../lib/readStdin.js'
12
+ import { getVersion } from '../lib/version.js'
12
13
 
13
14
  // Force colors for packages that depend on https://www.npmjs.com/package/supports-color
14
15
  if (supportsColor) {
@@ -20,45 +21,42 @@ const debugLog = debug('lint-staged:bin')
20
21
  // Do not terminate main Listr process on SIGINT
21
22
  process.on('SIGINT', () => {})
22
23
 
23
- const packageJson = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url)))
24
- const version = packageJson.version
24
+ program.version(await getVersion())
25
25
 
26
- const cli = program.version(version)
26
+ program.option('--allow-empty', 'allow empty commits when tasks revert all staged changes', false)
27
27
 
28
- cli.option('--allow-empty', 'allow empty commits when tasks revert all staged changes', false)
29
-
30
- cli.option(
28
+ program.option(
31
29
  '-p, --concurrent <number|boolean>',
32
30
  'the number of tasks to run concurrently, or false for serial',
33
31
  true
34
32
  )
35
33
 
36
- cli.option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
34
+ program.option('-c, --config [path]', 'path to configuration file, or - to read from stdin')
37
35
 
38
- cli.option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
36
+ program.option('--cwd [path]', 'run all tasks in specific directory, instead of the current')
39
37
 
40
- cli.option('-d, --debug', 'print additional debug information', false)
38
+ program.option('-d, --debug', 'print additional debug information', false)
41
39
 
42
- cli.addOption(
40
+ program.addOption(
43
41
  new Option(
44
42
  '--diff [string]',
45
43
  'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".'
46
44
  ).implies({ stash: false })
47
45
  )
48
46
 
49
- cli.option(
47
+ program.option(
50
48
  '--diff-filter [string]',
51
49
  'override the default "--diff-filter=ACMR" flag of "git diff" to get list of files'
52
50
  )
53
51
 
54
- cli.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
52
+ program.option('--max-arg-length [number]', 'maximum length of the command-line argument string', 0)
55
53
 
56
54
  /**
57
55
  * We don't want to show the `--stash` flag because it's on by default, and only show the
58
56
  * negatable flag `--no-stash` in stead. There seems to be a bug in Commander.js where
59
57
  * configuring only the latter won't actually set the default value.
60
58
  */
61
- cli
59
+ program
62
60
  .addOption(
63
61
  new Option('--stash', 'enable the backup stash, and revert in case of errors')
64
62
  .default(true)
@@ -78,7 +76,7 @@ cli
78
76
  * negatable flag `--no-hide-partially-staged` in stead. There seems to be a bug in Commander.js where
79
77
  * configuring only the latter won't actually set the default value.
80
78
  */
81
- cli
79
+ program
82
80
  .addOption(
83
81
  new Option('--hide-partially-staged', 'hide unstaged changes from partially staged files')
84
82
  .default(true)
@@ -91,26 +89,26 @@ cli
91
89
  ).default(false)
92
90
  )
93
91
 
94
- cli.option('-q, --quiet', 'disable lint-staged’s own console output', false)
92
+ program.option('-q, --quiet', 'disable lint-staged’s own console output', false)
95
93
 
96
- cli.option('-r, --relative', 'pass relative filepaths to tasks', false)
94
+ program.option('-r, --relative', 'pass relative filepaths to tasks', false)
97
95
 
98
- cli.option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
96
+ program.option('-x, --shell [path]', 'skip parsing of tasks for better shell support', false)
99
97
 
100
- cli.option(
98
+ program.option(
101
99
  '-v, --verbose',
102
100
  'show task output even when tasks succeed; by default only failed output is shown',
103
101
  false
104
102
  )
105
103
 
106
- const cliOptions = cli.parse(process.argv).opts()
104
+ program.addHelpText('afterAll', '\n' + RESTORE_STASH_EXAMPLE)
105
+
106
+ const cliOptions = program.parse(process.argv).opts()
107
107
 
108
108
  if (cliOptions.debug) {
109
109
  debug.enable('lint-staged*')
110
110
  }
111
111
 
112
- debugLog('Running `lint-staged@%s` on Node.js %s (%s)', version, process.version, process.platform)
113
-
114
112
  const options = {
115
113
  allowEmpty: !!cliOptions.allowEmpty,
116
114
  concurrent: JSON.parse(cliOptions.concurrent),
@@ -128,6 +126,7 @@ const options = {
128
126
  verbose: !!cliOptions.verbose,
129
127
  }
130
128
 
129
+ debugLog('Using shell: %s', userInfo().shell)
131
130
  debugLog('Options parsed from command-line: %o', options)
132
131
 
133
132
  if (options.configPath === '-') {
@@ -98,7 +98,11 @@ export class GitWorkflow {
98
98
  */
99
99
  async getBackupStash(ctx) {
100
100
  const stashes = await this.execGit(['stash', 'list'])
101
- const index = stashes.split('\n').findIndex((line) => line.includes(STASH))
101
+
102
+ const index = stashes
103
+ .split('\n')
104
+ .findIndex((line) => line.includes(STASH) && line.includes(ctx.backupHash))
105
+
102
106
  if (index === -1) {
103
107
  ctx.errors.add(GetBackupStashError)
104
108
  throw new Error('lint-staged automatic backup is missing!')
@@ -190,9 +194,9 @@ export class GitWorkflow {
190
194
  /**
191
195
  * Create a diff of partially staged files and backup stash if enabled.
192
196
  */
193
- async prepare(ctx) {
197
+ async prepare(ctx, task) {
194
198
  try {
195
- debugLog('Backing up original state...')
199
+ debugLog(task.title)
196
200
 
197
201
  // Get a list of files with bot staged and unstaged changes.
198
202
  // Unstaged changes to these files should be hidden before the tasks run.
@@ -223,10 +227,19 @@ export class GitWorkflow {
223
227
  // Save stash of all staged files.
224
228
  // The `stash create` command creates a dangling commit without removing any files,
225
229
  // and `stash store` saves it as an actual stash.
226
- const hash = await this.execGit(['stash', 'create'])
227
- await this.execGit(['stash', 'store', '--quiet', '--message', STASH, hash])
230
+ const stashHash = await this.execGit(['stash', 'create'])
231
+ ctx.backupHash = await this.execGit(['rev-parse', '--short', stashHash])
232
+ await this.execGit([
233
+ 'stash',
234
+ 'store',
235
+ '--quiet',
236
+ '--message',
237
+ `${STASH} (${ctx.backupHash})`,
238
+ ctx.backupHash,
239
+ ])
228
240
 
229
- debugLog('Done backing up original state!')
241
+ task.title = `Backed up original state in git stash (${ctx.backupHash})`
242
+ debugLog(task.title)
230
243
  } catch (error) {
231
244
  handleError(error, ctx)
232
245
  }
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import debug from 'debug'
1
+ import debugLib from 'debug'
2
2
 
3
3
  import { execGit } from './execGit.js'
4
4
  import {
@@ -9,6 +9,7 @@ import {
9
9
  } from './messages.js'
10
10
  import { printTaskOutput } from './printTaskOutput.js'
11
11
  import { runAll } from './runAll.js'
12
+ import { cleanupSkipped } from './state.js'
12
13
  import {
13
14
  ApplyEmptyCommitError,
14
15
  ConfigNotFoundError,
@@ -16,8 +17,9 @@ import {
16
17
  GitError,
17
18
  } from './symbols.js'
18
19
  import { validateOptions } from './validateOptions.js'
20
+ import { getVersion } from './version.js'
19
21
 
20
- const debugLog = debug('lint-staged')
22
+ const debugLog = debugLib('lint-staged')
21
23
 
22
24
  /**
23
25
  * Get the maximum length of a command-line argument string based on current platform
@@ -83,6 +85,18 @@ const lintStaged = async (
83
85
  } = {},
84
86
  logger = console
85
87
  ) => {
88
+ // Seemingly enable debug twice (also done in bin), so that it also works when using the Node.js API
89
+ if (debug) {
90
+ debugLib.enable('lint-staged*')
91
+
92
+ debugLog(
93
+ 'Running `lint-staged@%s` on Node.js %s (%s)',
94
+ await getVersion(),
95
+ process.version,
96
+ process.platform
97
+ )
98
+ }
99
+
86
100
  const gitVersion = await execGit(['version', '--build-options'], { cwd })
87
101
  debugLog('%s', gitVersion)
88
102
 
@@ -123,11 +137,14 @@ const lintStaged = async (
123
137
  logger.error(NO_CONFIGURATION)
124
138
  } else if (ctx.errors.has(ApplyEmptyCommitError)) {
125
139
  logger.warn(PREVENTED_EMPTY_COMMIT)
126
- } else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
140
+ } else if (
141
+ (ctx.errors.has(GitError) || cleanupSkipped(ctx)) &&
142
+ !ctx.errors.has(GetBackupStashError)
143
+ ) {
127
144
  logger.error(GIT_ERROR)
128
145
  if (ctx.shouldBackup) {
129
146
  // No sense to show this if the backup stash itself is missing.
130
- logger.error(RESTORE_STASH_EXAMPLE)
147
+ logger.error(RESTORE_STASH_EXAMPLE + '\n')
131
148
  }
132
149
  }
133
150
 
package/lib/messages.js CHANGED
@@ -77,12 +77,11 @@ export const PREVENTED_EMPTY_COMMIT = `
77
77
  Use the --allow-empty option to continue, or check your task configuration`)}
78
78
  `
79
79
 
80
- export const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored from a git stash:
80
+ export const RESTORE_STASH_EXAMPLE = `Any lost modifications can be restored from a git stash:
81
81
 
82
- > git stash list
83
- stash@{0}: automatic lint-staged backup
84
- > git stash apply --index stash@{0}
85
- `
82
+ > git stash list
83
+ stash@{0}: automatic lint-staged backup
84
+ > git stash apply --index stash@{0}`
86
85
 
87
86
  export const CONFIG_STDIN_ERROR = chalk.redBright(`${error} Failed to read config from stdin.`)
88
87
 
package/lib/runAll.js CHANGED
@@ -291,8 +291,8 @@ export const runAll = async (
291
291
  const runner = new Listr(
292
292
  [
293
293
  {
294
- title: 'Preparing lint-staged...',
295
- task: (ctx) => git.prepare(ctx),
294
+ title: ctx.shouldBackup ? 'Backing up original state...' : 'Preparing lint-staged...',
295
+ task: (ctx, task) => git.prepare(ctx, task),
296
296
  },
297
297
  {
298
298
  title: 'Hiding unstaged changes to partially staged files...',
package/lib/state.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  export const getInitialState = ({ quiet = false } = {}) => ({
13
13
  hasPartiallyStagedFiles: null,
14
14
  shouldBackup: null,
15
+ backupHash: null,
15
16
  shouldHidePartiallyStaged: true,
16
17
  errors: new Set([]),
17
18
  events: new EventEmitter(),
@@ -40,6 +41,7 @@ export const restoreUnstagedChangesSkipped = (ctx) => {
40
41
  if (ctx.errors.has(GitError)) {
41
42
  return GIT_ERROR
42
43
  }
44
+
43
45
  // Should be skipped when tasks fail
44
46
  if (ctx.errors.has(TaskError)) {
45
47
  return TASK_ERROR
@@ -67,13 +69,10 @@ export const cleanupEnabled = (ctx) => ctx.shouldBackup
67
69
 
68
70
  export const cleanupSkipped = (ctx) => {
69
71
  // Should be skipped in case of unknown git errors
70
- if (
71
- ctx.errors.has(GitError) &&
72
- !ctx.errors.has(ApplyEmptyCommitError) &&
73
- !ctx.errors.has(RestoreUnstagedChangesError)
74
- ) {
72
+ if (restoreOriginalStateSkipped(ctx)) {
75
73
  return GIT_ERROR
76
74
  }
75
+
77
76
  // Should be skipped when reverting to original state fails
78
77
  if (ctx.errors.has(RestoreOriginalStateError)) {
79
78
  return GIT_ERROR
package/lib/version.js ADDED
@@ -0,0 +1,6 @@
1
+ import fs from 'node:fs/promises'
2
+
3
+ export const getVersion = async () => {
4
+ const packageJson = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url)))
5
+ return packageJson.version
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-staged",
3
- "version": "15.2.11",
3
+ "version": "15.3.0",
4
4
  "description": "Lint files staged by git",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/lint-staged/lint-staged",
@@ -36,7 +36,7 @@
36
36
  "tag": "npx changeset tag"
37
37
  },
38
38
  "dependencies": {
39
- "chalk": "~5.3.0",
39
+ "chalk": "~5.4.1",
40
40
  "commander": "~12.1.0",
41
41
  "debug": "~4.4.0",
42
42
  "execa": "~8.0.1",
@@ -49,16 +49,16 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "@changesets/changelog-github": "0.5.0",
52
- "@changesets/cli": "2.27.10",
53
- "@commitlint/cli": "19.6.0",
52
+ "@changesets/cli": "2.27.11",
53
+ "@commitlint/cli": "19.6.1",
54
54
  "@commitlint/config-conventional": "19.6.0",
55
- "@eslint/js": "9.16.0",
55
+ "@eslint/js": "9.17.0",
56
56
  "consolemock": "1.1.0",
57
57
  "cross-env": "7.0.3",
58
- "eslint": "9.16.0",
58
+ "eslint": "9.17.0",
59
59
  "eslint-config-prettier": "9.1.0",
60
- "eslint-plugin-jest": "28.9.0",
61
- "eslint-plugin-n": "17.15.0",
60
+ "eslint-plugin-jest": "28.10.0",
61
+ "eslint-plugin-n": "17.15.1",
62
62
  "eslint-plugin-prettier": "5.2.1",
63
63
  "eslint-plugin-simple-import-sort": "12.1.1",
64
64
  "husky": "9.1.7",