From 2c1ed449da1ece4d7b0f2b5a06c948a002fd5c35 Mon Sep 17 00:00:00 2001 From: Barry Walker Date: Tue, 13 Jan 2026 14:18:31 -0500 Subject: [PATCH] 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 --- .woodpecker.yml | 192 +++++++++++++++++++++++++++++++----------------- CONTRIBUTING.md | 155 ++++++++++++++++++++++++++++++++++++++ README.md | 29 ++++---- 3 files changed, 293 insertions(+), 83 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.woodpecker.yml b/.woodpecker.yml index b025a4d..a4e4585 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -2,53 +2,124 @@ variables: - &dotnet_image "mcr.microsoft.com/dotnet/sdk:10.0-preview" - &docker_image "woodpeckerci/plugin-docker-buildx" +# ============================================================================= +# PULL REQUESTS - Build and test only +# ============================================================================= 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: - # Restore dependencies restore: image: *dotnet_image commands: - dotnet restore - # Build the project build: image: *dotnet_image commands: - dotnet build --no-restore -c Release depends_on: [restore] - # Run tests test: image: *dotnet_image commands: - dotnet test --no-build -c Release --logger "console;verbosity=detailed" depends_on: [build] - # Determine version from git tags + # Determine next version based on conventional commits version: image: alpine/git commands: + - apk add --no-cache bash - | - if [ -n "$CI_COMMIT_TAG" ]; then - # Use tag as version (strip 'v' prefix if present) - VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//') + # Get the latest tag or default to v0.0.0 + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "Latest tag: $LATEST_TAG" + + # 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 - # 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") - BASE_VERSION=$(echo "$LATEST_TAG" | sed 's/^v//') - VERSION="${BASE_VERSION}-dev.${BRANCH}.${SHORT_SHA}" + COMMITS=$(git log --pretty=format:"%s" ${LATEST_TAG}..HEAD) 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] - # Package as NuGet (for library distribution) - package-nuget: + # Package NuGet + package: image: *dotnet_image commands: - 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 - ls -la ./artifacts/ depends_on: [version] - when: - - event: [push, tag] - branch: main - # Build and push Docker image (main branch - dev tag) - docker-dev: + # Prepare Docker tags + 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 settings: repo: ghcr.io/barryw/paperlessmcp @@ -70,54 +148,34 @@ steps: platforms: - linux/amd64 - linux/arm64 - tag: dev + tags_file: .docker-tags registry: ghcr.io username: from_secret: github_username password: from_secret: github_token - depends_on: [version] - when: - - event: push - branch: main + depends_on: [docker-tags] - # Build and push Docker image (tags - release) - docker-release: - image: *docker_image - settings: - repo: ghcr.io/barryw/paperlessmcp - dockerfile: PaperlessMCP/Dockerfile - context: PaperlessMCP - platforms: - - linux/amd64 - - linux/arm64 - tag: [latest, "${CI_COMMIT_TAG}"] - build_args: - - VERSION=${CI_COMMIT_TAG} - registry: ghcr.io - username: - from_secret: github_username - password: - from_secret: github_token - depends_on: [version] - when: - - event: tag + # Create git tag and push + git-tag: + image: alpine/git + secrets: [github_token] + commands: + - TAG=$(cat .tag) + - VERSION=$(cat .version) + - echo "Creating tag $TAG" + - | + # Configure git for pushing + 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 - # Build Docker for PRs (no push, just verify it builds) - docker-verify: - image: *docker_image - settings: - repo: ghcr.io/barryw/paperlessmcp - dockerfile: PaperlessMCP/Dockerfile - context: PaperlessMCP - platforms: - - linux/amd64 - dry_run: true - depends_on: [version] - when: - - event: pull_request + # Create and push tag + git tag -a "$TAG" -m "Release $VERSION" + git push origin "$TAG" + depends_on: [package, docker] - # Create GitHub release (tags only) + # Create GitHub release release: image: woodpeckerci/plugin-github-release settings: @@ -125,7 +183,5 @@ steps: from_secret: github_token files: - artifacts/*.nupkg - title: ${CI_COMMIT_TAG} - depends_on: [package-nuget, docker-release] - when: - - event: tag + prerelease: false + depends_on: [git-tag] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6feb88b --- /dev/null +++ b/CONTRIBUTING.md @@ -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 + +``` +(): + +[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. diff --git a/README.md b/README.md index cbf02c4..73afad2 100644 --- a/README.md +++ b/README.md @@ -141,24 +141,23 @@ dotnet test # Run tests ### 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 | |-------|---------| -| **Push/PR** | Build → Test | -| **Push to main** | Build → Test → Package NuGet | -| **Tag (vX.Y.Z)** | Build → Test → Package → Docker → GitHub Release | +| **Pull Request** | Build → Test → Docker verify | +| **Merge to main** | Build → Test → Version → Package → Docker → Tag → 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 -git tag v1.0.0 -git push origin v1.0.0 -``` +| Commit Type | Version Bump | Example | +|-------------|--------------|---------| +| `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:** - `ghcr.io/barryw/paperlessmcp:latest` — Latest release -- `ghcr.io/barryw/paperlessmcp:dev` — Latest main branch - `ghcr.io/barryw/paperlessmcp:vX.Y.Z` — Specific version ### Project Structure @@ -178,12 +177,12 @@ PaperlessMCP/ ## 🤝 Contributing -Contributions welcome! Please: +Contributions welcome! We use trunk-based development with conventional commits. -1. Fork the repository -2. Create a feature branch -3. Write tests for new functionality -4. Submit a pull request +See **[CONTRIBUTING.md](CONTRIBUTING.md)** for guidelines on: +- Commit message format +- Pull request process +- Local development setup ---