home / skills / hitoshura25 / claude-devtools / version-management

version-management skill

/skills/version-management

This skill helps you manage versions across technologies by using Git tags as truth and updating language-specific adapters.

npx playbooks add skill hitoshura25/claude-devtools --skill version-management

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
9.4 KB
---
name: version-management
description: Technology-agnostic version management system with Git tags as source of truth
category: general
version: 1.0.0
inputs:
  - project_path: Path to project root
  - platform: Platform adapter (gradle, npm, python)
outputs:
  - scripts/version-manager.sh
  - scripts/{platform}-version.sh
  - version.properties (for gradle)
verify: "bash scripts/version-manager.sh latest"
---

# Version Management

A technology-agnostic version management system using Git tags as the source of truth, with platform-specific adapters.

## Overview

**Design Philosophy:**
- Git tags are the **source of truth** for versions
- Committed version files (`version.properties`, `package.json`, etc.) cache the version for fast builds
- Release workflows update both git tags and version files
- Supports semantic versioning (major.minor.patch)

## Prerequisites

- Git repository initialized
- Git tags enabled
- Bash shell available
- Write access to project directory

## Inputs

| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| project_path | Yes | . | Project root directory |
| platform | Yes | gradle | Platform adapter: gradle, npm, python |

## Process

### Step 1: Create Core Version Manager Script

Create `scripts/version-manager.sh` (technology-agnostic):

```bash
#!/bin/bash
# Core version manager - technology agnostic
# Handles: tag parsing, semver calculation, version bumping

set -euo pipefail

# === CONFIGURATION ===
BASE_VERSION="${BASE_VERSION:-1.0}"
TAG_PREFIX="${TAG_PREFIX:-v}"

# === CORE FUNCTIONS ===

get_latest_version() {
    local prefix="${1:-$TAG_PREFIX}"
    git tag -l "${prefix}*" 2>/dev/null |
        sed "s/^${prefix}//" |
        sort -V |
        tail -1 || echo "0.0.0"
}

bump_version() {
    local version="$1"
    local bump_type="${2:-patch}"

    IFS='.' read -r major minor patch <<< "$version"
    major="${major:-0}"; minor="${minor:-0}"; patch="${patch:-0}"

    case "$bump_type" in
        major) major=$((major + 1)); minor=0; patch=0 ;;
        minor) minor=$((minor + 1)); patch=0 ;;
        patch) patch=$((patch + 1)) ;;
    esac

    echo "${major}.${minor}.${patch}"
}

generate_version() {
    local bump_type="${1:-patch}"
    local latest
    latest=$(get_latest_version)

    if [[ "$latest" == "0.0.0" ]]; then
        echo "${BASE_VERSION}.0"
    else
        bump_version "$latest" "$bump_type"
    fi
}

output_version() {
    local version="$1"
    local is_prerelease="${2:-false}"

    if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
        echo "version=$version" >> "$GITHUB_OUTPUT"
        echo "is-prerelease=$is_prerelease" >> "$GITHUB_OUTPUT"
        echo "tag=${TAG_PREFIX}${version}" >> "$GITHUB_OUTPUT"
    fi

    echo "$version"
}

main() {
    local command="${1:-generate}"
    local arg="${2:-patch}"

    case "$command" in
        generate) output_version "$(generate_version "$arg")" "false" ;;
        latest)   get_latest_version ;;
        bump)     bump_version "$(get_latest_version)" "$arg" ;;
        *)        echo "Usage: $0 {generate|latest|bump} [patch|minor|major]" >&2; exit 1 ;;
    esac
}

main "$@"
```

Make it executable:

```bash
chmod +x scripts/version-manager.sh
```

### Step 2: Create Platform Adapter

#### For Gradle/Android Projects

Create `scripts/gradle-version.sh`:

