Skip to main content

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:

  1. Analyzes commits in the MR
  2. Determines version bump based on commit types:
    • fix: PATCH bump
    • feat: MINOR bump (pre-1.0) or PATCH bump (in release branch)
    • BREAKING CHANGE MAJOR bump (or MINOR pre-1.0)
  3. Fetches previous version from git tags
  4. Calculates next version
  5. Creates git tag (e.g., v0.3.5)
  6. Creates GitLab release with auto-generated notes

Conventional Commits Mapping

Commit Type to Version Bump

Commit TypeExampleVersion BumpNotes
fix:fix: resolve authentication bugPATCHBug fixes only
feat:feat: add new API endpointPATCH*Features (in release branch)
feat!:feat!: change API structurePATCH*Breaking (pre-1.0)
BREAKING CHANGE:In commit bodyPATCH*Breaking (pre-1.0)
chore:chore: update dependenciesPATCHMaintenance
docs:docs: update API documentationPATCHDocumentation
refactor:refactor: optimize database queriesPATCHCode refactoring
perf:perf: improve query performancePATCHPerformance
test:test: add integration testsPATCHTesting

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:

  1. Settings Repository Protected Tags
  2. Allow "Maintainers" to create tags matching v*
  3. Or use personal access token with write_repository scope

Best Practices

DO

  1. Use Conventional Commits consistently

    • feat: for features
    • fix: for bug fixes
    • BREAKING CHANGE: for breaking changes
  2. Automate everything

    • Let CI calculate versions
    • Auto-generate changelogs
    • Auto-create releases
  3. Validate commits early

    • Use commitlint hooks
    • Fail MRs with invalid commits
    • Provide clear error messages
  4. Test automation

    • Dry-run semantic-release
    • Test in feature branches
    • Verify changelog generation
  5. Document exceptions

    • Hotfixes
    • Manual version bumps
    • Emergency releases

DON'T

  1. Don't manually edit versions

    • Let CI handle it
    • Avoid manual package.json edits
  2. Don't skip commit validation

    • Always use conventional format
    • Never bypass commitlint
  3. Don't create arbitrary tags

    • Follow release branch pattern
    • Use auto-generated versions only
  4. Don't modify released versions

    • Never delete/recreate tags
    • Release new version for fixes
  5. 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

  1. Merge configuration to main
  2. Create release branch
  3. Test with non-critical MR
  4. Monitor first automated release

Resources

See Also