Add Commitlint, Commitizen, Standard Version, and Husky to SvelteKit Project
Tags: javascript, git, svelte, devops
Published: 2022-04-10 06:39:22

> 4/19/2022 Updates. I published a second post according to this one: [Add lint-staged to SvelteKit Project](https://dev.to/davipon/add-lint-staged-to-sveltekit-project-108l)

Recently I spent a lot of time improving our internal SvelteKit project by aligning coding style & repo quality, especially the **commit message** and **versioning**. 

In this post, I will share how I added **Conventional Commits** and **SemVer** standards to our project and walk you through the setup of commitlint, commitlizen, standard-version, and husky.

## What are Conventional Commits & SemVer?

> Here, I copy the summary from each website. You can find more via the link.

### [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)

> The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history, which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages.

The commit message should be structured as follows:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]

/* Example */
feat(landing): add register button and authentication
```
### [SemVer - Semantic Versioning](https://semver.org/)

>Given a version number MAJOR.MINOR.PATCH, increment the:

>1. MAJOR version when you make incompatible API changes

>2. MINOR version when you add functionality in a backward-compatible manner

>3. PATCH version when you make backward-compatible bug fixes

>Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

## How to adopt in your existing SvelteKit (or any other JavaScript) Project?

> You can find the demo repo here: [conventional-commits-sveltekit](https://github.com/davipon/conventional-commits-sveltekit).

### [commitlint](https://github.com/conventional-changelog/commitlint)

First, install `commitlint`, a linting tool to check if your commit messages meet the conventional commit format:
```zsh
# Install commitlint cli and conventional config
pnpm add -D @commitlint/config-conventional @commitlint/cli

# Configure commitlint to use conventional config
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.cjs
```
> Use .cjs instead of .js because `"type": "module"` is set in `package.json` in SvelteKit project.

Now let's test if commitlint works (no prompt if linting passed):
![pnpm commitlint.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1649556297536/jWHQmDOx9.gif)

### 🐶 [husky](https://github.com/typicode/husky)

It's recommended to use `husky` with `commitlint`, a handy git hook helper, to check your commit message before making any `git commit`.

> Will add more git hooks later.

```zsh
# Install husky
pnpm add -D husky

# Active hooks
pnpx husky install

# Add hook
npx husky add .husky/commit-msg 'pnpx --no -- commitlint --edit $1'
```
Let's make a `git commit`:
![commitlint and husky.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1649556852955/X_W1vOodY.gif)
> `husky` would abort the commit if it didn't pass the commitlint

### [commitizen](https://github.com/commitizen/cz-cli)

It isn't very pleasant if we need to type the valid commit message every time.

`commitizen` is a CLI tool to help us fill out any required commit fields at commit time.
```zsh
# Install commitizen, 
# @commitlint/cz-commitlint (adapter), and
# inquirer (peer dependency of @commitlint/cz-commitlint)
pnpm add -D commitizen @commitlint/cz-commitlint inquirer
```
Simply use `pnpm cz` instead of `git commit` when committing. Or add a pnpm script to your `package.json`:
```
...
  "scripts": {
    "commit": "cz"
  }
```
Let's give it a try:
![commitizen.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1649557138590/VOOwid7_L.gif)
> We use `@commitlint/cz-commitlint` as a commitizen adapter to ensure the commit adheres to the commit convention configured in `commitlint.config.cjs`.

### [standard-version](https://github.com/conventional-changelog/standard-version)

We've configured commitlint, husky, and commitizen to easily create and validate our commit message. 

Now, let's see how we can automate versioning and changelog generation using `standard-version`.
```zsh
# Install standard-version
pnpm add -D standard-version
```
Add pnpm script to your `package.json`.
```
...
  "scripts": {
    "release": "standard-version --no-verify" 
    // use --no-verify to skip git hooks we'll introduce later
  }
