From 0a3a835e0890e515b85b40f001c7ec1fc9f4124d Mon Sep 17 00:00:00 2001 From: Barry Walker Date: Tue, 13 Jan 2026 19:08:18 -0500 Subject: [PATCH] ci: add centralized versioning with conventional commit bumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .woodpecker/release.yml | 187 ++++++++++++++++++++++++++++++++-------- Directory.Build.props | 26 ++++++ version.json | 3 + 3 files changed, 179 insertions(+), 37 deletions(-) create mode 100644 Directory.Build.props create mode 100644 version.json diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml index 80e0d6c..a4c6205 100644 --- a/.woodpecker/release.yml +++ b/.woodpecker/release.yml @@ -11,66 +11,119 @@ clone: image: woodpeckerci/plugin-git settings: lfs: false - depth: 50 + depth: 0 # Full clone needed for version calculation tags: true steps: - - name: build-and-test - image: mcr.microsoft.com/dotnet/sdk:10.0 - commands: - - dotnet restore - - dotnet build -c Release - - dotnet test -c Release --logger "console;verbosity=detailed" - # Determine next version based on conventional commits - - name: version - image: alpine/git + - 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: $MAJOR.$MINOR.$PATCH" + 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 - BUMP="patch" - if echo "$COMMITS" | grep -qiE "^feat(\(.+\))?!:|BREAKING CHANGE:"; then + + 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" - elif echo "$COMMITS" | grep -qiE "^feat(\(.+\))?:"; then + # 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 - echo "New version: $MAJOR.$MINOR.$PATCH" - echo "$MAJOR.$MINOR.$PATCH" > .version - echo "v$MAJOR.$MINOR.$PATCH" > .tag + + 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 - depends_on: [build-and-test] + + # 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 restore - dotnet build -c Release - dotnet pack PaperlessMCP/PaperlessMCP.csproj -c Release -o ./artifacts /p:Version=$VERSION /p:PackageVersion=$VERSION + dotnet pack PaperlessMCP/PaperlessMCP.csproj -c Release -o ./artifacts /p:Version=$VERSION ls -la ./artifacts/ - depends_on: [version] + depends_on: [build-and-test] # Build and push Docker with Kaniko - name: docker @@ -82,22 +135,54 @@ steps: 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: [version] + /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] - # Create git tag and push + # Commit version.json, create git tag and push - name: git-tag image: alpine/git environment: GITHUB_TOKEN: from_secret: github_token commands: - - echo "Token length $${#GITHUB_TOKEN}" - - 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" && git tag -a "$$TAG" -m "Release $$VERSION" && git push origin "$$TAG" + - | + 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 @@ -107,29 +192,57 @@ steps: GITHUB_TOKEN: from_secret: github_token commands: - - apk add --no-cache curl - | - TAG=$$(cat .tag) - VERSION=$$(cat .version) - echo "Creating GitHub release for $$TAG" + 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 "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\":\"Release $$VERSION\",\"draft\":false,\"prerelease\":false}" + -d "{\"tag_name\":\"${TAG}\",\"name\":\"Release ${VERSION}\",\"body\":\"${BODY}\",\"draft\":false,\"prerelease\":false}" depends_on: [git-tag] - # Deploy to Kubernetes (uses in-cluster service account) + # Deploy to Kubernetes - name: deploy image: bitnami/kubectl:latest commands: - | - VERSION=$$(cat .version) - echo "Deploying version $$VERSION to Kubernetes" + 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 \ + paperless-mcp=ghcr.io/barryw/paperlessmcp:v$VERSION \ -n default # Wait for rollout to complete diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..cd6e9e7 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,26 @@ + + + + + 0.0.0-local + + + <_VersionPrefix>$([System.Text.RegularExpressions.Regex]::Match($(Version), '^\d+\.\d+\.\d+').Value) + <_VersionPrefix Condition="'$(_VersionPrefix)' == ''">0.0.0 + + + $(_VersionPrefix).0 + $(_VersionPrefix).0 + $(Version) + $(Version) + + + latest + false + enable + + diff --git a/version.json b/version.json new file mode 100644 index 0000000..b688b12 --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "version": "0.1.9" +}