automation
Automated Version Bumping
Overview
The Agent Platform uses automated version bumping based on Conventional Commits to eliminate manual version management. CI/CD pipelines analyze commit messages and automatically determine the next version number following Semantic Versioning rules.
How It Works
1. Developer Workflow
# Developer makes changes and commits using conventional format git commit -m "feat: add OAuth2 authentication" git commit -m "fix: resolve memory leak in agent runtime" git commit -m "feat!: redesign authentication API BREAKING CHANGE: /auth endpoint now requires OAuth2 tokens" # Push to feature branch git push origin 106-add-oauth2-support
2. Merge Request
# Create MR targeting release branch glab mr create --title "Add OAuth2 support" \ --target-branch release/v0.3.x \ --milestone v0.3.x
3. Automatic Version Calculation
When MR merges to release/v0.3.x, CI/CD:
- Analyzes commits in the MR
- Determines version bump based on commit types:
fix:PATCH bumpfeat:MINOR bump (pre-1.0) or PATCH bump (in release branch)BREAKING CHANGEMAJOR bump (or MINOR pre-1.0)
- Fetches previous version from git tags
- Calculates next version
- Creates git tag (e.g.,
v0.3.5) - Creates GitLab release with auto-generated notes
Conventional Commits Mapping
Commit Type to Version Bump
| Commit Type | Example | Version Bump | Notes |
|---|---|---|---|
fix: | fix: resolve authentication bug | PATCH | Bug fixes only |
feat: | feat: add new API endpoint | PATCH* | Features (in release branch) |
feat!: | feat!: change API structure | PATCH* | Breaking (pre-1.0) |
BREAKING CHANGE: | In commit body | PATCH* | Breaking (pre-1.0) |
chore: | chore: update dependencies | PATCH | Maintenance |
docs: | docs: update API documentation | PATCH | Documentation |
refactor: | refactor: optimize database queries | PATCH | Code refactoring |
perf: | perf: improve query performance | PATCH | Performance |
test: | test: add integration tests | PATCH | Testing |
Note: In the Agent Platform's release branch model, all commits merged to a release branch trigger a PATCH bump. MINOR/MAJOR bumps happen when creating new release branches.
Examples
Example 1: Bug Fix
# Commit git commit -m "fix: resolve memory leak in agent runtime" # Result v0.3.4 v0.3.5 (PATCH bump)
Example 2: New Feature
# Commit git commit -m "feat: add distributed tracing support" # Result v0.3.5 v0.3.6 (PATCH bump in release branch)
Example 3: Multiple Commits
# Commits in MR git commit -m "feat: add OAuth2 authentication" git commit -m "test: add OAuth2 integration tests" git commit -m "docs: document OAuth2 configuration" # Result v0.3.6 v0.3.7 (PATCH bump - one version per MR merge)
CI/CD Implementation
GitLab CI Configuration
# .gitlab-ci.yml stages: - validate - test - build - version - release variables: # Semantic versioning configuration RELEASE_BRANCH_PATTERN: '^release/v[0-9]+\.[0-9]+\.x$' VERSION_PREFIX: 'v' # Validate conventional commits validate-commits: stage: validate image: node:20-alpine script: - npm install -g @commitlint/cli @commitlint/config-conventional - | # Get commits in MR git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME COMMITS=$(git log origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME..HEAD --format=%H) # Validate each commit for commit in $COMMITS; do git log --format=%B -n 1 $commit | commitlint done only: - merge_requests # Calculate next version calculate-version: stage: version image: node:20-alpine before_script: - npm install -g semantic-release @semantic-release/git @semantic-release/gitlab @semantic-release/changelog script: - | # Extract release branch version (e.g., release/v0.3.x 0.3) BRANCH_VERSION=$(echo $CI_COMMIT_REF_NAME | sed 's/release\/v\([0-9]*\.[0-9]*\)\.x/\1/') # Get latest tag for this release branch LATEST_TAG=$(git tag -l "v${BRANCH_VERSION}.*" --sort=-v:refname | head -n 1) if [ -z "$LATEST_TAG" ]; then # First release for this branch NEXT_VERSION="v${BRANCH_VERSION}.0" else # Increment patch version PATCH=$(echo $LATEST_TAG | sed 's/v[0-9]*\.[0-9]*\.\([0-9]*\)/\1/') NEXT_PATCH=$((PATCH + 1)) NEXT_VERSION="v${BRANCH_VERSION}.${NEXT_PATCH}" fi echo "Next version: $NEXT_VERSION" echo "NEXT_VERSION=$NEXT_VERSION" >> version.env artifacts: reports: dotenv: version.env only: - /^release\/v[0-9]+\.[0-9]+\.x$/ # Create git tag and release create-release: stage: release image: node:20-alpine before_script: - apk add --no-cache git - npm install -g semantic-release @semantic-release/git @semantic-release/gitlab @semantic-release/changelog script: - | # Configure git git config user.email "ci@agent-platform.io" git config user.name "GitLab CI" # Create and push tag git tag -a $NEXT_VERSION -m "Release $NEXT_VERSION" git push origin $NEXT_VERSION # Generate changelog npx semantic-release --dry-run > /tmp/release-notes.txt # Create GitLab release glab release create $NEXT_VERSION \ --notes-file /tmp/release-notes.txt \ --repo $CI_PROJECT_PATH dependencies: - calculate-version only: - /^release\/v[0-9]+\.[0-9]+\.x$/ when: manual
Semantic Release Configuration
// .releaserc.json { "branches": [ "main", { "name": "release/v+([0-9])?(.{+([0-9]),x}).x", "prerelease": false } ], "plugins": [ [ "@semantic-release/commit-analyzer", { "preset": "conventionalcommits", "releaseRules": [ { "type": "feat", "release": "patch" }, { "type": "fix", "release": "patch" }, { "type": "perf", "release": "patch" }, { "type": "refactor", "release": "patch" }, { "type": "docs", "release": "patch" }, { "type": "chore", "release": "patch" }, { "type": "test", "release": "patch" }, { "breaking": true, "release": "patch" } ] } ], [ "@semantic-release/release-notes-generator", { "preset": "conventionalcommits", "presetConfig": { "types": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, { "type": "perf", "section": "Performance Improvements" }, { "type": "refactor", "section": "Code Refactoring" }, { "type": "docs", "section": "Documentation" }, { "type": "test", "section": "Tests" }, { "type": "chore", "hidden": true } ] } } ], [ "@semantic-release/changelog", { "changelogFile": "CHANGELOG.md", "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." } ], [ "@semantic-release/gitlab", { "gitlabUrl": "https://gitlab.com", "assets": [ { "path": "dist/**/*" } ] } ], [ "@semantic-release/git", { "assets": ["CHANGELOG.md", "package.json"], "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } ] ] }
Tools and Libraries
semantic-release
Primary tool for automating the entire release workflow.
Installation:
npm install --save-dev semantic-release \ @semantic-release/git \ @semantic-release/gitlab \ @semantic-release/changelog \ @semantic-release/commit-analyzer \ @semantic-release/release-notes-generator
Key Features:
- Analyzes commits using Conventional Commits
- Determines next version automatically
- Generates changelog
- Creates git tags
- Publishes GitLab releases
- Updates version in package.json
Usage:
# Dry run (preview) npx semantic-release --dry-run # Execute release npx semantic-release
standard-version
Lighter alternative focused on version bumping and changelog.
Installation:
npm install --save-dev standard-version
Usage:
# Bump version and generate changelog npx standard-version # First release npx standard-version --first-release # Pre-release npx standard-version --prerelease alpha # Dry run npx standard-version --dry-run
commitlint
Validates commit messages against Conventional Commits.
Installation:
npm install --save-dev @commitlint/cli @commitlint/config-conventional
Configuration (commitlint.config.js):
module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', [ 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert', 'ci' ] ], 'subject-case': [2, 'never', ['upper-case']], 'subject-empty': [2, 'never'], 'subject-full-stop': [2, 'never', '.'], 'header-max-length': [2, 'always', 100] } };
Usage:
# Validate last commit echo "$(git log -1 --pretty=%B)" | npx commitlint # Validate commit message echo "feat: add new feature" | npx commitlint
Git Hooks with Husky
Enforce conventional commits at commit time.
Installation:
npm install --save-dev husky npx husky init
Setup (.husky/commit-msg):
#!/bin/sh npx --no-install commitlint --edit $1
Activation:
# Make hook executable chmod +x .husky/commit-msg # Test git commit -m "invalid message" # Will fail git commit -m "feat: valid message" # Will succeed
Release Branch Automation
Creating New Release Branch
#!/bin/bash # create-release-branch.sh NEW_VERSION=$1 # e.g., 0.4 if [ -z "$NEW_VERSION" ]; then echo "Usage: $0 <version>" echo "Example: $0 0.4" exit 1 fi # Create release branch from main git checkout main git pull origin main git checkout -b release/v${NEW_VERSION}.x git push origin release/v${NEW_VERSION}.x # Create milestone in GitLab glab milestone create v${NEW_VERSION}.x \ --title "v${NEW_VERSION}.x Release Series" \ --description "Release branch for v${NEW_VERSION}.x series" echo "Created release branch: release/v${NEW_VERSION}.x" echo "Created milestone: v${NEW_VERSION}.x"
Automatic Patch Versioning
# .gitlab-ci.yml - Simple auto-patch auto-version: stage: version image: alpine/git script: - | # Extract branch version (release/v0.3.x 0.3) BRANCH_VERSION=$(echo $CI_COMMIT_REF_NAME | sed 's/release\/v\([0-9]*\.[0-9]*\)\.x/\1/') # Get latest tag for this release branch LATEST_TAG=$(git tag -l "v${BRANCH_VERSION}.*" --sort=-v:refname | head -n 1) if [ -z "$LATEST_TAG" ]; then # First release NEXT_VERSION="v${BRANCH_VERSION}.0" else # Increment patch PATCH=$(echo $LATEST_TAG | awk -F. '{print $NF}') NEXT_PATCH=$((PATCH + 1)) NEXT_VERSION="v${BRANCH_VERSION}.${NEXT_PATCH}" fi # Create tag git tag $NEXT_VERSION git push origin $NEXT_VERSION echo "Released: $NEXT_VERSION" only: - /^release\/v[0-9]+\.[0-9]+\.x$/
Version Validation
Pre-Merge Validation
# Validate commits in MR validate-semver: stage: validate image: node:20-alpine script: - npm install -g @commitlint/cli @commitlint/config-conventional - | # Get commits in MR git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME COMMITS=$(git log origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME..HEAD --format=%H) echo "Validating commits against Conventional Commits specification..." INVALID=0 for commit in $COMMITS; do MESSAGE=$(git log --format=%B -n 1 $commit) echo "Checking commit: $commit" echo "$MESSAGE" | commitlint || INVALID=1 done if [ $INVALID -eq 1 ]; then echo "" echo "ERROR: Some commits do not follow Conventional Commits format" echo "See: https://www.conventionalcommits.org/" exit 1 fi echo "All commits are valid!" only: - merge_requests
Version Bump Validation
# Ensure version increases correctly validate-version-bump: stage: validate image: node:20-alpine before_script: - npm install -g semver script: - | # Get previous and new version PREV_VERSION=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "v0.0.0") NEW_VERSION=$CI_COMMIT_TAG # Validate format if ! echo "$NEW_VERSION" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'; then echo "ERROR: Tag $NEW_VERSION does not match SemVer format (vX.Y.Z)" exit 1 fi # Validate increment if ! npx semver $NEW_VERSION -r ">$PREV_VERSION"; then echo "ERROR: New version $NEW_VERSION must be greater than $PREV_VERSION" exit 1 fi # Validate branch consistency BRANCH_VERSION=$(echo $CI_COMMIT_REF_NAME | sed 's/release\/v\([0-9]*\.[0-9]*\)\.x/\1/') TAG_VERSION=$(echo $NEW_VERSION | sed 's/v\([0-9]*\.[0-9]*\)\..*/\1/') if [ "$BRANCH_VERSION" != "$TAG_VERSION" ]; then echo "ERROR: Tag $NEW_VERSION does not match branch release/v${BRANCH_VERSION}.x" exit 1 fi echo "Version $NEW_VERSION is valid!" only: - tags
Troubleshooting
Issue: Commits Not Following Convention
Problem:
git commit -m "fixed a bug" # Invalid
Solution:
# Use correct format git commit -m "fix: resolve authentication bug" # Valid # Or amend last commit git commit --amend -m "fix: resolve authentication bug"
Prevention: Install commitlint hook:
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional npx husky init echo 'npx --no-install commitlint --edit $1' > .husky/commit-msg chmod +x .husky/commit-msg
Issue: Wrong Version Bump
Problem:
feat: add feature PATCH bump (expected MINOR)
Solution: In release branch model, all merges = PATCH bump. For MINOR/MAJOR, create new release branch:
# Create new release branch for v0.4.x git checkout main git checkout -b release/v0.4.x git push origin release/v0.4.x
Issue: Version Already Exists
Problem:
ERROR: Tag v0.3.5 already exists
Solution:
# Check existing tags git tag -l "v0.3.*" # Version was already created, fetch it git fetch --tags # CI will calculate next available version (v0.3.6)
Issue: CI Cannot Push Tags
Problem:
ERROR: Permission denied (protected tags)
Solution: Configure CI/CD permissions in GitLab:
- Settings Repository Protected Tags
- Allow "Maintainers" to create tags matching
v* - Or use personal access token with
write_repositoryscope
Best Practices
DO
-
Use Conventional Commits consistently
feat:for featuresfix:for bug fixesBREAKING CHANGE:for breaking changes
-
Automate everything
- Let CI calculate versions
- Auto-generate changelogs
- Auto-create releases
-
Validate commits early
- Use commitlint hooks
- Fail MRs with invalid commits
- Provide clear error messages
-
Test automation
- Dry-run semantic-release
- Test in feature branches
- Verify changelog generation
-
Document exceptions
- Hotfixes
- Manual version bumps
- Emergency releases
DON'T
-
Don't manually edit versions
- Let CI handle it
- Avoid manual package.json edits
-
Don't skip commit validation
- Always use conventional format
- Never bypass commitlint
-
Don't create arbitrary tags
- Follow release branch pattern
- Use auto-generated versions only
-
Don't modify released versions
- Never delete/recreate tags
- Release new version for fixes
-
Don't mix manual and automated
- Choose one strategy
- Stick with it consistently
Migration from Manual Versioning
Step 1: Audit Current Versions
# List all existing tags git tag -l | sort -V # Check for inconsistencies git tag -l | grep -v '^v[0-9]*\.[0-9]*\.[0-9]*$'
Step 2: Clean Up Tags (if needed)
# Delete invalid tags (backup first!) git tag -l | grep -v '^v[0-9]*\.[0-9]*\.[0-9]*$' | xargs git tag -d git push origin --delete $(git tag -l | grep -v '^v[0-9]*\.[0-9]*\.[0-9]*$')
Step 3: Install Dependencies
npm install --save-dev \ semantic-release \ @semantic-release/git \ @semantic-release/gitlab \ @semantic-release/changelog \ @commitlint/cli \ @commitlint/config-conventional \ husky
Step 4: Configure Tools
Create .releaserc.json, commitlint.config.js, and update .gitlab-ci.yml.
Step 5: Test with Dry Run
# Test semantic-release npx semantic-release --dry-run # Check what version would be created npx semantic-release --dry-run | grep "next release version"
Step 6: Enable Automation
- Merge configuration to main
- Create release branch
- Test with non-critical MR
- Monitor first automated release
Resources
- semantic-release Documentation
- Conventional Commits Specification
- commitlint Documentation
- GitLab CI/CD Documentation
- Agent Platform SemVer Guide
- Release Branch Strategy