Run commands against files staged in git, ignoring unstaged changes and untracked files.
- 🧵 Lint new changes before commit.
- 🧪 Test changes in isolation before commit.
- ✨ ???
- Unstaged changes and untracked files are hidden in a git stash. This inludes unstaged deletions, which are temporarily restored.
- User-configured tasks are run, with staged files passed in according to configuration.
- Any changes made by tasks are added to the git index. This includes additions and deletions.
- The stashed changes are restored.
- If any step fails, the initial state is fully restored.
Install from npm, using your preferred package manager:
npm install --save-dev exec-stagedIf an exec-staged configuration file is present, it will be loaded by the executable:
npx exec-stagedIf no configuration is present, the executable can still run tasks passed as arguments:
npx exec-staged "npm test"import { execStaged } from 'exec-staged';
const cwd = process.cwd();
const tasks = [`npm test`];
const options = { quiet: true };
const result = await execStaged(cwd, tasks, options);
if (!result) {
throw new Error('exec-staged task failed');
}Husky is recommended for handling pre-commit hooks.
Install Husky:
npm install --save-dev huskyAdd a hook to .husky/pre-commit:
#!/bin/sh
npx exec-stagedRun husky:
npx huskyAdd a package.json script to run husky whenever your repository is cloned:
{
"scripts": {
"prepare": "husky"
}
}exec-staged will do nothing unless configured. See configuration information below.
exec-staged configuration consists of a list of commands to execute against the stage. Each command may be formatted as a plain string, or as an object containing additional attributes.
All Cosmiconfig-compatible configuration files are supported.
Here is an example configuration:
// exec-staged.config.ts
import type { ExecStagedUserConfig } from 'exec-staged/types';
const config: ExecStagedUserConfig = [
'knip',
'knip --production',
{ task: 'prettier --write $FILES', glob: '*.{js,ts,json,md}' },
];
export default config;Plain commands are run every time, as-is:
'knip --production'Commands which include the $FILES token are only run if staged files are found, and those files are interpolated into the command in place of the token.
'prettier --write $FILES'
// => prettier --write new_file.js modified_file.jsFile filtering can be customized.
To filter files by name, add a glob filter (defaults to '*'):
{ task: 'prettier --write $FILES', glob: '*.{js,ts,json,md}' }To filter files by git status, add a diff filter (defaults to 'AM'; see here):
{ task: 'prettier --write $FILES', diff: 'A' }Defining diff or glob on a task that does not include the $FILES token has no effect:
{ task: 'knip', diff: 'NO EFFECT', glob: 'NO EFFECT' }Before running any potentially destructive scripts, exec-staged stores all outstanding changes, including untracked files, in a backup stash. If any task fails, or if exec-staged is interrupted by an end-process signal (such as via Ctrl + C), the repository's original state is restored using this stash. Avoid running any tasks that interact with git, especially those that make commits or modify the stash.
If exec-staged fails to exit safely, such as due to power loss or if its process is killed via SIGKILL, its backup stash should still be present.
To verify, run git log and look for a stash with the message 💾 exec-staged backup stash. It should be the most recent stash. If it isn't, one of your tasks probably created a stash for some reason. This is very unlikely. Remove any such stashes before proceeding.
exec-staged also creates a short-lived temporary commit with the message 💾 exec-staged staged changes. If it's present, it can be removed with git reset --hard HEAD~1.
The following commands should return your repository to its original state:
git add -A
git reset --hard HEAD
git stash pop --indexTo prevent data loss, exec-staged will not run if a stash or commit from a previous run is present.
lint-staged: the inspiration forexec-staged.knip: a linter that analyzes interactions between files, which is outside of the designed scope oflint-staged.