Semantic Versioning
Automate version management and changelog generation following SemVer principles.
When to Use This Skill
Use this skill when:
-
Implementing version numbering standards
-
Automating release versioning
-
Generating changelogs automatically
-
Setting up release pipelines
-
Managing package versions
Prerequisites
-
Git repository with commit history
-
Node.js (for most tools)
-
Conventional commits (recommended)
Semantic Versioning Basics
Version Format
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
Examples: 1.0.0 2.1.3 1.0.0-alpha.1 1.0.0-beta.2+build.123
Version Components
Component When to Increment
MAJOR Breaking changes (incompatible API changes)
MINOR New features (backward compatible)
PATCH Bug fixes (backward compatible)
PRERELEASE Pre-release versions (alpha, beta, rc)
BUILD Build metadata (ignored in precedence)
Version Precedence
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 < 2.0.0
Conventional Commits to Version
Commit Type → Version Bump: feat: → MINOR fix: → PATCH docs: → PATCH (or no release) style: → PATCH (or no release) refactor: → PATCH perf: → PATCH test: → No release chore: → No release
BREAKING CHANGE: → MAJOR feat!: → MAJOR fix!: → MAJOR
semantic-release
Installation
npm install --save-dev semantic-release
@semantic-release/changelog
@semantic-release/git
Configuration
// .releaserc.json { "branches": ["main"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", ["@semantic-release/changelog", { "changelogFile": "CHANGELOG.md" }], ["@semantic-release/npm", { "npmPublish": true }], ["@semantic-release/git", { "assets": ["CHANGELOG.md", "package.json", "package-lock.json"], "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" }], "@semantic-release/github" ] }
Advanced Configuration
// release.config.js module.exports = { branches: [ 'main', { name: 'beta', prerelease: true }, { name: 'alpha', prerelease: true } ], plugins: [ ['@semantic-release/commit-analyzer', { preset: 'angular', releaseRules: [ { type: 'docs', release: 'patch' }, { type: 'refactor', release: 'patch' }, { type: 'style', release: 'patch' }, { type: 'perf', release: 'patch' }, { breaking: true, release: 'major' } ] }], ['@semantic-release/release-notes-generator', { preset: 'angular', writerOpts: { commitsSort: ['subject', 'scope'] } }], '@semantic-release/changelog', '@semantic-release/npm', '@semantic-release/git', '@semantic-release/github' ] };
GitHub Actions Integration
.github/workflows/release.yml
name: Release
on: push: branches: [main]
jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
standard-version
Installation
npm install --save-dev standard-version
Configuration
// .versionrc.json { "types": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, { "type": "docs", "section": "Documentation" }, { "type": "style", "section": "Styling" }, { "type": "refactor", "section": "Code Refactoring" }, { "type": "perf", "section": "Performance" }, { "type": "test", "section": "Tests" }, { "type": "chore", "section": "Maintenance" } ], "skip": { "bump": false, "changelog": false, "commit": false, "tag": false }, "commitUrlFormat": "https://github.com/owner/repo/commit/{{hash}}", "compareUrlFormat": "https://github.com/owner/repo/compare/{{previousTag}}...{{currentTag}}" }
Usage
First release
npx standard-version --first-release
Regular release (auto-detect version bump)
npx standard-version
Specific version bump
npx standard-version --release-as minor npx standard-version --release-as 1.1.0
Pre-release
npx standard-version --prerelease alpha npx standard-version --prerelease beta
Dry run
npx standard-version --dry-run
Skip specific steps
npx standard-version --skip.changelog
NPM Scripts
// package.json { "scripts": { "release": "standard-version", "release:minor": "standard-version --release-as minor", "release:major": "standard-version --release-as major", "release:alpha": "standard-version --prerelease alpha", "release:beta": "standard-version --prerelease beta", "release:dry": "standard-version --dry-run" } }
Changelog Generation
conventional-changelog
Install
npm install -g conventional-changelog-cli
Generate changelog
conventional-changelog -p angular -i CHANGELOG.md -s
Generate all history
conventional-changelog -p angular -i CHANGELOG.md -s -r 0
git-cliff
Install
cargo install git-cliff
Generate changelog
git cliff -o CHANGELOG.md
cliff.toml
[changelog] header = "# Changelog\n\n" body = """ {% for group, commits in commits | group_by(attribute="group") %}
{{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }}
{% endfor %} {% endfor %} """ trim = true
[git] conventional_commits = true filter_unconventional = true commit_preprocessors = [ { pattern = '((\w+)\s#([0-9]+))', replace = "(#${2})" }, ] commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Bug Fixes" }, { message = "^doc", group = "Documentation" }, { message = "^perf", group = "Performance" }, { message = "^refactor", group = "Refactoring" }, { message = "^style", group = "Styling" }, { message = "^test", group = "Testing" }, { message = "^chore", group = "Miscellaneous" }, ] filter_commits = true tag_pattern = "v[0-9]*"
Version Bumping Scripts
Bash Script
#!/bin/bash
bump-version.sh
CURRENT_VERSION=$(cat package.json | jq -r '.version') echo "Current version: $CURRENT_VERSION"
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
case $1 in major) NEW_VERSION="$((MAJOR + 1)).0.0" ;; minor) NEW_VERSION="$MAJOR.$((MINOR + 1)).0" ;; patch) NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" ;; *) echo "Usage: $0 {major|minor|patch}" exit 1 ;; esac
echo "New version: $NEW_VERSION"
Update package.json
npm version $NEW_VERSION --no-git-tag-version
Create git tag
git add package.json package-lock.json git commit -m "chore: bump version to $NEW_VERSION" git tag -a "v$NEW_VERSION" -m "Version $NEW_VERSION"
Python Script
#!/usr/bin/env python3
bump_version.py
import re import sys import subprocess
def get_current_version(): with open('setup.py', 'r') as f: content = f.read() match = re.search(r"version='"['"]", content) return match.group(1) if match else None
def bump_version(current, bump_type): major, minor, patch = map(int, current.split('.'))
if bump_type == 'major':
return f'{major + 1}.0.0'
elif bump_type == 'minor':
return f'{major}.{minor + 1}.0'
elif bump_type == 'patch':
return f'{major}.{minor}.{patch + 1}'
else:
raise ValueError(f'Invalid bump type: {bump_type}')
def update_version(old_version, new_version): with open('setup.py', 'r') as f: content = f.read()
content = content.replace(f"version='{old_version}'", f"version='{new_version}'")
with open('setup.py', 'w') as f:
f.write(content)
if name == 'main': bump_type = sys.argv[1] if len(sys.argv) > 1 else 'patch' current = get_current_version() new = bump_version(current, bump_type)
print(f'Bumping version: {current} → {new}')
update_version(current, new)
subprocess.run(['git', 'add', 'setup.py'])
subprocess.run(['git', 'commit', '-m', f'chore: bump version to {new}'])
subprocess.run(['git', 'tag', '-a', f'v{new}', '-m', f'Version {new}'])
Multi-Package Versioning
Lerna
// lerna.json { "version": "independent", "npmClient": "npm", "command": { "version": { "conventionalCommits": true, "message": "chore(release): publish" }, "publish": { "conventionalCommits": true } } }
Version all changed packages
npx lerna version
Publish all changed packages
npx lerna publish
Changesets
Initialize
npx @changesets/cli init
Add changeset
npx changeset add
Version packages
npx changeset version
Publish
npx changeset publish
Common Issues
Issue: No Version Bump
Problem: semantic-release not creating release Solution: Check commit format, verify branch configuration
Issue: Wrong Version Calculated
Problem: Major/minor/patch incorrectly determined Solution: Review commit analyzer rules, check for missing prefixes
Issue: Duplicate Tags
Problem: Tag already exists Solution: Clean up tags, verify version wasn't already released
Best Practices
-
Use conventional commits consistently
-
Automate version bumping in CI
-
Generate changelogs automatically
-
Tag releases in Git
-
Use pre-release versions for testing
-
Document breaking changes clearly
-
Include migration guides for major versions
-
Lock dependencies with exact versions
Related Skills
-
git-workflow - Branching strategies
-
github-actions - CI automation
-
feature-flags - Progressive rollout