For rapid development, I discovered it’s helpful to update the version with every commit. This approach uses commit message prefixes to automatically bump patch, minor, or major versions, saving valuable time. However, it turned out to be trickier than I initially expected, taking me a few hours to get it right.
I’ve tried automating version updates with typical libraries like standard-version
and various release tools. While these tools promise a lot, I encountered constant errors — whether it was misconfigured CI/CD pipelines or unexpected local behavior. After much trial and error, I finally found a solution that worked like a charm: jusky + Git hooks. I’m sharing it here in the hope it helps someone else.
The Problem: Version Updates That Stick with Commits
My goal was simple yet tricky: I wanted to automatically bump the version in package.json
based on my commit messages—following the Conventional Commits format like feat:
, fix:
, or BREAKING CHANGE:
. More importantly, I needed this version update to be part of the same commit as my changes. Tools like standard-version
are fantastic for creating releases, but they’re not designed for per-commit updates. They often require intricate configurations, especially for CI/CD, which felt like overkill for my local development needs. I wanted something simpler, reliable, and tied directly to my Git workflow.
Why Git Hooks?
Enter Git hooks—small scripts that run at specific points in the Git lifecycle, like before or after a commit. They’re lightweight, built into Git, and perfect for customizing your workflow. For my version update challenge, two hooks stood out:
-
commit-msg
: Runs after you write a commit message, letting you analyze it and make changes before the commit is finalized. -
post-commit
: Runs after the commit is created, allowing you to tweak it further.
Using these hooks together, I devised a solution that updates the version and includes it in the commit—all without external dependencies.
The Solution: A Two-Hook Approach
Here’s how I made it work with a two-step process using commit-msg
and post-commit
hooks. I’ll share the scripts so you can try it yourself.
Step 1: The commit-msg
Hook
This hook reads your commit message, decides how to bump the version, updates package.json
, and stages it. It also adds a temporary marker to signal that the commit needs refinement.
Create a file at .git/hooks/commit-msg
and add this script (make it executable with chmod +x .git/hooks/commit-msg
):
#!/bin/bash
# Read the commit message
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# Function to bump version (simplified for this example)
bump_version() {
CURRENT_VERSION=$(node -p "require('./package.json').version")
IFS='.' read -r -a VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
if echo "$COMMIT_MSG" | grep -q "^BREAKING CHANGE:"; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif echo "$COMMIT_MSG" | grep -q "^feat:"; then
MINOR=$((MINOR + 1))
PATCH=0
elif echo "$COMMIT_MSG" | grep -q "^fix:"; then
PATCH=$((PATCH + 1))
else
exit 0 # No version bump needed
fi
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
# Update package.json
node -e "const pkg = require('./package.json'); pkg.version = '$NEW_VERSION'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
}
# Check if commit message matches Conventional Commits
if echo "$COMMIT_MSG" | grep -q -E "^(feat|fix|BREAKING CHANGE):"; then
bump_version
git add package.json
# Append a marker to the commit message
echo "$COMMIT_MSG" > "$COMMIT_MSG_FILE"
echo "[amend-package-json]" >> "$COMMIT_MSG_FILE"
fi
Step 2: The post-commit
Hook
This hook checks for the marker, removes it, and amends the commit to include the updated package.json
with a clean message.
Create .git/hooks/post-commit
and add (make it executable too):
#!/bin/bash
# Get the latest commit message
COMMIT_MSG=$(git log -1 --pretty=%B)
# Check for the marker
if echo "$COMMIT_MSG" | grep -q "\[amend-package-json\]"; then
# Remove the marker from the message
CLEAN_MSG=$(echo "$COMMIT_MSG" | sed 's/\[amend-package-json\]//')
# Amend the commit with the updated package.json and clean message
git commit --amend --no-verify -m "$CLEAN_MSG"
fi
How It Works
Here’s the flow in action:
- You run
git commit -m "feat: add new feature"
. - The
commit-msg
hook:- Detects
feat:
, bumps the version (e.g.,1.0.0
to1.1.0
). - Updates and stages
package.json
. - Adds
[amend-package-json]
to the commit message.
- Detects
- The initial commit is created with the marker (e.g.,
feat: add new feature [amend-package-json]
). - The
post-commit
hook:- Spots the marker.
- Amends the commit, removing the marker and including the staged
package.json
.
- The final commit contains your changes, the updated
package.json
, and a clean message:feat: add new feature
.
Why a Single Git Hook Won’t Work—and the Issues I Encountered
When I set out to automate version updates in package.json
based on commit messages, I hoped a single Git hook could handle it all. However, I hit a wall due to how Git’s commit process works. I first tried the commit-msg
hook, which runs after the commit message is written but before the commit is finalized—it seemed ideal for analyzing the message and updating the version. The problem? At that point, the commit is already in progress, so any changes I staged, like an updated package.json
, wouldn’t be included in that commit; they’d just sit there for the next one. I experimented with tricks to force it in, but nothing worked. The pre-commit
hook was no better—it runs before the message is even written, so I couldn’t base the version bump on it. After wrestling with these limitations and a lot of trial-and-error, I realized one hook couldn’t cut it. I needed two: commit-msg
to update and stage the file, and post-commit
to amend the commit, ensuring the version update landed in the same commit as intended. It was a frustrating journey, but this combo finally did the trick!
Testing and Validation
To ensure it works, I tested it thoroughly:
-
feat: add something
→ Minor version bump (e.g.,1.0.0
to1.1.0
). -
fix: tweak bug
→ Patch bump (e.g.,1.1.0
to1.1.1
). -
BREAKING CHANGE: overhaul system
→ Major bump (e.g.,1.1.1
to2.0.0
).
After each commit, I checked git log
to confirm the message was clean and git diff
to verify package.json
was included. It worked like a charm every time.
A Note on Edge Cases
This solution assumes single commits. For merge commits or rebases, you might need to tweak the hooks (e.g., skip version bumps during merges). I’ll leave that as an exercise for you, but the core idea remains solid.
Top comments (0)