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' | ||
@@ -22,4 +20,3 @@ import { supportsColor } from 'chalk' | ||
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 | ||
@@ -117,3 +114,3 @@ | ||
try { | ||
options.config = fs.readFileSync(process.stdin.fd, 'utf8').toString().trim() | ||
options.config = await fs.readFile(process.stdin.fd, 'utf8').toString().trim() | ||
} catch { | ||
@@ -131,8 +128,7 @@ console.error(CONFIG_STDIN_ERROR) | ||
lintStaged(options) | ||
.then((passed) => { | ||
process.exitCode = passed ? 0 : 1 | ||
}) | ||
.catch(() => { | ||
process.exitCode = 1 | ||
}) | ||
try { | ||
const passed = await lintStaged(options) | ||
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 @@ |
@@ -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 |
@@ -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) | ||
@@ -65,6 +46,4 @@ const commandArray = Array.isArray(commands) ? commands : [commands] | ||
// 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 }) | ||
} | ||
@@ -71,0 +50,0 @@ } |
@@ -0,3 +1,4 @@ | ||
import { inspect } from 'node:util' | ||
import chalk from 'chalk' | ||
import inspect from 'object-inspect' | ||
@@ -9,5 +10,3 @@ import { error, info, warning } from './figures.js' | ||
Invalid value for '${chalk.bold(opt)}': ${chalk.bold( | ||
inspect(value, { inlineCharacterLimit: Number.POSITIVE_INFINITY }) | ||
)} | ||
Invalid value for '${chalk.bold(opt)}': ${chalk.bold(inspect(value))} | ||
@@ -14,0 +13,0 @@ ${helpMsg}` |
@@ -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) |
@@ -8,3 +8,2 @@ /** @typedef {import('./index').Logger} Logger */ | ||
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' | ||
@@ -156,3 +156,3 @@ import { | ||
registerSignalListeners: false, | ||
...getRenderer({ debug, quiet }), | ||
...getRenderer({ debug, quiet }, logger), | ||
} | ||
@@ -166,3 +166,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' | ||
@@ -189,3 +189,2 @@ const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative }) | ||
gitDir, | ||
renderer: listrOptions.renderer, | ||
shell, | ||
@@ -201,3 +200,3 @@ verbose, | ||
? file | ||
: normalize(path.join(groupCwd, file)) | ||
: normalizePath(path.join(groupCwd, file)) | ||
@@ -204,0 +203,0 @@ matchedFiles.add(normalizedFile) |
@@ -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.2.3", | ||
"version": "13.3.0", | ||
"description": "Lint files staged by git", | ||
@@ -17,3 +17,3 @@ "license": "MIT", | ||
"engines": { | ||
"node": "^14.13.1 || >=16.0.0" | ||
"node": "^16.14.0 || >=18.0.0" | ||
}, | ||
@@ -37,33 +37,29 @@ "type": "module", | ||
"dependencies": { | ||
"chalk": "5.2.0", | ||
"cli-truncate": "^3.1.0", | ||
"commander": "^10.0.0", | ||
"debug": "^4.3.4", | ||
"execa": "^7.0.0", | ||
"chalk": "5.3.0", | ||
"commander": "11.0.0", | ||
"debug": "4.3.4", | ||
"execa": "7.2.0", | ||
"lilconfig": "2.1.0", | ||
"listr2": "^5.0.7", | ||
"micromatch": "^4.0.5", | ||
"normalize-path": "^3.0.0", | ||
"object-inspect": "^1.12.3", | ||
"pidtree": "^0.6.0", | ||
"string-argv": "^0.3.1", | ||
"yaml": "^2.2.2" | ||
"listr2": "6.6.1", | ||
"micromatch": "4.0.5", | ||
"pidtree": "0.6.0", | ||
"string-argv": "0.3.2", | ||
"yaml": "2.3.1" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.21.0", | ||
"@babel/eslint-parser": "^7.19.1", | ||
"@babel/preset-env": "^7.20.2", | ||
"babel-jest": "^29.5.0", | ||
"@babel/core": "7.22.10", | ||
"@babel/eslint-parser": "7.22.10", | ||
"@babel/preset-env": "7.22.10", | ||
"babel-jest": "29.6.2", | ||
"babel-plugin-transform-imports": "2.0.0", | ||
"consolemock": "^1.1.0", | ||
"eslint": "^8.35.0", | ||
"eslint-config-prettier": "^8.7.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"fs-extra": "^11.1.0", | ||
"husky": "^8.0.3", | ||
"jest": "^29.5.0", | ||
"jest-snapshot-serializer-ansi": "^1.0.0", | ||
"prettier": "^2.8.4" | ||
"consolemock": "1.1.0", | ||
"eslint": "8.46.0", | ||
"eslint-config-prettier": "9.0.0", | ||
"eslint-plugin-import": "2.28.0", | ||
"eslint-plugin-node": "11.1.0", | ||
"eslint-plugin-prettier": "5.0.0", | ||
"husky": "8.0.3", | ||
"jest": "29.6.2", | ||
"jest-snapshot-serializer-ansi": "2.1.0", | ||
"prettier": "3.0.1" | ||
}, | ||
@@ -70,0 +66,0 @@ "keywords": [ |
@@ -61,3 +61,3 @@ # 🚫💩 lint-staged  [](https://badge.fury.io/js/lint-staged) [](https://codecov.io/gh/okonet/lint-staged) | ||
- for example: `{ "*.js": "eslint" }` to run ESLint for all staged JS files | ||
- See [Configuration](#Configuration) for more info | ||
- See [Configuration](#configuration) for more info | ||
@@ -76,2 +76,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 | ||
@@ -223,7 +227,7 @@ | ||
- 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` | ||
@@ -630,5 +634,3 @@ When matching, lint-staged will do the following | ||
const buildEslintCommand = (filenames) => | ||
`next lint --fix --file ${filenames | ||
.map((f) => path.relative(process.cwd(), f)) | ||
.join(' --file ')}` | ||
`next lint --fix --file ${filenames.map((f) => path.relative(process.cwd(), f)).join(' --file ')}` | ||
@@ -635,0 +637,0 @@ module.exports = { |
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
113261
1.45%10
-23.08%15
-6.25%32
3.23%1960
3.43%910
0.22%10
11.11%+ 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
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated