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>
This commit is contained in:
+150
-37
@@ -11,66 +11,119 @@ clone:
|
|||||||
image: woodpeckerci/plugin-git
|
image: woodpeckerci/plugin-git
|
||||||
settings:
|
settings:
|
||||||
lfs: false
|
lfs: false
|
||||||
depth: 50
|
depth: 0 # Full clone needed for version calculation
|
||||||
tags: true
|
tags: true
|
||||||
|
|
||||||
steps:
|
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
|
# Determine next version based on conventional commits
|
||||||
- name: version
|
- name: calculate-version
|
||||||
image: alpine/git
|
image: alpine
|
||||||
commands:
|
commands:
|
||||||
|
- apk add --no-cache git jq
|
||||||
- |
|
- |
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Get latest tag
|
||||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
echo "Latest tag: $LATEST_TAG"
|
echo "Latest tag: $LATEST_TAG"
|
||||||
|
|
||||||
|
# Parse current version
|
||||||
VERSION=$(echo "$LATEST_TAG" | sed 's/^v//')
|
VERSION=$(echo "$LATEST_TAG" | sed 's/^v//')
|
||||||
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||||
MINOR=$(echo "$VERSION" | cut -d. -f2)
|
MINOR=$(echo "$VERSION" | cut -d. -f2)
|
||||||
PATCH=$(echo "$VERSION" | cut -d. -f3)
|
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
|
if [ "$LATEST_TAG" = "v0.0.0" ]; then
|
||||||
COMMITS=$(git log --pretty=format:"%s" HEAD)
|
COMMITS=$(git log --pretty=format:"%s" HEAD)
|
||||||
else
|
else
|
||||||
COMMITS=$(git log --pretty=format:"%s" "${LATEST_TAG}..HEAD")
|
COMMITS=$(git log --pretty=format:"%s" "${LATEST_TAG}..HEAD")
|
||||||
fi
|
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"
|
BUMP="major"
|
||||||
elif echo "$COMMITS" | grep -qiE "^feat(\(.+\))?:"; then
|
# Check for features (minor bump)
|
||||||
|
elif echo "$COMMITS" | grep -qE "^feat(\(.+\))?:"; then
|
||||||
BUMP="minor"
|
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
|
fi
|
||||||
|
|
||||||
echo "Bump type: $BUMP"
|
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
|
case $BUMP in
|
||||||
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
|
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
|
||||||
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
|
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
|
||||||
patch) PATCH=$((PATCH + 1)) ;;
|
patch) PATCH=$((PATCH + 1)) ;;
|
||||||
esac
|
esac
|
||||||
echo "New version: $MAJOR.$MINOR.$PATCH"
|
|
||||||
echo "$MAJOR.$MINOR.$PATCH" > .version
|
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
|
||||||
echo "v$MAJOR.$MINOR.$PATCH" > .tag
|
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 .version
|
||||||
cat .tag
|
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
|
# Package NuGet
|
||||||
- name: package
|
- name: package
|
||||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
image: mcr.microsoft.com/dotnet/sdk:10.0
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
|
BUMP_TYPE=$(cat .bump-type)
|
||||||
|
if [ "$BUMP_TYPE" = "none" ]; then
|
||||||
|
echo "No version bump, skipping package"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
VERSION=$(cat .version)
|
VERSION=$(cat .version)
|
||||||
echo "Packaging version $VERSION"
|
echo "Packaging version $VERSION"
|
||||||
dotnet restore
|
dotnet pack PaperlessMCP/PaperlessMCP.csproj -c Release -o ./artifacts /p:Version=$VERSION
|
||||||
dotnet build -c Release
|
|
||||||
dotnet pack PaperlessMCP/PaperlessMCP.csproj -c Release -o ./artifacts /p:Version=$VERSION /p:PackageVersion=$VERSION
|
|
||||||
ls -la ./artifacts/
|
ls -la ./artifacts/
|
||||||
depends_on: [version]
|
depends_on: [build-and-test]
|
||||||
|
|
||||||
# Build and push Docker with Kaniko
|
# Build and push Docker with Kaniko
|
||||||
- name: docker
|
- name: docker
|
||||||
@@ -82,22 +135,54 @@ steps:
|
|||||||
from_secret: github_token
|
from_secret: github_token
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
|
BUMP_TYPE=$(cat .bump-type)
|
||||||
|
if [ "$BUMP_TYPE" = "none" ]; then
|
||||||
|
echo "No version bump, skipping docker build"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
VERSION=$(cat .version)
|
VERSION=$(cat .version)
|
||||||
echo "Building Docker image version: $VERSION"
|
echo "Building Docker image version: $VERSION"
|
||||||
mkdir -p /kaniko/.docker
|
mkdir -p /kaniko/.docker
|
||||||
echo "{\"auths\":{\"ghcr.io\":{\"username\":\"$GHCR_USERNAME\",\"password\":\"$GHCR_TOKEN\"}}}" > /kaniko/.docker/config.json
|
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"
|
/kaniko/executor \
|
||||||
depends_on: [version]
|
--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
|
- name: git-tag
|
||||||
image: alpine/git
|
image: alpine/git
|
||||||
environment:
|
environment:
|
||||||
GITHUB_TOKEN:
|
GITHUB_TOKEN:
|
||||||
from_secret: github_token
|
from_secret: github_token
|
||||||
commands:
|
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]
|
depends_on: [package, docker]
|
||||||
|
|
||||||
# Create GitHub release
|
# Create GitHub release
|
||||||
@@ -107,29 +192,57 @@ steps:
|
|||||||
GITHUB_TOKEN:
|
GITHUB_TOKEN:
|
||||||
from_secret: github_token
|
from_secret: github_token
|
||||||
commands:
|
commands:
|
||||||
- apk add --no-cache curl
|
|
||||||
- |
|
- |
|
||||||
TAG=$$(cat .tag)
|
BUMP_TYPE=$(cat .bump-type)
|
||||||
VERSION=$$(cat .version)
|
if [ "$BUMP_TYPE" = "none" ]; then
|
||||||
echo "Creating GitHub release for $$TAG"
|
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 \
|
curl -X POST \
|
||||||
-H "Authorization: token $$GITHUB_TOKEN" \
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
https://api.github.com/repos/barryw/PaperlessMCP/releases \
|
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]
|
depends_on: [git-tag]
|
||||||
|
|
||||||
# Deploy to Kubernetes (uses in-cluster service account)
|
# Deploy to Kubernetes
|
||||||
- name: deploy
|
- name: deploy
|
||||||
image: bitnami/kubectl:latest
|
image: bitnami/kubectl:latest
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
VERSION=$$(cat .version)
|
BUMP_TYPE=$(cat .bump-type)
|
||||||
echo "Deploying version $$VERSION to Kubernetes"
|
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
|
# Update deployment image to specific version tag
|
||||||
kubectl set image deployment/paperless-mcp \
|
kubectl set image deployment/paperless-mcp \
|
||||||
paperless-mcp=ghcr.io/barryw/paperlessmcp:v$$VERSION \
|
paperless-mcp=ghcr.io/barryw/paperlessmcp:v$VERSION \
|
||||||
-n default
|
-n default
|
||||||
|
|
||||||
# Wait for rollout to complete
|
# Wait for rollout to complete
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project>
|
||||||
|
<!--
|
||||||
|
Centralized version management for all projects.
|
||||||
|
Version is set by CI via /p:Version parameter, or defaults to 0.0.0-local for local builds.
|
||||||
|
This ensures all assemblies and packages have consistent versioning.
|
||||||
|
-->
|
||||||
|
<PropertyGroup>
|
||||||
|
<!-- Default version for local development builds -->
|
||||||
|
<Version Condition="'$(Version)' == ''">0.0.0-local</Version>
|
||||||
|
|
||||||
|
<!-- Extract major.minor.patch for AssemblyVersion (doesn't support prerelease tags) -->
|
||||||
|
<_VersionPrefix>$([System.Text.RegularExpressions.Regex]::Match($(Version), '^\d+\.\d+\.\d+').Value)</_VersionPrefix>
|
||||||
|
<_VersionPrefix Condition="'$(_VersionPrefix)' == ''">0.0.0</_VersionPrefix>
|
||||||
|
|
||||||
|
<!-- Apply version to all version-related properties -->
|
||||||
|
<AssemblyVersion>$(_VersionPrefix).0</AssemblyVersion>
|
||||||
|
<FileVersion>$(_VersionPrefix).0</FileVersion>
|
||||||
|
<InformationalVersion>$(Version)</InformationalVersion>
|
||||||
|
<PackageVersion>$(Version)</PackageVersion>
|
||||||
|
|
||||||
|
<!-- Common properties for all projects -->
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "0.1.9"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user