feat: add trunk-based CI/CD with automatic versioning

- Implement conventional commits for semantic versioning
- PRs: build, test, Docker verify
- Merges to main: full release pipeline
  - Automatic version bump based on commit types
  - NuGet packaging
  - Multi-arch Docker build and push to GHCR
  - Git tag creation
  - GitHub release

Add CONTRIBUTING.md with:
- Trunk-based development workflow
- Conventional commits guide
- Local development instructions
This commit is contained in:
Barry Walker
2026-01-13 14:18:31 -05:00
parent 088bf244f4
commit 2c1ed449da
3 changed files with 293 additions and 83 deletions
+124 -68
View File
@@ -2,53 +2,124 @@ variables:
- &dotnet_image "mcr.microsoft.com/dotnet/sdk:10.0-preview" - &dotnet_image "mcr.microsoft.com/dotnet/sdk:10.0-preview"
- &docker_image "woodpeckerci/plugin-docker-buildx" - &docker_image "woodpeckerci/plugin-docker-buildx"
# =============================================================================
# PULL REQUESTS - Build and test only
# =============================================================================
when: when:
- event: [push, pull_request, tag] - event: pull_request
steps:
pr-restore:
image: *dotnet_image
commands:
- dotnet restore
pr-build:
image: *dotnet_image
commands:
- dotnet build --no-restore -c Release
depends_on: [pr-restore]
pr-test:
image: *dotnet_image
commands:
- dotnet test --no-build -c Release --logger "console;verbosity=detailed"
depends_on: [pr-build]
pr-docker-verify:
image: *docker_image
settings:
repo: ghcr.io/barryw/paperlessmcp
dockerfile: PaperlessMCP/Dockerfile
context: PaperlessMCP
dry_run: true
depends_on: [pr-test]
---
# =============================================================================
# MAIN BRANCH - Build, test, version, release
# =============================================================================
when:
- event: push
branch: main
steps: steps:
# Restore dependencies
restore: restore:
image: *dotnet_image image: *dotnet_image
commands: commands:
- dotnet restore - dotnet restore
# Build the project
build: build:
image: *dotnet_image image: *dotnet_image
commands: commands:
- dotnet build --no-restore -c Release - dotnet build --no-restore -c Release
depends_on: [restore] depends_on: [restore]
# Run tests
test: test:
image: *dotnet_image image: *dotnet_image
commands: commands:
- dotnet test --no-build -c Release --logger "console;verbosity=detailed" - dotnet test --no-build -c Release --logger "console;verbosity=detailed"
depends_on: [build] depends_on: [build]
# Determine version from git tags # Determine next version based on conventional commits
version: version:
image: alpine/git image: alpine/git
commands: commands:
- apk add --no-cache bash
- | - |
if [ -n "$CI_COMMIT_TAG" ]; then # Get the latest tag or default to v0.0.0
# Use tag as version (strip 'v' prefix if present)
VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//')
else
# Generate dev version from branch and short SHA
BRANCH=$(echo "$CI_COMMIT_BRANCH" | sed 's/[^a-zA-Z0-9]/-/g')
SHORT_SHA=$(echo "$CI_COMMIT_SHA" | cut -c1-7)
# Get latest tag or default to 0.0.0
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")
BASE_VERSION=$(echo "$LATEST_TAG" | sed 's/^v//') echo "Latest tag: $LATEST_TAG"
VERSION="${BASE_VERSION}-dev.${BRANCH}.${SHORT_SHA}"
# Parse current version
VERSION=${LATEST_TAG#v}
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# 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 fi
echo "$VERSION" > .version
echo "Building version: $VERSION" # Determine bump type from conventional commits
BUMP="patch"
if echo "$COMMITS" | grep -qiE "^feat(\(.+\))?!:|BREAKING CHANGE:"; then
BUMP="major"
elif echo "$COMMITS" | grep -qiE "^feat(\(.+\))?:"; then
BUMP="minor"
fi
echo "Bump type: $BUMP"
# 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"
# Save version for other steps
echo "$NEW_VERSION" > .version
echo "v${NEW_VERSION}" > .tag
depends_on: [test] depends_on: [test]
# Package as NuGet (for library distribution) # Package NuGet
package-nuget: package:
image: *dotnet_image image: *dotnet_image
commands: commands:
- VERSION=$(cat .version) - VERSION=$(cat .version)
@@ -56,12 +127,19 @@ steps:
- dotnet pack PaperlessMCP/PaperlessMCP.csproj --no-build -c Release -o ./artifacts /p:Version=$VERSION /p:PackageVersion=$VERSION - dotnet pack PaperlessMCP/PaperlessMCP.csproj --no-build -c Release -o ./artifacts /p:Version=$VERSION /p:PackageVersion=$VERSION
- ls -la ./artifacts/ - ls -la ./artifacts/
depends_on: [version] depends_on: [version]
when:
- event: [push, tag]
branch: main
# Build and push Docker image (main branch - dev tag) # Prepare Docker tags
docker-dev: docker-tags:
image: alpine
commands:
- VERSION=$(cat .version)
- echo "latest" > .docker-tags
- echo "v${VERSION}" >> .docker-tags
- cat .docker-tags
depends_on: [version]
# Build and push Docker
docker:
image: *docker_image image: *docker_image
settings: settings:
repo: ghcr.io/barryw/paperlessmcp repo: ghcr.io/barryw/paperlessmcp
@@ -70,54 +148,34 @@ steps:
platforms: platforms:
- linux/amd64 - linux/amd64
- linux/arm64 - linux/arm64
tag: dev tags_file: .docker-tags
registry: ghcr.io registry: ghcr.io
username: username:
from_secret: github_username from_secret: github_username
password: password:
from_secret: github_token from_secret: github_token
depends_on: [version] depends_on: [docker-tags]
when:
- event: push
branch: main
# Build and push Docker image (tags - release) # Create git tag and push
docker-release: git-tag:
image: *docker_image image: alpine/git
settings: secrets: [github_token]
repo: ghcr.io/barryw/paperlessmcp commands:
dockerfile: PaperlessMCP/Dockerfile - TAG=$(cat .tag)
context: PaperlessMCP - VERSION=$(cat .version)
platforms: - echo "Creating tag $TAG"
- linux/amd64 - |
- linux/arm64 # Configure git for pushing
tag: [latest, "${CI_COMMIT_TAG}"] git config user.email "ci@woodpecker.local"
build_args: git config user.name "Woodpecker CI"
- VERSION=${CI_COMMIT_TAG} git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/barryw/PaperlessMCP.git
registry: ghcr.io
username:
from_secret: github_username
password:
from_secret: github_token
depends_on: [version]
when:
- event: tag
# Build Docker for PRs (no push, just verify it builds) # Create and push tag
docker-verify: git tag -a "$TAG" -m "Release $VERSION"
image: *docker_image git push origin "$TAG"
settings: depends_on: [package, docker]
repo: ghcr.io/barryw/paperlessmcp
dockerfile: PaperlessMCP/Dockerfile
context: PaperlessMCP
platforms:
- linux/amd64
dry_run: true
depends_on: [version]
when:
- event: pull_request
# Create GitHub release (tags only) # Create GitHub release
release: release:
image: woodpeckerci/plugin-github-release image: woodpeckerci/plugin-github-release
settings: settings:
@@ -125,7 +183,5 @@ steps:
from_secret: github_token from_secret: github_token
files: files:
- artifacts/*.nupkg - artifacts/*.nupkg
title: ${CI_COMMIT_TAG} prerelease: false
depends_on: [package-nuget, docker-release] depends_on: [git-tag]
when:
- event: tag
+155
View File
@@ -0,0 +1,155 @@
# Contributing to PaperlessMCP
Thank you for your interest in contributing! This document outlines our development workflow and guidelines.
## Development Workflow
We use **trunk-based development**:
1. **Create a feature branch** from `main`
2. **Make your changes** with conventional commits
3. **Open a pull request** to `main`
4. **CI runs** build and tests
5. **Merge to main** triggers automatic release
```
main ←── feature/add-search-filters
←── fix/upload-timeout
←── feat/bulk-export
```
## Conventional Commits
We use [Conventional Commits](https://www.conventionalcommits.org/) for automatic versioning. Your commit messages determine the version bump:
### Commit Format
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
### Types and Version Bumps
| Type | Description | Version Bump |
|------|-------------|--------------|
| `fix:` | Bug fixes | Patch (0.0.X) |
| `feat:` | New features | Minor (0.X.0) |
| `feat!:` | Breaking changes | Major (X.0.0) |
| `docs:` | Documentation only | Patch |
| `style:` | Code style (formatting) | Patch |
| `refactor:` | Code refactoring | Patch |
| `test:` | Adding/updating tests | Patch |
| `chore:` | Maintenance tasks | Patch |
### Examples
```bash
# Bug fix → v1.0.1
git commit -m "fix: handle null response from API"
# New feature → v1.1.0
git commit -m "feat: add document export functionality"
# Breaking change → v2.0.0
git commit -m "feat!: change API response format"
# Or with BREAKING CHANGE footer
git commit -m "feat: redesign search API
BREAKING CHANGE: search now returns paginated results by default"
```
### Scopes (Optional)
Use scopes to indicate the affected area:
```bash
git commit -m "fix(upload): increase timeout for large files"
git commit -m "feat(tags): add bulk delete operation"
git commit -m "docs(readme): update installation instructions"
```
## Pull Request Process
1. **Branch naming**: Use descriptive names
- `feat/description` for features
- `fix/description` for bug fixes
- `docs/description` for documentation
2. **PR title**: Use conventional commit format
- The PR title becomes the merge commit message
- Example: `feat: add document preview endpoint`
3. **Description**: Include
- What changes were made
- Why the changes were needed
- How to test the changes
4. **CI checks**: Ensure all checks pass
- Build succeeds
- Tests pass
- Docker builds
## Local Development
### Prerequisites
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- Docker (optional, for container testing)
### Setup
```bash
# Clone the repo
git clone https://github.com/barryw/PaperlessMCP.git
cd PaperlessMCP
# Restore and build
dotnet restore
dotnet build
# Run tests
dotnet test
```
### Running Locally
```bash
# Set environment variables
export PAPERLESS_BASE_URL=https://your-instance.com
export PAPERLESS_API_TOKEN=your-token
# Run in stdio mode (for testing with Claude)
dotnet run --project PaperlessMCP/PaperlessMCP -- --stdio
# Run in HTTP mode
dotnet run --project PaperlessMCP/PaperlessMCP
```
### Running Tests
```bash
# All tests
dotnet test
# With verbose output
dotnet test --logger "console;verbosity=detailed"
# Specific test class
dotnet test --filter "FullyQualifiedName~DocumentToolsTests"
```
## Code Style
- Follow existing code patterns
- Use meaningful variable and method names
- Add XML documentation for public APIs
- Keep methods focused and small
## Questions?
Open an issue for questions or discussions about contributing.
+14 -15
View File
@@ -141,24 +141,23 @@ dotnet test # Run tests
### CI/CD ### CI/CD
This project uses [Woodpecker CI](https://woodpecker-ci.org/) for continuous integration: This project uses [Woodpecker CI](https://woodpecker-ci.org/) with trunk-based development:
| Event | Actions | | Event | Actions |
|-------|---------| |-------|---------|
| **Push/PR** | Build → Test | | **Pull Request** | Build → Test → Docker verify |
| **Push to main** | Build → Test → Package NuGet | | **Merge to main** | Build → Test → Version → Package → Docker → Tag → Release |
| **Tag (vX.Y.Z)** | Build → Test → Package → Docker → GitHub Release |
**Versioning:** Semantic versioning via git tags. Create a release with: **Automatic Versioning:** Version bumps are determined by [Conventional Commits](https://www.conventionalcommits.org/):
```bash | Commit Type | Version Bump | Example |
git tag v1.0.0 |-------------|--------------|---------|
git push origin v1.0.0 | `fix:` | Patch (0.0.X) | `fix: handle null response` |
``` | `feat:` | Minor (0.X.0) | `feat: add bulk export` |
| `feat!:` | Major (X.0.0) | `feat!: change API format` |
**Docker Images:** **Docker Images:**
- `ghcr.io/barryw/paperlessmcp:latest` — Latest release - `ghcr.io/barryw/paperlessmcp:latest` — Latest release
- `ghcr.io/barryw/paperlessmcp:dev` — Latest main branch
- `ghcr.io/barryw/paperlessmcp:vX.Y.Z` — Specific version - `ghcr.io/barryw/paperlessmcp:vX.Y.Z` — Specific version
### Project Structure ### Project Structure
@@ -178,12 +177,12 @@ PaperlessMCP/
## 🤝 Contributing ## 🤝 Contributing
Contributions welcome! Please: Contributions welcome! We use trunk-based development with conventional commits.
1. Fork the repository See **[CONTRIBUTING.md](CONTRIBUTING.md)** for guidelines on:
2. Create a feature branch - Commit message format
3. Write tests for new functionality - Pull request process
4. Submit a pull request - Local development setup
--- ---