Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom configuration #62

Merged
merged 4 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 48 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
# simple-git-hooks


![](https://img.shields.io/badge/dependencies-zero-green) [![Tests](https://github.com/toplenboren/simple-git-hooks/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/toplenboren/simple-git-hooks/actions/workflows/tests.yml)
![](https://img.shields.io/badge/dependencies-zero-green) [![Tests](https://github.com/toplenboren/simple-git-hooks/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/toplenboren/simple-git-hooks/actions/workflows/tests.yml)

A tool that lets you easily manage git hooks

> The package was recently renamed from `simple-pre-commit`.

> The package was recently renamed from `simple-pre-commit`.

> See **Releases** for the `simple-pre-commit` documentation and changelog

- Zero dependency
- Small configuration (1 object in package.json)
- Lightweight:

| Package | Unpacked size | With deps |
| ------------- | ------------- | ------------- |
| husky v4 `4.3.8` | `53.5 kB` | `~1 mB` |
| husky v6 `6.0.0` | `6.86 kB` | `6.86 kB` |
| pre-commit `1.2.2` | `~80 kB` | `~850 kB` |
| **simple-git-hooks** `2.2.0` | `10.1 kB` | `10.1 kB` |
| Package | Unpacked size | With deps |
| ---------------------------- | ------------- | --------- |
| husky v4 `4.3.8` | `53.5 kB` | `~1 mB` |
| husky v6 `6.0.0` | `6.86 kB` | `6.86 kB` |
| pre-commit `1.2.2` | `~80 kB` | `~850 kB` |
| **simple-git-hooks** `2.2.0` | `10.1 kB` | `10.1 kB` |

### Who uses simple-git-hooks?

> The package is recommended by [`lint-staged`](https://github.com/okonet/lint-staged)
> The package is recommended by [`lint-staged`](https://github.com/okonet/lint-staged)

* [Autoprefixer](https://github.com/postcss/autoprefixer)
* [PostCSS](https://github.com/postcss/postcss.org)
* [Browserslist](https://github.com/browserslist/browserslist)
* [Nano ID](https://github.com/ai/nanoid)
* [Size Limit](https://github.com/ai/size-limit)
* [Storeon](https://github.com/storeon/storeon)
* [Directus](https://github.com/directus/directus)
* [Vercel/pkg](https://github.com/vercel/pkg)
* More, see [full list](https://github.com/toplenboren/simple-git-hooks/network/dependents?package_id=UGFja2FnZS0xOTk1ODMzMTA4)
- [Autoprefixer](https://github.com/postcss/autoprefixer)
- [PostCSS](https://github.com/postcss/postcss.org)
- [Browserslist](https://github.com/browserslist/browserslist)
- [Nano ID](https://github.com/ai/nanoid)
- [Size Limit](https://github.com/ai/size-limit)
- [Storeon](https://github.com/storeon/storeon)
- [Directus](https://github.com/directus/directus)
- [Vercel/pkg](https://github.com/vercel/pkg)
- More, see [full list](https://github.com/toplenboren/simple-git-hooks/network/dependents?package_id=UGFja2FnZS0xOTk1ODMzMTA4)

### What is a git hook?

A git hook is a command or script that is going to be run every time you perform a git action, like `git commit` or `git push`.

If the execution of a git hook fails, then the git action aborts.

For example, if you want to run `linter` on every commit to ensure code quality in your project, then you can create a `pre-commit` hook that would call `npx lint-staged`.
Expand All @@ -53,57 +51,56 @@ You can look up about git hooks on the [Pro Git book](https://git-scm.com/book/e

However, this package requires you to manually apply the changes to git hooks. If you update them often, this is probably not the best choice.

Also, this package allows you to set only one command per git hook.
Also, this package allows you to set only one command per git hook.

If you need multiple verbose commands per git hook, flexible configuration or automatic update of git hooks, please check out the other packages:

* [Lefthook](https://github.com/Arkweid/lefthook)
* [husky](https://github.com/typicode/husky)
* [pre-commit](https://github.com/pre-commit/pre-commit)

- [Lefthook](https://github.com/Arkweid/lefthook)
- [husky](https://github.com/typicode/husky)
- [pre-commit](https://github.com/pre-commit/pre-commit)

## Usage

### Add simple-git-hooks to the project

1. Install simple-git-hooks as a dev dependency:

```sh
npm install simple-git-hooks --save-dev
```

2. Add `simple-git-hooks` to your `package.json`. Fill it with git hooks and the corresponding commands.

For example:
For example:

```jsonc
{
"simple-git-hooks": {
"pre-commit": "npx lint-staged",
"pre-push": "cd ../../ && npm run format",
```jsonc
{
"simple-git-hooks": {
"pre-commit": "npx lint-staged",
"pre-push": "cd ../../ && npm run format",

// All unused hooks will be removed automatically by default
// but you can use the `preserveUnused` option like following to prevent this behavior
// All unused hooks will be removed automatically by default
// but you can use the `preserveUnused` option like following to prevent this behavior

// if you'd prefer preserve all unused hooks
"preserveUnused": true,
// if you'd prefer preserve all unused hooks
"preserveUnused": true,

// if you'd prefer preserve specific unused hooks
"preserveUnused": ["commit-msg"]
}
}
```
// if you'd prefer preserve specific unused hooks
"preserveUnused": ["commit-msg"]
}
}
```

This configuration is going to run all linters on every `commit` and formatter on `push`.

This configuration is going to run all linters on every `commit` and formatter on `push`.

> There are more ways to configure the package. Check out [Additional configuration options](#additional-configuration-options).

3. Run the CLI script to update the git hooks with the commands from the config:

```sh
npx simple-git-hooks
```

Now all the git hooks are created.

### Update git hooks command
Expand All @@ -116,7 +113,6 @@ Note for **yarn2** users: Please run `yarn dlx simple-git-hooks` instead of the

Note that you should manually run `npx simple-git-hooks` **every time you change a command**.


### Additional configuration options

You can also add a `.simple-git-hooks.cjs`, `.simple-git-hooks.js`, `simple-git-hooks.cjs`, `simple-git-hooks.js`, `.simple-git-hooks.json` or `simple-git-hooks.json` file to the project and write the configuration inside it.
Expand All @@ -128,8 +124,8 @@ This way `simple-git-hooks` configuration in `package.json` will not take effect
```js
module.exports = {
"pre-commit": "npx lint-staged",
"pre-push": "cd ../../ && npm run format"
}
"pre-push": "cd ../../ && npm run format",
};
```

`.simple-git-hooks.json` or `simple-git-hooks.json` should look like the following.
Expand All @@ -141,6 +137,8 @@ module.exports = {
}
```

If you need to have multiple configuration files or just your-own configuration file, you install hooks manually from it by `npx simple-git-hooks ./my-config.js`.

### Uninstall simple-git-hooks

> Uninstallation will remove all the existing git hooks.
Expand All @@ -149,14 +147,13 @@ module.exports = {
npm uninstall simple-git-hooks
```


## Common issues

### When migrating from `husky` git hooks are not running

**Why is this happening?**

Husky might change the `core.gitHooks` value to `.husky`, this way, git hooks would search `.husky` directory instead of `.git/hooks/`.
Husky might change the `core.gitHooks` value to `.husky`, this way, git hooks would search `.husky` directory instead of `.git/hooks/`.

Read more on git configuration in [Git book](https://git-scm.com/docs/githooks)

Expand Down
4 changes: 4 additions & 0 deletions _tests/project_with_custom_configuration/git-hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
"pre-push": "exit 1",
"pre-commit": "exit 1"
}
7 changes: 7 additions & 0 deletions _tests/project_with_custom_configuration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "simple-pre-commit-test-package",
"version": "1.0.0",
"devDependencies": {
"simple-git-hooks": "1.0.0"
}
}
2 changes: 1 addition & 1 deletion cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
const {setHooksFromConfig} = require('./simple-git-hooks')

try {
setHooksFromConfig()
setHooksFromConfig(process.cwd(), process.argv)
console.log('[INFO] Successfully set all git hooks')
} catch (e) {
console.log('[ERROR], Was not able to set git hooks. Error: ' + e)
Expand Down
30 changes: 26 additions & 4 deletions simple-git-hooks.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const fs = require('fs')
const path = require('path');
const path = require('path')

const VALID_GIT_HOOKS = [
'applypatch-msg',
Expand Down Expand Up @@ -131,9 +131,11 @@ function checkSimpleGitHooksInDependencies(projectRootPath) {
/**
* Parses the config and sets git hooks
* @param {string} projectRootPath
* @param {string[]} [argv]
*/
function setHooksFromConfig(projectRootPath=process.cwd()) {
const config = _getConfig(projectRootPath)
function setHooksFromConfig(projectRootPath=process.cwd(), argv=process.argv) {
const customConfigPath = _getCustomConfigPath(argv)
const config = _getConfig(projectRootPath, customConfigPath)

if (!config) {
throw('[ERROR] Config was not found! Please add `.simple-git-hooks.js` or `simple-git-hooks.js` or `.simple-git-hooks.json` or `simple-git-hooks.json` or `simple-git-hooks` entry in package.json.\r\nCheck README for details')
Expand Down Expand Up @@ -222,16 +224,31 @@ function _getPackageJson(projectPath = process.cwd()) {
return { packageJsonContent: JSON.parse(packageJsonDataRaw), packageJsonPath: targetPackageJson }
}

/**
* Takes the first argument from current process argv and returns it
* Returns empty string when argument wasn't passed
* @param {string[]} [argv]
* @returns {string}
*/
function _getCustomConfigPath(argv=[]) {
const cmdIdx = argv.findIndex(val => val === 'simple-git-hooks')

if (cmdIdx === -1) return ''

return argv[cmdIdx + 1] || ''
}

/**
* Gets user-set command either from sources
* First try to get command from .simple-pre-commit.json
* If not found -> try to get command from package.json
* @param {string} projectRootPath
* @param {string} [configFileName]
* @throws TypeError if projectRootPath is not string
* @return {{string: string} | undefined}
* @private
*/
function _getConfig(projectRootPath) {
function _getConfig(projectRootPath, configFileName='') {
if (typeof projectRootPath !== 'string') {
throw TypeError("Check project root path! Expected a string, but got " + typeof projectRootPath)
}
Expand All @@ -247,6 +264,11 @@ function _getConfig(projectRootPath) {
() => _getConfigFromPackageJson(projectRootPath),
]

// if user pass his-own config path prepend custom path before the default ones
if (configFileName) {
sources.unshift(() => _getConfigFromFile(projectRootPath, configFileName))
}

for (let executeSource of sources) {
let config = executeSource()
if (config && _validateHooks(config)) {
Expand Down
11 changes: 11 additions & 0 deletions simple-git-hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const projectWithConfigurationInAlternativeSeparateJsPath = path.normalize(path.
const projectWithConfigurationInSeparateJsonPath = path.normalize(path.join(testsFolder, 'project_with_configuration_in_separate_json'))
const projectWithConfigurationInAlternativeSeparateJsonPath = path.normalize(path.join(testsFolder, 'project_with_configuration_in_alternative_separate_json'))
const projectWithUnusedConfigurationInPackageJsonPath = path.normalize(path.join(testsFolder, 'project_with_unused_configuration_in_package_json'))
const projectWithCustomConfigurationFilePath = path.normalize(path.join(testsFolder, 'project_with_custom_configuration'))

// Incorrect configurations

Expand Down Expand Up @@ -260,3 +261,13 @@ test('creates git hooks and removes unused but preserves specific git hooks', ()

removeGitHooksFolder(projectWithUnusedConfigurationInPackageJsonPath)
})

test('creates git hooks and removes unused but preserves specific git hooks', () => {
createGitHooksFolder(projectWithCustomConfigurationFilePath)

spc.setHooksFromConfig(projectWithCustomConfigurationFilePath, ['npx', 'simple-git-hooks', './git-hooks.js'])
const installedHooks = getInstalledGitHooks(path.normalize(path.join(projectWithCustomConfigurationFilePath, '.git', 'hooks')))
expect(JSON.stringify(installedHooks)).toBe(JSON.stringify({'pre-commit':`#!/bin/sh\nexit 1`, 'pre-push':`#!/bin/sh\nexit 1`}))

removeGitHooksFolder(projectWithCustomConfigurationFilePath)
})