diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..e469e58
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+# Ignore artifacts:
+build
+coverage
+node_modules
+dist
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1 @@
+{}
diff --git a/package-lock.json b/package-lock.json
index 423a663..2b737aa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "better-commits",
- "version": "1.10.0",
+ "version": "1.15.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "better-commits",
- "version": "1.10.0",
+ "version": "1.15.0",
"license": "MIT",
"dependencies": {
"@clack/core": "^0.3.1",
@@ -28,6 +28,7 @@
"@types/configstore": "^6.0.0",
"@types/node": "^18.14.5",
"jiti": "^1.17.0",
+ "prettier": "3.2.5",
"semantic-release": "^21.0.1",
"tsup": "^6.6.3",
"tsx": "^3.12.3"
@@ -6510,6 +6511,21 @@
}
}
},
+ "node_modules/prettier": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -12530,6 +12546,12 @@
"yaml": "^1.10.2"
}
},
+ "prettier": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "dev": true
+ },
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
diff --git a/package.json b/package.json
index 2633ffd..b23e0a7 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"@types/configstore": "^6.0.0",
"@types/node": "^18.14.5",
"jiti": "^1.17.0",
+ "prettier": "3.2.5",
"semantic-release": "^21.0.1",
"tsup": "^6.6.3",
"tsx": "^3.12.3"
diff --git a/readme.md b/readme.md
index 6d08361..a7c2ab8 100644
--- a/readme.md
+++ b/readme.md
@@ -1,39 +1,39 @@
![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://www.npmjs.com/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.
-
https://github.com/Everduin94/better-commits/assets/14320878/8fb15d46-17c4-4e5d-80d9-79abe0a2a00a
-
## โจ Features
+
- Generate conventional commits through a series of prompts
- Highly configurable with sane defaults
-- Infers ticket and commit-type from branch for consistent & fast commits
+- Infers ticket 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
+- Preview commit messages in color
- Support for git emojis per commit-type
- Configure globally or per repository
- Config validation and error messaging
As a side-effect of formatting messages
+
- Auto populate PR title / body
- Automate semantic releases
-- Automate changelogs
+- Automate changelogs
- Automatically link & close related tickets / issues
## ๐ฆ Installation
-
+
```sh
npm install -g better-commits
```
@@ -54,15 +54,17 @@ Some of the values in these prompts will be infered by your branch name and auto
To better understand these prompts and their intention, read [Conventional Commits Summary](https://www.conventionalcommits.org/en/v1.0.0-beta.4/#summary)
## โ๏ธ Configuration
-
+
### Global
Your first time running `better-commits`, a default config will be generated in your `$HOME` directory, named `.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.json`
- Properties such as `confirm_with_editor` and `overrides` will prefer the global config
@@ -73,6 +75,7 @@ Better-commits (& better-branch) are highly flexible with sane defaults. These o
> [!NOTE]
> All properties are optional and can be removed from the config. It will be replaced by the default at run-time.
+>
> - See `.better-commits.json` in this repository as an example
### ๐ซ Default JSON Config
@@ -80,184 +83,172 @@ Better-commits (& better-branch) are highly flexible with sane defaults. These o
Expand / Collapse
- ```json
+```json
{
- "check_status": true,
- "commit_type": {
- "enable": true,
- "initial_value": "feat",
- "infer_type_from_branch": true,
- "append_emoji_to_label": false,
- "append_emoji_to_commit": false,
- "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,
- "custom_scope": false,
- "initial_value": "app",
- "options": [
- {
- "value": "app",
- "label": "app"
- },
- {
- "value": "shared",
- "label": "shared"
- },
- {
- "value": "server",
- "label": "server"
- },
- {
- "value": "tools",
- "label": "tools"
- },
- {
- "value": "",
- "label": "none"
- }
- ]
- },
- "check_ticket": {
- "infer_ticket": true,
- "confirm_ticket": true,
- "add_to_title": true,
- "append_hashtag": false,
- "prepend_hashtag": "Never",
- "surround": "",
- "title_position": "start"
- },
- "commit_title": {
- "max_size": 70
- },
- "commit_body": {
- "enable": true,
- "required": false
- },
- "commit_footer": {
- "enable": true,
- "initial_value": [],
- "options": [
- "closes",
- "trailer",
- "breaking-change",
- "deprecated",
- "custom"
- ]
- },
- "breaking_change": {
- "add_exclamation_to_title": true
- },
- "confirm_commit": true,
- "confirm_with_editor": false,
- "print_commit_output": true,
- "branch_pre_commands": [],
- "branch_post_commands": [],
- "worktree_pre_commands": [],
- "worktree_post_commands": [],
- "branch_user": {
- "enable": true,
- "required": false,
- "separator": "/"
- },
- "branch_type": {
- "enable": true,
- "separator": "/"
- },
- "branch_version": {
- "enable": false,
- "required": false,
- "separator": "/"
- },
- "branch_ticket": {
- "enable": true,
- "required": false,
- "separator": "-"
- },
- "branch_description": {
- "max_length": 70,
- "separator": ""
- },
- "branch_action_default": "branch",
- "branch_order": [
- "user",
- "version",
- "type",
- "ticket",
- "description"
- ],
- "enable_worktrees": true,
- "overrides": {
- "shell": "/bin/sh"
- }
+ "check_status": true,
+ "commit_type": {
+ "enable": true,
+ "initial_value": "feat",
+ "infer_type_from_branch": true,
+ "append_emoji_to_label": false,
+ "append_emoji_to_commit": false,
+ "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,
+ "custom_scope": false,
+ "initial_value": "app",
+ "options": [
+ {
+ "value": "app",
+ "label": "app"
+ },
+ {
+ "value": "shared",
+ "label": "shared"
+ },
+ {
+ "value": "server",
+ "label": "server"
+ },
+ {
+ "value": "tools",
+ "label": "tools"
+ },
+ {
+ "value": "",
+ "label": "none"
+ }
+ ]
+ },
+ "check_ticket": {
+ "infer_ticket": true,
+ "confirm_ticket": true,
+ "add_to_title": true,
+ "append_hashtag": false,
+ "prepend_hashtag": "Never",
+ "surround": "",
+ "title_position": "start"
+ },
+ "commit_title": {
+ "max_size": 70
+ },
+ "commit_body": {
+ "enable": true,
+ "required": false
+ },
+ "commit_footer": {
+ "enable": true,
+ "initial_value": [],
+ "options": ["closes", "trailer", "breaking-change", "deprecated", "custom"]
+ },
+ "breaking_change": {
+ "add_exclamation_to_title": true
+ },
+ "confirm_commit": true,
+ "confirm_with_editor": false,
+ "print_commit_output": true,
+ "branch_pre_commands": [],
+ "branch_post_commands": [],
+ "worktree_pre_commands": [],
+ "worktree_post_commands": [],
+ "branch_user": {
+ "enable": true,
+ "required": false,
+ "separator": "/"
+ },
+ "branch_type": {
+ "enable": true,
+ "separator": "/"
+ },
+ "branch_version": {
+ "enable": false,
+ "required": false,
+ "separator": "/"
+ },
+ "branch_ticket": {
+ "enable": true,
+ "required": false,
+ "separator": "-"
+ },
+ "branch_description": {
+ "max_length": 70,
+ "separator": ""
+ },
+ "branch_action_default": "branch",
+ "branch_order": ["user", "version", "type", "ticket", "description"],
+ "enable_worktrees": true,
+ "overrides": {
+ "shell": "/bin/sh"
+ }
}
```
@@ -265,7 +256,8 @@ Better-commits (& better-branch) are highly flexible with sane defaults. These o
> [!NOTE]
> Some properties allow a set of specifc string values
-> - See *config file explanations* for possible values
+>
+> - See _config file explanations_ for possible values
### ๐ญ Config File Explanations
@@ -282,78 +274,80 @@ Expand to see explanations and possible values
}
```
-| Property | Description |
-| -------- | ----------- |
-| `check_status` | If true run interactive `git status` |
-| `commit_type.enable` | If true include commit type |
-| `commit_type.initial_value` | Initial selection of commit type |
-| `commit_type.infer_type_from_branch` | If true infer type from branch name |
-| `commit_type.append_emoji_to_label` | If true append emoji to prompt |
-| `commit_type.append_emoji_to_commit` | If true append emoji to commit |
-| `commit_type.options.value` | Commit type prompt value |
-| `commit_type.options.label` | Commit type prompt label |
-| `commit_type.options.hint` | Commit type inline hint (like this) |
-| `commit_type.options.emoji` | Commit type emoji |
-| `commit_type.options.trailer` | Commit type trailer |
-| `commit_scope.enable` | If true include commit scope |
-| `commit_scope.custom_scope` | If true allow custom scope at run-time |
-| `commit_scope.initial_value` | Default commit scope selected |
-| `commit_scope.options.value` | Commit scope value |
-| `commit_scope.options.label` | Commit scope label |
-| `check_ticket.infer_ticket` | If true infer ticket from branch name |
-| `check_ticket.confirm_ticket` | If true manually confirm inference |
-| `check_ticket.add_to_title` | If true add ticket to title |
-| `check_ticket.append_hashtag` | **Deprecated**: see prepend_hashtag |
-| `check_ticket.prepend_hashtag` | "Never" (default), "Prompt", or "Always" |
-| `check_ticket.title_position` | "start" (of description) (default), "end", "before-colon", "beginning" (of the entire commit title) |
-| `check_ticket.surround` | "" (default), "[]", "()", "{}" - Wraps ticket in title |
-| `commit_title.max_size` | Max size of title including scope, type, etc... |
-| `commit_body.enable` | If true include body |
-| `commit_body.required` | If true body is required |
-| `commit_footer.enable` | If true include footer |
-| `commit_footer.initial_value` | Initial values selected in footer |
-| `commit_footer.options` | Footer options |
-| `breaking_change.add_exclamation_to_title` | If true adds exclamation mark to title for breaking changes |
-| `confirm_commit` | If true manually confirm commit at end |
-| `confirm_with_editor` | Confirm / Edit commit with $GIT_EDITOR / $EDITOR |
-| `print_commit_output` | If true pretty print commit preview |
-| `overrides.shell` | Override default shell, useful for windows users |
+| Property | Description |
+| ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
+| `check_status` | If true run interactive `git status` |
+| `commit_type.enable` | If true include commit type |
+| `commit_type.initial_value` | Initial selection of commit type |
+| `commit_type.infer_type_from_branch` | If true infer type from branch name |
+| `commit_type.append_emoji_to_label` | If true append emoji to prompt |
+| `commit_type.append_emoji_to_commit` | If true append emoji to commit |
+| `commit_type.options.value` | Commit type prompt value |
+| `commit_type.options.label` | Commit type prompt label |
+| `commit_type.options.hint` | Commit type inline hint (like this) |
+| `commit_type.options.emoji` | Commit type emoji |
+| `commit_type.options.trailer` | Commit type trailer |
+| `commit_scope.enable` | If true include commit scope |
+| `commit_scope.custom_scope` | If true allow custom scope at run-time |
+| `commit_scope.initial_value` | Default commit scope selected |
+| `commit_scope.options.value` | Commit scope value |
+| `commit_scope.options.label` | Commit scope label |
+| `check_ticket.infer_ticket` | If true infer ticket from branch name |
+| `check_ticket.confirm_ticket` | If true manually confirm inference |
+| `check_ticket.add_to_title` | If true add ticket to title |
+| `check_ticket.append_hashtag` | **Deprecated**: see prepend_hashtag |
+| `check_ticket.prepend_hashtag` | "Never" (default), "Prompt", or "Always" |
+| `check_ticket.title_position` | "start" (of description) (default), "end", "before-colon", "beginning" (of the entire commit title) |
+| `check_ticket.surround` | "" (default), "[]", "()", "{}" - Wraps ticket in title |
+| `commit_title.max_size` | Max size of title including scope, type, etc... |
+| `commit_body.enable` | If true include body |
+| `commit_body.required` | If true body is required |
+| `commit_footer.enable` | If true include footer |
+| `commit_footer.initial_value` | Initial values selected in footer |
+| `commit_footer.options` | Footer options |
+| `breaking_change.add_exclamation_to_title` | If true adds exclamation mark to title for breaking changes |
+| `confirm_commit` | If true manually confirm commit at end |
+| `confirm_with_editor` | Confirm / Edit commit with $GIT_EDITOR / $EDITOR |
+| `print_commit_output` | If true pretty print commit preview |
+| `overrides.shell` | Override default shell, useful for windows users |
Branch configuration (same config file, split for readability)
-| Property | Description |
-| -------- | ----------- |
-| `branch_pre_commands` | Array of shell commands to run before branching |
-| `branch_post_commands` | Array of shell commands to run after branching |
-| `worktree_pre_commands` | Array of shell commands to run before creating worktree |
-| `worktree_post_commands` | Array of shell commands to run after creating worktree |
-| `branch_user.enable` | If enabled include user name |
-| `branch_user.required` | If enabled require user name |
-| `branch_user.separator` | Branch delimeter - "/" (default), "-", "_" |
-| `branch_type.enable` | If enabled include type |
-| `branch_type.separator` | Branch delimeter - "/" (default), "-", "_" |
-| `branch_ticket.enable` | If enabled include ticket |
-| `branch_ticket.required` | If enabled require ticket |
-| `branch_ticket.separator` | Branch delimeter - "/", "-" (default), "_" |
-| `branch_description.max_length` | Max length branch name |
-| `branch_description.separator` | Branch delimeter - "" (default), "/", "-", "_" |
-| `branch_version.enable` | If enabled include version |
-| `branch_version.required` | If enabled require version |
-| `branch_version.separator` | Branch delimeter - "", "/" (default), "-", "_" |
-| `branch_order` | Order of branch name values (doesn't effect prompt order) |
-| `branch_action_default` | "branch" or "worktree" |
-| `enable_worktrees` | If false, always default to branch action |
+| Property | Description |
+| ------------------------------- | --------------------------------------------------------- |
+| `branch_pre_commands` | Array of shell commands to run before branching |
+| `branch_post_commands` | Array of shell commands to run after branching |
+| `worktree_pre_commands` | Array of shell commands to run before creating worktree |
+| `worktree_post_commands` | Array of shell commands to run after creating worktree |
+| `branch_user.enable` | If enabled include user name |
+| `branch_user.required` | If enabled require user name |
+| `branch_user.separator` | Branch delimeter - "/" (default), "-", "\_" |
+| `branch_type.enable` | If enabled include type |
+| `branch_type.separator` | Branch delimeter - "/" (default), "-", "\_" |
+| `branch_ticket.enable` | If enabled include ticket |
+| `branch_ticket.required` | If enabled require ticket |
+| `branch_ticket.separator` | Branch delimeter - "/", "-" (default), "\_" |
+| `branch_description.max_length` | Max length branch name |
+| `branch_description.separator` | Branch delimeter - "" (default), "/", "-", "\_" |
+| `branch_version.enable` | If enabled include version |
+| `branch_version.required` | If enabled require version |
+| `branch_version.separator` | Branch delimeter - "", "/" (default), "-", "\_" |
+| `branch_order` | Order of branch name values (doesn't effect prompt order) |
+| `branch_action_default` | "branch" or "worktree" |
+| `enable_worktrees` | If false, always default to branch action |
### ๐ Inference
-`better-commits` will attempt to infer the ticket/issue and the commit-type from your branch name. It will auto populate the corresponding field if found.
+`better-commits` will attempt to infer the ticket/issue and the commit-type from your branch name. It will auto populate the corresponding field if found.
+
+**Ticket / Issue-Number**
-**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 `/`
## ๐ณ Better Branch
@@ -363,6 +357,7 @@ Branch configuration (same config file, split for readability)
> Make sure to try it out!
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 & ticket
@@ -386,20 +381,25 @@ The worktree flow creates a folder/worktree with your **branch description** and
### 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)
+See _branch_pre_commands_ and _branch_post_commands_ in default config. (or _worktree_pre_commands_ and _worktree_post_commands_ for creating worktrees)
## ๐ Mildly Interesting
### 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
+
+- 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
+
+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**.
@@ -410,22 +410,24 @@ If you're using Github issues to track your work, and select the `closes` footer
`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.
### Fun Facts
+
[better-commits](https://packagephobia.com/result?p=better-commits) is much smaller than its alternative [commitizen](https://packagephobia.com/result?p=commitizen)
`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"`
> [!NOTE]
-> Enjoy learning, open source technology, and note-taking? [Join our Discord!](https://discord.gg/grHVnZwYup)
+> Enjoy learning, open source technology, and note-taking? [Join our Discord!](https://discord.gg/grHVnZwYup)
-You can add this badge to your repository to display that you're using a better-commits repository config
+You can add this badge to your repository to display that you're using a better-commits repository config
-| Markdown | Result |
-| -------- | ------ |
-| `[![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)` | [![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) |
+| Markdown | Result |
+| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `[![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)` | [![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) |
---
@@ -440,19 +442,19 @@ You can add this badge to your repository to display that you're using a better-
If your are having issues with multilines for commits on windows, you can override the shell via your `.better-commits.json` 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)
diff --git a/src/branch.ts b/src/branch.ts
index 1d0b57c..9a56f20 100644
--- a/src/branch.ts
+++ b/src/branch.ts
@@ -130,8 +130,8 @@ async function main(config: z.infer) {
});
p.log.info(
`Switched to a new branch '${color.bgGreen(
- " " + color.black(branch_name) + " "
- )}'`
+ " " + color.black(branch_name) + " ",
+ )}'`,
);
} catch (err) {
process.exit(0);
@@ -144,18 +144,18 @@ async function main(config: z.infer) {
`git worktree add ${worktree_name} ${branch_flag} ${branch_name}`,
{
stdio: "inherit",
- }
+ },
);
p.log.info(
`Created a new worktree ${color.bgGreen(
- +" " + color.black(worktree_name) + " "
+ +" " + color.black(worktree_name) + " ",
)}, checked out branch ${color.bgGreen(
- " " + color.black(branch_name) + " "
- )}`
+ " " + color.black(branch_name) + " ",
+ )}`,
);
p.log.info(
color.bgMagenta(color.black(` cd ${worktree_name} `)) +
- " to navigate to your new worktree"
+ " to navigate to your new worktree",
);
chdir(worktree_name);
} catch (err) {
@@ -179,14 +179,14 @@ async function main(config: z.infer) {
function build_branch(
branch: z.infer,
- config: z.infer
+ config: z.infer,
) {
let res = "";
config.branch_order.forEach((b: z.infer) => {
- const config_key: z.infer = `branch_${b}`
- if (branch[b]) res += branch[b] + config[config_key].separator
- })
- if (res.endsWith('-') || res.endsWith('/') || res.endsWith('_')) {
+ const config_key: z.infer = `branch_${b}`;
+ if (branch[b]) res += branch[b] + config[config_key].separator;
+ });
+ if (res.endsWith("-") || res.endsWith("/") || res.endsWith("_")) {
return res.slice(0, -1).trim();
}
return res.trim();
@@ -198,7 +198,7 @@ function get_user_from_cache(): string {
return config_store.get("username") ?? "";
} catch (err) {
p.log.warn(
- 'There was an issue accessing username from cache. Check that the folder "~/.config" exists'
+ 'There was an issue accessing username from cache. Check that the folder "~/.config" exists',
);
}
@@ -212,8 +212,8 @@ function verify_branch_name(branch_name: string): string {
execSync(`git show-ref ${branch_name}`, { encoding: "utf-8" });
p.log.warning(
color.yellow(
- `${branch_name} already exists! Checking out existing branch.`
- )
+ `${branch_name} already exists! Checking out existing branch.`,
+ ),
);
} catch (err) {
// Branch does not exist
diff --git a/src/index.ts b/src/index.ts
index 4d80859..e274157 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,92 +1,131 @@
#! /usr/bin/env node
-import * as p from '@clack/prompts';
-import color from 'picocolors';
-import { execSync } from 'child_process';
+import * as p from "@clack/prompts";
+import color from "picocolors";
+import { execSync } from "child_process";
import { chdir } from "process";
import { z } from "zod";
-import { CommitState, Config } from './zod-state';
-import { load_setup, addNewLine, SPACE_TO_SELECT, REGEX_SLASH_TAG, REGEX_SLASH_NUM, REGEX_START_TAG, REGEX_START_NUM, OPTIONAL_PROMPT, clean_commit_title, COMMIT_FOOTER_OPTIONS, infer_type_from_branch, Z_FOOTER_OPTIONS, CUSTOM_SCOPE_KEY, get_git_root, REGEX_SLASH_UND, REGEX_START_UND } from './utils';
-import { git_add, git_status } from './git';
+import { CommitState, Config } from "./zod-state";
+import {
+ load_setup,
+ addNewLine,
+ SPACE_TO_SELECT,
+ REGEX_SLASH_TAG,
+ REGEX_SLASH_NUM,
+ REGEX_START_TAG,
+ REGEX_START_NUM,
+ OPTIONAL_PROMPT,
+ clean_commit_title,
+ COMMIT_FOOTER_OPTIONS,
+ infer_type_from_branch,
+ Z_FOOTER_OPTIONS,
+ CUSTOM_SCOPE_KEY,
+ get_git_root,
+ REGEX_SLASH_UND,
+ REGEX_START_UND,
+} from "./utils";
+import { git_add, git_status } from "./git";
main(load_setup());
export async function main(config: z.infer) {
- let commit_state = CommitState.parse({})
+ let commit_state = CommitState.parse({});
chdir(get_git_root());
if (config.check_status) {
- let {index, work_tree} = git_status()
- p.log.step(color.black(color.bgGreen(' Checking Git Status ')))
- const staged_files = index.reduce((acc,curr,i: number) => color.green(acc+curr+addNewLine(index,i)), '');
- p.log.success('Changes to be committed:\n'+staged_files)
+ let { index, work_tree } = git_status();
+ p.log.step(color.black(color.bgGreen(" Checking Git Status ")));
+ const staged_files = index.reduce(
+ (acc, curr, i: number) => color.green(acc + curr + addNewLine(index, i)),
+ "",
+ );
+ p.log.success("Changes to be committed:\n" + staged_files);
if (work_tree.length) {
- const unstaged_files = work_tree.reduce((acc,curr,i: number) => color.red(acc+curr+addNewLine(work_tree,i)), '');
- p.log.error('Changes not staged for commit:\n'+unstaged_files)
- const selected_for_staging = await p.multiselect({
- message: `Some files have not been staged, would you like to add them now? ${SPACE_TO_SELECT}`,
- options: [{value: '.', label: '.'}, ...work_tree.map(v => ({value: v, label: v}))],
- required: false,
- }) as string[]
- if (p.isCancel(selected_for_staging)) process.exit(0)
+ const unstaged_files = work_tree.reduce(
+ (acc, curr, i: number) =>
+ color.red(acc + curr + addNewLine(work_tree, i)),
+ "",
+ );
+ p.log.error("Changes not staged for commit:\n" + unstaged_files);
+ const selected_for_staging = (await p.multiselect({
+ message: `Some files have not been staged, would you like to add them now? ${SPACE_TO_SELECT}`,
+ options: [
+ { value: ".", label: "." },
+ ...work_tree.map((v) => ({ value: v, label: v })),
+ ],
+ required: false,
+ })) as string[];
+ if (p.isCancel(selected_for_staging)) process.exit(0);
git_add(selected_for_staging);
}
- let updated_status = git_status()
+ let updated_status = git_status();
if (!updated_status.index.length) {
- p.log.error(color.red('no changes added to commit (use "git add" and/or "git commit -a")'))
+ p.log.error(
+ color.red(
+ 'no changes added to commit (use "git add" and/or "git commit -a")',
+ ),
+ );
process.exit(0);
}
- }
+ }
if (config.commit_type.enable) {
- let message = 'Select a commit type';
- let initial_value = config.commit_type.initial_value
+ let message = "Select a commit type";
+ let initial_value = config.commit_type.initial_value;
if (config.commit_type.infer_type_from_branch) {
- const options = config.commit_type.options.map(o => o.value)
- const type_from_branch = infer_type_from_branch(options)
+ const options = config.commit_type.options.map((o) => o.value);
+ const type_from_branch = infer_type_from_branch(options);
if (type_from_branch) {
- message = `Commit type inferred from branch ${color.dim('(confirm / edit)')}`
- initial_value = type_from_branch
- }
+ message = `Commit type inferred from branch ${color.dim("(confirm / edit)")}`;
+ initial_value = type_from_branch;
+ }
}
- const value_to_data: Record = config.commit_type.options.reduce(
- (acc, curr) => ({ ...acc, [curr.value]: {emoji: curr.emoji ?? '', trailer: curr.trailer ?? '' }}), {}
- )
- const commit_type = await p.select(
- {
- message,
- initialValue: initial_value,
- options: config.commit_type.options,
- }
- )
- if (p.isCancel(commit_type)) process.exit(0)
+ const value_to_data: Record =
+ config.commit_type.options.reduce(
+ (acc, curr) => ({
+ ...acc,
+ [curr.value]: {
+ emoji: curr.emoji ?? "",
+ trailer: curr.trailer ?? "",
+ },
+ }),
+ {},
+ );
+ const commit_type = await p.select({
+ message,
+ initialValue: initial_value,
+ options: config.commit_type.options,
+ });
+ if (p.isCancel(commit_type)) process.exit(0);
commit_state.trailer = value_to_data[commit_type].trailer;
- commit_state.type = config.commit_type.append_emoji_to_commit ?
- `${value_to_data[commit_type].emoji} ${commit_type}`.trim()
- : commit_type;
+ commit_state.type = config.commit_type.append_emoji_to_commit
+ ? `${value_to_data[commit_type].emoji} ${commit_type}`.trim()
+ : commit_type;
}
if (config.commit_scope.enable) {
- let commit_scope = await p.select({
- message: 'Select a commit scope',
+ let commit_scope = await p.select({
+ message: "Select a commit scope",
initialValue: config.commit_scope.initial_value,
- options: config.commit_scope.options
- })
- if (p.isCancel(commit_scope)) process.exit(0)
+ options: config.commit_scope.options,
+ });
+ if (p.isCancel(commit_scope)) process.exit(0);
if (commit_scope === CUSTOM_SCOPE_KEY && config.commit_scope.custom_scope) {
- commit_scope = await p.text({
- message: 'Write a custom scope',
- placeholder: ''
- })
- if (p.isCancel(commit_scope)) process.exit(0)
+ commit_scope = await p.text({
+ message: "Write a custom scope",
+ placeholder: "",
+ });
+ if (p.isCancel(commit_scope)) process.exit(0);
}
commit_state.scope = commit_scope;
}
if (config.check_ticket.infer_ticket) {
try {
- const branch = execSync('git branch --show-current', {stdio : 'pipe' }).toString();
+ const branch = execSync("git branch --show-current", {
+ stdio: "pipe",
+ }).toString();
const found: string[] = [
branch.match(REGEX_START_UND),
branch.match(REGEX_SLASH_UND),
@@ -95,170 +134,214 @@ export async function main(config: z.infer) {
branch.match(REGEX_START_TAG),
branch.match(REGEX_START_NUM),
]
- .filter(v => v != null)
- .map(v => v && v.length >= 2 ? v[1] : '')
+ .filter((v) => v != null)
+ .map((v) => (v && v.length >= 2 ? v[1] : ""));
if (found.length && found[0]) {
- commit_state.ticket = config.check_ticket.append_hashtag
- || config.check_ticket.prepend_hashtag === 'Prompt'
- ? '#' + found[0] : found[0]
+ commit_state.ticket =
+ config.check_ticket.append_hashtag ||
+ config.check_ticket.prepend_hashtag === "Prompt"
+ ? "#" + found[0]
+ : found[0];
}
- } catch(err: any) {
+ } catch (err: any) {
// Can't find branch, fail silently
}
}
if (config.check_ticket.confirm_ticket) {
const user_commit_ticket = await p.text({
- message: commit_state.ticket ? `Ticket / issue inferred from branch ${color.dim('(confirm / edit)')}`: `Add ticket / issue ${OPTIONAL_PROMPT}`,
- placeholder: '',
+ message: commit_state.ticket
+ ? `Ticket / issue inferred from branch ${color.dim("(confirm / edit)")}`
+ : `Add ticket / issue ${OPTIONAL_PROMPT}`,
+ placeholder: "",
initialValue: commit_state.ticket,
- })
- if (p.isCancel(user_commit_ticket)) process.exit(0)
- commit_state.ticket = user_commit_ticket ?? '';
+ });
+ if (p.isCancel(user_commit_ticket)) process.exit(0);
+ commit_state.ticket = user_commit_ticket ?? "";
}
- if (config.check_ticket.prepend_hashtag === 'Always'
- && commit_state.ticket
- && !commit_state.ticket.startsWith('#')) {
- commit_state.ticket = '#' + commit_state.ticket;
+ if (
+ config.check_ticket.prepend_hashtag === "Always" &&
+ commit_state.ticket &&
+ !commit_state.ticket.startsWith("#")
+ ) {
+ commit_state.ticket = "#" + commit_state.ticket;
}
- const commit_title = await p.text(
- {
- message: 'Write a brief title describing the commit',
- placeholder: '',
- validate: (value) => {
- if (!value) return 'Please enter a title';
- const commit_scope_size = commit_state.scope ? commit_state.scope.length + 2 : 0
- const commit_type_size = commit_state.type.length;
- const commit_ticket_size = config.check_ticket.add_to_title ? commit_state.ticket.length : 0
- if (commit_scope_size + commit_type_size + commit_ticket_size + value.length > config.commit_title.max_size) return `Exceeded max length. Title max [${config.commit_title.max_size}]`;
- },
-
- }
- )
- if (p.isCancel(commit_title)) process.exit(0)
+ const commit_title = await p.text({
+ message: "Write a brief title describing the commit",
+ placeholder: "",
+ validate: (value) => {
+ if (!value) return "Please enter a title";
+ const commit_scope_size = commit_state.scope
+ ? commit_state.scope.length + 2
+ : 0;
+ const commit_type_size = commit_state.type.length;
+ const commit_ticket_size = config.check_ticket.add_to_title
+ ? commit_state.ticket.length
+ : 0;
+ if (
+ commit_scope_size +
+ commit_type_size +
+ commit_ticket_size +
+ value.length >
+ config.commit_title.max_size
+ )
+ return `Exceeded max length. Title max [${config.commit_title.max_size}]`;
+ },
+ });
+ if (p.isCancel(commit_title)) process.exit(0);
commit_state.title = clean_commit_title(commit_title);
if (config.commit_body.enable) {
const commit_body = await p.text({
- message: `Write a detailed description of the changes ${OPTIONAL_PROMPT}`,
- placeholder: '',
- validate: (val) => {
- if (config.commit_body.required && !val) return 'Please enter a description'
- }
-
- })
- if (p.isCancel(commit_body)) process.exit(0)
- commit_state.body = commit_body ?? '';
+ message: `Write a detailed description of the changes ${OPTIONAL_PROMPT}`,
+ placeholder: "",
+ validate: (val) => {
+ if (config.commit_body.required && !val)
+ return "Please enter a description";
+ },
+ });
+ if (p.isCancel(commit_body)) process.exit(0);
+ commit_state.body = commit_body ?? "";
}
if (config.commit_footer.enable) {
const commit_footer = await p.multiselect({
- message: `Select optional footers ${SPACE_TO_SELECT}`,
- initialValues: config.commit_footer.initial_value,
- options: COMMIT_FOOTER_OPTIONS as {value: z.infer, label: string, hint: string}[],
- required: false
- })
- if (p.isCancel(commit_footer)) process.exit(0)
-
- if (commit_footer.includes('breaking-change')) {
+ message: `Select optional footers ${SPACE_TO_SELECT}`,
+ initialValues: config.commit_footer.initial_value,
+ options: COMMIT_FOOTER_OPTIONS as {
+ value: z.infer;
+ label: string;
+ hint: string;
+ }[],
+ required: false,
+ });
+ if (p.isCancel(commit_footer)) process.exit(0);
+
+ if (commit_footer.includes("breaking-change")) {
const breaking_changes_title = await p.text({
- message: 'Breaking changes: Write a short title / summary',
- placeholder: '',
- validate: (value) => {
- if (!value) return 'Please enter a title / summary'
- }
- })
- if (p.isCancel(breaking_changes_title)) process.exit(0)
+ message: "Breaking changes: Write a short title / summary",
+ placeholder: "",
+ validate: (value) => {
+ if (!value) return "Please enter a title / summary";
+ },
+ });
+ if (p.isCancel(breaking_changes_title)) process.exit(0);
const breaking_changes_body = await p.text({
- message: `Breaking Changes: Write a description & migration instructions ${OPTIONAL_PROMPT}`,
- placeholder: '',
- })
- if (p.isCancel(breaking_changes_body)) process.exit(0)
+ message: `Breaking Changes: Write a description & migration instructions ${OPTIONAL_PROMPT}`,
+ placeholder: "",
+ });
+ if (p.isCancel(breaking_changes_body)) process.exit(0);
commit_state.breaking_title = breaking_changes_title;
commit_state.breaking_body = breaking_changes_body;
}
- if (commit_footer.includes('deprecated')) {
+ if (commit_footer.includes("deprecated")) {
const deprecated_title = await p.text({
- message: 'Deprecated: Write a short title / summary',
- placeholder: '',
- validate: (value) => {
- if (!value) return 'Please enter a title / summary'
- }
- })
- if (p.isCancel(deprecated_title)) process.exit(0)
+ message: "Deprecated: Write a short title / summary",
+ placeholder: "",
+ validate: (value) => {
+ if (!value) return "Please enter a title / summary";
+ },
+ });
+ if (p.isCancel(deprecated_title)) process.exit(0);
const deprecated_body = await p.text({
- message: `Deprecated: Write a description ${OPTIONAL_PROMPT}`,
- placeholder: '',
- })
- if (p.isCancel(deprecated_body)) process.exit(0)
+ message: `Deprecated: Write a description ${OPTIONAL_PROMPT}`,
+ placeholder: "",
+ });
+ if (p.isCancel(deprecated_body)) process.exit(0);
commit_state.deprecates_body = deprecated_body;
commit_state.deprecates_title = deprecated_title;
}
- if (commit_footer.includes('closes')) {
- commit_state.closes = 'Closes:'
+ if (commit_footer.includes("closes")) {
+ commit_state.closes = "Closes:";
}
- if (commit_footer.includes('custom')) {
+ if (commit_footer.includes("custom")) {
const custom_footer = await p.text({
- message: 'Write a custom footer',
- placeholder: '',
- })
- if (p.isCancel(custom_footer)) process.exit(0)
+ message: "Write a custom footer",
+ placeholder: "",
+ });
+ if (p.isCancel(custom_footer)) process.exit(0);
commit_state.custom_footer = custom_footer;
}
- if (!commit_footer.includes('trailer')) {
- commit_state.trailer = '';
+ if (!commit_footer.includes("trailer")) {
+ commit_state.trailer = "";
}
}
if (config.confirm_with_editor) {
- const options = config.overrides.shell ? { shell: config.overrides.shell, stdio: 'inherit' } : { stdio: 'inherit' }
- const trailer = commit_state.trailer ? `--trailer="${commit_state.trailer}"` : '';
- execSync(`git commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer} --edit`, options);
+ const options = config.overrides.shell
+ ? { shell: config.overrides.shell, stdio: "inherit" }
+ : { stdio: "inherit" };
+ const trailer = commit_state.trailer
+ ? `--trailer="${commit_state.trailer}"`
+ : "";
+ execSync(
+ `git commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer} --edit`,
+ options,
+ );
process.exit(0);
}
let continue_commit = true;
- p.note(build_commit_string(commit_state, config, true, false, true), 'Commit Preview')
+ p.note(
+ build_commit_string(commit_state, config, true, false, true),
+ "Commit Preview",
+ );
if (config.confirm_commit) {
- continue_commit = await p.confirm({message: 'Confirm Commit?'}) as boolean;
- if (p.isCancel(continue_commit)) process.exit(0)
+ continue_commit = (await p.confirm({
+ message: "Confirm Commit?",
+ })) as boolean;
+ if (p.isCancel(continue_commit)) process.exit(0);
}
if (!continue_commit) {
- p.log.info('Exiting without commit')
- process.exit(0)
- }
-
- try {
- const options = config.overrides.shell ? { shell: config.overrides.shell } : {}
- const trailer = commit_state.trailer ? `--trailer="${commit_state.trailer}"` : '';
- const output = execSync(`git commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer}`, options).toString().trim();
- if (config.print_commit_output) p.log.info(output)
- } catch(err) {
- p.log.error('Something went wrong when committing: ' + err)
+ p.log.info("Exiting without commit");
+ process.exit(0);
+ }
+
+ try {
+ const options = config.overrides.shell
+ ? { shell: config.overrides.shell }
+ : {};
+ const trailer = commit_state.trailer
+ ? `--trailer="${commit_state.trailer}"`
+ : "";
+ const output = execSync(
+ `git commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer}`,
+ options,
+ )
+ .toString()
+ .trim();
+ if (config.print_commit_output) p.log.info(output);
+ } catch (err) {
+ p.log.error("Something went wrong when committing: " + err);
}
}
-function build_commit_string(commit_state: z.infer,
+function build_commit_string(
+ commit_state: z.infer,
config: z.infer,
colorize: boolean = false,
escape_quotes: boolean = false,
- include_trailer: boolean = false
+ include_trailer: boolean = false,
): string {
- let commit_string = '';
+ let commit_string = "";
if (commit_state.type) {
- commit_string += colorize ? color.blue(commit_state.type) : commit_state.type
- }
+ commit_string += colorize
+ ? color.blue(commit_state.type)
+ : commit_state.type;
+ }
if (commit_state.scope) {
- const scope = colorize ? color.cyan(commit_state.scope) : commit_state.scope;
- commit_string += `(${scope})`
+ const scope = colorize
+ ? color.cyan(commit_state.scope)
+ : commit_state.scope;
+ commit_string += `(${scope})`;
}
let title_ticket = commit_state.ticket;
@@ -266,84 +349,122 @@ function build_commit_string(commit_state: z.infer,
if (commit_state.ticket && surround) {
const open_token = surround.charAt(0);
const close_token = surround.charAt(1);
- title_ticket = `${open_token}${commit_state.ticket}${close_token}`
+ title_ticket = `${open_token}${commit_state.ticket}${close_token}`;
}
-
- const position_beginning = config.check_ticket.title_position === 'beginning';
+
+ const position_beginning = config.check_ticket.title_position === "beginning";
if (title_ticket && config.check_ticket.add_to_title && position_beginning) {
commit_string = `${colorize ? color.magenta(title_ticket) : title_ticket} ${commit_string}`;
}
- const position_before_colon = config.check_ticket.title_position === "before-colon"
- if (title_ticket && config.check_ticket.add_to_title && position_before_colon) {
- const spacing = commit_state.scope || (commit_state.type && !config.check_ticket.surround) ? ' ' : '';
- commit_string += colorize ? color.magenta(spacing + title_ticket) : spacing + title_ticket
+ const position_before_colon =
+ config.check_ticket.title_position === "before-colon";
+ if (
+ title_ticket &&
+ config.check_ticket.add_to_title &&
+ position_before_colon
+ ) {
+ const spacing =
+ commit_state.scope || (commit_state.type && !config.check_ticket.surround)
+ ? " "
+ : "";
+ commit_string += colorize
+ ? color.magenta(spacing + title_ticket)
+ : spacing + title_ticket;
}
- if (commit_state.breaking_title && config.breaking_change.add_exclamation_to_title) {
- commit_string += colorize ? color.red('!') : '!'
+ if (
+ commit_state.breaking_title &&
+ config.breaking_change.add_exclamation_to_title
+ ) {
+ commit_string += colorize ? color.red("!") : "!";
}
- if (commit_state.scope || commit_state.type || (title_ticket && position_before_colon)) {
- commit_string += ': '
+ if (
+ commit_state.scope ||
+ commit_state.type ||
+ (title_ticket && position_before_colon)
+ ) {
+ commit_string += ": ";
}
- const position_start = config.check_ticket.title_position === "start"
- const position_end = config.check_ticket.title_position === "end"
- if(title_ticket && config.check_ticket.add_to_title && position_start) {
- commit_string += colorize ? color.magenta(title_ticket) + ' ' : title_ticket + ' '
+ const position_start = config.check_ticket.title_position === "start";
+ const position_end = config.check_ticket.title_position === "end";
+ if (title_ticket && config.check_ticket.add_to_title && position_start) {
+ commit_string += colorize
+ ? color.magenta(title_ticket) + " "
+ : title_ticket + " ";
}
if (commit_state.title) {
- commit_string += colorize ? color.reset(commit_state.title) : commit_state.title
+ commit_string += colorize
+ ? color.reset(commit_state.title)
+ : commit_state.title;
}
- if(title_ticket && config.check_ticket.add_to_title && position_end) {
- commit_string += ' ' + (colorize ? color.magenta(title_ticket) : title_ticket)
+ if (title_ticket && config.check_ticket.add_to_title && position_end) {
+ commit_string +=
+ " " + (colorize ? color.magenta(title_ticket) : title_ticket);
}
if (commit_state.body) {
- const temp = commit_state.body.split('\\n') // literal \n, not new-line.
- const res = temp.map(v => colorize ? color.reset(v.trim()) : v.trim()).join('\n')
+ const temp = commit_state.body.split("\\n"); // literal \n, not new-line.
+ const res = temp
+ .map((v) => (colorize ? color.reset(v.trim()) : v.trim()))
+ .join("\n");
commit_string += colorize ? `\n\n${res}` : `\n\n${res}`;
}
if (commit_state.breaking_title) {
- const title = colorize ? color.red(`BREAKING CHANGE: ${commit_state.breaking_title}`) : `BREAKING CHANGE: ${commit_state.breaking_title}`;
- commit_string += `\n\n${title}`
+ const title = colorize
+ ? color.red(`BREAKING CHANGE: ${commit_state.breaking_title}`)
+ : `BREAKING CHANGE: ${commit_state.breaking_title}`;
+ commit_string += `\n\n${title}`;
}
if (commit_state.breaking_body) {
- const body = colorize ? color.red(commit_state.breaking_body) : commit_state.breaking_body;
- commit_string += `\n\n${body}`
+ const body = colorize
+ ? color.red(commit_state.breaking_body)
+ : commit_state.breaking_body;
+ commit_string += `\n\n${body}`;
}
if (commit_state.deprecates_title) {
- const title = colorize ? color.yellow(`DEPRECATED: ${commit_state.deprecates_title}`) : `DEPRECATED: ${commit_state.deprecates_title}`;
- commit_string += `\n\n${title}`
+ const title = colorize
+ ? color.yellow(`DEPRECATED: ${commit_state.deprecates_title}`)
+ : `DEPRECATED: ${commit_state.deprecates_title}`;
+ commit_string += `\n\n${title}`;
}
if (commit_state.deprecates_body) {
- const body = colorize ? color.yellow(commit_state.deprecates_body) : commit_state.deprecates_body;
- commit_string += `\n\n${body}`
+ const body = colorize
+ ? color.yellow(commit_state.deprecates_body)
+ : commit_state.deprecates_body;
+ commit_string += `\n\n${body}`;
}
if (commit_state.custom_footer) {
- const temp = commit_state.custom_footer.split('\\n')
- const res = temp.map(v => colorize ? color.reset(v.trim()) : v.trim()).join('\n')
+ const temp = commit_state.custom_footer.split("\\n");
+ const res = temp
+ .map((v) => (colorize ? color.reset(v.trim()) : v.trim()))
+ .join("\n");
commit_string += colorize ? `\n\n${res}` : `\n\n${res}`;
}
if (commit_state.closes && commit_state.ticket) {
- commit_string += colorize ? `\n\n${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}` : `\n\n${commit_state.closes} ${commit_state.ticket}`;
+ commit_string += colorize
+ ? `\n\n${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}`
+ : `\n\n${commit_state.closes} ${commit_state.ticket}`;
}
if (include_trailer && commit_state.trailer) {
- commit_string += colorize ? `\n\n${color.dim(commit_state.trailer)}` : `\n\n${commit_state.trailer}`
+ commit_string += colorize
+ ? `\n\n${color.dim(commit_state.trailer)}`
+ : `\n\n${commit_state.trailer}`;
}
if (escape_quotes) {
- commit_string = commit_string.replaceAll('"', '\\"')
+ commit_string = commit_string.replaceAll('"', '\\"');
}
return commit_string;
diff --git a/src/init.ts b/src/init.ts
index 1489349..f605f43 100644
--- a/src/init.ts
+++ b/src/init.ts
@@ -1,20 +1,24 @@
#! /usr/bin/env node
import { Config } from "./zod-state";
-import color from 'picocolors';
-import fs from 'fs'
-import * as p from '@clack/prompts';
+import color from "picocolors";
+import fs from "fs";
+import * as p from "@clack/prompts";
import { CONFIG_FILE_NAME, get_git_root, load_setup } from "./utils";
try {
console.clear();
- p.intro(`${color.bgCyan(color.black(' better-commits-init '))}`)
+ p.intro(`${color.bgCyan(color.black(" better-commits-init "))}`);
const root = get_git_root();
- const root_path = `${root}/${CONFIG_FILE_NAME}`
- const default_config = Config.parse({})
+ const root_path = `${root}/${CONFIG_FILE_NAME}`;
+ const default_config = Config.parse({});
fs.writeFileSync(root_path, JSON.stringify(default_config, null, 4));
- p.log.success(`${color.green('Successfully created .better-commits.json')}`)
- p.outro(`Run ${color.bgBlack(color.white('better-commits'))} to start the CLI`)
+ p.log.success(`${color.green("Successfully created .better-commits.json")}`);
+ p.outro(
+ `Run ${color.bgBlack(color.white("better-commits"))} to start the CLI`,
+ );
} catch (err: any) {
- p.log.error(`${color.red('Could not determine git root folder. better-commits-init must be used in a git repository')}`)
+ p.log.error(
+ `${color.red("Could not determine git root folder. better-commits-init must be used in a git repository")}`,
+ );
}
diff --git a/src/utils.ts b/src/utils.ts
index 6bd12a3..8c31d49 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -10,7 +10,7 @@ import { Config } from "./zod-state";
export const CONFIG_FILE_NAME = ".better-commits.json";
export const SPACE_TO_SELECT = `${color.dim("( to select)")}`;
export const A_FOR_ALL = `${color.dim(
- "( to select, to select all)"
+ "( to select, to select all)",
)}`;
export const OPTIONAL_PROMPT = `${color.dim("(optional)")}`;
export const CACHE_PROMPT = `${color.dim("(value will be saved)")}`;
@@ -27,56 +27,68 @@ export const REGEX_SLASH_NUM = new RegExp(/\/(\d+)/);
export const REGEX_START_NUM = new RegExp(/^(\d+)/);
export const DEFAULT_TYPE_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: "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"
+ trailer: "Changelog: documentation",
},
{
value: "refactor",
label: "refactor",
hint: "A code change that neither fixes a bug nor adds a feature",
emoji: "๐จ",
- trailer: "Changelog: refactor"
+ trailer: "Changelog: refactor",
},
{
value: "perf",
label: "perf",
hint: "A code change that improves performance",
emoji: "๐",
- trailer: "Changelog: performance"
+ trailer: "Changelog: performance",
},
{
value: "test",
label: "test",
hint: "Adding missing tests or correcting existing tests",
emoji: "๐จ",
- trailer: "Changelog: test"
+ trailer: "Changelog: test",
},
{
value: "build",
label: "build",
hint: "Changes that affect the build system or external dependencies",
emoji: "๐ง",
- trailer: "Changelog: build"
+ trailer: "Changelog: build",
},
{
value: "ci",
label: "ci",
hint: "Changes to our CI configuration files and scripts",
emoji: "๐ค",
- trailer: "Changelog: ci"
+ trailer: "Changelog: ci",
},
{
value: "chore",
label: "chore",
hint: "Other changes that do not modify src or test files",
emoji: "๐งน",
- trailer: "Changelog: chore"
+ trailer: "Changelog: chore",
},
{ value: "", label: "none" },
];
@@ -115,15 +127,27 @@ export const Z_FOOTER_OPTIONS = z.enum([
"deprecated",
"custom",
]);
-export const Z_BRANCH_FIELDS = z.enum(["user", "version", "type", "ticket", "description"]);
+export const Z_BRANCH_FIELDS = z.enum([
+ "user",
+ "version",
+ "type",
+ "ticket",
+ "description",
+]);
export const Z_BRANCH_CONFIG_FIELDS = z.enum([
"branch_user",
"branch_version",
"branch_type",
"branch_ticket",
- "branch_description"
+ "branch_description",
]);
-export const BRANCH_ORDER_DEFAULTS: z.infer[] = ["user", "version", "type", "ticket", "description"]
+export const BRANCH_ORDER_DEFAULTS: z.infer[] = [
+ "user",
+ "version",
+ "type",
+ "ticket",
+ "description",
+];
export const Z_BRANCH_ACTIONS = z.enum(["branch", "worktree"]);
export const FOOTER_OPTION_VALUES: z.infer[] = [
"closes",
@@ -143,12 +167,12 @@ export const BRANCH_ACTION_OPTIONS: {
/* LOAD */
export function load_setup(
- cli_name = " better-commits "
+ cli_name = " better-commits ",
): z.infer {
console.clear();
p.intro(`${color.bgCyan(color.black(cli_name))}`);
- let global_config = null
+ let global_config = null;
const home_path = get_default_config_path();
if (fs.existsSync(home_path)) {
p.log.step("Found global config");
@@ -160,18 +184,22 @@ export function load_setup(
if (fs.existsSync(root_path)) {
p.log.step("Found repository config");
const repo_config = read_config_from_path(root_path);
- return global_config ? {
- ...repo_config,
- overrides: global_config.overrides.shell ? global_config.overrides : repo_config.overrides,
- confirm_with_editor: global_config.confirm_with_editor
- } : repo_config
+ return global_config
+ ? {
+ ...repo_config,
+ overrides: global_config.overrides.shell
+ ? global_config.overrides
+ : repo_config.overrides,
+ confirm_with_editor: global_config.confirm_with_editor,
+ }
+ : repo_config;
}
- if (global_config) return global_config
+ if (global_config) return global_config;
const default_config = Config.parse({});
p.log.step(
- "Config not found. Generating default .better-commit.json at $HOME"
+ "Config not found. Generating default .better-commit.json at $HOME",
);
fs.writeFileSync(home_path, JSON.stringify(default_config, null, 4));
return default_config;
@@ -190,7 +218,7 @@ function read_config_from_path(config_path: string) {
}
function validate_config(
- config: z.infer
+ config: z.infer,
): z.infer {
try {
return Config.parse(config);
@@ -234,7 +262,7 @@ export function get_git_root(): string {
path = execSync("git rev-parse --show-toplevel").toString().trim();
} catch (err) {
p.log.warn(
- "Could not find git root. If in a --bare repository, ignore this warning."
+ "Could not find git root. If in a --bare repository, ignore this warning.",
);
}
return path;
diff --git a/src/zod-state.ts b/src/zod-state.ts
index cb2f4c4..a150f64 100644
--- a/src/zod-state.ts
+++ b/src/zod-state.ts
@@ -1,6 +1,6 @@
import { z } from "zod";
import {
- BRANCH_ORDER_DEFAULTS,
+ BRANCH_ORDER_DEFAULTS,
CUSTOM_SCOPE_KEY,
DEFAULT_SCOPE_OPTIONS,
DEFAULT_TYPE_OPTIONS,
@@ -28,7 +28,7 @@ export const Config = z
hint: z.string().optional(),
emoji: z.string().emoji().optional(),
trailer: z.string().optional(),
- })
+ }),
)
.default(DEFAULT_TYPE_OPTIONS),
})
@@ -47,7 +47,7 @@ export const Config = z
(val) => val.options.map((v) => v.value).includes(val.initial_value),
(val) => ({
message: `Type: initial_value "${val.initial_value}" must exist in options`,
- })
+ }),
),
commit_scope: z
.object({
@@ -60,7 +60,7 @@ export const Config = z
value: z.string(),
label: z.string().optional(),
hint: z.string().optional(),
- })
+ }),
)
.default(DEFAULT_SCOPE_OPTIONS),
})
@@ -89,7 +89,7 @@ export const Config = z
},
(val) => ({
message: `Scope: initial_value "${val.initial_value}" must exist in options`,
- })
+ }),
),
check_ticket: z
.object({
@@ -97,9 +97,11 @@ export const Config = z
confirm_ticket: z.boolean().default(true),
add_to_title: z.boolean().default(true),
append_hashtag: z.boolean().default(false),
- prepend_hashtag: z.enum(['Never', 'Always', 'Prompt']).default("Never"),
+ prepend_hashtag: z.enum(["Never", "Always", "Prompt"]).default("Never"),
surround: z.enum(["", "()", "[]", "{}"]).default(""),
- title_position: z.enum(["start", "end", "before-colon", "beginning"]).default("start"),
+ title_position: z
+ .enum(["start", "end", "before-colon", "beginning"])
+ .default("start"),
})
.default({}),
commit_title: z
@@ -186,7 +188,7 @@ export const CommitState = z
deprecates_title: z.string().default(""),
deprecates_body: z.string().default(""),
custom_footer: z.string().default(""),
- trailer: z.string().default("")
+ trailer: z.string().default(""),
})
.default({});
@@ -196,6 +198,6 @@ export const BranchState = z
type: z.string().default(""),
ticket: z.string().default(""),
description: z.string().default(""),
- version: z.string().default("")
+ version: z.string().default(""),
})
.default({});