Ctrl K
ComunidadGuías…

docs/VERSIONING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Versioning Strategy

## Overview

Actual-sync uses **Semantic Versioning 2.0.0** with build metadata for consistent, automated version management across all environments.

## Version Format

### Semantic Versioning Pattern

```
MAJOR.MINOR.PATCH[-PRERELEASE][+METADATA]
```

### Version Examples

| Stage          | Version Format      | Example          | Notes                                           |
|----------------|---------------------|------------------|-------------------------------------------------|
| Stable Release | `MAJOR.MINOR.PATCH` | `1.4.0`          | Clean semantic version from git tags            |
| Dev Build      | `X.Y.Z-dev+HASH`    | `1.4.1-dev+abcdef7` | Pre-release + build metadata (all branches) |
| RC Preview     | `X.Y.Z-rc.N`        | `1.5.0-rc.1`     | Release candidate (optional, manual tagging)    |

### Version Components

- **MAJOR**: Breaking changes (incompatible API changes)
- **MINOR**: New features (backwards compatible)
- **PATCH**: Bug fixes (backwards compatible)
- **PRERELEASE**: `-dev`, `-rc.1`, etc. (indicates unstable/pre-release)
- **METADATA**: `+abcdef7` (commit hash, ignored in version precedence)

## How It Works

### 1. Version Source

The base version is stored in the `VERSION` file at the repository root:

```bash
1.1.0
```

**Fallback**: If `VERSION` doesn't exist, reads from `package.json` → `version` field.

### 2. Version Generation

The `scripts/get_version.sh` script generates versions based on Git state:

#### Stable Release (Tagged)
```bash
# On commit with tag v1.4.0
$ ./scripts/get_version.sh
1.4.0
```

#### Development Build (Any Branch)
```bash
# On main branch (no tag)
$ ./scripts/get_version.sh
1.4.1-dev+71f02b6

# On development branch
$ ./scripts/get_version.sh
1.4.1-dev+a3b8f21

# On feature branch
$ ./scripts/get_version.sh
1.4.1-dev+9cd4e12
```

**Key Points:**
- All non-tagged builds use `-dev+HASH` format
- Build metadata (`+HASH`) includes 7-character commit hash
- Fully compliant with [Semantic Versioning 2.0.0](https://semver.org/)

## Automated Releases

Every successful **CI/CD Pipeline** run on `main` triggers the **Auto Release**
workflow (`.github/workflows/auto-release.yml`), which:

1. Runs only if the CI pipeline **succeeded** (`workflow_run.conclusion == 'success'`) — broken code is never released.
2. **Patch-bumps** the version via `npm run version:bump -- patch`, keeping `VERSION`, `package.json`, and `package-lock.json` in sync.
3. Commits `chore(release): bump version to X.Y.Z`, creates the `vX.Y.Z` tag, and pushes both to `main`.
4. Creates the GitHub Release with auto-generated notes.

**Recursion guard:** the bump commit re-triggers CI; the workflow skips when the
head commit message starts with `chore(release): bump version`, so it releases
once and stops.

**Authentication:** the workflow uses a **GitHub App token** (`APP_ID` /
`APP_PRIVATE_KEY` secrets). The default `GITHUB_TOKEN` is intentionally not used —
tags it pushes do not trigger downstream workflows, so the new `v*` tag would not
trigger the tag-based Docker publish.

> Manual bumps are still possible locally with `npm run release:patch` (or
> `:minor` / `:major`), which run the same `version-bump.js` script. The script
> aborts if your local version is behind the latest released tag (use `--force`
> to override).

### CI/CD Integration

The GitHub Actions workflow automatically handles versioning:

#### For Development Builds (All Branches)
```yaml
- name: Generate dynamic version
  id: version
  run: |
    chmod +x ./scripts/get_version.sh
    VERSION=$(./scripts/get_version.sh)
    echo "version=$VERSION" >> $GITHUB_OUTPUT
```

Output: `1.4.1-dev+abcdef7`

#### For Stable Releases (Tags)
```bash
# Create and push tag
git tag v1.4.0
git push origin v1.4.0
```

The workflow detects the tag and generates clean version: `1.4.0`

### Docker Image Tagging

Docker images are tagged based on the version type:

**Important**: Docker tags don't support the `+` character, so build metadata is converted from `+` to `-` for Docker tags only. The full semver version (with `+`) is preserved in image labels and build args.

#### Development Builds
```bash
# Semver version: 1.4.1-dev+abcdef7
# Docker tags (+ replaced with -)
agigante80/actual-sync:1.4.1-dev-abcdef7
agigante80/actual-sync:dev
ghcr.io/agigante80/actual-sync:1.4.1-dev-abcdef7
ghcr.io/agigante80/actual-sync:dev

# Image label still contains full semver
org.opencontainers.image.version=1.4.1-dev+abcdef7
```

#### Stable Releases
```bash
# Semver version: 1.4.0 (no build metadata)
# Docker tags (unchanged)
agigante80/actual-sync:1.4.0
agigante80/actual-sync:1.4
agigante80/actual-sync:1
agigante80/actual-sync:latest
ghcr.io/agigante80/actual-sync:1.4.0
ghcr.io/agigante80/actual-sync:latest
```

## Version Bumping Workflow

### Manual Bumping (Recommended)

1. **Update VERSION file** (on `main` branch):
   ```bash
   echo "1.5.0" > VERSION
   git add VERSION
   git commit -m "chore: bump version to 1.5.0"
   git push origin main
   ```

2. **Create release tag**:
   ```bash
   git tag v1.5.0
   git push origin v1.5.0
   ```

3. **GitHub Actions automatically**:
   - Builds Docker images with `1.5.0` tags
   - Publishes to Docker Hub and GHCR
   - Creates GitHub Release with changelog
   - Marks images as stable (`:latest` tag)

### Automated Bumping (Optional)

You can automate version bumping on merge to `main`:

```yaml
# .github/workflows/bump-version.yml
name: Auto Bump Version
on:
  push:
    branches:
      - main

jobs:
  bump:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Bump version
        id: bump
        uses: anothrNick/github-tag-action@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DEFAULT_BUMP: patch
          WITH_V: true
          RELEASE_BRANCHES: main
      
      - name: Update VERSION file
        run: |
          echo "${{ steps.bump.outputs.new_tag }}" | sed 's/^v//' > VERSION
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add VERSION
          git commit -m "chore: bump VERSION to ${{ steps.bump.outputs.new_tag }}"
          git push
```

**Note**: This is optional and not currently implemented. Manual control is preferred for production releases.

## Version Bumping Guidelines

### When to Bump MAJOR (X.0.0)

Breaking changes that require user action:

- ❌ Configuration schema changes (removed/renamed fields)
- ❌ Removed API endpoints or features
- ❌ Changed CLI argument behavior
- ❌ Database schema changes requiring migration
- ❌ Docker image breaking changes (new required env vars)

**Example**: `1.4.2` → `2.0.0`

### When to Bump MINOR (x.Y.0)

New features that are backwards compatible:

- ✅ New dashboard tabs or features
- ✅ New notification channels
- ✅ New configuration options (with defaults)
- ✅ New API endpoints
- ✅ New CLI commands
- ✅ Enhanced logging or monitoring

**Example**: `1.4.2` → `1.5.0`

### When to Bump PATCH (x.y.Z)

Bug fixes and minor improvements:

- 🐛 Bug fixes
- 🔒 Security patches
- 📝 Documentation updates
- 🎨 UI/styling tweaks
- ⚡ Performance improvements
- 🔧 Dependency updates (no breaking changes)

**Example**: `1.4.2` → `1.4.3`

## Release Process

### Full Release Workflow

1. **Prepare release branch** (optional for complex releases):
   ```bash
   git checkout -b release/1.5.0
   ```

2. **Update VERSION file**:
   ```bash
   echo "1.5.0" > VERSION
   git add VERSION
   git commit -m "chore: prepare release 1.5.0"
   ```

3. **Update CHANGELOG.md** (if maintained):
   ```markdown
   ## [1.5.0] - 2024-12-10
   
   ### Added
   - New feature X
   - Enhanced Y with Z
   
   ### Fixed
   - Bug in component A
   - Issue with B when C
   ```

4. **Merge to main**:
   ```bash
   git checkout main
   git merge --no-ff release/1.5.0
   git push origin main
   ```

5. **Do not tag by hand.** Once CI is green on `main`, the Auto Release
   workflow tags `v1.5.0` and creates the GitHub Release itself (see
   [Automated releases](#automated-releases)). A tag pushed by hand races it:
   whoever tags second fails, and if the human wins there is no release.

6. **GitHub Actions handles**:
   - ✅ Runs all tests
   - ✅ Tags and creates the GitHub Release with generated notes
   - ✅ Builds Docker images
   - ✅ Publishes to registries, with provenance attestations and the SBOM
     attached to the release (#252)

   A tag pushed by hand (outside this flow) builds and publishes images but
   does **not** create a GitHub Release or attach the SBOM.

### Quick Patch Release

For urgent fixes:

```bash
# 1. Update VERSION
echo "1.4.3" > VERSION
git add VERSION
git commit -m "chore: bump to 1.4.3"
git push origin main

# 2. Nothing else: Auto Release tags v1.4.3 and creates the release
#    once CI on main is green.
```

## Pre-Release Versions (Optional)

For release candidates or beta testing:

```bash
# Create RC tag
git tag v1.5.0-rc.1
git push origin v1.5.0-rc.1
```

Docker images will be tagged as:
```
agigante80/actual-sync:1.5.0-rc.1
```

**Note**: Pre-releases are not tagged as `:latest`.

## Version Verification

### Check Current Version

```bash
# From script
./scripts/get_version.sh

# From package.json
node -p "require('./package.json').version"

# From VERSION file
cat VERSION

# From Docker image
docker run --rm agigante80/actual-sync:latest node -p "require('./package.json').version"
```

### View Git Tags

```bash
# List all tags
git tag -l

# Show latest tag
git describe --tags --abbrev=0

# Show tags for current commit
git tag --points-at HEAD
```

## Troubleshooting

### VERSION File Not Found

**Symptom**: Script falls back to `package.json`

**Solution**: Create VERSION file at repository root
```bash
echo "1.1.0" > VERSION
git add VERSION
git commit -m "chore: add VERSION file"
```

### Wrong Version Generated

**Symptom**: Unexpected version format

**Debug**:
```bash
# Run with verbose logging
VERBOSE=true ./scripts/get_version.sh

# Check git state
git status
git describe --tags --exact-match  # Should show tag if on tagged commit
git rev-parse --short HEAD         # Shows commit hash
```

### Docker Image Has Wrong Version

**Symptom**: Image metadata shows incorrect version

**Verify**:
```bash
# Check image labels (should show full semver with +)
docker inspect agigante80/actual-sync:latest | jq '.[0].Config.Labels."org.opencontainers.image.version"'

# Check package.json in image
docker run --rm agigante80/actual-sync:latest cat package.json | jq .version
```

### Docker Build Fails with "invalid tag" Error

**Symptom**: `ERROR: invalid tag "agigante80/actual-sync:1.1.0-dev+abc123": invalid reference format`

**Cause**: Docker tags cannot contain the `+` character (used in semver build metadata)

**Solution**: The CI/CD workflow automatically converts `+` to `-` for Docker tags:
- Semver version: `1.1.0-dev+abc123`
- Docker tag: `1.1.0-dev-abc123`
- Image label: `org.opencontainers.image.version=1.1.0-dev+abc123` (preserves full semver)

This is handled automatically in `.github/workflows/ci-cd.yml`

## Best Practices

### ✅ Do

- **Keep VERSION file in sync** with tags
- **Use semantic versioning** strictly (MAJOR.MINOR.PATCH)
- **Tag releases** from `main` branch only
- **Write descriptive tag messages** (`git tag -a v1.5.0 -m "..."`)
- **Test before tagging** (ensure CI passes on main)
- **Document breaking changes** in commit messages and releases

### ❌ Don't

- **Don't delete tags** after publishing (breaks version history)
- **Don't reuse tags** (creates confusion and breaks caching)
- **Don't manually edit** version in Docker images
- **Don't skip version numbers** (go 1.4.0 → 1.5.0, not 1.4.0 → 1.6.0)
- **Don't use custom version formats** (stick to semver)

## Integration with Other Tools

### Dependabot / Renovate

Update `package.json` version automatically:

```json
{
  "version": "1.5.0",
  "name": "actual-sync"
}
```

Keep `VERSION` file synced manually or with automation.

### Docker Compose

Pin to specific versions:

```yaml
services:
  actual-sync:
    image: agigante80/actual-sync:1.5.0  # Stable
    # OR
    image: agigante80/actual-sync:dev    # Latest dev build
```

### Kubernetes

Use image pull policy based on version:

```yaml
spec:
  containers:
    - name: actual-sync
      image: agigante80/actual-sync:1.5.0
      imagePullPolicy: IfNotPresent  # Stable versions
      # OR
      image: agigante80/actual-sync:dev
      imagePullPolicy: Always         # Dev builds
```

## References

- [Semantic Versioning 2.0.0](https://semver.org/)
- [GitHub Tags Documentation](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository)
- [Docker Image Tagging Best Practices](https://docs.docker.com/engine/reference/commandline/tag/)
- [Keep a Changelog](https://keepachangelog.com/)

## Quick Reference

| Action | Command |
|--------|---------|
| Get current version | `./scripts/get_version.sh` |
| Update base version | `echo "1.5.0" > VERSION` |
| Create release tag | `git tag v1.5.0 && git push origin v1.5.0` |
| Create RC tag | `git tag v1.5.0-rc.1 && git push origin v1.5.0-rc.1` |
| List all tags | `git tag -l` |
| Delete local tag | `git tag -d v1.5.0` |
| Delete remote tag | `git push origin :refs/tags/v1.5.0` |
| Check tag on commit | `git describe --exact-match` |

---

**Last Updated**: December 2025  
**Versioning Scheme**: Semantic Versioning 2.0.0  
**Current Version**: See `VERSION` file