lint-staged 11.3.0-beta.2 → 12.0.3

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.
@@ -1,21 +1,20 @@
1
- 'use strict'
1
+ import path from 'path'
2
2
 
3
- const debug = require('debug')('lint-staged:git')
4
- const fs = require('fs')
5
- const path = require('path')
6
- const { promisify } = require('util')
3
+ import debug from 'debug'
7
4
 
8
- const execGit = require('./execGit')
9
- const {
5
+ import { execGit } from './execGit.js'
6
+ import { readFile, unlink, writeFile } from './file.js'
7
+ import {
10
8
  GitError,
9
+ RestoreOriginalStateError,
11
10
  ApplyEmptyCommitError,
11
+ GetBackupStashError,
12
12
  HideUnstagedChangesError,
13
- RestoreOriginalStateError,
13
+ RestoreMergeStatusError,
14
14
  RestoreUnstagedChangesError,
15
- } = require('./symbols')
16
- const unlink = require('./unlink')
15
+ } from './symbols.js'
17
16
 
18
- const fsReadFile = promisify(fs.readFile)
17
+ const debugLog = debug('lint-staged:GitWorkflow')
19
18
 
20
19
  const MERGE_HEAD = 'MERGE_HEAD'
21
20
  const MERGE_MODE = 'MERGE_MODE'
@@ -43,8 +42,9 @@ const processRenames = (files, includeRenameFrom = true) =>
43
42
  return flattened
44
43
  }, [])
45
44
 
45
+ const STASH = 'lint-staged automatic backup'
46
+
46
47
  const PATCH_UNSTAGED = 'lint-staged_unstaged.patch'
47
- const PATCH_PARTIAL = 'lint-staged_partial.patch'
48
48
 