```bash
#!/bin/bash
# Gradle/Android version adapter

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/version-manager.sh"

# Convert semver to Android versionCode: "1.2.34" → 1002034
semver_to_version_code() {
    local version="$1"
    IFS='.' read -r major minor patch <<< "$version"
    echo $((major * 1000000 + minor * 1000 + patch))
}

# Update version.properties
update_version_properties() {
    local version="$1"
    local version_code
    version_code=$(semver_to_version_code "$version")

    cat > version.properties << VERSIONEOF
# Auto-generated by release workflow - do not edit manually
VERSION_NAME=$version
VERSION_CODE=$version_code
VERSIONEOF
    echo "Updated version.properties: VERSION_NAME=$version, VERSION_CODE=$version_code"
}

# Output Android-specific values to GitHub Actions
output_android_version() {
    local version="$1"
    local version_code
    version_code=$(semver_to_version_code "$version")

    if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
        echo "version-code=$version_code" >> "$GITHUB_OUTPUT"
    fi
}

main() {
    local command="${1:-generate}"
    shift || true

    case "$command" in
        generate)
            local version
            version=$(generate_version "${1:-patch}")
            output_version "$version" "false"
            output_android_version "$version"
            ;;
        update)
            local version="${1:-}"
            [[ -z "$version" ]] && version=$(get_latest_version)
            update_version_properties "$version"
            ;;
        version-code)
            local version="${1:-}"
            [[ -z "$version" ]] && version=$(get_latest_version)
            semver_to_version_code "$version"
            ;;
        *)
            # Delegate to core
            source "$SCRIPT_DIR/version-manager.sh"
            main "$command" "$@"
            ;;
    esac
}

main "$@"
```

Make it executable:

```bash
chmod +x scripts/gradle-version.sh
```

### Step 3: Create Initial Version File

For Gradle projects, create `version.properties`:

```bash
cat > version.properties << 'EOF'
# Auto-generated by release workflow - do not edit manually
VERSION_NAME=1.0.0
VERSION_CODE=1000000
EOF
```

### Step 4: Update Build Configuration

#### For Gradle Projects

Update `app/build.gradle.kts` to read from version.properties:

```kotlin
import java.util.Properties

// Read version from version.properties (fast, no git commands)
val versionProps = Properties().apply {
    val versionFile = rootProject.file("version.properties")
    if (versionFile.exists()) {
        versionFile.inputStream().use { load(it) }
    }
}

android {
    defaultConfig {
        versionName = versionProps.getProperty("VERSION_NAME", "0.0.1-dev")
        versionCode = versionProps.getProperty("VERSION_CODE", "1")?.toIntOrNull() ?: 1
    }
}
```

**Detection logic:**
- Check if versionName/versionCode already set in build.gradle.kts
- If using a different version scheme, ask user if they want to migrate

### Step 5: Commit Version File

```bash
git add version.properties scripts/
git commit -m "chore: add version management system"
```

## Usage Examples

### Local Development

```bash
# Get current version
./scripts/version-manager.sh latest

# Generate next patch version
./scripts/version-manager.sh generate patch

# Generate next minor version
./scripts/version-manager.sh generate minor

# Generate next major version
./scripts/version-manager.sh generate major
```

### For Gradle Projects

```bash
# Generate version and update version.properties
./scripts/gradle-version.sh generate patch

# Update version.properties with specific version
./scripts/gradle-version.sh update 1.2.3

# Get version code for a version
./scripts/gradle-version.sh version-code 1.2.3
```

### In GitHub Actions

See the release workflow created by `android-playstore-setup` skill for full integration example.

## Verification

**MANDATORY:** Run these commands:

```bash
# Verify scripts exist
test -f scripts/version-manager.sh && echo "✓ Core script exists"
test -f scripts/gradle-version.sh && echo "✓ Gradle adapter exists"

# Verify scripts are executable
test -x scripts/version-manager.sh && echo "✓ Core script is executable"
test -x scripts/gradle-version.sh && echo "✓ Gradle adapter is executable"

# Test version generation
./scripts/version-manager.sh latest
./scripts/version-manager.sh generate patch
```

