Socket
Socket
Sign inDemoInstall

lint-staged

Package Overview
Dependencies
Maintainers
1
Versions
250
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lint-staged - npm Package Compare versions

Comparing version 12.1.7 to 12.2.0

lib/dynamicImport.js

5

lib/generateTasks.js

@@ -19,7 +19,6 @@ import path from 'path'

*/
export const generateTasks = ({ config, cwd = process.cwd(), gitDir, files, relative = false }) => {
export const generateTasks = ({ config, cwd = process.cwd(), files, relative = false }) => {
debugLog('Generating linter tasks')
const absoluteFiles = files.map((file) => normalize(path.resolve(gitDir, file)))
const relativeFiles = absoluteFiles.map((file) => normalize(path.relative(cwd, file)))
const relativeFiles = files.map((file) => normalize(path.relative(cwd, file)))

@@ -26,0 +25,0 @@ return Object.entries(config).map(([rawPattern, commands]) => {

26

lib/getStagedFiles.js

@@ -0,14 +1,26 @@

import path from 'path'
import normalize from 'normalize-path'
import { execGit } from './execGit.js'
export const getStagedFiles = async (options) => {
export const getStagedFiles = async ({ cwd = process.cwd() } = {}) => {
try {
// Docs for --diff-filter option: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt---diff-filterACDMRTUXB82308203
// Docs for -z option: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt--z
const lines = await execGit(
['diff', '--staged', '--diff-filter=ACMR', '--name-only', '-z'],
options
const lines = await execGit(['diff', '--staged', '--diff-filter=ACMR', '--name-only', '-z'], {
cwd,
})
if (!lines) return []
// With `-z`, git prints `fileA\u0000fileB\u0000fileC\u0000` so we need to
// remove the last occurrence of `\u0000` before splitting
return (
lines
// eslint-disable-next-line no-control-regex
.replace(/\u0000$/, '')
.split('\u0000')
.map((file) => normalize(path.resolve(cwd, file)))
)
// With `-z`, git prints `fileA\u0000fileB\u0000fileC\u0000` so we need to remove the last occurrence of `\u0000` before splitting
// eslint-disable-next-line no-control-regex
return lines ? lines.replace(/\u0000$/, '').split('\u0000') : []
} catch {

@@ -15,0 +27,0 @@ return null

import debug from 'debug'
import inspect from 'object-inspect'
import { loadConfig } from './loadConfig.js'
import { PREVENTED_EMPTY_COMMIT, GIT_ERROR, RESTORE_STASH_EXAMPLE } from './messages.js'
import { printTaskOutput } from './printTaskOutput.js'
import { runAll } from './runAll.js'
import {
ApplyEmptyCommitError,
ConfigNotFoundError,
GetBackupStashError,
GitError,
} from './symbols.js'
import { validateConfig } from './validateConfig.js'
import { ApplyEmptyCommitError, GetBackupStashError, GitError } from './symbols.js'
import { validateOptions } from './validateOptions.js'

@@ -61,21 +53,2 @@

const inputConfig = configObject || (await loadConfig({ configPath, cwd }, logger))
if (!inputConfig) {
logger.error(`${ConfigNotFoundError.message}.`)
throw ConfigNotFoundError
}
const config = validateConfig(inputConfig, logger)
if (debug) {
// Log using logger to be able to test through `consolemock`.
logger.log('Running lint-staged with the following config:')
logger.log(inspect(config, { indent: 2 }))
} 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

@@ -90,3 +63,4 @@ debugLog('Unset GIT_LITERAL_PATHSPECS (was `%s`)', process.env.GIT_LITERAL_PATHSPECS)

concurrent,
config,
configObject,
configPath,
cwd,

@@ -93,0 +67,0 @@ debug,

/** @typedef {import('./index').Logger} Logger */
import { pathToFileURL } from 'url'
import debug from 'debug'

@@ -9,2 +7,3 @@ import { lilconfig } from 'lilconfig'

import { dynamicImport } from './dynamicImport.js'
import { resolveConfig } from './resolveConfig.js'

@@ -32,5 +31,2 @@

/** exported for tests */
export const dynamicImport = (path) => import(pathToFileURL(path)).then((module) => module.default)
const jsonParse = (path, content) => JSON.parse(content)

@@ -56,2 +52,4 @@

const explorer = lilconfig('lint-staged', { searchPlaces, loaders })
/**

@@ -70,20 +68,20 @@ * @param {object} options

const explorer = lilconfig('lint-staged', { searchPlaces, loaders })
const result = await (configPath
? explorer.load(resolveConfig(configPath))
: explorer.search(cwd))
if (!result) return null
if (!result) return {}
// config is a promise when using the `dynamicImport` loader
const config = await result.config
const filepath = result.filepath
debugLog('Successfully loaded config from `%s`:\n%O', result.filepath, config)
debugLog('Successfully loaded config from `%s`:\n%O', filepath, config)
return config
return { config, filepath }
} catch (error) {
debugLog('Failed to load configuration!')
logger.error(error)
return null
return {}
}
}
/** @typedef {import('./index').Logger} Logger */
import path from 'path'
import { dim } from 'colorette'
import debug from 'debug'
import { Listr } from 'listr2'
import normalize from 'normalize-path'

@@ -9,2 +13,3 @@ import { chunkFiles } from './chunkFiles.js'

import { generateTasks } from './generateTasks.js'
import { getConfigGroups } from './getConfigGroups.js'
import { getRenderer } from './getRenderer.js'

@@ -44,6 +49,7 @@ import { getStagedFiles } from './getStagedFiles.js'

* @param {object} options
* @param {Object} [options.allowEmpty] - Allow empty commits when tasks revert all staged changes
* @param {boolean} [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] - Task configuration
* @param {Object} [options.cwd] - Current working directory
* @param {Object} [options.configObject] - Explicit config object from the js API
* @param {string} [options.configPath] - Explicit path to a config file
* @param {string} [options.cwd] - Current working directory
* @param {boolean} [options.debug] - Enable debug mode

@@ -63,3 +69,4 @@ * @param {number} [options.maxArgLength] - Maximum argument string length

concurrent = true,
config,
configObject,
configPath,
cwd = process.cwd(),

@@ -113,5 +120,3 @@ debug = false,

const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
const chunkCount = stagedFileChunks.length
if (chunkCount > 1) debugLog(`Chunked staged files into ${chunkCount} part`, chunkCount)
const configGroups = await getConfigGroups({ configObject, configPath, files }, logger)

@@ -135,39 +140,67 @@ // lint-staged 10 will automatically add modifications to index

for (const [index, files] of stagedFileChunks.entries()) {
const chunkTasks = generateTasks({ config, cwd, gitDir, files, relative })
const chunkListrTasks = []
for (const [configPath, { config, files }] of Object.entries(configGroups)) {
const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })
for (const task of chunkTasks) {
const subTasks = await makeCmdTasks({
commands: task.commands,
cwd,
files: task.fileList,
gitDir,
renderer: listrOptions.renderer,
shell,
verbose,
})
const chunkCount = stagedFileChunks.length
if (chunkCount > 1) {
debugLog('Chunked staged files from `%s` into %d part', configPath, chunkCount)
}
// Add files from task to match set
task.fileList.forEach((file) => {
matchedFiles.add(file)
})
for (const [index, files] of stagedFileChunks.entries()) {
const relativeConfig = normalize(path.relative(cwd, configPath))
hasDeprecatedGitAdd =
hasDeprecatedGitAdd || subTasks.some((subTask) => subTask.command === 'git add')
const chunkListrTasks = await Promise.all(
generateTasks({ config, cwd, files, relative }).map((task) =>
makeCmdTasks({
commands: task.commands,
cwd,
files: task.fileList,
gitDir,
renderer: listrOptions.renderer,
shell,
verbose,
}).then((subTasks) => {
// Add files from task to match set
task.fileList.forEach((file) => {
matchedFiles.add(file)
})
chunkListrTasks.push({
title: `Running tasks for ${task.pattern}`,
task: async () =>
new Listr(subTasks, {
// In sub-tasks we don't want to run concurrently
// and we want to abort on errors
...listrOptions,
concurrent: false,
exitOnError: true,
}),
hasDeprecatedGitAdd =
hasDeprecatedGitAdd || subTasks.some((subTask) => subTask.command === 'git add')
const fileCount = task.fileList.length
return {
title: `${task.pattern}${dim(` — ${fileCount} ${fileCount > 1 ? 'files' : 'file'}`)}`,
task: async () =>
new Listr(subTasks, {
// In sub-tasks we don't want to run concurrently
// and we want to abort on errors
...listrOptions,
concurrent: false,
exitOnError: true,
}),
skip: () => {
// Skip task when no files matched
if (fileCount === 0) {
return `${task.pattern}${dim(' — no files')}`
}
return false
},
}
})
)
)
listrTasks.push({
title:
`${relativeConfig}${dim(` — ${files.length} ${files.length > 1 ? 'files' : 'file'}`)}` +
(chunkCount > 1 ? dim(` (chunk ${index + 1}/${chunkCount})...`) : ''),
task: () => new Listr(chunkListrTasks, { ...listrOptions, concurrent, exitOnError: true }),
skip: () => {
// Skip task when no files matched
if (task.fileList.length === 0) {
return `No staged files match ${task.pattern}`
// Skip if the first step (backup) failed
if (ctx.errors.has(GitError)) return SKIPPED_GIT_ERROR
// Skip chunk when no every task is skipped (due to no matches)
if (chunkListrTasks.every((task) => task.skip())) {
return `${relativeConfig}${dim(' — no tasks to run')}`
}

@@ -178,16 +211,2 @@ return false

}
listrTasks.push({
// No need to show number of task chunks when there's only one
title:
chunkCount > 1 ? `Running tasks (chunk ${index + 1}/${chunkCount})...` : 'Running tasks...',
task: () => new Listr(chunkListrTasks, { ...listrOptions, concurrent }),
skip: () => {
// Skip if the first step (backup) failed
if (ctx.errors.has(GitError)) return SKIPPED_GIT_ERROR
// Skip chunk when no every task is skipped (due to no matches)
if (chunkListrTasks.every((task) => task.skip())) return 'No tasks to run.'
return false
},
})
}

@@ -220,3 +239,3 @@

{
title: 'Preparing...',
title: 'Preparing lint-staged...',
task: (ctx) => git.prepare(ctx),

@@ -229,5 +248,9 @@ },

},
...listrTasks,
{
title: 'Applying modifications...',
title: `Running tasks for staged files...`,
task: () => new Listr(listrTasks, { ...listrOptions, concurrent }),
skip: () => listrTasks.every((task) => task.skip()),
},
{
title: 'Applying modifications from tasks...',
task: (ctx) => git.applyModifications(ctx),

@@ -249,3 +272,3 @@ skip: applyModificationsSkipped,

{
title: 'Cleaning up...',
title: 'Cleaning up temporary files...',
task: (ctx) => git.cleanup(ctx),

@@ -252,0 +275,0 @@ enabled: cleanupEnabled,

@@ -0,2 +1,5 @@

/** @typedef {import('./index').Logger} Logger */
import debug from 'debug'
import inspect from 'object-inspect'

@@ -24,7 +27,9 @@ import { configurationError } from './messages.js'

* Runs config validation. Throws error if the config is not valid.
* @param config {Object}
* @returns config {Object}
* @param {Object} config
* @param {string} configPath
* @param {Logger} logger
* @returns {Object} config
*/
export const validateConfig = (config, logger) => {
debugLog('Validating config')
export const validateConfig = (config, configPath, logger) => {
debugLog('Validating config from `%s`...', configPath)

@@ -107,3 +112,6 @@ if (!config || (typeof config !== 'object' && typeof config !== 'function')) {

debugLog('Validated config from `%s`:', configPath)
debugLog(inspect(config, { indent: 2 }))
return validatedConfig
}
{
"name": "lint-staged",
"version": "12.1.7",
"version": "12.2.0",
"description": "Lint files staged by git",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -5,4 +5,26 @@ # 🚫💩 lint-staged ![GitHub Actions](https://github.com/okonet/lint-staged/workflows/CI/badge.svg) [![npm version](https://badge.fury.io/js/lint-staged.svg)](https://badge.fury.io/js/lint-staged) [![Codecov](https://codecov.io/gh/okonet/lint-staged/branch/master/graph/badge.svg)](https://codecov.io/gh/okonet/lint-staged)

```
$ git commit
✔ Preparing lint-staged...
❯ Running tasks for staged files...
❯ packages/frontend/.lintstagedrc.json — 1 file
↓ *.js — no files [SKIPPED]
❯ *.{json,md} — 1 file
⠹ prettier --write
↓ packages/backend/.lintstagedrc.json — 2 files
❯ *.js — 2 files
⠼ eslint --fix
↓ *.{json,md} — no files [SKIPPED]
◼ Applying modifications from tasks...
◼ Cleaning up temporary files...
```
<details>
<summary>See asciinema video</summary>
[![asciicast](https://asciinema.org/a/199934.svg)](https://asciinema.org/a/199934)
</details>
## Why

@@ -120,2 +142,4 @@

You can also place multiple configuration files in different directories inside a project. For a given staged file, the closest configuration file will always be used. See ["How to use `lint-staged` in a multi-package monorepo?"](#how-to-use-lint-staged-in-a-multi-package-monorepo) for more info and an example.
#### `package.json` example:

@@ -649,9 +673,29 @@

Starting with v5.0, `lint-staged` automatically resolves the git root **without any** additional configuration. You configure `lint-staged` as you normally would if your project root and git root were the same directory.
Install _lint-staged_ on the monorepo root level, and add separate configuration files in each package. When running, _lint-staged_ will always use the configuration closest to a staged file, so having separate configuration files makes sure linters do not "leak" into other packages.
If you wish to use `lint-staged` in a multi package monorepo, it is recommended to install [`husky`](https://github.com/typicode/husky) in the root package.json.
[`lerna`](https://github.com/lerna/lerna) can be used to execute the `precommit` script in all sub-packages.
For example, in a monorepo with `packages/frontend/.lintstagedrc.json` and `packages/backend/.lintstagedrc.json`, a staged file inside `packages/frontend/` will only match that configuration, and not the one in `packages/backend/`.
Example repo: [sudo-suhas/lint-staged-multi-pkg](https://github.com/sudo-suhas/lint-staged-multi-pkg).
**Note**: _lint-staged_ discovers the closest configuration to each staged file, even if that configuration doesn't include any matching globs. Given these example configurations:
```js
// ./.lintstagedrc.json
{ "*.md": "prettier --write" }
```
```js
// ./packages/frontend/.lintstagedrc.json
{ "*.js": "eslint --fix" }
```
When committing `./packages/frontend/README.md`, it **will not run** _prettier_, because the configuration in the `frontend/` directory is closer to the file and doesn't include it. You should treat all _lint-staged_ configuration files as isolated and separated from each other. You can always use JS files to "extend" configurations, for example:
```js
import baseConfig from '../.lintstagedrc.js'
export default {
...baseConfig,
'*.js': 'eslint --fix',
}
```
</details>

@@ -658,0 +702,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc