Automatic Versioning with Lerna and Conventional Commits
October 17, 2025 ยท View on GitHub
This project uses Lerna with Conventional Commits for automatic semantic versioning and changelog generation.
๐ฏ How It Works
When code is merged to the main branch, the CI/CD pipeline automatically:
- Analyzes commit messages since the last release
- Determines version bump based on commit types:
feat:โ Minor version bump (0.x.0)fix:โ Patch version bump (0.0.x)BREAKING CHANGE:โ Major version bump (x.0.0)
- Updates package.json versions in affected packages
- Generates CHANGELOG.md for each package
- Creates git tags for each released package
- Pushes changes back to the repository
- Creates GitHub releases with release notes
- Publishes packages to NPM registry
๐ Commit Message Format
We follow the Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
Commit Types
| Type | Description | Version Bump | Example |
|---|---|---|---|
feat | New feature | Minor (0.x.0) | feat(core): add lazy loading support |
fix | Bug fix | Patch (0.0.x) | fix(testing): resolve mock cleanup issue |
docs | Documentation only | None | docs(readme): update installation guide |
style | Code style changes | None | style: format code with biome |
refactor | Code refactoring | None | refactor(core): simplify container logic |
perf | Performance improvements | Patch (0.0.x) | perf(core): optimize dependency resolution |
test | Adding tests | None | test(cli): add template generation tests |
chore | Maintenance tasks | None | chore: update dependencies |
ci | CI/CD changes | None | ci: add coverage check workflow |
build | Build system changes | None | build: update tsconfig |
Breaking Changes
To trigger a major version bump, include BREAKING CHANGE: in the commit footer:
feat(core)!: redesign module API
BREAKING CHANGE: Module.register() now requires options object instead of individual parameters
Or use the ! suffix after the type/scope:
feat(core)!: redesign module API
๐ Versioning Workflow
Independent Versioning
This monorepo uses independent versioning mode. Each package maintains its own version number and is versioned independently based on its changes.
Example:
- If only
@nexus-ioc/corehas changes โ only@nexus-ioc/coreversion bumps - If both
@nexus-ioc/coreand@nexus-ioc/clihave changes โ both versions bump independently
Version Bump Logic
Current version: 0.4.2
feat: new feature โ 0.5.0 (minor bump)
fix: bug fix โ 0.4.3 (patch bump)
BREAKING CHANGE โ 1.0.0 (major bump)
docs: update docs โ 0.4.2 (no bump)
Multiple Commits
When multiple commits are merged, the highest version bump wins:
Commits:
- fix: resolve bug A
- feat: add feature B
- fix: resolve bug C
Result: Minor bump (0.x.0) because feat > fix
๐ Publishing Process
Automatic Publishing (Recommended)
-
Create feature branch:
git checkout -b feature/my-feature -
Make changes and commit using conventional commits:
git add . npm run commit # Interactive commitizen prompt # OR git commit -m "feat(core): add new feature" -
Push and create PR:
git push origin feature/my-feature -
Merge PR to main:
- Once PR is approved and merged to
main - CI/CD automatically versions and publishes packages
- No manual version updates needed!
- Once PR is approved and merged to
Manual Publishing (Emergency)
If you need to manually publish:
# 1. Version packages
npx lerna version --conventional-commits
# 2. Publish to NPM
npx lerna publish from-git
๐ Configuration
lerna.json
{
"version": "independent",
"command": {
"version": {
"conventionalCommits": true,
"changelogPreset": "angular",
"message": "chore(release): publish %s",
"createRelease": "github"
}
}
}
Key Options
conventionalCommits: true- Enable automatic version determinationchangelogPreset: "angular"- Use Angular commit conventionmessage: "chore(release): publish %s"- Commit message templatecreateRelease: "github"- Automatically create GitHub releases
๐ Examples
Example 1: Bug Fix
# Commit
git commit -m "fix(core): resolve circular dependency detection"
# Result after merge to main:
# - @nexus-ioc/core: 0.4.2 โ 0.4.3 (patch bump)
# - CHANGELOG.md updated with fix
# - Git tag: @nexus-ioc/core@0.4.3
# - Published to NPM
Example 2: New Feature
# Commit
git commit -m "feat(cli): add module scaffolding command"
# Result after merge to main:
# - @nexus-ioc/cli: 0.2.1 โ 0.3.0 (minor bump)
# - CHANGELOG.md updated with feature
# - Git tag: @nexus-ioc/cli@0.3.0
# - Published to NPM
Example 3: Breaking Change
# Commit
git commit -m "feat(core)!: redesign container API
BREAKING CHANGE: Container.get() now returns Promise instead of sync value"
# Result after merge to main:
# - @nexus-ioc/core: 0.4.2 โ 1.0.0 (major bump)
# - CHANGELOG.md updated with breaking change
# - Git tag: @nexus-ioc/core@1.0.0
# - Published to NPM
Example 4: Multiple Packages
# Commits in PR:
git commit -m "feat(core): add lazy loading"
git commit -m "feat(testing): add lazy loading test utilities"
git commit -m "docs(readme): update lazy loading examples"
# Result after merge to main:
# - @nexus-ioc/core: 0.4.2 โ 0.5.0 (minor bump)
# - @nexus-ioc/testing: 0.4.2 โ 0.5.0 (minor bump)
# - @nexus-ioc/cli: 0.2.1 โ 0.2.1 (no change)
# - Both packages published to NPM
๐ ๏ธ Troubleshooting
No Version Bump Occurred
Possible reasons:
- No commits with
feat:,fix:, orBREAKING CHANGE:since last release - Only commits with
docs:,chore:,style:, etc. - Check CI logs for "No version changes detected"
Solution:
- Ensure commits follow conventional commit format
- Use
feat:orfix:for changes that should trigger releases
Version Bump Too Large/Small
Problem: Expected patch but got minor, or vice versa
Solution:
- Review commit messages - ensure correct type is used
feat:always triggers minor bumpfix:always triggers patch bump- Use correct type for your change
Failed to Push Tags
Problem: CI fails at "Push version changes and tags" step
Solution:
- Check GitHub token permissions
- Ensure
GITHUB_TOKENhas write access to repository - Verify branch protection rules allow CI to push
๐ Resources
๐ Best Practices
- Always use conventional commits - Enables automatic versioning
- Be specific in scope - Helps identify which package changed
- Write clear subjects - Appears in CHANGELOG
- Include body for complex changes - Provides context
- Mark breaking changes explicitly - Prevents accidental major bumps
- Review CHANGELOG before release - Ensure it makes sense
- Test in feature branch - CI validates before merge
Questions? Check CONTRIBUTING.md or open an issue.