Skip to content

Instantly share code, notes, and snippets.

@btoo
Last active June 17, 2024 07:55
Show Gist options
  • Save btoo/ee33b4490e3d2efbb2c5962871e12b9b to your computer and use it in GitHub Desktop.
Save btoo/ee33b4490e3d2efbb2c5962871e12b9b to your computer and use it in GitHub Desktop.
husky pre-commit hook for handling ONLY staged files that will prevent commit if there are linting errors and auto-fix them
/**
* @see {@tutorial https://github.com/typicode/husky}
* _Requires Node >= 10 and Git >= 2.13.0._
*/
module.exports = {
hooks: {
/**
* @see {@tutorial https://stackoverflow.com/a/15656652/3942699}
*
* ___only works with files staged in git___
* because doing on the entire repo is overkill atm
*
* if linting succeeds, proceed to committing the staged files.
* if linting fails, prevent the commit from happening and fix the auto-fixable errors
*/
'pre-commit': `
stagedFiles=$(git diff --diff-filter=d --cached --name-only);
if [ -n "$stagedFiles" ]; then # at least one file is staged
{
npx eslint $stagedFiles
} || {
npx eslint --fix $stagedFiles && exit 1
}
fi
`,
},
};
/**
* performs linting (eslint) and formatting (prettier) on __staged files only__
*
* (probably as a pre-commit hook @see .huskyrc.js)
*/
export default async () => {
const { default: chalk } = await import('chalk');
const { read, run } = await import('./utils');
const stagedFiles = (await read('git diff --diff-filter=d --cached --name-only'))
.split('\n')
.filter(Boolean);
if (stagedFiles.length) {
console.log(chalk.blueBright('\nLinting staged files with ESLint before committing...'));
/** eslint only works with js and ts for now */
const stagedFilesForEslint = stagedFiles.filter((f) => f.match(/\.(js|jsx|ts|tsx|vue|json)$/));
const stagedFilesForEslintStr = stagedFilesForEslint.join(' ');
/**
* a reusable function for formatting with Prettier.
* this function should be called after either:
* - linting succeeds or
* - linting fails and then lint --fix is attempted
*/
const formatStagedFilesWithPrettier = async () => {
try {
console.log(
chalk.blueBright('\nFormatting staged files with Prettier before committing...\n')
);
const stagedFilesStr = stagedFiles.join(' ');
await read(`npx prettier --list-different ${stagedFilesStr}`);
// nothing to format
} catch (resultOfCheckingToSeeIfAnythingNeedsToBeFormatted) {
const filesThatNeedToBeFormatted = (resultOfCheckingToSeeIfAnythingNeedsToBeFormatted.stdout as string)
.split('\n')
.filter(Boolean);
if (filesThatNeedToBeFormatted.length) {
try {
await read(`npx prettier --write ${filesThatNeedToBeFormatted.join(' ')}`);
} finally {
const { default: path } = await import('path');
console.log(chalk.redBright.bold('The following files needed formatting:'));
const cwd = process.cwd();
filesThatNeedToBeFormatted.forEach((f) =>
console.log(chalk.underline(path.join(cwd, f)))
);
console.log();
process.exit(1);
}
}
}
};
try {
stagedFilesForEslint.length && (await run(`npx eslint ${stagedFilesForEslintStr}`));
await formatStagedFilesWithPrettier();
} catch (lintingErr) {
// there are linting problems, so:
// 1. try to fix the linting problems with eslint --fix
// 2. try to format the code with prettier --write
// 3. prevent the commit from happening
console.log(chalk.blueBright('Attempting to `--fix` problems...'));
try {
stagedFilesForEslint.length && (await run(`npx eslint --fix ${stagedFilesForEslintStr}`));
await formatStagedFilesWithPrettier();
} finally {
process.exit(1);
}
}
} else {
console.log(chalk.blueBright('\nThere are no files staged in git to lint/format\n'));
}
};
export const LINT_MESSAGE = 'Lint and format staged files';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment