Skip to content

Commit

Permalink
Merge pull request #62 from lamartire/feat-multiple-configurations
Browse files Browse the repository at this point in the history
feat: custom configuration
  • Loading branch information
toplenboren committed Oct 27, 2021
2 parents 1333537 + 7ae0943 commit bad8802
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 56 deletions.
99 changes: 48 additions & 51 deletions README.md
@@ -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
@@ -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
@@ -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
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
@@ -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
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)
})

0 comments on commit bad8802

Please sign in to comment.