'use strict'
|
|
const dedent = require('dedent')
|
const { cosmiconfig } = require('cosmiconfig')
|
const debugLog = require('debug')('lint-staged')
|
const stringifyObject = require('stringify-object')
|
|
const { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } = require('./messages')
|
const printTaskOutput = require('./printTaskOutput')
|
const runAll = require('./runAll')
|
const { ApplyEmptyCommitError, GetBackupStashError, GitError } = require('./symbols')
|
const formatConfig = require('./formatConfig')
|
const validateConfig = require('./validateConfig')
|
|
const errConfigNotFound = new Error('Config could not be found')
|
|
function resolveConfig(configPath) {
|
try {
|
return require.resolve(configPath)
|
} catch {
|
return configPath
|
}
|
}
|
|
function loadConfig(configPath) {
|
const explorer = cosmiconfig('lint-staged', {
|
searchPlaces: [
|
'package.json',
|
'.lintstagedrc',
|
'.lintstagedrc.json',
|
'.lintstagedrc.yaml',
|
'.lintstagedrc.yml',
|
'.lintstagedrc.js',
|
'.lintstagedrc.cjs',
|
'lint-staged.config.js',
|
'lint-staged.config.cjs',
|
],
|
})
|
|
return configPath ? explorer.load(resolveConfig(configPath)) : explorer.search()
|
}
|
|
/**
|
* @typedef {(...any) => void} LogFunction
|
* @typedef {{ error: LogFunction, log: LogFunction, warn: LogFunction }} Logger
|
*
|
* Root lint-staged function that is called from `bin/lint-staged`.
|
*
|
* @param {object} options
|
* @param {Object} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
|
* @param {boolean | number} [options.concurrent] - The number of tasks to run concurrently, or false to run tasks serially
|
* @param {object} [options.config] - Object with configuration for programmatic API
|
* @param {string} [options.configPath] - Path to configuration file
|
* @param {Object} [options.cwd] - Current working directory
|
* @param {boolean} [options.debug] - Enable debug mode
|
* @param {number} [options.maxArgLength] - Maximum argument string length
|
* @param {boolean} [options.quiet] - Disable lint-staged’s own console output
|
* @param {boolean} [options.relative] - Pass relative filepaths to tasks
|
* @param {boolean} [options.shell] - Skip parsing of tasks for better shell support
|
* @param {boolean} [options.stash] - Enable the backup stash, and revert in case of errors
|
* @param {boolean} [options.verbose] - Show task output even when tasks succeed; by default only failed output is shown
|
* @param {Logger} [logger]
|
*
|
* @returns {Promise<boolean>} Promise of whether the linting passed or failed
|
*/
|
module.exports = async function lintStaged(
|
{
|
allowEmpty = false,
|
concurrent = true,
|
config: configObject,
|
configPath,
|
cwd = process.cwd(),
|
debug = false,
|
maxArgLength,
|
quiet = false,
|
relative = false,
|
shell = false,
|
stash = true,
|
verbose = false,
|
} = {},
|
logger = console
|
) {
|
try {
|
debugLog('Loading config using `cosmiconfig`')
|
|
const resolved = configObject
|
? { config: configObject, filepath: '(input)' }
|
: await loadConfig(configPath)
|
if (resolved == null) throw errConfigNotFound
|
|
debugLog('Successfully loaded config from `%s`:\n%O', resolved.filepath, resolved.config)
|
// resolved.config is the parsed configuration object
|
// resolved.filepath is the path to the config file that was found
|
const formattedConfig = formatConfig(resolved.config)
|
const config = validateConfig(formattedConfig)
|
if (debug) {
|
// Log using logger to be able to test through `consolemock`.
|
logger.log('Running lint-staged with the following config:')
|
logger.log(stringifyObject(config, { indent: ' ' }))
|
} else {
|
// We might not be in debug mode but `DEBUG=lint-staged*` could have
|
// been set.
|
debugLog('lint-staged config:\n%O', config)
|
}
|
|
// Unset GIT_LITERAL_PATHSPECS to not mess with path interpretation
|
debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)
|
delete process.env.GIT_LITERAL_PATHSPECS
|
|
try {
|
const ctx = await runAll(
|
{
|
allowEmpty,
|
concurrent,
|
config,
|
cwd,
|
debug,
|
maxArgLength,
|
quiet,
|
relative,
|
shell,
|
stash,
|
verbose,
|
},
|
logger
|
)
|
debugLog('Tasks were executed successfully!')
|
printTaskOutput(ctx, logger)
|
return true
|
} catch (runAllError) {
|
if (runAllError && runAllError.ctx && runAllError.ctx.errors) {
|
const { ctx } = runAllError
|
if (ctx.errors.has(ApplyEmptyCommitError)) {
|
logger.warn(PREVENTED_EMPTY_COMMIT)
|
} else if (ctx.errors.has(GitError) && !ctx.errors.has(GetBackupStashError)) {
|
logger.error(GIT_ERROR)
|
if (ctx.shouldBackup) {
|
// No sense to show this if the backup stash itself is missing.
|
logger.error(RESTORE_STASH_EXAMPLE)
|
}
|
}
|
|
printTaskOutput(ctx, logger)
|
return false
|
}
|
|
// Probably a compilation error in the config js file. Pass it up to the outer error handler for logging.
|
throw runAllError
|
}
|
} catch (lintStagedError) {
|
if (lintStagedError === errConfigNotFound) {
|
logger.error(`${lintStagedError.message}.`)
|
} else {
|
// It was probably a parsing error
|
logger.error(dedent`
|
Could not parse lint-staged config.
|
|
${lintStagedError}
|
`)
|
}
|
logger.error() // empty line
|
// Print helpful message for all errors
|
logger.error(dedent`
|
Please make sure you have created it correctly.
|
See https://github.com/okonet/lint-staged#configuration.
|
`)
|
|
throw lintStagedError
|
}
|
}
|