Files
PaperlessMCP/.woodpecker/release.yml
T
Barry Walker 0a3a835e08 ci: add centralized versioning with conventional commit bumps
- Add Directory.Build.props for consistent version across all projects
- Add version.json to track current version in source control
- Update release pipeline to:
  - Calculate version bumps from conventional commits
  - Pass /p:Version to all build/test/pack commands
  - Commit version.json back before tagging
  - Generate changelog in GitHub releases
- Version bump rules:
  - feat!: or BREAKING CHANGE: → major bump
  - feat: → minor bump
  - fix/perf/refactor/build/ci/docs/style/test: → patch bump

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 19:08:18 -05:00

253 lines
7.9 KiB
YAML

# Release Pipeline - Build, test, version, package, release
when:
- event: push
branch: main
labels:
platform: linux/amd64
clone:
- name: clone
image: woodpeckerci/plugin-git
settings:
lfs: false
depth: 0 # Full clone needed for version calculation
tags: true
steps:
# Determine next version based on conventional commits
- name: calculate-version
image: alpine
commands:
- apk add --no-cache git jq
- |
set -e
# Get latest tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Latest tag: $LATEST_TAG"
# Parse current version
VERSION=$(echo "$LATEST_TAG" | sed 's/^v//')
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
echo "Current version: $MAJOR.$MINOR.$PATCH"
# Get commits since last tag
if [ "$LATEST_TAG" = "v0.0.0" ]; then
COMMITS=$(git log --pretty=format:"%s" HEAD)
else
COMMITS=$(git log --pretty=format:"%s" "${LATEST_TAG}..HEAD")
fi
echo "Commits since last tag:"
echo "$COMMITS"
# Determine bump type from conventional commits
BUMP="none"
# Check for breaking changes (major bump)
if echo "$COMMITS" | grep -qE "^[a-z]+(\(.+\))?!:|BREAKING CHANGE:"; then
BUMP="major"
# Check for features (minor bump)
elif echo "$COMMITS" | grep -qE "^feat(\(.+\))?:"; then
BUMP="minor"
# Check for fixes or other conventional commits (patch bump)
elif echo "$COMMITS" | grep -qE "^(fix|perf|refactor|build|ci|docs|style|test)(\(.+\))?:"; then
BUMP="patch"
fi
echo "Bump type: $BUMP"
# Exit if no version bump needed
if [ "$BUMP" = "none" ]; then
echo "No conventional commits found, skipping version bump"
echo "none" > .bump-type
exit 0
fi
# Calculate new version
case $BUMP in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "New version: $NEW_VERSION"
# Write version files for subsequent steps
echo "$NEW_VERSION" > .version
echo "v$NEW_VERSION" > .tag
echo "$BUMP" > .bump-type
# Update version.json
echo "{\"version\": \"$NEW_VERSION\"}" > version.json
cat .version
cat .tag
# Build and test with the new version
- name: build-and-test
image: mcr.microsoft.com/dotnet/sdk:10.0
commands:
- |
BUMP_TYPE=$(cat .bump-type)
if [ "$BUMP_TYPE" = "none" ]; then
echo "No version bump, building with current version"
dotnet restore
dotnet build -c Release
dotnet test -c Release --logger "console;verbosity=detailed"
else
VERSION=$(cat .version)
echo "Building version $VERSION"
dotnet restore
dotnet build -c Release /p:Version=$VERSION
dotnet test -c Release /p:Version=$VERSION --logger "console;verbosity=detailed"
fi
depends_on: [calculate-version]
# Package NuGet
- name: package
image: mcr.microsoft.com/dotnet/sdk:10.0
commands:
- |
BUMP_TYPE=$(cat .bump-type)
if [ "$BUMP_TYPE" = "none" ]; then
echo "No version bump, skipping package"
exit 0
fi
VERSION=$(cat .version)
echo "Packaging version $VERSION"
dotnet pack PaperlessMCP/PaperlessMCP.csproj -c Release -o ./artifacts /p:Version=$VERSION
ls -la ./artifacts/
depends_on: [build-and-test]
# Build and push Docker with Kaniko
- name: docker
image: gcr.io/kaniko-project/executor:debug
environment:
GHCR_USERNAME:
from_secret: github_username
GHCR_TOKEN:
from_secret: github_token
commands:
- |
BUMP_TYPE=$(cat .bump-type)
if [ "$BUMP_TYPE" = "none" ]; then
echo "No version bump, skipping docker build"
exit 0
fi
VERSION=$(cat .version)
echo "Building Docker image version: $VERSION"
mkdir -p /kaniko/.docker
echo "{\"auths\":{\"ghcr.io\":{\"username\":\"$GHCR_USERNAME\",\"password\":\"$GHCR_TOKEN\"}}}" > /kaniko/.docker/config.json
/kaniko/executor \
--context="$CI_WORKSPACE/PaperlessMCP" \
--dockerfile="$CI_WORKSPACE/PaperlessMCP/Dockerfile" \
--destination="ghcr.io/barryw/paperlessmcp:v$VERSION" \
--destination="ghcr.io/barryw/paperlessmcp:latest" \
--build-arg="VERSION=$VERSION"
depends_on: [build-and-test]
# Commit version.json, create git tag and push
- name: git-tag
image: alpine/git
environment:
GITHUB_TOKEN:
from_secret: github_token
commands:
- |
BUMP_TYPE=$(cat .bump-type)
if [ "$BUMP_TYPE" = "none" ]; then
echo "No version bump, skipping git tag"
exit 0
fi
TAG=$(cat .tag)
VERSION=$(cat .version)
echo "Creating tag $TAG for version $VERSION"
git config user.email "ci@woodpecker.local"
git config user.name "Woodpecker CI"
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/barryw/PaperlessMCP.git"
# Commit version.json update
git add version.json
git diff --staged --quiet || git commit -m "chore(release): bump version to $VERSION [skip ci]"
git push origin HEAD:main
# Create and push tag
git tag -a "$TAG" -m "Release $VERSION"
git push origin "$TAG"
depends_on: [package, docker]
# Create GitHub release
- name: release
image: alpine
environment:
GITHUB_TOKEN:
from_secret: github_token
commands:
- |
BUMP_TYPE=$(cat .bump-type)
if [ "$BUMP_TYPE" = "none" ]; then
echo "No version bump, skipping release"
exit 0
fi
apk add --no-cache curl git
TAG=$(cat .tag)
VERSION=$(cat .version)
LATEST_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "v0.0.0")
echo "Creating GitHub release for $TAG"
# Generate changelog from commits
if [ "$LATEST_TAG" = "v0.0.0" ]; then
CHANGELOG=$(git log --pretty=format:"- %s" HEAD | head -20)
else
CHANGELOG=$(git log --pretty=format:"- %s" "${LATEST_TAG}..HEAD^" | head -20)
fi
# Escape JSON special characters
CHANGELOG_JSON=$(echo "$CHANGELOG" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
BODY="## What's Changed\\n\\n${CHANGELOG_JSON}\\n\\n**Full Changelog**: https://github.com/barryw/PaperlessMCP/compare/${LATEST_TAG}...${TAG}"
curl -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/barryw/PaperlessMCP/releases \
-d "{\"tag_name\":\"${TAG}\",\"name\":\"Release ${VERSION}\",\"body\":\"${BODY}\",\"draft\":false,\"prerelease\":false}"
depends_on: [git-tag]
# Deploy to Kubernetes
- name: deploy
image: bitnami/kubectl:latest
commands:
- |
BUMP_TYPE=$(cat .bump-type)
if [ "$BUMP_TYPE" = "none" ]; then
echo "No version bump, skipping deploy"
exit 0
fi
VERSION=$(cat .version)
echo "Deploying version $VERSION to Kubernetes"
# Update deployment image to specific version tag
kubectl set image deployment/paperless-mcp \
paperless-mcp=ghcr.io/barryw/paperlessmcp:v$VERSION \
-n default
# Wait for rollout to complete
kubectl rollout status deployment/paperless-mcp -n default --timeout=120s
echo "Deployment complete!"
depends_on: [docker, release]