49
49
  const GIT_DIFF_ARGS = [
50
50
  '--binary', // support binary files
@@ -64,7 +64,7 @@ const handleError = (error, ctx, symbol) => {
64
64
  throw error
65
65
  }
66
66
 
67
- class GitWorkflow {
67
+ export class GitWorkflow {
68
68
  constructor({ allowEmpty, gitConfigDir, gitDir, matchedFileChunks }) {
69
69
  this.execGit = (args, options = {}) => execGit(args, { ...options, cwd: gitDir })
70
70
  this.deletedFiles = []
@@ -91,13 +91,76 @@ class GitWorkflow {
91
91
  return path.resolve(this.gitConfigDir, `./${filename}`)
92
92
  }
93
93
 
94
+ /**
95
+ * Get name of backup stash
96
+ */
97
+ async getBackupStash(ctx) {
98
+ const stashes = await this.execGit(['stash', 'list'])
99
+ const index = stashes.split('\n').findIndex((line) => line.includes(STASH))
100
+ if (index === -1) {
101
+ ctx.errors.add(GetBackupStashError)
102
+ throw new Error('lint-staged automatic backup is missing!')
103
+ }
104
+ return `refs/stash@{${index}}`
105
+ }
106
+
107
+ /**
108
+ * Get a list of unstaged deleted files
109
+ */
110
+ async getDeletedFiles() {
111
+ debugLog('Getting deleted files...')
112
+ const lsFiles = await this.execGit(['ls-files', '--deleted'])
113
+ const deletedFiles = lsFiles
114
+ .split('\n')
115
+ .filter(Boolean)
116
+ .map((file) => path.resolve(this.gitDir, file))
117
+ debugLog('Found deleted files:', deletedFiles)
118
+ return deletedFiles
119
+ }
120
+
121
+ /**
122
+ * Save meta information about ongoing git merge
123
+ */
124
+ async backupMergeStatus() {
125
+ debugLog('Backing up merge state...')
126
+ await Promise.all([
127
+ readFile(this.mergeHeadFilename).then((buffer) => (this.mergeHeadBuffer = buffer)),
128
+ readFile(this.mergeModeFilename).then((buffer) => (this.mergeModeBuffer = buffer)),
129
+ readFile(this.mergeMsgFilename).then((buffer) => (this.mergeMsgBuffer = buffer)),
130
+ ])
131
+ debugLog('Done backing up merge state!')
132
+ }
133
+
134
+ /**
135
+ * Restore meta information about ongoing git merge
136
+ */
137
+ async restoreMergeStatus(ctx) {
138
+ debugLog('Restoring merge state...')
139
+ try {
140
+ await Promise.all([
141
+ this.mergeHeadBuffer && writeFile(this.mergeHeadFilename, this.mergeHeadBuffer),
142
+ this.mergeModeBuffer && writeFile(this.mergeModeFilename, this.mergeModeBuffer),
143
+ this.mergeMsgBuffer && writeFile(this.mergeMsgFilename, this.mergeMsgBuffer),
144
+ ])
145
+ debugLog('Done restoring merge state!')
146
+ } catch (error) {
147
+ debugLog('Failed restoring merge state with error:')
148
+ debugLog(error)
149
+ handleError(
150
+ new Error('Merge state could not be restored due to an error!'),
151
+ ctx,
152
+ RestoreMergeStatusError
153
+ )
154
+ }
155
+ }
156
+
94
157
  /**
95
158
  * Get a list of all files with both staged and unstaged modifications.
96
159
  * Renames have special treatment, since the single status line includes
97
160
  * both the "from" and "to" filenames, where "from" is no longer on disk.
98
161
  */
99
162
  async getPartiallyStagedFiles() {
100
- debug('Getting partially staged files...')
163
+ debugLog('Getting partially staged files...')
101
164
  const status = await this.execGit(['status', '-z'])
102
165
  /**
103
166
  * See https://git-scm.com/docs/git-status#_short_format
@@ -117,19 +180,16 @@ class GitWorkflow {
117
180
  })
118
181
  .map((line) => line.substr(3)) // Remove first three letters (index, workingTree, and a whitespace)
119
182
  .filter(Boolean) // Filter empty string
120
- debug('Found partially staged files:', partiallyStaged)
183
+ debugLog('Found partially staged files:', partiallyStaged)
121
184
  return partiallyStaged.length ? partiallyStaged : null
122
185
  }
123
186
 
124
187
  /**
125
- * Create a diff of partially staged files.
188
+ * Create a diff of partially staged files and backup stash if enabled.
126
189
  */
127
190
  async prepare(ctx) {
128
191
  try {
129
- debug('Backing up original state...')
130
-
131
- const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
132
- await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch])
192
+ debugLog('Backing up original state...')
133
193
 
134
194
  // Get a list of files with bot staged and unstaged changes.
135
195
  // Unstaged changes to these files should be hidden before the tasks run.
@@ -137,14 +197,33 @@ class GitWorkflow {
137
197
 
138
198
  if (this.partiallyStagedFiles) {
139
199
  ctx.hasPartiallyStagedFiles = true
140
- const partialPatch = this.getHiddenFilepath(PATCH_PARTIAL)
200
+ const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
141
201
  const files = processRenames(this.partiallyStagedFiles)
142
- await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', partialPatch, '--', ...files])
202
+ await this.execGit(['diff', ...GIT_DIFF_ARGS, '--output', unstagedPatch, '--', ...files])
143
203
  } else {
144
204
  ctx.hasPartiallyStagedFiles = false
145
205
  }
146
206
 
147
- debug('Done backing up original state!')
207
+ /**
208
+ * If backup stash should be skipped, no need to continue
209
+ */
210
+ if (!ctx.shouldBackup) return
211
+
212
+ // When backup is enabled, the revert will clear ongoing merge status.
213
+ await this.backupMergeStatus()
214
+
215
+ // Get a list of unstaged deleted files, because certain bugs might cause them to reappear:
216
+ // - in git versions =< 2.13.0 the `git stash --keep-index` option resurrects deleted files
217
+ // - git stash can't infer RD or MD states correctly, and will lose the deletion
218
+ this.deletedFiles = await this.getDeletedFiles()
219
+
220
+ // Save stash of all staged files.
221
+ // The `stash create` command creates a dangling commit without removing any files,
222
+ // and `stash store` saves it as an actual stash.
223
+ const hash = await this.execGit(['stash', 'create'])
224
+ await this.execGit(['stash', 'store', '--quiet', '--message', STASH, hash])
225
+
226
+ debugLog('Done backing up original state!')
148
227
  } catch (error) {
149
228
  handleError(error, ctx)
150
229
  }
@@ -166,18 +245,12 @@ class GitWorkflow {
166
245
  }
167
246
  }
168
247
 
169
- /** Add all task modifications to index for files that were staged before running. */
248
+ /**
249
+ * Applies back task modifications, and unstaged changes hidden in the stash.
250
+ * In case of a merge-conflict retry with 3-way merge.
251
+ */
170
252
  async applyModifications(ctx) {
171
- debug('Adding task modifications to index...')
172
-
173
- if (ctx.hasInitialCommit) {
174
- const stagedFilesAfterAdd = await this.execGit(['diff', 'HEAD'])
175
- if (!stagedFilesAfterAdd && !this.allowEmpty) {
176
- // Tasks reverted all staged changes and the commit would be empty
177
- // Throw error to stop commit unless `--allow-empty` was used
178
- handleError(new Error('Prevented an empty git commit!'), ctx, ApplyEmptyCommitError)
179
- }
180
- }
253
+ debugLog('Adding task modifications to index...')
181
254
 
182
255
  // `matchedFileChunks` includes staged files that lint-staged originally detected and matched against a task.
183
256
  // Add only these files so any 3rd-party edits to other files won't be included in the commit.
@@ -187,26 +260,13 @@ class GitWorkflow {
187
260
  await this.execGit(['add', '--', ...files])
188
261
  }
189
262
 
190
- debug('Done adding task modifications to index!')
191
- }
192
-
193
- /**
194
- * Restore original HEAD state in case of errors
195
- */
196
- async restoreOriginalState(ctx) {
197
- try {
198
- debug('Restoring original state...')
199
- await this.execGit(['checkout', '--force', '--', '.'])
200
-
201
- const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
202
- const hasContents = !!(await fsReadFile(unstagedPatch)).toString()
203
- if (hasContents) {
204
- await this.execGit(['apply', ...GIT_APPLY_ARGS, unstagedPatch])
205
- }
263
+ debugLog('Done adding task modifications to index!')
206
264
 
207
- debug('Done restoring original state!')
208
- } catch (error) {
209
- handleError(error, ctx, RestoreOriginalStateError)
265
+ const stagedFilesAfterAdd = await this.execGit(['diff', '--name-only', '--cached'])
266
+ if (!stagedFilesAfterAdd && !this.allowEmpty) {
267
+ // Tasks reverted all staged changes and the commit would be empty
268
+ // Throw error to stop commit unless `--allow-empty` was used
269
+ handleError(new Error('Prevented an empty git commit!'), ctx, ApplyEmptyCommitError)
210
270
  }
211
271
  }
212
272
 
@@ -215,21 +275,21 @@ class GitWorkflow {
215
275
  * this is probably because of conflicts between new task modifications.
216
276
  * 3-way merge usually fixes this, and in case it doesn't we should just give up and throw.
217
277
  */
218
- async restorePartialChanges(ctx) {
219
- debug('Restoring unstaged changes...')
220
- const partialPatch = this.getHiddenFilepath(PATCH_PARTIAL)
278
+ async restoreUnstagedChanges(ctx) {
279
+ debugLog('Restoring unstaged changes...')
280
+ const unstagedPatch = this.getHiddenFilepath(PATCH_UNSTAGED)
221
281
  try {
222
- await this.execGit(['apply', ...GIT_APPLY_ARGS, partialPatch])
282
+ await this.execGit(['apply', ...GIT_APPLY_ARGS, unstagedPatch])
223
283
  } catch (applyError) {
224
- debug('Error while restoring changes:')
225
- debug(applyError)
226
- debug('Retrying with 3-way merge')
284
+ debugLog('Error while restoring changes:')
285
+ debugLog(applyError)
286
+ debugLog('Retrying with 3-way merge')
227
287
  try {
228
288
  // Retry with a 3-way merge if normal apply fails
229
- await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', partialPatch])
289
+ await this.execGit(['apply', ...GIT_APPLY_ARGS, '--3way', unstagedPatch])
230
290
  } catch (threeWayApplyError) {
231
- debug('Error while restoring unstaged changes using 3-way merge:')
232
- debug(threeWayApplyError)
291
+ debugLog('Error while restoring unstaged changes using 3-way merge:')
292
+ debugLog(threeWayApplyError)
233
293
  handleError(
234
294
  new Error('Unstaged changes could not be restored due to a merge conflict!'),
235
295
  ctx,
@@ -240,15 +300,39 @@ class GitWorkflow {
240
300
  }
241
301
 
242
302
  /**
243
- * Drop the created diff file after everything has run.
244
- * Won't throw if the file has already been deleted (theoretically).
303
+ * Restore original HEAD state in case of errors
304
+ */
305
+ async restoreOriginalState(ctx) {
306
+ try {
307
+ debugLog('Restoring original state...')
308
+ await this.execGit(['reset', '--hard', 'HEAD'])
309
+ await this.execGit(['stash', 'apply', '--quiet', '--index', await this.getBackupStash(ctx)])
310
+
311
+ // Restore meta information about ongoing git merge
312
+ await this.restoreMergeStatus(ctx)
313
+
314
+ // If stashing resurrected deleted files, clean them out
315
+ await Promise.all(this.deletedFiles.map((file) => unlink(file)))
316
+
317
+ // Clean out patch
318
+ await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
319
+
320
+ debugLog('Done restoring original state!')
321
+ } catch (error) {
322
+ handleError(error, ctx, RestoreOriginalStateError)
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Drop the created stashes after everything has run
245
328
  */
246
- async cleanup() {
247
- debug('Removing temp files...')
248
- await unlink(this.getHiddenFilepath(PATCH_UNSTAGED))
249
- await unlink(this.getHiddenFilepath(PATCH_PARTIAL))
250
- debug('Done removing temp files!')
329
+ async cleanup(ctx) {
330
+ try {
331
+ debugLog('Dropping backup stash...')
332
+ await this.execGit(['stash', 'drop', '--quiet', await this.getBackupStash(ctx)])
333
+ debugLog('Done dropping backup stash!')
334
+ } catch (error) {
335
+ handleError(error, ctx)
336
+ }
251
337
  }
252
338
  }
253
-
254
- module.exports = GitWorkflow
package/lib/index.js CHANGED
@@ -1,15 +1,20 @@
1
- 'use strict'
2
-
3
- const { cosmiconfig } = require('cosmiconfig')
4
- const debugLog = require('debug')('lint-staged')
5
- const stringifyObject = require('stringify-object')
6
-
7
- const { PREVENTED_EMPTY_COMMIT, GIT_ERROR } = require('./messages')
8
- const printTaskOutput = require('./printTaskOutput')
9
- const runAll = require('./runAll')
10
- const { ApplyEmptyCommitError, ConfigNotFoundError, GitError } = require('./symbols')
11
- const validateConfig = require('./validateConfig')
12
- const validateOptions = require('./validateOptions')
1
+ import { cosmiconfig } from 'cosmiconfig'
2
+ import debug from 'debug'
3
+ import inspect from 'object-inspect'
4
+
5
+ import { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } from './messages.js'
6
+ import { printTaskOutput } from './printTaskOutput.js'
7
+ import { runAll } from './runAll.js'
8
+ import {
9
+ ApplyEmptyCommitError,
10
+ ConfigNotFoundError,
11
+ GetBackupStashError,
12
+ GitError,
13
+ } from './symbols.js'
14
+ import { validateConfig } from './validateConfig.js'
15
+ import { validateOptions } from './validateOptions.js'
16
+
17
+ const debugLog = debug('lint-staged')
13
18
 
14
19
  const resolveConfig = (configPath) => {
15
20
  try {
@@ -53,8 +58,8 @@ const loadConfig = (configPath) => {
53
58
  * @param {number} [options.maxArgLength] - Maximum argument string length
54
59
  * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
55
60
  * @param {boolean} [options.relative] - Pass relative filepaths to tasks
56
- * @param {boolean} [options.reset] - Reset changes in case of errors
57
61
  * @param {boolean|string} [options.shell] - Skip parsing of tasks for better shell support
62
+ * @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
58
63
  * @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
59
64
  * @param {Logger} [logger]
60
65
  *
@@ -71,14 +76,13 @@ const lintStaged = async (
71
76
  maxArgLength,
72
77
  quiet = false,
73
78
  relative = false,
74
- reset = true,
75
79
  shell = false,
76
- stash, // kept for backwards-compatibility
80
+ stash = true,
77
81
  verbose = false,
78
82
  } = {},
79
83
  logger = console
80
84
  ) => {
81
- await validateOptions({ shell, stash }, logger)
85
+ await validateOptions({ shell }, logger)
82
86
 
83
87
  debugLog('Loading config using `cosmiconfig`')
84
88
 
@@ -100,7 +104,7 @@ const lintStaged = async (
100
104
  if (debug) {
101
105
  // Log using logger to be able to test through `consolemock`.
102
106
  logger.log('Running lint-staged with the following config:')
103
- logger.log(stringifyObject(config, { indent: ' ' }))
107
+ logger.log(inspect(config, { indent: 2 }))
104
108
  } else {
105
109
  // We might not be in debug mode but `DEBUG=lint-staged*` could have
106
110
  // been set.
@@ -122,8 +126,8 @@ const lintStaged = async (
122
126
  maxArgLength,
123
127
  quiet,
124
128
  relative,
125
- reset: reset || !!stash,
126
129
  shell,
130
+ stash,
127
131
  verbose,
128
132
  },
129
133
  logger
@@ -136,8 +140,12 @@ const lintStaged = async (
136
140
  const { ctx } = runAllError
137
141
  if (ctx.errors.has(ApplyEmptyCommitError)) {
138
142
  logger.warn(PREVENTED_EMPTY_COMMIT)
139
- } else if (ctx.errors.has(GitError)) {
143
+ } else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
140
144
  logger.error(GIT_ERROR)
145
+ if (ctx.shouldBackup) {
146
+ // No sense to show this if the backup stash itself is missing.
147
+ logger.error(RESTORE_STASH_EXAMPLE)
148
+ }
141
149
  }
142
150
 
143
151
  printTaskOutput(ctx, logger)
@@ -149,4 +157,4 @@ const lintStaged = async (
149
157
  }
150
158
  }
151
159
 
152
- module.exports = lintStaged
160
+ export default lintStaged
@@ -1,10 +1,10 @@
1
- 'use strict'
1
+ import cliTruncate from 'cli-truncate'
2
+ import debug from 'debug'
2
3
 
3
- const cliTruncate = require('cli-truncate')
4
- const debug = require('debug')('lint-staged:make-cmd-tasks')
4
+ import { configurationError } from './messages.js'
5
+ import { resolveTaskFn } from './resolveTaskFn.js'
5
6
 
6
- const { configurationError } = require('./messages')
7
- const resolveTaskFn = require('./resolveTaskFn')
7
+ const debugLog = debug('lint-staged:makeCmdTasks')
8
8
 
9
9
  const STDOUT_COLUMNS_DEFAULT = 80
10
10
 
@@ -34,8 +34,8 @@ const getTitleLength = (renderer, columns = process.stdout.columns) => {
34
34
  * @param {Boolean} shell
35
35
  * @param {Boolean} verbose
36
36
  */
37
- const makeCmdTasks = async ({ commands, files, gitDir, renderer, shell, verbose }) => {
38
- debug('Creating listr tasks for commands %o', commands)
37
+ export const makeCmdTasks = async ({ commands, files, gitDir, renderer, shell, verbose }) => {
38
+ debugLog('Creating listr tasks for commands %o', commands)
39
39
  const commandArray = Array.isArray(commands) ? commands : [commands]
40
40
  const cmdTasks = []
41
41
 
@@ -68,5 +68,3 @@ const makeCmdTasks = async ({ commands, files, gitDir, renderer, shell, verbose
68
68
 
69
69
  return cmdTasks
70
70
  }
71
-
72
- module.exports = makeCmdTasks
package/lib/messages.js CHANGED
@@ -1,55 +1,48 @@
1
- 'use strict'
1
+ import { redBright, bold, yellow } from 'colorette'
2
+ import inspect from 'object-inspect'
2
3
 
3
- const { redBright, bold, yellow } = require('colorette')
4
- const format = require('stringify-object')
4
+ import { error, info, warning } from './figures.js'
5
5
 
6
- const { error, info, warning } = require('./figures')
7
-
8
- const configurationError = (opt, helpMsg, value) =>
6
+ export const configurationError = (opt, helpMsg, value) =>
9
7
  `${redBright(`${error} Validation Error:`)}
10
8
 
11
9
  Invalid value for '${bold(opt)}': ${bold(
12
- format(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY })
10
+ inspect(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY })
13
11
  )}
14
12
 
15
13
  ${helpMsg}`
16
14
 
17
- const NOT_GIT_REPO = redBright(`${error} Current directory is not a git directory!`)
15
+ export const NOT_GIT_REPO = redBright(`${error} Current directory is not a git directory!`)
18
16
 
19
- const FAILED_GET_STAGED_FILES = redBright(`${error} Failed to get staged files!`)
17
+ export const FAILED_GET_STAGED_FILES = redBright(`${error} Failed to get staged files!`)
20
18
 
21
- const incorrectBraces = (before, after) =>
19
+ export const incorrectBraces = (before, after) =>
22
20
  yellow(
23
21
  `${warning} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\`
24
22
  `
25
23
  )
26
24
 
27
- const NO_STAGED_FILES = `${info} No staged files found.`
25
+ export const NO_STAGED_FILES = `${info} No staged files found.`
28
26
 
29
- const NO_TASKS = `${info} No staged files match any configured task.`
27
+ export const NO_TASKS = `${info} No staged files match any configured task.`
30
28
 
31
- const skippingReset = (hasInitialCommit) => {
32
- const reason = hasInitialCommit ? '`--no-reset` was used' : 'there’s no initial commit yet'
33
- return yellow(`${warning} Changes won't be reset because ${reason}.\n`)
29
+ export const skippingBackup = (hasInitialCommit) => {
30
+ const reason = hasInitialCommit ? '`--no-stash` was used' : 'there’s no initial commit yet'
31
+ return yellow(`${warning} Skipping backup because ${reason}.\n`)
34
32
  }
35
33
 
36
- const DEPRECATED_GIT_ADD = yellow(
34
+ export const DEPRECATED_GIT_ADD = yellow(
37
35
  `${warning} Some of your tasks use \`git add\` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.
38
36
  `
39
37
  )
40
38
 
41
- const DEPRECATED_NO_STASH = yellow(
42
- `${warning} The \`--no-stash\` option has been renamed to \`--no-reset\`.
43
- `
44
- )
45
-
46
- const TASK_ERROR = 'Skipped because of errors from tasks.'
39
+ export const TASK_ERROR = 'Skipped because of errors from tasks.'
47
40
 
48
- const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'
41
+ export const SKIPPED_GIT_ERROR = 'Skipped because of previous git error.'
49
42
 
50
- const GIT_ERROR = `\n ${redBright(`${error} lint-staged failed due to a git error.`)}`
43
+ export const GIT_ERROR = `\n ${redBright(`${error} lint-staged failed due to a git error.`)}`
51
44
 
52
- const invalidOption = (name, value, message) => `${redBright(`${error} Validation Error:`)}
45
+ export const invalidOption = (name, value, message) => `${redBright(`${error} Validation Error:`)}
53
46
 
54
47
  Invalid value for option '${bold(name)}': ${bold(value)}
55
48
 
@@ -57,27 +50,16 @@ const invalidOption = (name, value, message) => `${redBright(`${error} Validatio
57
50
 
58
51
  See https://github.com/okonet/lint-staged#command-line-flags`
59
52
 
60
- const PREVENTED_EMPTY_COMMIT = `
53
+ export const PREVENTED_EMPTY_COMMIT = `
61
54
  ${yellow(`${warning} lint-staged prevented an empty git commit.
62
55
  Use the --allow-empty option to continue, or check your task configuration`)}
63
56
  `
64
57
 
65
- const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'
66
-
67
- module.exports = {
68
- CONFIG_STDIN_ERROR,
69
- configurationError,
70
- DEPRECATED_GIT_ADD,
71
- DEPRECATED_NO_STASH,
72
- FAILED_GET_STAGED_FILES,
73
- GIT_ERROR,
74
- incorrectBraces,
75
- invalidOption,
76
- NO_STAGED_FILES,
77
- NO_TASKS,
78
- NOT_GIT_REPO,
79
- PREVENTED_EMPTY_COMMIT,
80
- SKIPPED_GIT_ERROR,
81
- skippingReset,
82
- TASK_ERROR,
83
- }
58
+ export const RESTORE_STASH_EXAMPLE = ` Any lost modifications can be restored from a git stash:
59
+
60
+ > git stash list
61
+ stash@{0}: automatic lint-staged backup
62
+ > git stash apply --index stash@{0}
63
+ `
64
+
65
+ export const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.'
@@ -1,16 +1,12 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * Handle logging of listr `ctx.output` to the specified `logger`
5
3
  * @param {Object} ctx - The listr initial state
6
4
  * @param {Object} logger - The logger
7
5
  */
8
- const printTaskOutput = (ctx = {}, logger) => {
6
+ export const printTaskOutput = (ctx = {}, logger) => {
9
7
  if (!Array.isArray(ctx.output)) return
10
8
  const log = ctx.errors && ctx.errors.size > 0 ? logger.error : logger.log
11
9
  for (const line of ctx.output) {
12
10
  log(line)
13
11
  }
14
12
  }
15
-
16
- module.exports = printTaskOutput
@@ -1,15 +1,13 @@
1
- 'use strict'
1
+ import { promises as fs } from 'fs'
2
+ import path from 'path'
2
3
 
3
- const normalize = require('normalize-path')
4
- const debugLog = require('debug')('lint-staged:resolveGitRepo')
5
- const fs = require('fs')
6
- const path = require('path')
7
- const { promisify } = require('util')
4
+ import debug from 'debug'
5
+ import normalize from 'normalize-path'
8
6
 
9
- const execGit = require('./execGit')
7
+ import { execGit } from './execGit.js'
8
+ import { readFile } from './file.js'
10
9
 
11
- const fsLstat = promisify(fs.lstat)
12
- const fsReadFile = promisify(fs.readFile)
10
+ const debugLog = debug('lint-staged:resolveGitRepo')
13
11
 
14
12
  /**
15
13
  * Resolve path to the .git directory, with special handling for
@@ -17,15 +15,16 @@ const fsReadFile = promisify(fs.readFile)
17
15
  */
18
16
  const resolveGitConfigDir = async (gitDir) => {
19
17
  const defaultDir = normalize(path.join(gitDir, '.git'))
20
- const stats = await fsLstat(defaultDir)
18
+ const stats = await fs.lstat(defaultDir)
21
19
  // If .git is a directory, use it
22
20
  if (stats.isDirectory()) return defaultDir
23
21
  // Otherwise .git is a file containing path to real location
24
- const file = (await fsReadFile(defaultDir)).toString()
22
+ const file = (await readFile(defaultDir)).toString()
25
23
  return path.resolve(gitDir, file.replace(/^gitdir: /, '')).trim()
26
24
  }
27
25
 
28
- const determineGitDir = (cwd, relativeDir) => {
26
+ // exported for test
27
+ export const determineGitDir = (cwd, relativeDir) => {
29
28
  // if relative dir and cwd have different endings normalize it
30
29
  // this happens under windows, where normalize is unable to normalize git's output
31
30
  if (relativeDir && relativeDir.endsWith(path.sep)) {
@@ -43,7 +42,7 @@ const determineGitDir = (cwd, relativeDir) => {
43
42
  /**
44
43
  * Resolve git directory and possible submodule paths
45
44
  */
46
- const resolveGitRepo = async (cwd = process.cwd()) => {
45
+ export const resolveGitRepo = async (cwd = process.cwd()) => {
47
46
  try {
48
47
  debugLog('Resolving git repo from `%s`', cwd)
49
48
 
@@ -68,8 +67,3 @@ const resolveGitRepo = async (cwd = process.cwd()) => {
68
67
  return { error, gitDir: null, gitConfigDir: null }
69
68
  }
70
69
  }
71
-
72
- module.exports = resolveGitRepo
73
-
74
- // exported for test
75
- module.exports.determineGitDir = determineGitDir