Rush and Commitlint
Tags: rush, node, monorepo
Published: 2022-03-02 10:43:21

[Commitlint](https://commitlint.js.org/) ensures that the commit messages adhere to [conventional commits](conventionalcommits.org) convention. 

If you don't want to install it globally, there's a [Guide: Local setup](https://commitlint.js.org/#/guides-local-setup) procedure, which... is **not compatible with Rush**, because we are [not supposed](https://rushjs.io/pages/developer/new_developer/#a-couple-caveats) to use package manager commands (e.g. `npm install`) and Rush will never install dependencies in repo's root.

 
## What now?
Since Rush supports git hooks, and allows us defining [custom commands with package dependencies](https://rushjs.io/pages/maintainer/custom_commands/), you can use the steps below to configure rush to commitlint the commit messages.
 
### Commitlint config
First, configure commitlint to use conventional config. 
Following the [local setup](https://commitlint.js.org/#/guides-local-setup) procedure, create a commitlint.config.js in the root of your repo. 
> If you are using the `echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js` command, make sure the file encoding is **UTF-8** and NOT UTF-16 (LE).

You may also define [rules](https://commitlint.js.org/#/reference-rules) that should be enforced. The `subject-case` in the rule below adds an additional 'sentence-case' value; the `2` parameter ensures that an error will be displayed if the rule checks fail.

#### commitlint.config.js
```json
module.exports = {
    extends: [
        "@commitlint/config-conventional"
    ],
    rules: {
        "subject-case":[2, 'always', ['lower-case','sentence-case']]
    }
} 
```

### Create autoinstaller

Create rush **autoinstaller** and add all packages and peer dependencies needed to run **commitlint** command. This will insure that all required packages are installed before the `commitlint` command is executed.

```powershell
rush init-autoinstaller --name rush-commitlint
cd common/autoinstallers/rush-commitlint

pnpm install @commitlint/config-conventional @commitlint/cli @types/node

# When you are finished, run this command to ensure that the
# common/autoinstallers/rush-commitizen/ppnpm-lock.yaml file is up to date
rush update-autoinstaller --name rush-commitlint
```

### Create custom command
`commitlint --edit` reads the last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG. 
Add the below command and parameter to the **command-line.json** file:

#### common\config\rush\command-line.json
```json
{
  "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
  "commands": [
    {
      "name": "commitlint",
      "commandKind": "global",
      "summary": "Used by the pre-commit Git hook. This command invokes commitlint to ensure that the commit messages meet the conventional commit format",
      "safeForSimultaneousRushProcesses": true,
      "autoinstallerName": "rush-commitlint",
      "shellCommand": "commitlint"
    }
  ],
  "parameters": [
    {
      "parameterKind": "string",
      "argumentName": "MESSAGE",
      "longName": "--edit",
      "description": "",
      "associatedCommands": [
        "commitlint"
      ]
    }
  ]
}
```
The command is using **rush-commitlint autoinstaller** created in the previous step, to ensure the commitlint module and peer dependecies are installed.
When executing `rush commitlint --edit $arg` rush will invoke the `shellCommand` with the provided parameters: `commitlint --edit $arg`.

### Add git hook
In order to lint the commit message, use **commit-msg** hook that calls the rush custom command created above.
> The commit-msg hook takes **one parameter**, which is the **path (.git/COMMIT_EDITMSG)** to a temporary file that contains the commit message written by the developer. The hook can also be used to refuse the commit after inspecting the message file. If this script exits non-zero, Git aborts the commit process.

Create a **commit-msg** file:

#### common\git-hooks\commit-msg
```powershell
#!/bin/sh
node common/scripts/install-run-rush.js commitlint --edit $1
```

Rush supports [git hooks](https://rushjs.io/pages/maintainer/git_hooks/) natively, and will copy the **common\git-hooks\commit-msg** to **.git\hooks\commit-msg** during `rush install`.
```powershell
rush install
```

Next time you run `git commit`, Git will find your script and invoke it.
Remember that the first time the `rush commitlint` is invoked, rush will execute a number of additional steps to install the dependencies. The next time, however, you will see no delay.

### Rush commit messages
Did you define rush commit messages in **rush.json**? Make sure they conform to the **commitlint rules**.

#### rush.json 
```
//...
  "gitPolicy": {
    "versionBumpCommitMessage": "chore: Bump versions [skip ci]",
    "changeLogUpdateCommitMessage": "chore: Update changelogs [skip ci]",
//...
``` 

## Source Code
You may find the source code on [GitHub](https://github.com/kkazala/rush-conventionalcommits/tree/11804308e03fb79f7fbe565ae3fceb66be6a34fe).