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:
+124
-68
@@ -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)
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
VERSION=$(echo "$CI_COMMIT_TAG" | sed 's/^v//')
|
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
|
else
|
||||||
# Generate dev version from branch and short SHA
|
COMMITS=$(git log --pretty=format:"%s" ${LATEST_TAG}..HEAD)
|
||||||
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}"
|
|
||||||
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
@@ -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.
|
||||||
@@ -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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user