lint-staged
Advanced tools
Comparing version
#!/usr/bin/env node | ||
import fs from 'node:fs' | ||
import path from 'node:path' | ||
import { fileURLToPath } from 'node:url' | ||
import fs from 'node:fs/promises' | ||
import { isColorSupported } from 'colorette' | ||
import { supportsColor } from 'chalk' | ||
import { Option, program } from 'commander' | ||
@@ -13,18 +11,17 @@ import debug from 'debug' | ||
import { CONFIG_STDIN_ERROR } from '../lib/messages.js' | ||
import { readStdin } from '../lib/readStdin.js' | ||
// Force colors for packages that depend on https://www.npmjs.com/package/supports-color | ||
if (isColorSupported) { | ||
process.env.FORCE_COLOR = '1' | ||
if (supportsColor) { | ||
process.env.FORCE_COLOR = supportsColor.level.toString() | ||
} | ||
const debugLog = debug('lint-staged:bin') | ||
// Do not terminate main Listr process on SIGINT | ||
process.on('SIGINT', () => {}) | ||
const packageJsonPath = path.join(fileURLToPath(import.meta.url), '../../package.json') | ||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)) | ||
const packageJson = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url))) | ||
const version = packageJson.version | ||
const debugLog = debug('lint-staged:bin') | ||
debugLog('Running `lint-staged@%s`', version) | ||
const cli = program.version(version) | ||
@@ -46,5 +43,7 @@ | ||
cli.option( | ||
'--diff [string]', | ||
'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".' | ||
cli.addOption( | ||
new Option( | ||
'--diff [string]', | ||
'override the default "--staged" flag of "git diff" to get list of files. Implies "--no-stash".' | ||
).implies({ stash: false }) | ||
) | ||
@@ -95,2 +94,4 @@ | ||
debugLog('Running `lint-staged@%s` on Node.js %s (%s)', version, process.version, process.platform) | ||
const options = { | ||
@@ -117,21 +118,16 @@ allowEmpty: !!cliOptions.allowEmpty, | ||
try { | ||
options.config = fs.readFileSync(process.stdin.fd, 'utf8').toString().trim() | ||
} catch { | ||
debugLog('Reading config from stdin') | ||
options.config = JSON.parse(await readStdin()) | ||
} catch (error) { | ||
debugLog(CONFIG_STDIN_ERROR, error) | ||
console.error(CONFIG_STDIN_ERROR) | ||
process.exit(1) | ||
} | ||
} | ||
try { | ||
options.config = JSON.parse(options.config) | ||
} catch { | ||
// Let config parsing complain if it's not JSON | ||
} | ||
try { | ||
const passed = await lintStaged(options) | ||
process.exitCode = passed ? 0 : 1 | ||
} catch { | ||
process.exitCode = 1 | ||
} | ||
lintStaged(options) | ||
.then((passed) => { | ||
process.exitCode = passed ? 0 : 1 | ||
}) | ||
.catch(() => { | ||
process.exitCode = 1 | ||
}) |
import path from 'node:path' | ||
import debug from 'debug' | ||
import normalize from 'normalize-path' | ||
import { normalizePath } from './normalizePath.js' | ||
const debugLog = debug('lint-staged:chunkFiles') | ||
@@ -38,3 +39,3 @@ | ||
const normalizedFiles = files.map((file) => | ||
normalize(relative || !baseDir ? file : path.resolve(baseDir, file)) | ||
normalizePath(relative || !baseDir ? file : path.resolve(baseDir, file)) | ||
) | ||
@@ -41,0 +42,0 @@ |
@@ -1,8 +0,8 @@ | ||
import { blue, redBright, yellow } from 'colorette' | ||
import chalk from 'chalk' | ||
import { figures } from 'listr2' | ||
export const info = blue(figures.arrowRight) | ||
export const info = chalk.blue(figures.arrowRight) | ||
export const error = redBright(figures.cross) | ||
export const error = chalk.redBright(figures.cross) | ||
export const warning = yellow(figures.warning) | ||
export const warning = chalk.yellow(figures.warning) |
@@ -5,4 +5,5 @@ import path from 'node:path' | ||
import micromatch from 'micromatch' | ||
import normalize from 'normalize-path' | ||
import { normalizePath } from './normalizePath.js' | ||
const debugLog = debug('lint-staged:generateTasks') | ||
@@ -23,3 +24,3 @@ | ||
const relativeFiles = files.map((file) => normalize(path.relative(cwd, file))) | ||
const relativeFiles = files.map((file) => normalizePath(path.relative(cwd, file))) | ||
@@ -47,3 +48,3 @@ return Object.entries(config).map(([pattern, commands]) => { | ||
const fileList = matches.map((file) => normalize(relative ? file : path.resolve(cwd, file))) | ||
const fileList = matches.map((file) => normalizePath(relative ? file : path.resolve(cwd, file))) | ||
@@ -50,0 +51,0 @@ const task = { pattern, commands, fileList } |
@@ -1,16 +0,51 @@ | ||
const getMainRendererOptions = ({ debug, quiet }, env) => { | ||
if (quiet) return { renderer: 'silent' } | ||
import { EOL } from 'node:os' | ||
import { Writable } from 'node:stream' | ||
import { ListrLogger, ProcessOutput } from 'listr2' | ||
const EOLRegex = new RegExp(EOL + '$') | ||
const bindLogger = (consoleLogMethod) => | ||
new Writable({ | ||
write: function (chunk, encoding, next) { | ||
consoleLogMethod(chunk.toString().replace(EOLRegex, '')) | ||
next() | ||
}, | ||
}) | ||
const getMainRendererOptions = ({ debug, quiet }, logger, env) => { | ||
if (quiet) { | ||
return { | ||
renderer: 'silent', | ||
} | ||
} | ||
if (env.NODE_ENV === 'test') { | ||
return { | ||
renderer: 'test', | ||
rendererOptions: { | ||
logger: new ListrLogger({ | ||
processOutput: new ProcessOutput(bindLogger(logger.log), bindLogger(logger.error)), | ||
}), | ||
}, | ||
} | ||
} | ||
// Better support for dumb terminals: https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals | ||
const isDumbTerminal = env.TERM === 'dumb' | ||
if (debug || isDumbTerminal || env.NODE_ENV === 'test') return { renderer: 'verbose' } | ||
return { renderer: 'update', rendererOptions: { dateFormat: false } } | ||
if (debug || env.TERM === 'dumb') { | ||
return { | ||
renderer: 'verbose', | ||
} | ||
} | ||
return { | ||
renderer: 'update', | ||
rendererOptions: { | ||
formatOutput: 'truncate', | ||
}, | ||
} | ||
} | ||
const getFallbackRenderer = ({ renderer }, { FORCE_COLOR }) => { | ||
if (renderer === 'silent') { | ||
return 'silent' | ||
} | ||
// If colors are being forced, then also force non-fallback rendering | ||
if (Number(FORCE_COLOR) > 0) { | ||
if (renderer === 'silent' || renderer === 'test' || Number(FORCE_COLOR) > 0) { | ||
return renderer | ||
@@ -22,8 +57,9 @@ } | ||
export const getRenderer = (options, env = process.env) => { | ||
const mainRendererOptions = getMainRendererOptions(options, env) | ||
export const getRenderer = (options, logger, env = process.env) => { | ||
const mainRendererOptions = getMainRendererOptions(options, logger, env) | ||
return { | ||
...mainRendererOptions, | ||
nonTTYRenderer: getFallbackRenderer(mainRendererOptions, env), | ||
fallbackRenderer: getFallbackRenderer(mainRendererOptions, env), | ||
} | ||
} |
import path from 'node:path' | ||
import normalize from 'normalize-path' | ||
import { execGit } from './execGit.js' | ||
import { getDiffCommand } from './getDiffCommand.js' | ||
import { normalizePath } from './normalizePath.js' | ||
import { parseGitZOutput } from './parseGitZOutput.js' | ||
@@ -14,3 +13,3 @@ | ||
return parseGitZOutput(lines).map((file) => normalize(path.resolve(cwd, file))) | ||
return parseGitZOutput(lines).map((file) => normalizePath(path.resolve(cwd, file))) | ||
} catch { | ||
@@ -17,0 +16,0 @@ return null |
@@ -107,12 +107,3 @@ import path from 'node:path' | ||
/** | ||
* https://github.com/okonet/lint-staged/issues/1121 | ||
* Detect MSYS in login shell mode and escape braces | ||
* to prevent interpolation | ||
*/ | ||
if (!!process.env.MSYSTEM && !!process.env.LOGINSHELL) { | ||
return `refs/stash@\\{${index}\\}` | ||
} | ||
return `refs/stash@{${index}}` | ||
return String(index) | ||
} | ||
@@ -119,0 +110,0 @@ |
@@ -78,3 +78,4 @@ import debug from 'debug' | ||
shell = false, | ||
stash = true, | ||
// Stashing should be disabled by default when the `diff` option is used | ||
stash = diff === undefined, | ||
verbose = false, | ||
@@ -81,0 +82,0 @@ } = {}, |
@@ -12,2 +12,4 @@ /** @typedef {import('./index').Logger} Logger */ | ||
const PACKAGE_JSON = 'package.json' | ||
/** | ||
@@ -18,3 +20,3 @@ * The list of files `lint-staged` will read configuration | ||
export const searchPlaces = [ | ||
'package.json', | ||
PACKAGE_JSON, | ||
'.lintstagedrc', | ||
@@ -32,4 +34,15 @@ '.lintstagedrc.json', | ||
const jsonParse = (path, content) => JSON.parse(content) | ||
const jsonParse = (path, content) => { | ||
try { | ||
return JSON.parse(content) | ||
} catch (error) { | ||
if (path.endsWith(PACKAGE_JSON)) { | ||
debugLog('Ignoring invalid package file `%s` with content:\n%s', path, content) | ||
return undefined | ||
} | ||
throw error | ||
} | ||
} | ||
const yamlParse = (path, content) => YAML.parse(content) | ||
@@ -36,0 +49,0 @@ |
@@ -1,2 +0,1 @@ | ||
import cliTruncate from 'cli-truncate' | ||
import debug from 'debug' | ||
@@ -9,20 +8,3 @@ | ||
const STDOUT_COLUMNS_DEFAULT = 80 | ||
const listrPrefixLength = { | ||
update: ` X `.length, // indented task title where X is a checkmark or a cross (failure) | ||
verbose: `[STARTED] `.length, // verbose renderer uses 7-letter STARTED/SUCCESS prefixes | ||
} | ||
/** | ||
* Get length of title based on the number of available columns prefix length | ||
* @param {string} renderer The name of the Listr renderer | ||
* @returns {number} | ||
*/ | ||
const getTitleLength = (renderer, columns = process.stdout.columns) => { | ||
const prefixLength = listrPrefixLength[renderer] || 0 | ||
return (columns || STDOUT_COLUMNS_DEFAULT) - prefixLength | ||
} | ||
/** | ||
* Creates and returns an array of listr tasks which map to the given commands. | ||
@@ -35,7 +17,6 @@ * | ||
* @param {string} options.gitDir | ||
* @param {string} options.renderer | ||
* @param {Boolean} shell | ||
* @param {Boolean} verbose | ||
*/ | ||
export const makeCmdTasks = async ({ commands, cwd, files, gitDir, renderer, shell, verbose }) => { | ||
export const makeCmdTasks = async ({ commands, cwd, files, gitDir, shell, verbose }) => { | ||
debugLog('Creating listr tasks for commands %o', commands) | ||
@@ -48,4 +29,6 @@ const commandArray = Array.isArray(commands) ? commands : [commands] | ||
const isFn = typeof cmd === 'function' | ||
const resolved = isFn ? await cmd(files) : cmd | ||
/** Pass copy of file list to prevent mutation by function from config file. */ | ||
const resolved = isFn ? await cmd([...files]) : cmd | ||
const resolvedArray = Array.isArray(resolved) ? resolved : [resolved] // Wrap non-array command as array | ||
@@ -66,6 +49,4 @@ | ||
// Truncate title to single line based on renderer | ||
const title = cliTruncate(command, getTitleLength(renderer)) | ||
const task = resolveTaskFn({ command, cwd, files, gitDir, isFn, shell, verbose }) | ||
cmdTasks.push({ title, command, task }) | ||
cmdTasks.push({ title: command, command, task }) | ||
} | ||
@@ -72,0 +53,0 @@ } |
@@ -1,21 +0,20 @@ | ||
import { redBright, bold, yellow } from 'colorette' | ||
import inspect from 'object-inspect' | ||
import { inspect } from 'node:util' | ||
import chalk from 'chalk' | ||
import { error, info, warning } from './figures.js' | ||
export const configurationError = (opt, helpMsg, value) => | ||
`${redBright(`${error} Validation Error:`)} | ||
`${chalk.redBright(`${error} Validation Error:`)} | ||
Invalid value for '${bold(opt)}': ${bold( | ||
inspect(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY }) | ||
)} | ||
Invalid value for '${chalk.bold(opt)}': ${chalk.bold(inspect(value))} | ||
${helpMsg}` | ||
export const NOT_GIT_REPO = redBright(`${error} Current directory is not a git directory!`) | ||
export const NOT_GIT_REPO = chalk.redBright(`${error} Current directory is not a git directory!`) | ||
export const FAILED_GET_STAGED_FILES = redBright(`${error} Failed to get staged files!`) | ||
export const FAILED_GET_STAGED_FILES = chalk.redBright(`${error} Failed to get staged files!`) | ||
export const incorrectBraces = (before, after) => | ||
yellow( | ||
chalk.yellow( | ||
`${warning} Detected incorrect braces with only single value: \`${before}\`. Reformatted as: \`${after}\` | ||
@@ -39,6 +38,6 @@ ` | ||
return yellow(`${warning} Skipping backup because ${reason}.\n`) | ||
return chalk.yellow(`${warning} Skipping backup because ${reason}.\n`) | ||
} | ||
export const DEPRECATED_GIT_ADD = yellow( | ||
export const DEPRECATED_GIT_ADD = chalk.yellow( | ||
`${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. | ||
@@ -52,7 +51,9 @@ ` | ||
export const GIT_ERROR = `\n ${redBright(`${error} lint-staged failed due to a git error.`)}` | ||
export const GIT_ERROR = `\n ${chalk.redBright(`${error} lint-staged failed due to a git error.`)}` | ||
export const invalidOption = (name, value, message) => `${redBright(`${error} Validation Error:`)} | ||
export const invalidOption = (name, value, message) => `${chalk.redBright( | ||
`${error} Validation Error:` | ||
)} | ||
Invalid value for option '${bold(name)}': ${bold(value)} | ||
Invalid value for option '${chalk.bold(name)}': ${chalk.bold(value)} | ||
@@ -64,3 +65,3 @@ ${message} | ||
export const PREVENTED_EMPTY_COMMIT = ` | ||
${yellow(`${warning} lint-staged prevented an empty git commit. | ||
${chalk.yellow(`${warning} lint-staged prevented an empty git commit. | ||
Use the --allow-empty option to continue, or check your task configuration`)} | ||
@@ -76,2 +77,2 @@ ` | ||
export const CONFIG_STDIN_ERROR = 'Error: Could not read config from stdin.' | ||
export const CONFIG_STDIN_ERROR = chalk.redBright(`${error} Failed to read config from stdin.`) |
@@ -5,6 +5,6 @@ import fs from 'node:fs/promises' | ||
import debug from 'debug' | ||
import normalize from 'normalize-path' | ||
import { execGit } from './execGit.js' | ||
import { readFile } from './file.js' | ||
import { normalizePath } from './normalizePath.js' | ||
@@ -19,3 +19,3 @@ const debugLog = debug('lint-staged:resolveGitRepo') | ||
// Get the real path in case it's a symlink | ||
const defaultDir = normalize(await fs.realpath(path.join(gitDir, '.git'))) | ||
const defaultDir = normalizePath(await fs.realpath(path.join(gitDir, '.git'))) | ||
const stats = await fs.lstat(defaultDir) | ||
@@ -38,6 +38,6 @@ // If .git is a directory, use it | ||
// the current working dir is inside the git top-level directory | ||
return normalize(cwd.substring(0, cwd.lastIndexOf(relativeDir))) | ||
return normalizePath(cwd.substring(0, cwd.lastIndexOf(relativeDir))) | ||
} else { | ||
// the current working dir is the top-level git directory | ||
return normalize(cwd) | ||
return normalizePath(cwd) | ||
} | ||
@@ -61,5 +61,5 @@ } | ||
// don't read the toplevel directly, it will lead to an posix conform path on non posix systems (cygwin) | ||
const gitRel = normalize(await execGit(['rev-parse', '--show-prefix'], { cwd })) | ||
const gitDir = determineGitDir(normalize(cwd), gitRel) | ||
const gitConfigDir = normalize(await resolveGitConfigDir(gitDir)) | ||
const gitRel = normalizePath(await execGit(['rev-parse', '--show-prefix'], { cwd })) | ||
const gitDir = determineGitDir(normalizePath(cwd), gitRel) | ||
const gitConfigDir = normalizePath(await resolveGitConfigDir(gitDir)) | ||
@@ -66,0 +66,0 @@ debugLog('Resolved git directory to be `%s`', gitDir) |
@@ -1,2 +0,2 @@ | ||
import { redBright, dim } from 'colorette' | ||
import chalk from 'chalk' | ||
import { execa, execaCommand } from 'execa' | ||
@@ -35,3 +35,3 @@ import debug from 'debug' | ||
if (hasOutput) { | ||
const outputTitle = isError ? redBright(`${error} ${command}:`) : `${info} ${command}:` | ||
const outputTitle = isError ? chalk.redBright(`${error} ${command}:`) : `${info} ${command}:` | ||
const output = [] | ||
@@ -45,3 +45,3 @@ .concat(ctx.quiet ? [] : ['', outputTitle]) | ||
const tag = getTag(result) | ||
const message = redBright(`\n${error} ${command} failed without output (${tag}).`) | ||
const message = chalk.redBright(`\n${error} ${command} failed without output (${tag}).`) | ||
if (!ctx.quiet) ctx.output.push(message) | ||
@@ -121,3 +121,3 @@ } | ||
const tag = getTag(result) | ||
return new Error(`${redBright(command)} ${dim(`[${tag}]`)}`) | ||
return new Error(`${chalk.redBright(command)} ${chalk.dim(`[${tag}]`)}`) | ||
} | ||
@@ -124,0 +124,0 @@ |
@@ -5,6 +5,5 @@ /** @typedef {import('./index').Logger} Logger */ | ||
import { dim } from 'colorette' | ||
import chalk from 'chalk' | ||
import debug from 'debug' | ||
import { Listr } from 'listr2' | ||
import normalize from 'normalize-path' | ||
@@ -28,2 +27,3 @@ import { chunkFiles } from './chunkFiles.js' | ||
} from './messages.js' | ||
import { normalizePath } from './normalizePath.js' | ||
import { resolveGitRepo } from './resolveGitRepo.js' | ||
@@ -82,3 +82,4 @@ import { | ||
shell = false, | ||
stash = true, | ||
// Stashing should be disabled by default when the `diff` option is used | ||
stash = diff === undefined, | ||
verbose = false, | ||
@@ -110,5 +111,5 @@ }, | ||
// Lint-staged should create a backup stash only when there's an initial commit, | ||
// and when using the default list of staged files | ||
ctx.shouldBackup = hasInitialCommit && stash && diff === undefined | ||
// Lint-staged will create a backup stash only when there's an initial commit, | ||
// and when using the default list of staged files by default | ||
ctx.shouldBackup = hasInitialCommit && stash | ||
if (!ctx.shouldBackup) { | ||
@@ -157,3 +158,3 @@ logger.warn(skippingBackup(hasInitialCommit, diff)) | ||
registerSignalListeners: false, | ||
...getRenderer({ debug, quiet }), | ||
...getRenderer({ debug, quiet }, logger), | ||
} | ||
@@ -167,3 +168,3 @@ | ||
for (const [configPath, { config, files }] of Object.entries(filesByConfig)) { | ||
const configName = configPath ? normalize(path.relative(cwd, configPath)) : 'Config object' | ||
const configName = configPath ? normalizePath(path.relative(cwd, configPath)) : 'Config object' | ||
@@ -190,3 +191,2 @@ const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative }) | ||
gitDir, | ||
renderer: listrOptions.renderer, | ||
shell, | ||
@@ -202,3 +202,3 @@ verbose, | ||
? file | ||
: normalize(path.join(groupCwd, file)) | ||
: normalizePath(path.join(groupCwd, file)) | ||
@@ -214,3 +214,3 @@ matchedFiles.add(normalizedFile) | ||
return { | ||
title: `${task.pattern}${dim( | ||
title: `${task.pattern}${chalk.dim( | ||
` — ${fileCount} ${fileCount === 1 ? 'file' : 'files'}` | ||
@@ -227,3 +227,3 @@ )}`, | ||
if (fileCount === 0) { | ||
return `${task.pattern}${dim(' — no files')}` | ||
return `${task.pattern}${chalk.dim(' — no files')}` | ||
} | ||
@@ -239,4 +239,4 @@ return false | ||
title: | ||
`${configName}${dim(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` + | ||
(chunkCount > 1 ? dim(` (chunk ${index + 1}/${chunkCount})...`) : ''), | ||
`${configName}${chalk.dim(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` + | ||
(chunkCount > 1 ? chalk.dim(` (chunk ${index + 1}/${chunkCount})...`) : ''), | ||
task: (ctx, task) => task.newListr(chunkListrTasks, { concurrent, exitOnError: true }), | ||
@@ -248,3 +248,3 @@ skip: () => { | ||
if (chunkListrTasks.every((task) => task.skip())) { | ||
return `${configName}${dim(' — no tasks to run')}` | ||
return `${configName}${chalk.dim(' — no tasks to run')}` | ||
} | ||
@@ -298,3 +298,3 @@ return false | ||
{ | ||
title: `Running tasks for staged files...`, | ||
title: `Running tasks for ${diff ? 'changed' : 'staged'} files...`, | ||
task: (ctx, task) => task.newListr(listrTasks, { concurrent }), | ||
@@ -301,0 +301,0 @@ skip: () => listrTasks.every((task) => task.skip()), |
@@ -6,6 +6,6 @@ /** @typedef {import('./index').Logger} Logger */ | ||
import debug from 'debug' | ||
import normalize from 'normalize-path' | ||
import { execGit } from './execGit.js' | ||
import { loadConfig, searchPlaces } from './loadConfig.js' | ||
import { normalizePath } from './normalizePath.js' | ||
import { parseGitZOutput } from './parseGitZOutput.js' | ||
@@ -25,3 +25,3 @@ import { validateConfig } from './validateConfig.js' | ||
const isInsideDirectory = (dir) => (file) => file.startsWith(normalize(dir)) | ||
const isInsideDirectory = (dir) => (file) => file.startsWith(normalizePath(dir)) | ||
@@ -73,3 +73,3 @@ /** | ||
const possibleConfigFiles = [...cachedFiles, ...otherFiles] | ||
.map((file) => normalize(path.join(gitDir, file))) | ||
.map((file) => normalizePath(path.join(gitDir, file))) | ||
.filter(isInsideDirectory(cwd)) | ||
@@ -76,0 +76,0 @@ .sort(sortDeepestParth) |
@@ -20,4 +20,4 @@ import { incorrectBraces } from './messages.js' | ||
* @example <caption>Globs with brace expansions</caption> | ||
* - *.{js,tx} // expanded as *.js, *.ts | ||
* - *.{{j,t}s,css} // expanded as *.js, *.ts, *.css | ||
* - *.{js,tx} // expanded as *.js, *.ts | ||
* - *.{{j,t}s,css} // expanded as *.js, *.ts, *.css | ||
* - file_{1..10}.css // expanded as file_1.css, file_2.css, …, file_10.css | ||
@@ -32,3 +32,3 @@ * | ||
*/ | ||
export const BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g | ||
export const INCORRECT_BRACES_REGEXP = /(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g | ||
@@ -39,7 +39,7 @@ /** | ||
*/ | ||
const withoutIncorrectBraces = (pattern) => { | ||
const stripIncorrectBraces = (pattern) => { | ||
let output = `${pattern}` | ||
let match = null | ||
while ((match = BRACES_REGEXP.exec(pattern))) { | ||
while ((match = INCORRECT_BRACES_REGEXP.exec(pattern))) { | ||
const fullMatch = match[0] | ||
@@ -54,2 +54,26 @@ const withoutBraces = fullMatch.replace(/{/, '').replace(/}/, '') | ||
/** | ||
* This RegExp matches "duplicate" opening and closing braces, without any other braces | ||
* in between, where the duplication is redundant and should be removed. | ||
* | ||
* @example *.{{js,ts}} // should just be *.{js,ts} | ||
*/ | ||
export const DOUBLE_BRACES_REGEXP = /{{[^}{]*}}/ | ||
/** | ||
* @param {string} pattern | ||
* @returns {string} | ||
*/ | ||
const stripDoubleBraces = (pattern) => { | ||
let output = `${pattern}` | ||
const match = DOUBLE_BRACES_REGEXP.exec(pattern)?.[0] | ||
if (match) { | ||
const withoutBraces = match.replace('{{', '{').replace('}}', '}') | ||
output = output.replace(match, withoutBraces) | ||
} | ||
return output | ||
} | ||
/** | ||
* Validate and remove incorrect brace expansions from glob pattern. | ||
@@ -64,3 +88,3 @@ * For example `*.{js}` is incorrect because it doesn't contain a `,` or `..`, | ||
export const validateBraces = (pattern, logger) => { | ||
const fixedPattern = withoutIncorrectBraces(pattern) | ||
const fixedPattern = stripDoubleBraces(stripIncorrectBraces(pattern)) | ||
@@ -67,0 +91,0 @@ if (fixedPattern !== pattern) { |
/** @typedef {import('./index').Logger} Logger */ | ||
import { inspect } from 'node:util' | ||
import debug from 'debug' | ||
import inspect from 'object-inspect' | ||
@@ -112,5 +113,5 @@ import { configurationError } from './messages.js' | ||
debugLog('Validated config from `%s`:', configPath) | ||
debugLog(inspect(config, { indent: 2 })) | ||
debugLog(inspect(config, { compact: false })) | ||
return validatedConfig | ||
} |
{ | ||
"name": "lint-staged", | ||
"version": "13.0.3", | ||
"version": "15.0.2", | ||
"description": "Lint files staged by git", | ||
@@ -17,3 +17,3 @@ "license": "MIT", | ||
"engines": { | ||
"node": "^14.13.1 || >=16.0.0" | ||
"node": ">=18.12.0" | ||
}, | ||
@@ -24,2 +24,3 @@ "type": "module", | ||
".": "./lib/index.js", | ||
"./bin": "./bin/lint-staged.js", | ||
"./package.json": "./package.json" | ||
@@ -34,36 +35,40 @@ }, | ||
"test": "jest --coverage", | ||
"test:watch": "jest --watch" | ||
"test:watch": "jest --watch", | ||
"version": "npx changeset version", | ||
"postversion": "npm i --package-lock-only && git commit -am \"chore(changeset): release\"", | ||
"tag": "npx changeset tag" | ||
}, | ||
"dependencies": { | ||
"cli-truncate": "^3.1.0", | ||
"colorette": "^2.0.17", | ||
"commander": "^9.3.0", | ||
"debug": "^4.3.4", | ||
"execa": "^6.1.0", | ||
"lilconfig": "2.0.5", | ||
"listr2": "^4.0.5", | ||
"micromatch": "^4.0.5", | ||
"normalize-path": "^3.0.0", | ||
"object-inspect": "^1.12.2", | ||
"pidtree": "^0.6.0", | ||
"string-argv": "^0.3.1", | ||
"yaml": "^2.1.1" | ||
"chalk": "5.3.0", | ||
"commander": "11.1.0", | ||
"debug": "4.3.4", | ||
"execa": "8.0.1", | ||
"lilconfig": "2.1.0", | ||
"listr2": "7.0.2", | ||
"micromatch": "4.0.5", | ||
"pidtree": "0.6.0", | ||
"string-argv": "0.3.2", | ||
"yaml": "2.3.3" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.18.2", | ||
"@babel/eslint-parser": "^7.18.2", | ||
"@babel/preset-env": "^7.18.2", | ||
"babel-jest": "^28.1.1", | ||
"@babel/core": "7.23.2", | ||
"@babel/eslint-parser": "7.22.15", | ||
"@babel/preset-env": "7.23.2", | ||
"@changesets/changelog-github": "0.4.8", | ||
"@changesets/cli": "2.26.2", | ||
"@commitlint/cli": "17.8.0", | ||
"@commitlint/config-conventional": "17.8.0", | ||
"babel-jest": "29.7.0", | ||
"babel-plugin-transform-imports": "2.0.0", | ||
"consolemock": "^1.1.0", | ||
"eslint": "^8.17.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"fs-extra": "^10.1.0", | ||
"husky": "^8.0.1", | ||
"jest": "^28.1.1", | ||
"jest-snapshot-serializer-ansi": "^1.0.0", | ||
"prettier": "^2.6.2" | ||
"consolemock": "1.1.0", | ||
"eslint": "8.51.0", | ||
"eslint-config-prettier": "9.0.0", | ||
"eslint-plugin-import": "2.28.1", | ||
"eslint-plugin-node": "11.1.0", | ||
"eslint-plugin-prettier": "5.0.1", | ||
"husky": "8.0.3", | ||
"jest": "29.7.0", | ||
"jest-snapshot-serializer-ansi": "2.1.0", | ||
"mock-stdin": "1.0.0", | ||
"prettier": "3.0.3" | ||
}, | ||
@@ -70,0 +75,0 @@ "keywords": [ |
@@ -1,2 +0,2 @@ | ||
# 🚫💩 lint-staged  [](https://badge.fury.io/js/lint-staged) [](https://codecov.io/gh/okonet/lint-staged) | ||
# 🚫💩 lint-staged [](https://github.com/okonet/lint-staged/actions/workflows/push.yml) [](https://github.com/okonet/lint-staged/actions/workflows/tag.yml) [](https://badge.fury.io/js/lint-staged) [](https://codecov.io/gh/okonet/lint-staged) | ||
@@ -45,3 +45,3 @@ Run linters against staged git files and don't let :poop: slip into your code base! | ||
- [SurviveJS interview - Juho Vepsäläinen and Andrey Okonetchnikov, 2018](https://survivejs.com/blog/lint-staged-interview/) | ||
- [Prettier your CSharp with `dotnet-format` and `lint-staged`](https://blog.johnnyreilly.com/2020/12/prettier-your-csharp-with-dotnet-format-and-lint-staged.html) | ||
- [Prettier your CSharp with `dotnet-format` and `lint-staged`](https://johnnyreilly.com/2020/12/22/prettier-your-csharp-with-dotnet-format-and-lint-staged) | ||
@@ -62,3 +62,3 @@ > If you've written one, please submit a PR with the link to it! | ||
- for example: `{ "*.js": "eslint" }` to run ESLint for all staged JS files | ||
- See [Configuration](#Configuration) for more info | ||
- See [Configuration](#configuration) for more info | ||
@@ -77,2 +77,6 @@ Don't forget to commit changes to `package.json` and `.husky` to share this setup with your team! | ||
#### v14 | ||
- Since `v14.0.0` _lint-staged_ no longer supports Node.js 14. Please upgrade your Node.js version to at least `16.14.0`. | ||
#### v13 | ||
@@ -132,7 +136,8 @@ | ||
- uses [debug](https://github.com/visionmedia/debug) internally to log additional information about staged files, commands being executed, location of binaries, etc. Debug logs, which are automatically enabled by passing the flag, can also be enabled by setting the environment variable `$DEBUG` to `lint-staged*`. | ||
- uses [`verbose` renderer](https://github.com/SamVerschueren/listr-verbose-renderer) for `listr`; this causes serial, uncoloured output to the terminal, instead of the default (beautified, dynamic) output. | ||
- **`--diff`**: By default linters are filtered against all files staged in git, generated from `git diff --staged`. This option allows you to override the `--staged` flag with arbitrary revisions. For example to get a list of changed files between two branches, use `--diff="branch1...branch2"`. You can also read more from about [git diff](https://git-scm.com/docs/git-diff) and [gitrevisions](https://git-scm.com/docs/gitrevisions). | ||
- uses [`verbose` renderer](https://listr2.kilic.dev/renderers/verbose-renderer/) for `listr2`; this causes serial, uncoloured output to the terminal, instead of the default (beautified, dynamic) output. | ||
(the [`verbose` renderer](https://listr2.kilic.dev/renderers/verbose-renderer/) can also be activated by setting the `TERM=dumb` or `NODE_ENV=test` environment variables) | ||
- **`--diff`**: By default linters are filtered against all files staged in git, generated from `git diff --staged`. This option allows you to override the `--staged` flag with arbitrary revisions. For example to get a list of changed files between two branches, use `--diff="branch1...branch2"`. You can also read more from about [git diff](https://git-scm.com/docs/git-diff) and [gitrevisions](https://git-scm.com/docs/gitrevisions). This option also implies `--no-stash`. | ||
- **`--diff-filter`**: By default only files that are _added_, _copied_, _modified_, or _renamed_ are included. Use this flag to override the default `ACMR` value with something else: _added_ (`A`), _copied_ (`C`), _deleted_ (`D`), _modified_ (`M`), _renamed_ (`R`), _type changed_ (`T`), _unmerged_ (`U`), _unknown_ (`X`), or _pairing broken_ (`B`). See also the `git diff` docs for [--diff-filter](https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203). | ||
- **`--max-arg-length`**: long commands (a lot of files) are automatically split into multiple chunks when it detects the current shell cannot handle them. Use this flag to override the maximum length of the generated command string. | ||
- **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. | ||
- **`--no-stash`**: By default a backup stash will be created before running the tasks, and all task modifications will be reverted in case of an error. This option will disable creating the stash, and instead leave all modifications in the index when aborting the commit. Can be re-enabled with `--stash`. | ||
- **`--quiet`**: Supress all CLI output, except from tasks. | ||
@@ -224,7 +229,7 @@ - **`--relative`**: Pass filepaths relative to `process.cwd()` (where `lint-staged` runs) to tasks. Default is `false`. | ||
- If the glob pattern contains no slashes (`/`), micromatch's `matchBase` option will enabled, so globs match a file's basename regardless of directory: | ||
- **`"*.js"`** will match all JS files, like `/test.js` and `/foo/bar/test.js` | ||
- **`"!(*test).js"`**. will match all JS files, except those ending in `test.js`, so `foo.js` but not `foo.test.js` | ||
- `"*.js"` will match all JS files, like `/test.js` and `/foo/bar/test.js` | ||
- `"!(*test).js"` will match all JS files, except those ending in `test.js`, so `foo.js` but not `foo.test.js` | ||
- If the glob pattern does contain a slash (`/`), it will match for paths as well: | ||
- **`"./*.js"`** will match all JS files in the git repo root, so `/test.js` but not `/foo/bar/test.js` | ||
- **`"foo/**/\*.js"`** will match all JS files inside the`/foo`directory, so`/foo/bar/test.js`but not`/test.js` | ||
- `"./*.js"` will match all JS files in the git repo root, so `/test.js` but not `/foo/bar/test.js` | ||
- `"foo/**/*.js"` will match all JS files inside the `/foo` directory, so `/foo/bar/test.js` but not `/test.js` | ||
@@ -282,3 +287,3 @@ When matching, lint-staged will do the following | ||
Writing the configuration file in JavaScript is the most powerful way to configure lint-staged (`lint-staged.config.js`, [similar](https://github.com/okonet/lint-staged/README.md#configuration), or passed via `--config`). From the configuration file, you can export either a single function or an object. | ||
Writing the configuration file in JavaScript is the most powerful way to configure lint-staged (`lint-staged.config.js`, [similar](https://github.com/okonet/lint-staged#configuration), or passed via `--config`). From the configuration file, you can export either a single function or an object. | ||
@@ -367,3 +372,3 @@ If the `exports` value is a function, it will receive an array of all staged filenames. You can then build your own matchers for the files and return a command string or an array of command strings. These strings are considered complete and should include the filename arguments, if wanted. | ||
It's better to use the [function-based configuration (seen above)](https://github.com/okonet/lint-staged/README.md#example-export-a-function-to-build-your-own-matchers), if your use case is this. | ||
It's better to use the [function-based configuration (seen above)](https://github.com/okonet/lint-staged#example-export-a-function-to-build-your-own-matchers), if your use case is this. | ||
@@ -621,4 +626,43 @@ ```js | ||
### Integrate with Next.js | ||
<details> | ||
<summary>Click to expand</summary> | ||
```js | ||
// .lintstagedrc.js | ||
// See https://nextjs.org/docs/basic-features/eslint#lint-staged for details | ||
const path = require('path') | ||
const buildEslintCommand = (filenames) => | ||
`next lint --fix --file ${filenames.map((f) => path.relative(process.cwd(), f)).join(' --file ')}` | ||
module.exports = { | ||
'*.{js,jsx,ts,tsx}': [buildEslintCommand], | ||
} | ||
``` | ||
</details> | ||
## Frequently Asked Questions | ||
### The output of commit hook looks weird (no colors, duplicate lines, …) | ||
<details> | ||
<summary>Click to expand</summary> | ||
Git 2.36.0 introduced a change to hooks where they were no longer run in the original TTY. | ||
This was fixed in 2.37.0: | ||
https://raw.githubusercontent.com/git/git/master/Documentation/RelNotes/2.37.0.txt | ||
> - In Git 2.36 we revamped the way how hooks are invoked. One change | ||
> that is end-user visible is that the output of a hook is no longer | ||
> directly connected to the standard output of "git" that spawns the | ||
> hook, which was noticed post release. This is getting corrected. | ||
> (merge [a082345372](https://github.com/git/git/commit/a082345372) ab/hooks-regression-fix later to maint). | ||
</details> | ||
### Can I use `lint-staged` via node? | ||
@@ -754,2 +798,4 @@ | ||
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 either add an "empty" _lint-staged_ configuration to the root of the repo (so that there's two configs in total), or alternatively run _lint-staged_ with the `--cwd` option pointing to your package directory (for example, `lint-staged --cwd packages/frontend`). | ||
</details> | ||
@@ -872,2 +918,19 @@ | ||
#### ESLint >= 8.51.0 && [Flat ESLint config](https://eslint.org/docs/latest/use/configure/configuration-files-new) | ||
<details> | ||
<summary>Click to expand</summary> | ||
ESLint v8.51.0 introduced [`--no-warn-ignored` CLI flag](https://eslint.org/docs/latest/use/command-line-interface#--no-warn-ignored). It suppresses the `warning File ignored because of a matching ignore pattern. Use "--no-ignore" to override` warning, so manually ignoring files via `eslint.isPathIgnored` is no longer necessary. | ||
```json | ||
{ | ||
"*.js": "eslint --max-warnings=0 --no-warn-ignored" | ||
} | ||
``` | ||
**NOTE:** `--no-warn-ignored` flag is only available when [Flat ESLint config](https://eslint.org/docs/latest/use/configure/configuration-files-new) is used. | ||
</details> | ||
</details> |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
115225
5.29%10
-23.08%33
6.45%1976
4.77%927
7.29%10
-9.09%20
25%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated