lint-staged
Advanced tools
Comparing version 3.0.3 to 4.0.0-beta-1
@@ -0,1 +1,12 @@ | ||
# 4.0.0-beta-1 | ||
- Implemented a complete git workflow with stash that allows patch commits. Closes #62 | ||
- Split code into smaller modules + added tests for them. Closes #68 | ||
- Support for different configs via https://github.com/davidtheclark/cosmiconfig. Closes #64 | ||
- Run separate linters concurrently by default | ||
- Added concurrent option to the config. Closes #63 | ||
- Switched to https://github.com/okonet/eslint-config-okonet | ||
- lint-staged now work from sub-directories. #65 by @TheWolfNL | ||
- Output both `stdout` and `stderr` in case of error. Closes #66 | ||
# 3.0.3 | ||
@@ -7,3 +18,3 @@ | ||
- Removed unused dependecies | ||
- Removed unused dependencies | ||
@@ -10,0 +21,0 @@ # 3.0.1 |
#!/usr/bin/env node | ||
require('./src/index') | ||
require('./src') |
{ | ||
"name": "lint-staged", | ||
"version": "3.0.3", | ||
"version": "4.0.0-beta-1", | ||
"description": "Lint files staged by git", | ||
@@ -15,15 +15,7 @@ "main": "index.js", | ||
"release": "npmpub", | ||
"test": "mocha --compilers js:babel-core/register ./test/*.spec.js", | ||
"test": "mocha --compilers js:babel-register ./test/*.spec.js", | ||
"deps": "npm-check -s", | ||
"deps:update": "npm-check -u" | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"eslint --fix", | ||
"git add" | ||
] | ||
}, | ||
"pre-commit": [ | ||
"pre-commit" | ||
], | ||
"pre-commit": "pre-commit", | ||
"repository": { | ||
@@ -54,7 +46,7 @@ "type": "git", | ||
"app-root-path": "^2.0.0", | ||
"execa": "^0.4.0", | ||
"cosmiconfig": "^1.1.0", | ||
"execa": "^0.5.0", | ||
"listr": "^0.6.0", | ||
"minimatch": "^3.0.0", | ||
"npm-which": "^3.0.1", | ||
"object-assign": "^4.1.0", | ||
"staged-git-files": "0.0.4", | ||
@@ -67,15 +59,21 @@ "which": "^1.2.11" | ||
"babel-preset-stage-0": "^6.5.0", | ||
"eslint": "^3.2.0", | ||
"babel-register": "^6.16.3", | ||
"eslint": "^3.7.1", | ||
"eslint-config-okonet": "^1.1.1", | ||
"eslint-config-standard": "^6.0.0", | ||
"eslint-plugin-import": "^1.14.0", | ||
"eslint-plugin-import": "^2.0.0", | ||
"eslint-plugin-promise": "^2.0.1", | ||
"eslint-plugin-standard": "^1.3.2", | ||
"eslint-plugin-standard": "^2.0.1", | ||
"expect": "^1.20.2", | ||
"fs-promise": "^0.5.0", | ||
"is-promise": "^2.1.0", | ||
"mocha": "^2.5.3", | ||
"jsonlint-cli": "^1.0.1", | ||
"mocha": "^3.1.0", | ||
"npm-check": "^5.2.2", | ||
"npmpub": "^3.1.0", | ||
"pre-commit": "^1.1.3", | ||
"rewire": "^2.5.1" | ||
"rewire": "^2.5.1", | ||
"tmp": "^0.0.29", | ||
"yaspeller": "^2.9.1" | ||
} | ||
} |
168
README.md
@@ -1,4 +0,4 @@ | ||
# lint-staged | ||
# lint-staged [![Build Status](https://travis-ci.org/okonet/lint-staged.svg?branch=master)](https://travis-ci.org/okonet/lint-staged) | ||
Run linters against staged git files and don't let :poop: slip into your code base! | ||
Run linters against staged git files and don't let :poop: slip into your code base! | ||
@@ -9,5 +9,5 @@ ## Why | ||
Linting makes more sense when running before commiting you code. By doing that you can ensure no errors are going into repository and enforce code style. But running a lint process on a whole project is slow and linting results can be irrelevant. Ultimately you want to lint only files that will be committed. | ||
Linting makes more sense when running before committing you code. By doing that you can ensure no errors are going into repository and enforce code style. But running a lint process on a whole project is slow and linting results can be irrelevant. Ultimately you only want to lint files that will be committed. | ||
This project contains a script that will run arbitary npm tasks against staged files, filtered by a spicified glob pattern. | ||
This project contains a script that will run arbitrary npm and shell tasks with a list of staged files as argument, filtered by a specified glob pattern. | ||
@@ -17,22 +17,31 @@ ## Installation & Setup | ||
1. `npm install --save-dev lint-staged` | ||
1. Install and setup your linters just like you would do normally. Add appropriate `.eslintrc` and `.stylelintrc` etc. configs (see [ESLint](http://eslint.org) and [Stylelint](http://stylelint.io/) docs if you need help here). | ||
1. Install and setup your linters just like you would do normally. Add appropriate `.eslintrc` and `.stylelintrc` , etc., configs (see [ESLint](http://eslint.org) and [Stylelint](http://stylelint.io/) docs if you need help here). | ||
1. Add `{ "lint-staged": "lint-staged" }` to `scripts` section of `package.json` | ||
1. Add `"lint-staged": { "*.js": "eslint" }` to `package.json` (see [configuration](#configuration)) | ||
1. Add `{ "lint-staged": "lint-staged" }` to `scripts` section of `package.json` | ||
1. `npm install --save-dev pre-commit` *. | ||
1. Add `"pre-commit": "lint-staged"` to `package.json` | ||
Now `git add` a few files and `npm run lint-staged` to lint them. | ||
I recommend using [pre-commit](https://github.com/observing/pre-commit) or [husky](https://github.com/typicode/husky) to manage git hooks but you can use whatever you want. | ||
I recommend using an awesome [pre-commit](https://github.com/observing/pre-commit) to run `lint-staged` as pre-commit hook: | ||
Now change a few files, `git add` some of them to your commit and try to `git commit` them. | ||
1. `npm install --save-dev pre-commit` | ||
1. Add `"pre-commit": "lint-staged"` to `package.json` | ||
See [examples](#examples) below. | ||
See complete examples below. | ||
## Configuration | ||
You can configure lint-staged by adding a `lint-staged` section to your `package.json`. It should | ||
be an object where each value is a command to run and key is a glob pattern to use for this | ||
command. This package uses [minimatch](https://github.com/isaacs/minimatch) for glob patterns. | ||
See its [documentation](https://github.com/isaacs/minimatch) if you have questions regarding glob patterns. | ||
Starting with v3.1 you can now use different ways of configuring it: | ||
* `lint-staged` object in your `package.json` | ||
* `.lintstagedrc` file in JSON or YML format | ||
* `lint-staged.config.js` file in JS format | ||
See [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) for more details on what formats are supported. | ||
Lint-staged supports simple and advanced config formats. | ||
### Simple config format | ||
Should be an object where each value is a command to run and its key is a glob pattern to use for this command. This package uses [minimatch](https://github.com/isaacs/minimatch) for glob patterns. | ||
#### `pacakge.json` example: | ||
```json | ||
@@ -49,45 +58,71 @@ { | ||
This config will execute `npm run my-task` with the list of currently staged files passed as argument. | ||
#### `.lintstagedrc` example | ||
So, considering you did `git add file1.ext file2.ext`, lint-staged will run following command: | ||
```json | ||
{ | ||
"*": "my-task" | ||
} | ||
``` | ||
`npm run my-task — file1.ext file2.ext` | ||
This config will execute `npm run my-task` with the list of currently staged files passed as arguments. | ||
So, considering you did `git add file1.ext file2.ext`, lint-staged will run the following command: | ||
`npm run my-task -- file1.ext file2.ext` | ||
### Advanced config format | ||
To set options and keep lint-staged extensible, advanced format can be used. This should hold linters object in `linters` property. | ||
## Options | ||
* `linters` — `Object` — keys (`String`) are glob patterns, values (`Array<String> | String`) are commands to execute. | ||
* `git-root` — Sets the relative path to the `.git` root. Useful when your `package.json` is located in a sub-directory. See [working from a subdirectory](#working-from-a-subdirectory) | ||
* `concurrent` — *true* — runs linters for each glob pattern simultaneously. If you don’t want this, you can set `concurrent: false` | ||
## What commands are supported? | ||
Supported are both local npm scripts (`npm run-script`), or any executables installed locally or globally via `npm` as well as any executable from your $PATH. | ||
> Using globally installed scripts is discouraged, since in this case lint-staged might not work for someone who doesn’t have it installed. | ||
> Using globally installed scripts is discouraged, since lint-staged may not work for someone who doesn’t have it installed. | ||
`lint-staged` is using [npm-which](https://github.com/timoxley/npm-which) to locate locally installed scripts, so you don't need to add `{ "eslint": "eslint" }` to the `scripts` section of your `pacakge.json`. So, for simplicity, you can write: | ||
`lint-staged` is using [npm-which](https://github.com/timoxley/npm-which) to locate locally installed scripts, so you don't need to add `{ "eslint": "eslint" }` to the `scripts` section of your `package.json`. So in your `.lintstagedrc` you can write: | ||
```json | ||
{ | ||
"scripts": { | ||
... | ||
}, | ||
"lint-staged": { | ||
"*.js": "eslint —fix" | ||
} | ||
"*.js": "eslint --fix" | ||
} | ||
``` | ||
You can pass arguments to your linter separated by space. See examples below. | ||
Pass arguments to your commands separated by space as you would do in the shell. See [examples](#examples) below. | ||
Starting from [v2.0.0](https://github.com/okonet/lint-staged/releases/tag/2.0.0) sequences of commands are supported. Pass an array of commands instead of a single one and they will run sequentially. This is useful for running auto-formatting tools like `eslint --fix` or `stylefmt` but can be used for any arbitrary sequences. | ||
Starting from [v2.0.0](https://github.com/okonet/lint-staged/releases/tag/2.0.0) sequences of commands are supported. Pass an array of commands instead of a single one and they will run sequentially. This is useful for running auto-formatting tools like `eslint --fix` or `stylefmt` but can be used for any arbitrary sequences. | ||
In case of `eslint --fix`, after the code is reformatted we want it to be added to the same commit. This can be easily done using following config: | ||
## Re-formatting the code | ||
Tools like ESLint or stylefmt can re-format your code according to an appropriate config by running `eslint --fix`. After the code is re-formatted, we want it to be added to the same commit. This can be done using following config: | ||
```json | ||
{ | ||
"scripts": { | ||
... | ||
}, | ||
"lint-staged": { | ||
"*.js": ["eslint --fix", "git add"] | ||
}, | ||
"*.js": ["eslint --fix", "git add"] | ||
} | ||
``` | ||
Starting from v3.1, lint-staged will stash you remaining changes (not added to the index) and restore them from stash afterwards. This allows you to create partial commits with hunks using `git add --patch`. | ||
## Working from a subdirectory | ||
If your `package.json` is located in a subdirectory of the git root directory, you can use `git-root` relative path to point there in order to make lint-staged work. | ||
```json | ||
{ | ||
"git-root": "../", | ||
"linters":{ | ||
"*": "my-task" | ||
} | ||
} | ||
``` | ||
## Examples | ||
### ESLint with default parameters for `*.js` and `*.jsx` running as a pre-commit hook | ||
All examples assuming you’ve already set up lint-staged and pre-commit in the `package.json` | ||
@@ -101,5 +136,2 @@ ```json | ||
}, | ||
"lint-staged": { | ||
"*.@(js|jsx)": "eslint" | ||
}, | ||
"pre-commit": "lint-staged" | ||
@@ -109,20 +141,22 @@ } | ||
### Automatically fix code style with `--fix` and add to commit | ||
*Note we don’t pass a path as an argument for the runners. This is important since lint-staged will do this for you. Please don’t reuse you tasks with paths from package.json.* | ||
### 1. ESLint with default parameters for `*.js` and `*.jsx` running as a pre-commit hook | ||
```json | ||
{ | ||
"name": "My project", | ||
"version": "0.1.0", | ||
"scripts": { | ||
"lint-staged": "lint-staged", | ||
}, | ||
"lint-staged": { | ||
"*.js": ["eslint --fix", "git add"] | ||
}, | ||
"pre-commit": "lint-staged" | ||
"*.@(js|jsx)": "eslint" | ||
} | ||
``` | ||
This will run `eslint --fix` and automatically add changes to the commit. Please note, that it doesn’t work well with committing hunks (`git add -p`). | ||
### 2. Automatically fix code style with `--fix` and add to commit | ||
```json | ||
{ | ||
"*.js": ["eslint --fix", "git add"] | ||
} | ||
``` | ||
This will run `eslint --fix` and automatically add changes to the commit. ~~Please note, that it doesn’t work well with committing hunks (`git add -p`).~~ | ||
### Stylelint for CSS with defaults and for SCSS with SCSS syntax | ||
@@ -132,12 +166,4 @@ | ||
{ | ||
"name": "My project", | ||
"version": "0.1.0", | ||
"scripts": { | ||
"lint-staged": "lint-staged", | ||
}, | ||
"lint-staged": { | ||
"*.css": "stylelint", | ||
"*.scss": "stylelint --syntax=scss" | ||
}, | ||
"pre-commit": "lint-staged" | ||
"*.css": "stylelint", | ||
"*.scss": "stylelint --syntax=scss" | ||
} | ||
@@ -150,16 +176,8 @@ ``` | ||
{ | ||
"name": "My project", | ||
"version": "0.1.0", | ||
"scripts": { | ||
"lint-staged": "lint-staged" | ||
}, | ||
"lint-staged": { | ||
"*.scss": [ | ||
"postcss --config "[path to your config]" --replace" | ||
"git add", | ||
"stylelint" | ||
] | ||
}, | ||
"pre-commit": "lint-staged" | ||
"*.scss": [ | ||
"postcss --config "[path to your config]" --replace", | ||
"stylelint", | ||
"git add" | ||
] | ||
} | ||
``` |
@@ -6,3 +6,3 @@ 'use strict' | ||
module.exports = function findBin (cmd, paths, config) { | ||
module.exports = function findBin(cmd, paths, packageJson) { | ||
const defaultArgs = ['--'].concat(paths) | ||
@@ -13,3 +13,3 @@ /* | ||
*/ | ||
if (config.scripts && config.scripts[cmd] !== undefined) { | ||
if (packageJson.scripts && packageJson.scripts[cmd] !== undefined) { | ||
// Support for scripts from package.json | ||
@@ -42,3 +42,3 @@ return { | ||
let bin = parts[0] | ||
let args = parts.splice(1) | ||
const args = parts.splice(1) | ||
@@ -48,8 +48,8 @@ try { | ||
bin = npmWhich.sync(bin) | ||
} catch (e) { | ||
} catch (err) { | ||
/* If this fails, try to resolve binary in $PATH */ | ||
try { | ||
bin = which.sync(bin) | ||
} catch (e) { | ||
throw new Error(`${bin} could not be found. Try \`npm install ${bin}\`.`) | ||
} catch (error) { | ||
throw new Error(`${ bin } could not be found. Try \`npm install ${ bin }\`.`) | ||
} | ||
@@ -56,0 +56,0 @@ } |
/* global process */ | ||
/* eslint no-console: 0 */ | ||
'use strict' | ||
@@ -6,39 +8,64 @@ | ||
const path = require('path') | ||
const sgf = require('staged-git-files') | ||
const minimatch = require('minimatch') | ||
const assign = require('object-assign') | ||
const appRoot = require('app-root-path') | ||
const config = require(appRoot.resolve('package.json')) | ||
const runScript = require('./runScript') | ||
const Listr = require('listr') | ||
const cosmiconfig = require('cosmiconfig') | ||
const defaultLinters = {} | ||
const customLinters = config['lint-staged'] | ||
const linters = assign(defaultLinters, customLinters) | ||
const packageJson = require(appRoot.resolve('package.json')) // eslint-disable-line | ||
const runScript = require('./runScript') | ||
const resolvePaths = require('./resolvePaths') | ||
const generateTasks = require('./generateTasks') | ||
const git = require('./gitWorkflow') | ||
sgf('ACM', function (err, results) { | ||
if (err) { | ||
console.error(err) | ||
} | ||
const filePaths = results.map(file => file.filename) | ||
const tasks = Object.keys(linters).map(key => { | ||
const linter = linters[key] | ||
const fileList = filePaths.filter(minimatch.filter(key, { matchBase: true })) | ||
if (fileList.length) { | ||
return { | ||
title: `Running tasks for ${key}`, | ||
task: () => { | ||
return new Listr(runScript(linter, fileList, config)) | ||
} | ||
} | ||
cosmiconfig(packageJson.name, { | ||
rc: '.lintstagedrc' | ||
}) | ||
.then((result) => { | ||
// result.config is the parsed configuration object | ||
// result.filepath is the path to the config file that was found | ||
const config = result.config | ||
const concurrent = config.concurrent || true | ||
// If git-root is defined -> set git root as sgf's cwd | ||
if ('git-root' in config) { | ||
sgf.cwd = path.resolve(config['git-root']) | ||
} | ||
}).filter(task => typeof task !== 'undefined') // Filter undefined values | ||
if (tasks.length) { | ||
new Listr(tasks).run().catch(err => { | ||
console.error(err) | ||
process.exit(1) | ||
sgf('ACM', (err, files) => { | ||
if (err) { | ||
console.error(err) | ||
} | ||
const tasks = generateTasks(config, resolvePaths(files)) | ||
.map(task => ({ | ||
title: `Running tasks for ${ task.pattern }`, | ||
task: () => new Listr(runScript(task.commands, task.fileList, packageJson)) | ||
})) | ||
if (tasks.length) { | ||
git.gitStashSave() | ||
.then(() => new Listr(tasks, { concurrent }) | ||
.run() | ||
.then(() => git.gitStashPop()) | ||
.catch((error) => { | ||
git.gitStashPop() | ||
.then(() => { | ||
console.error(error) | ||
process.exit(1) | ||
}) | ||
}) | ||
) | ||
} | ||
}) | ||
} | ||
}) | ||
}) | ||
.catch((parsingError) => { | ||
console.error(`Could not parse ${ packageJson.name } config. | ||
Make sure you have created it. See ${ packageJson.homepage }. | ||
${ parsingError } | ||
`) | ||
process.exit(1) | ||
}) | ||
@@ -6,31 +6,30 @@ 'use strict' | ||
module.exports = function runScript (linters, pathsToLint, config) { | ||
const lintersArray = Array.isArray(linters) ? linters : [linters] | ||
return lintersArray.map(linter => { | ||
return { | ||
title: linter, | ||
task: () => { | ||
try { | ||
const res = findBin(linter, pathsToLint, config) | ||
return new Promise((resolve, reject) => { | ||
execa(res.bin, res.args) | ||
.then(() => { | ||
resolve(`${linter} passed!`) | ||
}) | ||
.catch(err => { | ||
reject(` | ||
🚨 ${linter} found some errors. Please fix them and try committing again. | ||
module.exports = function runScript(commands, pathsToLint, packageJson) { | ||
const lintersArray = Array.isArray(commands) ? commands : [commands] | ||
return lintersArray.map(linter => ({ | ||
title: linter, | ||
task: () => { | ||
try { | ||
const res = findBin(linter, pathsToLint, packageJson) | ||
return new Promise((resolve, reject) => { | ||
execa(res.bin, res.args) | ||
.then(() => { | ||
resolve(`${ linter } passed!`) | ||
}) | ||
.catch((err) => { | ||
reject(` | ||
🚨 ${ linter } found some errors. Please fix them and try committing again. | ||
${err.stdout} | ||
${ err.stderr } | ||
${ err.stdout } | ||
` | ||
) | ||
}) | ||
}) | ||
} catch (err) { | ||
throw new Error(`${linter} not found. Try 'npm install ${linter}'`) | ||
} | ||
) | ||
}) | ||
}) | ||
} catch (err) { | ||
throw new Error(`${ linter } not found. Try 'npm install ${ linter }'`) | ||
} | ||
} | ||
}) | ||
})) | ||
} | ||
@@ -1,5 +0,6 @@ | ||
/* eslint no-unused-expressions: 0 */ | ||
/* eslint no-underscore-dangle: 0 */ | ||
import expect from 'expect' | ||
import rewire from 'rewire' | ||
const findBin = rewire('../src/findBin') | ||
@@ -16,4 +17,4 @@ const packageJSON = { | ||
const npmWichMockBad = { | ||
sync: path => { | ||
throw new Error('not found: ' + path) | ||
sync: (path) => { | ||
throw new Error(`not found: ${ path }`) | ||
} | ||
@@ -20,0 +21,0 @@ } |
@@ -0,8 +1,11 @@ | ||
/* eslint no-underscore-dangle: 0 */ | ||
import expect from 'expect' | ||
import isPromise from 'is-promise' | ||
import rewire from 'rewire' | ||
const runScript = rewire('../src/runScript') | ||
expect.extend({ | ||
toBeAPromise () { | ||
toBeAPromise() { | ||
expect.assert( | ||
@@ -26,3 +29,3 @@ isPromise(this.actual), | ||
describe('runScript', () => { | ||
afterEach(function () { | ||
afterEach(() => { | ||
expect.restoreSpies() | ||
@@ -29,0 +32,0 @@ }) |
module.exports = function (wallaby) { | ||
return { | ||
files: [ | ||
{ pattern: 'test/__fixtures__/*', instrument: false }, | ||
{ pattern: 'test/__fixtures__/**/*', instrument: false }, | ||
'src/*.js' | ||
@@ -5,0 +7,0 @@ ], |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
38224
26
660
177
21
1
4
1
+ Addedcosmiconfig@^1.1.0
+ Addedargparse@1.0.10(transitive)
+ Addedcosmiconfig@1.1.0(transitive)
+ Addedcross-spawn@4.0.2(transitive)
+ Addederror-ex@1.3.2(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedexeca@0.5.1(transitive)
+ Addedget-stream@2.3.1(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedis-arrayish@0.2.1(transitive)
+ Addedjs-yaml@3.14.1(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addednpm-run-path@2.0.2(transitive)
+ Addedos-homedir@1.0.2(transitive)
+ Addedp-finally@1.0.0(transitive)
+ Addedparse-json@2.2.0(transitive)
+ Addedpath-key@2.0.1(transitive)
+ Addedpinkie@2.0.4(transitive)
+ Addedpinkie-promise@2.0.1(transitive)
+ Addedrequire-from-string@1.2.1(transitive)
+ Addedsignal-exit@3.0.7(transitive)
+ Addedsprintf-js@1.0.3(transitive)
- Removedobject-assign@^4.1.0
- Removedcross-spawn-async@2.2.5(transitive)
- Removedexeca@0.4.0(transitive)
- Removednpm-run-path@1.0.0(transitive)
- Removedpath-key@1.0.0(transitive)
Updatedexeca@^0.5.0