An open API service indexing awesome lists of open source software.

https://github.com/everduin94/better-commits

A CLI for creating better commits following the conventional commits specification
https://github.com/everduin94/better-commits

clack cli commit-message git typescript zod

Last synced: 14 days ago
JSON representation

A CLI for creating better commits following the conventional commits specification

Awesome Lists containing this project

README

          

![bc-gradient](https://github.com/Everduin94/better-commits/assets/14320878/2f94e6ea-a40f-4f3e-b0b2-5cc7d83a9a7d)

[![better commits is enabled](https://img.shields.io/badge/better--commits-enabled?style=for-the-badge&logo=git&color=a6e3a1&logoColor=D9E0EE&labelColor=302D41)](https://github.com/Everduin94/better-commits)
[![downloads](https://img.shields.io/npm/dt/better-commits.svg?style=for-the-badge&logo=npm&color=74c7ec&logoColor=D9E0EE&labelColor=302D41)](https://npmx.dev/package/better-commits)
[![discord](https://img.shields.io/badge/discord-join--discord?style=for-the-badge&logo=discord&color=cba6f7&logoColor=D9E0EE&labelColor=302D41)](https://discord.gg/grHVnZwYup)


A CLI for writing better commits, following the conventional commits specification.

## โœจ Features

- Generate conventional commits through a series of prompts
- Highly configurable with sane defaults
- Infers ticket, commit scope, and commit-type from branch for consistent & fast commits
- Consistent branch creation with flexible workflow hooks via `better-branch`
- Interactive git status/add on commit
- Preview commit messages in color
- Support for git emojis per commit-type
- Configure globally or per repository
- Config validation and error messaging
- [Lightweight](https://bundlejs.com/?q=better-commits&treeshake=%5B*%5D) (17kb)

As a side-effect of formatting messages

- Auto populate PR title / body
- Automate semantic releases
- Automate changelogs
- Automatically link & close related tickets / issues

## ๐Ÿ“ฆ Installation

> Requires Node.js 20 or newer.

```sh
npm install -g better-commits
```

## ๐Ÿš€ Usage

To run the CLI in your terminal:

```sh
better-commits # Create a new commit
better-branch # Create a new branch
```

`better-commits` will prompt a series of questions. These prompts will build a commit message, which you can preview, before confirming the commit. - To better understand these prompts and their intention, read [Conventional Commits Summary](https://www.conventionalcommits.org/en/v1.0.0-beta.4/#summary)

Some of the values in these prompts will be inferred by your branch name and auto populated. You can adjust this in your `.better-commits.jsonc` (or `.better-commits.json`) configuration file.

For documentation on passing commit values to `better-commits` via the CLI, see [CLI Flags](#cli-flags).

> [!TIP]
> The `--no-interactive` flag, allows automated workflows or AI agents like OpenCode and Claude Code, to use better-commits to generate consistent commit messages using less tokens.
>
> Run `better-commits --help` / `better-branch --help` for more information.

## โš™๏ธ Configuration

### Global

Your first time running `better-commits`, a default config will be generated in your `$HOME` directory, named `.better-commits.jsonc` (formerly `.better-commits.json`)

- This config will be used if a repository-specific config cannot be found.

### Repository

To create a **repository-specific config**, navigate to the root of your project.

- Run `better-commits-init`
- This will create a default config named `.better-commits.jsonc`
- Properties such as `confirm_with_editor` and `overrides` will prefer the global config

### ๐Ÿ’ซ Properties

> [!NOTE]

> All properties are optional and can be removed from the config. They will be replaced by the default at run-time.
>
> - See `.better-commits.json` in this repository as an example

```jsonc
{
// Run interactive `git status` before composing a commit
"check_status": true,
"check_status_autocomplete": true,

/* COMMIT FIELDS */
"commit_type": {
"enable": true,

// Default selected type from options
"initial_value": "feat",

"max_items": 20,

// Infer type from the current branch name: user/TYPE/my-branch
"infer_type_from_branch": true,

// Include emoji in prompt label
"append_emoji_to_label": false,

// Include emoji from prompt label in commit message
"append_emoji_to_commit": false,

// "Start" | "After-Colon"
"emoji_commit_position": "Start",

"autocomplete": true,

"options": [
{
"value": "feat",
"label": "feat",
"hint": "A new feature",
"emoji": "๐ŸŒŸ",
"trailer": "Changelog: feature",
},
{
"value": "fix",
"label": "fix",
"hint": "A bug fix",
"emoji": "๐Ÿ›",
"trailer": "Changelog: fix",
},
{
"value": "docs",
"label": "docs",
"hint": "Documentation only changes",
"emoji": "๐Ÿ“š",
"trailer": "Changelog: documentation",
},
{
"value": "refactor",
"label": "refactor",
"hint": "A code change that neither fixes a bug nor adds a feature",
"emoji": "๐Ÿ”จ",
"trailer": "Changelog: refactor",
},
{
"value": "perf",
"label": "perf",
"hint": "A code change that improves performance",
"emoji": "๐Ÿš€",
"trailer": "Changelog: performance",
},
{
"value": "test",
"label": "test",
"hint": "Adding missing tests or correcting existing tests",
"emoji": "๐Ÿšจ",
"trailer": "Changelog: test",
},
{
"value": "build",
"label": "build",
"hint": "Changes that affect the build system or external dependencies",
"emoji": "๐Ÿšง",
"trailer": "Changelog: build",
},
{
"value": "ci",
"label": "ci",
"hint": "Changes to our CI configuration files and scripts",
"emoji": "๐Ÿค–",
"trailer": "Changelog: ci",
},
{
"value": "chore",
"label": "chore",
"hint": "Other changes that do not modify src or test files",
"emoji": "๐Ÿงน",
"trailer": "Changelog: chore",
},
{
"value": "",
"label": "none",
},
],
},

"commit_scope": {
"enable": true,

// If true, users can type a scope not listed in options
"custom_scope": false,

// Default selected scope from options
"initial_value": "app",

// Infer scope from the current branch name: user/type/ticket-SCOPE-my-branch
"infer_scope_from_branch": true,

"max_items": 20,
"autocomplete": true,
"options": [
{ "value": "app", "label": "app" },
{ "value": "shared", "label": "shared" },
{ "value": "server", "label": "server" },
{ "value": "tools", "label": "tools" },
{ "value": "", "label": "none" },
],
},

"check_ticket": {
// Infer ticket / issue from the branch name - user/type/TICKET-my-branch
"infer_ticket": true,

// Prompt for confirmation / edit before using an inferred ticket
"confirm_ticket": true,

// Add the ticket to the commit title - feat(app): TICKET my commit title
"add_to_title": true,

// Deprecated, prefer `prepend_hashtag`
"append_hashtag": false,

// "Never" | "Prompt" | "Always" - 12345 --> #12345
"prepend_hashtag": "Never",

// Wrap the ticket in the commit title: "" | "[]" | "()" | "{}"
"surround": "",

// "start" | "end" | "before-colon" | "beginning"
"title_position": "start",
},

"commit_title": {
// Includes total size of title + type + scope + ticket
"max_size": 70,
},

"commit_body": {
"enable": true,
"required": false,

// Split sentences into multiple lines automatically
"split_by_period": false,
},

"commit_footer": {
"enable": true,
"initial_value": [],

// "closes", "trailer", "breaking-change", "deprecated", "custom"
"options": ["closes", "trailer", "breaking-change", "deprecated", "custom"],
},

"breaking_change": {
// Adds `!` to the commit title when a breaking change is selected
"add_exclamation_to_title": true,
},

// Confirm / edit with $GIT_EDITOR or $EDITOR
"confirm_with_editor": false,

// Show a final confirmation prompt before running git commit
"confirm_commit": true,

// Reuse the last known value from a previous canceled or failed commit
"cache_last_value": true,

// Pretty-print the final commit preview before execution
"print_commit_output": true,

/* BRANCH FIELDS */
// Optional shell commands to run before / after creating branches or worktrees
"branch_pre_commands": [],
"branch_post_commands": [],
"worktree_pre_commands": [],
"worktree_post_commands": [],

"branch_user": {
"enable": true,
"required": false,

// "/" | "-" | "_" - user/feat/my-branch
"separator": "/",
},

"branch_type": {
"enable": true,
"separator": "/",
"autocomplete": true,
},

"branch_scope": {
"enable": true,
"separator": "-",
"autocomplete": true,
},

"branch_ticket": {
"enable": true,
"required": false,
"separator": "-",
},

"branch_version": {
"enable": false,
"required": false,
"separator": "/",
},

"branch_description": {
// Maximum length for the description segment of the branch name
"max_length": 70,

// Allowed values: "" | "/" | "-" | "_"
"separator": "",
},

// "branch" | "worktree"
"branch_action_default": "branch",

// Order of values in the final branch name
"branch_order": ["user", "version", "type", "ticket", "scope", "description"],

// Deprecated, prefer `worktrees.enable`
"enable_worktrees": true,

"worktrees": {
// If false, always create a branch instead of prompting for a worktree
"enable": true,

// Directory where worktrees are created
"base_path": "..",

// Available template variables include:
// {{repo_name}}, {{branch_description}}, {{user}}, {{type}}, {{scope}}, {{ticket}}, {{version}}
"folder_template": "{{repo_name}}-{{ticket}}-{{branch_description}}",
},

/* OTHER FIELDS */
"overrides": {
// Useful on Windows or for shells with different multiline behavior
"shell": "/bin/sh",
},
}
```

### ๐Ÿ”Ž Inference

`better-commits` will attempt to infer the ticket/issue, commit-type, and commit scope from your branch name. It will auto populate the corresponding field if found.

**Ticket / Issue-Number**

- If a `STRING-NUMBER` or `NUMBER` are at the start of the branch name or after a `/`

**Commit Type**

- If a type is at the start of the branch or is followed by a `/`

**Commit Scope**

- If a configured scope appears as its own branch word, separated by `/`, `-`, or `_`
- If scope appears after a ticket, the expected shape is `TICKET-SCOPE-description`
- Empty scope values and the `custom` scope option are ignored during inference

## ๐ŸŒณ Better Branch

Better branch is a secondary feature that works with better commits

- Supports consistent branch naming conventions
- Uses same type-list/prompt from your config
- Enables better-commits to infer type, scope, and ticket
- Caches your username for speedy branching
- Convenient worktree creation

To run the CLI in your terminal:

```sh
better-branch
```

### Worktree Support

`better-branch` will prompt for **Branch** or **Worktree**. The Worktree flow creates a folder/worktree from your **branch description** and a git branch inside with your **full branch name**.

> [!NOTE]

> Creating a worktree named `everduin94/feat/TAC-123-add-worktrees` with the native git command would create a nested folder for each `/`. `better-branch` removes the hassle by creating 1 folder while still using the full name for the branch.

> [!TIP]
> By default, `better-branch` will create **worktrees** as a sibling folder. To change this, see `worktrees.base_path`.

### Pre/Post Branch Checkout Hooks

Optionally configure pre and post checkout commands, for example:

- checkout and rebase main before branching
- run `npm install` before branching
- run `npm run dev` after branching

See _branch_pre_commands_ and _branch_post_commands_ in default config. (or _worktree_pre_commands_ and _worktree_post_commands_ for creating worktrees)

## ๐Ÿ’ก Tips & Tricks

### Building / Versioning

`better-commits` works with [Semantic Release](https://github.com/semantic-release/semantic-release)

- See _package.json_ and _.github/workflows/publish.yml_ for example

### Github

If you use `better-commits` to create your _first_ commit on a new branch

- When you open a PR for that branch, it will properly **auto-populate the title and body**.
- When you squash/merge, all later commits like "addressing comments" or "fixing mistake". Will be prefixed with an asterisk for easy deletion. This way, you **maintain your pretty commit even when squashing**.

If you're using Github issues to track your work, and select the `closes` footer option when writing your commit. Github will **automatically link and close** that issue when your **pr is merged**

### Changelogs

`better-commits` can append a commit trailer per commit type. This allows you to [automate change logs](https://docs.gitlab.com/ee/user/project/changelogs.html) with tools like Gitlab.

### Git

`better-commits` uses native `git` commands under the hood. So any hooks, tools, or staging should work as if it was a normal commit.

Setting `confirm_with_editor=true` will allow you to edit/confirm a commit with your editor.

- For example, to edit with Neovim: `git config --global core.editor "nvim"`
- For VS Code, `git config --global core.editor "code -n --wait"`

You can pass arguments to `git` through `better-commits` like so:

```sh
better-commits --git-dir="$HOME/.config" --work-tree="$HOME"
```

A practical example of this would be managing dotfiles, as described in this [Atlassian Article](https://www.atlassian.com/git/tutorials/dotfiles)

### CLI Flags

Use CLI flags to pass commit values directly instead of answering prompts.

- Use `--no-interactive` to skip prompts, confirmation, and editor flows. This is the recommended mode for OpenCode, Claude Code, and other coding agents.
- Use `--dry-run` to validate the generated `git commit` command without creating a commit.
- Supported commit field flags: `--type`, `--scope`, `--title`, `--body`, `--ticket`, `--closes`, `--deprecates`, `--breaking-title`, `--breaking-body`, `--deprecates-title`, `--deprecates-body`, `--custom-footer`, `--trailer`.
- Supported branch field flags: `--user`, `--type`, `--scope`, `--description`, `--ticket`, `--branch-version`, `--checkout`.

**Examples**

```sh
better-commits --no-interactive --dry-run --type feat --scope cli --title "add parser"

better-branch --no-interactive --type feat --scope cli --ticket TAC-123 --description "add parser" --checkout worktree
```

---

### ๐ŸชŸ Troubleshooting Windows

#### Git Bash

`TTY initialization failed: uv_tty_init returned EBADF (bad file descriptor)`. This may happen because you're running something like git-bash on Windows. Try another terminal/command-prompt or `winpty` to see if its still an issue.

#### Multi-line

If you are having issues with multilines for commits on windows, you can override the shell via your `.better-commits.jsonc` config.

Example

```json
"overrides": {
"shell": "c:\\Program Files\\Git\\bin\\bash.exe"
}
```

๐ŸŒŸ Sponsors

[![flotes-g-2](https://github.com/Everduin94/Everduin94/assets/14320878/b0fd0aa5-ca9d-4a2d-8579-7616140927a7)](https://flotes.app)

[Markdown Notetaking - Built for Learning](https://flotes.app)