```
Here is how `standard-version` works:
>1. Retrieve the current version of your repository by looking at `packageFiles`, falling back to the last `git tag`.
>2. `bump` the version in `bumpFiles` based on your commits.
>3. Generates a `changelog` based on your commits (uses conventional-changelog under the hood).
>4. Creates a new `commit` including your `bumpFiles` and updated `CHANGELOG.md`.
>5. Creates a new `tag` with the new version number.

Let's try:
![standard-version.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1649557920166/OhVvHEJgL.gif)
By default, `CHANGELOG.md` would only show commits belonging to `feat` or `fix`, but I like to have every commit in my changelog and add emojis.

We can customize it by creating `.versionrc` under your project root folder. Here is my setting:
```JSON
{
  "types": [
    {
      "type": "feat",
      "section": "✨ Features"
    },
    {
      "type": "fix",
      "section": "🐛 Bug Fixes"
    },
    {
      "type": "chore",
      "hidden": false,
      "section": "🚚 Chores"
    },
    {
      "type": "docs",
      "hidden": false,
      "section": "📝 Documentation"
    },
    {
      "type": "style",
      "hidden": false,
      "section": "💄 Styling"
    },
    {
      "type": "refactor",
      "hidden": false,
      "section": "♻️ Code Refactoring"
    },
    {
      "type": "perf",
      "hidden": false,
      "section": "⚡️ Performance Improvements"
    },
    {
      "type": "test",
      "hidden": false,
      "section": "✅ Testing"
    }
  ]
}

```
> You can see more options in [Conventional Changelog Configuration Spec](https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.1.0/README.md).

### 🐶 husky - more git hooks

Before we push commits and tags, we can consider adding more git hooks to improve our code quality.
Like formatting, linting, and testing.

Maybe you've already set "Format On Save", run linting, or unit testing in watch mode, but it's a personal preference. To ensure a better code quality and efficient collaboration, we can add git hooks like `pre-commit`, and `pre-push` to format, lint, and test before opening a PR.

Below we'll take the SvelteKit skeleton project as an example. I choose Typescript, Prettier, ESLint, and Playwright.

First, add formatting and linting at `pre-commit` stage:

```zsh
# Add pre-commit hook
npx husky add .husky/pre-commit 'pnpm format && pnpm lint && git add .'
```
![pre-commit.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1649566562420/Zz47J9Z2l.gif)
Second (optional), add `svelte-check` at `pre-push` stage:
```zsh
# Add pre-push hook
npx husky add .husky/pre-push 'pnpm check'
```
![pre-push.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1649567132044/vyeDO5lHc.gif)
> I didn't put `playwright test` here because I think it's better to do the e2e test in CI. 
If you're using a test runner like jest, uvu, or vitest, you may consider running unit/ component tests in the `pre-commit` or `pre-push` stage.

#### Caveats

`git hooks` is not a silver bullet. Your colleague can still "bypass" it in specific ways, but it'd be good to use and align with your team.

> Some argue that "Run linting and testing in `git hooks` is just a drag on productivity and can't force anything. Why do this if we've already had them in CI? It's even useless if your tests didn't run in a production-like environment." 

That might be true and depends on your team and your focus. 

We use it anyway while thinking about the "Shift-Left" approach in DevOps.

## Wrap up

Thank you for your reading! 🙌

We've walked through the steps to adopt Conventional Commits and SemVer in your project.
Also, there are lots of different [commitizen adapters](https://dev.to/sohandutta/make-everyone-in-your-project-write-beautiful-commit-messages-using-commitlint-and-commitizen-1amn) and [commitlint configurations](https://dev.to/sohandutta/make-everyone-in-your-project-write-beautiful-commit-messages-using-commitlint-and-commitizen-1amn) you can try. 

I tried [gitmoji](https://gitmoji.dev/) and followed [Make everyone in your project write beautiful commit messages using commitlint and commitizen](https://dev.to/sohandutta/make-everyone-in-your-project-write-beautiful-commit-messages-using-commitlint-and-commitizen-1amn) by @sohandutta, but end up using the setting in this article because it's not compatible to changelog generation of `standard-version` (Ref: [Issue #859](https://github.com/conventional-changelog/standard-version/issues/859))

My team and I are still working on our first CI/ CD pipeline, and this article is our first step to improve our workflow.

Please kindly share your thoughts and experience. Love to learn from you!

You can find me on Twitter: [@davipon](https://twitter.com/davipon)

### Useful resources:

[Continuous Delivery by Dave Farley](https://www.youtube.com/c/ContinuousDelivery/featured) (Full of wisdom)

[Master the Code Review](https://curtiseinsmann.gumroad.com/l/code-review-course) by  Curtis Einsmann (I haven't finished yet, but the content is so great that I need to share it here!)