13870ff602f585d6a6fd3a0fa57f22aeeddc91f2e385984bee1f85d9cec2637628931022fb00befdc95e09291c06e98419e023768e110aefef7bc7403ce1a7 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. 'use strict'
  2. const dedent = require('dedent')
  3. const { cosmiconfig } = require('cosmiconfig')
  4. const debugLog = require('debug')('lint-staged')
  5. const stringifyObject = require('stringify-object')
  6. const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./messages')
  7. const printTaskOutput = require('./printTaskOutput')
  8. const runAll = require('./runAll')
  9. const { ApplyEmptyCommitError, GetBackupStashError, GitError } = require('./symbols')
  10. const formatConfig = require('./formatConfig')
  11. const validateConfig = require('./validateConfig')
  12. const errConfigNotFound = new Error('Config could not be found')
  13. function resolveConfig(configPath) {
  14. try {
  15. return require.resolve(configPath)
  16. } catch {
  17. return configPath
  18. }
  19. }
  20. function loadConfig(configPath) {
  21. const explorer = cosmiconfig('lint-staged', {
  22. searchPlaces: [
  23. 'package.json',
  24. '.lintstagedrc',
  25. '.lintstagedrc.json',
  26. '.lintstagedrc.yaml',
  27. '.lintstagedrc.yml',
  28. '.lintstagedrc.js',
  29. '.lintstagedrc.cjs',
  30. 'lint-staged.config.js',
  31. 'lint-staged.config.cjs',
  32. ],
  33. })
  34. return configPath ? explorer.load(resolveConfig(configPath)) : explorer.search()
  35. }
  36. /**
  37. * @typedef {(...any) => void} LogFunction
  38. * @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
  39. *
  40. * Root lint-staged function that is called from `bin/lint-staged`.
  41. *
  42. * @param {object} options
  43. * @param {Object} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
  44. * @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
  45. * @param {object} [options.config] - Object with configuration for programmatic API
  46. * @param {string} [options.configPath] - Path to configuration file
  47. * @param {Object} [options.cwd] - Current working directory
  48. * @param {boolean} [options.debug] - Enable debug mode
  49. * @param {number} [options.maxArgLength] - Maximum argument string length
  50. * @param {boolean} [options.quiet] - Disable lint-staged’s own console output
  51. * @param {boolean} [options.relative] - Pass relative filepaths to tasks
  52. * @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
  53. * @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
  54. * @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
  55. * @param {Logger} [logger]
  56. *
  57. * @returns {Promise<boolean>} Promise of whether the linting passed or failed
  58. */
  59. module.exports = async function lintStaged(
  60. {
  61. allowEmpty = false,
  62. concurrent = true,
  63. config: configObject,
  64. configPath,
  65. cwd = process.cwd(),
  66. debug = false,
  67. maxArgLength,
  68. quiet = false,
  69. relative = false,
  70. shell = false,
  71. stash = true,
  72. verbose = false,
  73. } = {},
  74. logger = console
  75. ) {
  76. try {
  77. debugLog('Loading config using `cosmiconfig`')
  78. const resolved = configObject
  79. ? { config: configObject, filepath: '(input)' }
  80. : await loadConfig(configPath)
  81. if (resolved == null) throw errConfigNotFound
  82. debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
  83. // resolved.config is the parsed configuration object
  84. // resolved.filepath is the path to the config file that was found
  85. const formattedConfig = formatConfig(resolved.config)
  86. const config = validateConfig(formattedConfig)
  87. if (debug) {
  88. // Log using logger to be able to test through `consolemock`.
  89. logger.log('Running lint-staged with the following config:')
  90. logger.log(stringifyObject(config, { indent: ' ' }))
  91. } else {
  92. // We might not be in debug mode but `DEBUG=lint-staged*` could have
  93. // been set.
  94. debugLog('lint-staged config:\n%O', config)
  95. }
  96. // Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
  97. debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
  98. delete process.env.GIT_LITERAL_PATHSPECS
  99. try {
  100. const ctx = await runAll(
  101. {
  102. allowEmpty,
  103. concurrent,
  104. config,
  105. cwd,
  106. debug,
  107. maxArgLength,
  108. quiet,
  109. relative,
  110. shell,
  111. stash,
  112. verbose,
  113. },
  114. logger
  115. )
  116. debugLog('Tasks were executed successfully!')
  117. printTaskOutput(ctx, logger)
  118. return true
  119. } catch (runAllError) {
  120. if (runAllError && runAllError.ctx && runAllError.ctx.errors) {
  121. const { ctx } = runAllError
  122. if (ctx.errors.has(ApplyEmptyCommitError)) {
  123. logger.warn(PREVENTED_EMPTY_COMMIT)
  124. } else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
  125. logger.error(GIT_ERROR)
  126. if (ctx.shouldBackup) {
  127. // No sense to show this if the backup stash itself is missing.
  128. logger.error(RESTORE_STASH_EXAMPLE)
  129. }
  130. }
  131. printTaskOutput(ctx, logger)
  132. return false
  133. }
  134. // Probably a compilation error in the config js file. Pass it up to the outer error handler for logging.
  135. throw runAllError
  136. }
  137. } catch (lintStagedError) {
  138. if (lintStagedError === errConfigNotFound) {
  139. logger.error(`${lintStagedError.message}.`)
  140. } else {
  141. // It was probably a parsing error
  142. logger.error(dedent`
  143. Could not parse lint-staged config.
  144. ${lintStagedError}
  145. `)
  146. }
  147. logger.error() // empty line
  148. // Print helpful message for all errors
  149. logger.error(dedent`
  150. Please make sure you have created it correctly.
  151. See https://github.com/okonet/lint-staged#configuration.
  152. `)
  153. throw lintStagedError
  154. }
  155. }