docs/VERSIONING.md
Rama main · réplica de lectura del repositorio original.
# 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