structured-proxy 1.0.1

Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service
Documentation
name: CI/CD Pipeline

permissions:
  contents: read

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: "-Dwarnings"

jobs:
  # Job 1: Quality checks
  quality-checks:
    name: Quality Checks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2

      - name: Format
        run: cargo fmt --all -- --check

      - name: Clippy
        run: cargo clippy --all-targets --all-features

      - name: Build
        run: cargo build --release

      - name: Test
        run: cargo test --all-features

      - name: Publish dry-run
        run: cargo publish --dry-run

  # Job 2: Semantic Release (only on main)
  semantic-release:
    name: Semantic Release
    runs-on: ubuntu-latest
    needs: [quality-checks]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'

    permissions:
      contents: write
      issues: write
      pull-requests: write
      id-token: write

    outputs:
      new-release-published: ${{ steps.semantic.outputs.new-release-published }}
      new-release-version: ${{ steps.semantic.outputs.new-release-version }}

    steps:
      - name: Generate release token
        uses: actions/create-github-app-token@v2
        id: app-token
        with:
          app-id: ${{ secrets.RELEASER_APP_ID }}
          private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ github.event.repository.name }}

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ steps.app-token.outputs.token }}

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install semantic-release plugins
        run: npm install -g semantic-release @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/changelog @semantic-release/exec @semantic-release/git @semantic-release/github conventional-changelog-conventionalcommits

      - name: Run semantic-release
        id: semantic
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          npx semantic-release

          if [ -f ".semantic-release-version" ]; then
            VERSION=$(cat .semantic-release-version)
            echo "new-release-published=true" >> $GITHUB_OUTPUT
            echo "new-release-version=$VERSION" >> $GITHUB_OUTPUT
          else
            echo "new-release-published=false" >> $GITHUB_OUTPUT
          fi

  # Job 3: Publish to crates.io (trusted publishing, no token needed)
  crates-publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: [semantic-release]
    if: needs.semantic-release.outputs.new-release-published == 'true'

    permissions:
      contents: read
      id-token: write

    steps:
      - uses: actions/checkout@v4
        with:
          ref: v${{ needs.semantic-release.outputs.new-release-version }}

      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: Verify version matches tag
        run: |
          TAG="${{ needs.semantic-release.outputs.new-release-version }}"
          CARGO_VER=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
          if [ "$TAG" != "$CARGO_VER" ]; then
            echo "::error::Tag v$TAG does not match Cargo.toml version $CARGO_VER"
            exit 1
          fi

      - name: Build
        run: cargo build --release

      - name: Test
        run: cargo test --all-features

      - name: Authenticate with crates.io (OIDC)
        id: crates-auth
        uses: rust-lang/crates-io-auth-action@v1

      - name: Publish to crates.io
        run: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}

  # Job 4: Summary
  summary:
    name: Pipeline Summary
    runs-on: ubuntu-latest
    permissions: {}
    needs: [quality-checks, semantic-release, crates-publish]
    if: always()
    steps:
      - name: Summary
        run: |
          echo "## Pipeline Summary" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          if [[ "${{ needs.quality-checks.result }}" == "success" ]]; then
            echo "✅ Quality checks: Passed" >> $GITHUB_STEP_SUMMARY
          else
            echo "❌ Quality checks: Failed" >> $GITHUB_STEP_SUMMARY
          fi

          if [[ "${{ needs.semantic-release.outputs.new-release-published }}" == "true" ]]; then
            echo "🚀 New version: v${{ needs.semantic-release.outputs.new-release-version }}" >> $GITHUB_STEP_SUMMARY
          else
            echo "ℹ️ No new version released" >> $GITHUB_STEP_SUMMARY
          fi

          if [[ "${{ needs.crates-publish.result }}" == "success" ]]; then
            echo "📦 Published to crates.io: structured-proxy v${{ needs.semantic-release.outputs.new-release-version }}" >> $GITHUB_STEP_SUMMARY
          elif [[ "${{ needs.crates-publish.result }}" == "skipped" ]]; then
            echo "⏭️ crates.io publish: Skipped (no new release)" >> $GITHUB_STEP_SUMMARY
          elif [[ "${{ needs.crates-publish.result }}" == "failure" ]]; then
            echo "❌ crates.io publish: Failed" >> $GITHUB_STEP_SUMMARY
          fi