fix: CI release versioning — use sorted tags instead of git describe #65
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [master, main, develop, 'feature/**', 'claude/**'] | |
| pull_request: | |
| branches: [master, main, develop] | |
| env: | |
| DOTNET_VERSION: '10.0.x' | |
| DOTNET_NOLOGO: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| jobs: | |
| build: | |
| name: Build & Test | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: pgvector/pgvector:pg16 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: contextdb | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| include-prerelease: true | |
| - name: Cache NuGet packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nuget- | |
| - name: Restore dependencies | |
| run: dotnet restore SerialMemoryServer.sln | |
| - name: Build solution | |
| run: dotnet build SerialMemoryServer.sln --configuration Release --no-restore | |
| - name: Run unit tests | |
| run: dotnet test SerialMemory.Tests/SerialMemory.Tests.csproj --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage" --filter "Category!=Integration&Category!=OpenAI&Category!=Chaos" | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: test-results | |
| path: '**/TestResults/**/*.trx' | |
| retention-days: 7 | |
| - name: Upload coverage reports | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage-reports | |
| path: '**/TestResults/**/coverage.cobertura.xml' | |
| retention-days: 7 | |
| security-scan: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| needs: build | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| include-prerelease: true | |
| - name: Restore dependencies | |
| run: dotnet restore SerialMemoryServer.sln | |
| - name: Run security scan | |
| run: | | |
| dotnet list package --vulnerable --include-transitive 2>&1 | tee security-report.txt | |
| if grep -q "has the following vulnerable packages" security-report.txt; then | |
| echo "::warning::Vulnerable packages detected" | |
| fi | |
| - name: Upload security report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: security-report | |
| path: security-report.txt | |
| retention-days: 30 | |
| build-docker: | |
| name: Build Docker Images | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build MCP Server image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./SerialMemory.Mcp/Dockerfile | |
| push: false | |
| tags: serialmemory-mcp:${{ github.sha }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Build API image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| file: ./SerialMemory.Api/Dockerfile | |
| push: false | |
| tags: serialmemory-api:${{ github.sha }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| publish: | |
| name: Publish Artifacts | |
| runs-on: ubuntu-latest | |
| needs: [build, security-scan] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| include-prerelease: true | |
| - name: Restore dependencies | |
| run: dotnet restore SerialMemoryServer.sln | |
| - name: Publish MCP Server (Linux x64) | |
| run: | | |
| dotnet publish SerialMemory.Mcp/SerialMemory.Mcp.csproj \ | |
| --configuration Release \ | |
| --runtime linux-x64 \ | |
| --self-contained true \ | |
| --output ./publish/mcp-linux-x64 | |
| - name: Publish MCP Server (Windows x64) | |
| run: | | |
| dotnet publish SerialMemory.Mcp/SerialMemory.Mcp.csproj \ | |
| --configuration Release \ | |
| --runtime win-x64 \ | |
| --self-contained true \ | |
| --output ./publish/mcp-win-x64 | |
| - name: Publish MCP Server (macOS x64) | |
| run: | | |
| dotnet publish SerialMemory.Mcp/SerialMemory.Mcp.csproj \ | |
| --configuration Release \ | |
| --runtime osx-x64 \ | |
| --self-contained true \ | |
| --output ./publish/mcp-osx-x64 | |
| - name: Publish MCP Server (macOS ARM64) | |
| run: | | |
| dotnet publish SerialMemory.Mcp/SerialMemory.Mcp.csproj \ | |
| --configuration Release \ | |
| --runtime osx-arm64 \ | |
| --self-contained true \ | |
| --output ./publish/mcp-osx-arm64 | |
| - name: Upload Linux build | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: serialmemory-mcp-linux-x64 | |
| path: ./publish/mcp-linux-x64 | |
| retention-days: 30 | |
| - name: Upload Windows build | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: serialmemory-mcp-win-x64 | |
| path: ./publish/mcp-win-x64 | |
| retention-days: 30 | |
| - name: Upload macOS x64 build | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: serialmemory-mcp-osx-x64 | |
| path: ./publish/mcp-osx-x64 | |
| retention-days: 30 | |
| - name: Upload macOS ARM64 build | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: serialmemory-mcp-osx-arm64 | |
| path: ./publish/mcp-osx-arm64 | |
| retention-days: 30 | |
| release: | |
| name: Create Release | |
| runs-on: ubuntu-latest | |
| needs: publish | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Determine version bump | |
| id: version | |
| run: | | |
| # Get highest semver tag (git describe can pick wrong tag when multiple tags share a commit) | |
| LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | head -1) | |
| LATEST_TAG="${LATEST_TAG:-v0.0.0}" | |
| echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT | |
| # Parse current version | |
| CURRENT="${LATEST_TAG#v}" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" | |
| # Determine bump from commit message prefix | |
| COMMIT_MSG="${{ github.event.head_commit.message }}" | |
| if echo "$COMMIT_MSG" | grep -qiE "^(feat|feature)(\(.+\))?!:"; then | |
| MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 | |
| elif echo "$COMMIT_MSG" | grep -qiE "^feat(\(.+\))?:"; then | |
| MINOR=$((MINOR + 1)); PATCH=0 | |
| else | |
| PATCH=$((PATCH + 1)) | |
| fi | |
| NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" | |
| echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT | |
| echo "Bumping $LATEST_TAG -> v$NEW_VERSION" | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: ./artifacts | |
| - name: Create archives | |
| run: | | |
| cd artifacts | |
| for dir in serialmemory-mcp-*; do | |
| if [ -d "$dir" ]; then | |
| zip -r "$dir.zip" "$dir" | |
| fi | |
| done | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.version.outputs.new_version }} | |
| name: v${{ steps.version.outputs.new_version }} | |
| draft: false | |
| prerelease: false | |
| files: | | |
| artifacts/*.zip | |
| generate_release_notes: true | |
| deploy: | |
| name: Deploy to Homeserver | |
| runs-on: [self-hosted, homeserver] | |
| needs: build | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Update deployment repo | |
| run: | | |
| cd /home/serialcoder/docker/serialmemory | |
| git fetch "$GITHUB_WORKSPACE" HEAD:refs/remotes/origin/master | |
| git reset --hard origin/master | |
| echo "Updated to $(git log --oneline -1)" | |
| - name: Rebuild API container | |
| run: | | |
| cd /home/serialcoder/docker/serialmemory | |
| docker compose -f docker-compose.prod.yml up -d --build api | |
| - name: Verify deployment | |
| run: | | |
| sleep 10 | |
| docker ps --format '{{.Names}} {{.Status}}' | grep serialmemory-api | |
| echo "Deploy complete: $(date)" |