|
| 1 | +# How to setup Conventional Commits in JavaScript project |
| 2 | + |
| 3 | +[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) is a specification for adding human and machine readable meaning to 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. |
| 4 | + |
| 5 | +<p align="center"> |
| 6 | + <img src="assets/commits.png" alt="Commits History" width="800px"> |
| 7 | +</p> |
| 8 | + |
| 9 | +## Basic Structure |
| 10 | + |
| 11 | +The commit message should be structured as follows: |
| 12 | + |
| 13 | +``` |
| 14 | +<type>[optional scope]: <description> |
| 15 | +
|
| 16 | +[optional body] |
| 17 | +
|
| 18 | +[optional footer(s)] |
| 19 | +``` |
| 20 | + |
| 21 | +Examples: |
| 22 | + |
| 23 | +```bash |
| 24 | +# Feature addition |
| 25 | +feat: add user authentication |
| 26 | + |
| 27 | +# Bug fix |
| 28 | +fix: resolve login redirect issue |
| 29 | + |
| 30 | +# Breaking change |
| 31 | +feat!: migrate to new API endpoint |
| 32 | + |
| 33 | +# Also breaking change |
| 34 | +feat: add new payment method |
| 35 | + |
| 36 | +BREAKING CHANGE: migrate to new payment gateway |
| 37 | + |
| 38 | +# With scope |
| 39 | +feat(auth): add password reset functionality |
| 40 | + |
| 41 | +# With body and footer |
| 42 | +fix: prevent racing of requests |
| 43 | + |
| 44 | +Introduce a request id and a reference to latest request. Dismiss |
| 45 | +incoming responses other than from latest request. |
| 46 | + |
| 47 | +Closes #123 |
| 48 | +``` |
| 49 | + |
| 50 | +## Common Types |
| 51 | + |
| 52 | +- **feat**: A new feature |
| 53 | +- **fix**: A bug fix |
| 54 | +- **docs**: Documentation changes |
| 55 | +- **style**: Code style changes (formatting, missing semicolons, etc.) |
| 56 | +- **refactor**: Code refactoring without changing functionality |
| 57 | +- **test**: Adding or updating tests |
| 58 | +- **chore**: Maintenance tasks, dependency updates |
| 59 | + |
| 60 | +## Benefits of Conventional Commits |
| 61 | + |
| 62 | +### 1. Automatic Version Determination |
| 63 | + |
| 64 | +Conventional commits enable automatic semantic versioning: |
| 65 | +- **feat**: triggers a minor version bump (1.0.0 → 1.1.0) |
| 66 | +- **fix**: triggers a patch version bump (1.0.0 → 1.0.1) |
| 67 | +- **BREAKING CHANGE**: triggers a major version bump (1.0.0 → 2.0.0) |
| 68 | + |
| 69 | +### 2. Automatic Changelog Generation |
| 70 | + |
| 71 | +Tools can automatically generate changelogs by parsing commit messages, grouping them by type, and extracting relevant information. |
| 72 | + |
| 73 | +<p align="center"> |
| 74 | + <img src="assets/changelog.png" alt="Changelog Example" width="800px"> |
| 75 | +</p> |
| 76 | + |
| 77 | +### 3. Better Project History |
| 78 | + |
| 79 | +Conventional commits create a more readable and searchable project history, making it easier to understand what changes were made and why. |
| 80 | + |
| 81 | +## Setting Up Full Automation |
| 82 | + |
| 83 | +To achieve complete automation of versioning and releases, you'll need several tools working together. Let's set them up step by step. |
| 84 | + |
| 85 | +### 1. Commitlint |
| 86 | + |
| 87 | +While not mandatory, [Commitlint](https://github.com/conventional-changelog/commitlint) helps enforce conventional commit standards by linting commit messages. |
| 88 | + |
| 89 | +First, install Commitlint and the Conventional Commits plugin: |
| 90 | + |
| 91 | +```bash |
| 92 | +npm install --save-dev @commitlint/cli @commitlint/config-conventional |
| 93 | +``` |
| 94 | + |
| 95 | +Then you need to set up a configuration. |
| 96 | + |
| 97 | +#### Simple Project Configuration |
| 98 | + |
| 99 | +For a basic project, create `.commitlintrc.json`: |
| 100 | + |
| 101 | +```json |
| 102 | +{ |
| 103 | + "extends": ["@commitlint/config-conventional"] |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +##### Git Hook |
| 108 | + |
| 109 | +Now you can set up commit validation during `git commit`, for example using [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks), though there are other similar tools available ([husky](https://github.com/typicode/husky), [pre-commit](https://github.com/observing/pre-commit), etc.). |
| 110 | + |
| 111 | +Install simple-git-hooks: |
| 112 | + |
| 113 | +```bash |
| 114 | +npm install --save-dev simple-git-hooks |
| 115 | +``` |
| 116 | + |
| 117 | +Create `.simple-git-hooks.json`: |
| 118 | + |
| 119 | +```json |
| 120 | +{ |
| 121 | + "commit-msg": "npx commitlint --edit \"$1\"" |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +Now run `npx simple-git-hooks` to activate the hooks and you're ready to go! |
| 126 | + |
| 127 | +##### GitHub Action |
| 128 | + |
| 129 | +You can also set up commit linting via GitHub Actions when commits are pushed to the repository. |
| 130 | + |
| 131 | +Create `.github/workflows/commit.yml`: |
| 132 | + |
| 133 | +```yaml |
| 134 | +name: Commit Validation |
| 135 | + |
| 136 | +on: |
| 137 | + pull_request: |
| 138 | + types: [opened, synchronize] |
| 139 | + |
| 140 | +jobs: |
| 141 | + commitlint: |
| 142 | + runs-on: ubuntu-latest |
| 143 | + steps: |
| 144 | + - uses: actions/checkout@v4 |
| 145 | + with: |
| 146 | + fetch-depth: 0 |
| 147 | + |
| 148 | + - name: Setup Node.js |
| 149 | + uses: actions/setup-node@v4 |
| 150 | + with: |
| 151 | + node-version: '18' |
| 152 | + cache: 'npm' |
| 153 | + |
| 154 | + - name: Install dependencies |
| 155 | + run: npm ci |
| 156 | + |
| 157 | + - name: Validate commit messages |
| 158 | + run: npx commitlint --from=HEAD~1 |
| 159 | +``` |
| 160 | +
|
| 161 | +#### Monorepo Project Configuration |
| 162 | +
|
| 163 | +In monorepos, it's common practice to use scopes in Conventional Commits that correspond to workspace names in the monorepo. This helps identify which workspace(s) are affected by each commit. For example: |
| 164 | +
|
| 165 | +```bash |
| 166 | +feat(store): add user authentication state |
| 167 | +fix(ui): resolve button styling issue |
| 168 | +docs(router): update routing configuration guide |
| 169 | +``` |
| 170 | +
|
| 171 | +You can use specialized packages to automatically get project scopes: |
| 172 | +
|
| 173 | +- [@commitlint/config-workspace-scopes](https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-workspace-scopes) - for generic workspaces |
| 174 | +- [@commitlint/config-pnpm-scopes](https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-pnpm-scopes) - for pnpm workspaces |
| 175 | +- [@commitlint/config-lerna-scopes](https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-lerna-scopes) - for Lerna projects |
| 176 | +
|
| 177 | +Let's look at an example for pnpm workspaces: |
| 178 | +
|
| 179 | +Install the packages: |
| 180 | +
|
| 181 | +```bash |
| 182 | +pnpm add -D @commitlint/cli @commitlint/config-conventional @commitlint/config-pnpm-scopes |
| 183 | +``` |
| 184 | + |
| 185 | +Create `.commitlintrc.js`: |
| 186 | + |
| 187 | +```javascript |
| 188 | +import scopes from '@commitlint/config-pnpm-scopes' |
| 189 | + |
| 190 | +export default { |
| 191 | + extends: ['@commitlint/config-conventional', '@commitlint/config-pnpm-scopes'], |
| 192 | + rules: { |
| 193 | + 'scope-enum': async (ctx) => { |
| 194 | + const scopeEnum = await scopes.rules['scope-enum'](ctx) |
| 195 | + return [ |
| 196 | + scopeEnum[0], |
| 197 | + scopeEnum[1], |
| 198 | + [ |
| 199 | + ...scopeEnum[2], |
| 200 | + 'deps', // for Dependabot or Renovate - dependency updates |
| 201 | + 'dev-deps', // for Dependabot or Renovate - dev dependency updates |
| 202 | + 'release' // for release commits |
| 203 | + ] |
| 204 | + ] |
| 205 | + } |
| 206 | + }, |
| 207 | + prompt: { |
| 208 | + settings: { |
| 209 | + enableMultipleScopes: true |
| 210 | + } |
| 211 | + } |
| 212 | +} |
| 213 | +``` |
| 214 | + |
| 215 | +### 2. Commitizen |
| 216 | + |
| 217 | +[Commitizen](https://github.com/commitizen/cz-cli) is an optional but very convenient tool that provides an interactive CLI for creating Conventional Commits. |
| 218 | + |
| 219 | +<p align="center"> |
| 220 | + <img src="assets/commitizen.gif" alt="Commitizen CLI Usage Example" width="800px"> |
| 221 | +</p> |
| 222 | + |
| 223 | +First, install Commitizen and the Commitlint adapter: |
| 224 | + |
| 225 | +```bash |
| 226 | +npm install --save-dev commitizen @commitlint/cz-commitlint |
| 227 | +``` |
| 228 | + |
| 229 | +Then create `.czrc`: |
| 230 | + |
| 231 | +```json |
| 232 | +{ |
| 233 | + "path": "@commitlint/cz-commitlint" |
| 234 | +} |
| 235 | +``` |
| 236 | + |
| 237 | +Add a script to `package.json`: |
| 238 | + |
| 239 | +```json |
| 240 | +{ |
| 241 | + "scripts": { |
| 242 | + "commit": "cz" |
| 243 | + } |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +Now you can use `npm run commit` instead of `git commit` for interactive commit creation. |
| 248 | + |
| 249 | +### 3. Simple Release Action |
| 250 | + |
| 251 | +The [simple-release-action](https://github.com/marketplace/actions/simple-release-action) is a comprehensive GitHub Action that automates version updates, changelog generation, and releases. The project is based on [simple-release](https://github.com/TrigenSoftware/simple-release) and [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog). |
| 252 | + |
| 253 | +> [!NOTE] |
| 254 | +> If you are not using GitHub, you can write your own custom CI script using the [@simple-release/core](https://github.com/TrigenSoftware/simple-release/tree/main/packages/core) package. |
| 255 | +
|
| 256 | +Key Features: |
| 257 | + |
| 258 | +- **First-class monorepo support** - A major differentiator from alternatives |
| 259 | +- **Automatic version bumping** based on Conventional Commits |
| 260 | +- **Changelog generation** with proper categorization |
| 261 | +- **Git tagging and GitHub releases** |
| 262 | +- **Configuration through adapters** for different project types |
| 263 | + |
| 264 | +Currently npm and pnpm projects with workspaces are supported ([npm adapter](https://github.com/TrigenSoftware/simple-release/blob/main/packages/npm#readme), [pnpm adapter](https://github.com/TrigenSoftware/simple-release/blob/main/packages/pnpm#readme)). |
| 265 | + |
| 266 | +Let's look at an example of configuring the action for a project with pnpm workspaces using [@simple-release/pnpm](https://github.com/TrigenSoftware/simple-release/blob/main/packages/pnpm#readme) addon: |
| 267 | + |
| 268 | +Create `.simple-release.json` config file in repository root: |
| 269 | + |
| 270 | +```json |
| 271 | +{ |
| 272 | + "project": ["@simple-release/pnpm#PnpmWorkspacesProject", { |
| 273 | + "mode": "fixed" |
| 274 | + }], |
| 275 | + "bump": { |
| 276 | + "extraScopes": ["deps"] |
| 277 | + } |
| 278 | +} |
| 279 | +``` |
| 280 | + |
| 281 | +*`extraScopes` is used to trigger version bump by commits like `fix(deps): ...` from Dependabot or Renovate.* |
| 282 | + |
| 283 | +Create `.github/workflows/release.yml`: |
| 284 | + |
| 285 | +```yaml |
| 286 | +name: Release |
| 287 | + |
| 288 | +on: |
| 289 | + issue_comment: |
| 290 | + types: [created, deleted] |
| 291 | + push: |
| 292 | + branches: |
| 293 | + - main |
| 294 | + |
| 295 | +jobs: |
| 296 | + check: |
| 297 | + runs-on: ubuntu-latest |
| 298 | + name: Context check |
| 299 | + outputs: |
| 300 | + continue: ${{ steps.check.outputs.continue }} |
| 301 | + workflow: ${{ steps.check.outputs.workflow }} |
| 302 | + steps: |
| 303 | + - name: Checkout the repository |
| 304 | + uses: actions/checkout@v4 |
| 305 | + - name: Context check |
| 306 | + id: check |
| 307 | + uses: TrigenSoftware/simple-release-action@v1 |
| 308 | + with: |
| 309 | + workflow: check |
| 310 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 311 | + |
| 312 | + pull-request: |
| 313 | + runs-on: ubuntu-latest |
| 314 | + name: Pull request |
| 315 | + needs: check |
| 316 | + if: needs.check.outputs.workflow == 'pull-request' |
| 317 | + steps: |
| 318 | + - name: Checkout the repository |
| 319 | + uses: actions/checkout@v4 |
| 320 | + - name: Create or update pull request |
| 321 | + uses: TrigenSoftware/simple-release-action@v1 |
| 322 | + with: |
| 323 | + workflow: pull-request |
| 324 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 325 | + |
| 326 | + release: |
| 327 | + runs-on: ubuntu-latest |
| 328 | + name: Release |
| 329 | + needs: check |
| 330 | + if: needs.check.outputs.workflow == 'release' |
| 331 | + steps: |
| 332 | + - name: Checkout the repository |
| 333 | + uses: actions/checkout@v4 |
| 334 | + - name: Install pnpm |
| 335 | + uses: pnpm/action-setup@v2 |
| 336 | + with: |
| 337 | + version: 10 |
| 338 | + - name: Install Node.js |
| 339 | + uses: actions/setup-node@v4 |
| 340 | + with: |
| 341 | + node-version: 18 |
| 342 | + cache: 'pnpm' |
| 343 | + registry-url: 'https://registry.npmjs.org' |
| 344 | + - name: Install dependencies |
| 345 | + run: pnpm install |
| 346 | + - name: Release |
| 347 | + uses: TrigenSoftware/simple-release-action@v1 |
| 348 | + with: |
| 349 | + workflow: release |
| 350 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 351 | + npm-token: ${{ secrets.NPM_TOKEN }} |
| 352 | +``` |
| 353 | +
|
| 354 | +This workflow consists of three jobs: |
| 355 | +- **check** - performs context check to determine if release changes are needed and which workflow to run |
| 356 | +- **pull-request** - creates or updates a pull request with release changes |
| 357 | +- **release** - performs the actual release when the PR is merged |
| 358 | +
|
| 359 | +Every time you push to the main branch, the action will create or update a pull request with a version bump and updated changelog if necessary. |
| 360 | +
|
| 361 | +<p align="center"> |
| 362 | + <img src="assets/pr.png" alt="Simple Release Action Pull Request Example" width="800px"> |
| 363 | +</p> |
| 364 | +
|
| 365 | +When the pull request is merged, it will automatically release the project. |
| 366 | +
|
| 367 | +<p align="center"> |
| 368 | + <img src="assets/release.png" alt="Simple Release Action Release Example" width="800px"> |
| 369 | +</p> |
| 370 | +
|
| 371 | +## Conclusion |
| 372 | +
|
| 373 | +There is also a recommendation to use squash merge for pull requests to keep the commit history in the main branch clean and well-structured. This ensures that each merge commit follows conventional commit standards and makes the project history more readable. |
| 374 | +
|
| 375 | +By implementing Conventional Commits with this toolchain, you'll have a fully automated release pipeline that maintains consistency, improves project history readability, and reduces manual overhead in version management. |
| 376 | +
|
| 377 | +If you want to see real projects using Conventional Commits and their configurations, here are a few examples: |
| 378 | +- [browserslist-useragent-regexp](https://github.com/browserslist/browserslist-useragent-regexp) |
| 379 | +- [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) |
| 380 | +- [simple-release](https://github.com/TrigenSoftware/simple-release) |
0 commit comments