**Expected output:**
- ✓ Core script exists
- ✓ Gradle adapter exists
- ✓ Core script is executable
- ✓ Gradle adapter is executable
- Version numbers displayed

## Outputs

| Output | Location | Description |
|--------|----------|-------------|
| Core script | scripts/version-manager.sh | Technology-agnostic version logic |
| Gradle adapter | scripts/gradle-version.sh | Android-specific version handling |
| Version cache | version.properties | Cached version for fast builds |
| Build config | app/build.gradle.kts | Updated to read from version file |

## Troubleshooting

### "No tags found"
**Cause:** No git tags exist yet
**Fix:** First version will be 1.0.0 (or BASE_VERSION.0)

### "version.properties not found"
**Cause:** File not created or committed
**Fix:** Run `./scripts/gradle-version.sh update 1.0.0` to create it

### "Permission denied"
**Cause:** Scripts not executable
**Fix:** `chmod +x scripts/*.sh`

## Platform Adapters

### NPM (Future)

For npm projects, create `scripts/npm-version.sh` that updates `package.json`:

```bash
# Updates package.json with new version
update_package_json() {
    local version="$1"
    npm version "$version" --no-git-tag-version
}
```

### Python (Future)

For Python projects, create `scripts/python-version.sh` that updates `__version__`:

```bash
# Updates __init__.py with new version
update_python_version() {
    local version="$1"
    sed -i "s/__version__ = .*/__version__ = \"$version\"/" src/__init__.py
}
```

## Completion Criteria

- [ ] `scripts/version-manager.sh` exists and is executable
- [ ] Platform adapter script exists and is executable
- [ ] Version file created (e.g., `version.properties`)
- [ ] Build configuration updated to read from version file
- [ ] Scripts can generate and bump versions correctly
- [ ] `git tag -l` shows version tags (after first release)

Overview

This skill provides a technology-agnostic version management system that treats Git tags as the source of truth and keeps a committed version file as a fast build cache. It includes a core shell script for semver parsing and bumping plus platform adapters (example: Gradle) to update project-specific version files. The design supports semantic versioning and is easy to integrate into local workflows and CI/CD pipelines.

How this skill works

A core Bash script reads Git tags, calculates the next semantic version (major.minor.patch), and can output or bump versions. Platform adapters consume the core output to update files like version.properties or package.json and to produce platform-specific artifacts such as Android versionCode. Release workflows create Git tags and commit the updated version files so builds can read versions quickly without running Git queries.

When to use it

  • You need a single source of truth for release versions across tools and CI
  • You want fast builds that avoid running Git during every build by reading a cached file
  • You need consistent semantic version bumps (patch/minor/major) driven by automation
  • You want to generate platform-specific metadata (e.g., Android versionCode) automatically
  • You are preparing release automation or GitHub Actions workflows that require a version output

Best practices

  • Keep Git tags as the canonical source of truth and ensure release workflows update tags and version files together
  • Commit the generated version file so CI and local builds can read versionName/versionCode without running Git
  • Make adapter scripts executable and place them under scripts/ to keep project layout consistent
  • Use the generate command in CI to produce the next version, then create a tag and commit the version file
  • Start with a BASE_VERSION to control initial version behavior and ensure predictable first releases

Example use cases

  • Local development: inspect current version or generate the next patch/minor/major
  • Gradle/Android: generate a semver, update version.properties, and compute versionCode for Play Store uploads
  • CI/CD: run generate in a GitHub Action and expose version/tag via GITHUB_OUTPUT for downstream steps
  • Migration: switch projects from ad-hoc version settings to a single cached version file read by build scripts
  • Tooling: add npm or python adapters to update package.json or __init__.py from the same core version logic

FAQ

What happens if no Git tags exist?

The system falls back to BASE_VERSION (default 1.0) and produces an initial version like 1.0.0.

How do I make scripts executable?

Run chmod +x scripts/version-manager.sh scripts/gradle-version.sh (or use a glob for all scripts).