ferrflow 2.8.1

Universal semantic versioning for monorepos and classic repos
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:
    inputs:
      dry_run:
        description: 'Dry run'
        type: boolean
        default: false

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2
      - name: Format check
        run: cargo fmt --check
      - name: Clippy
        run: cargo clippy -- -D warnings
        env:
          FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }}
      - name: Tests
        run: cargo test
        env:
          FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }}

  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - uses: Swatinem/rust-cache@v2
      - name: Install cargo-tarpaulin
        run: cargo install cargo-tarpaulin
      - name: Generate coverage
        run: cargo tarpaulin --out xml --skip-clean
        env:
          FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }}
      - uses: codecov/codecov-action@v6
        with:
          files: cobertura.xml
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: false

  micro-bench:
    name: Micro Benchmarks
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - uses: Swatinem/rust-cache@v2
      - uses: FerrFlow-Org/Benchmarks@v2
        with:
          type: micro
          ferrflow-token: ${{ secrets.GITHUB_TOKEN }}
        env:
          FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }}

  benchmark:
    name: Benchmark
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v6
      - uses: dtolnay/rust-toolchain@nightly
      - uses: Swatinem/rust-cache@v2
      - uses: actions/setup-node@v6
        with:
          node-version: '24'
      - uses: FerrFlow-Org/Benchmarks@v2
        with:
          type: full
          ferrflow-token: ${{ secrets.GITHUB_TOKEN }}
        env:
          FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }}

  release:
    name: Release
    needs: [test, benchmark]
    runs-on: ubuntu-latest
    concurrency:
      group: release-${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: false
    if: |
      always() &&
      needs.test.result == 'success' &&
      (needs.benchmark.result == 'success' || needs.benchmark.result == 'skipped') &&
      (
        (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
        github.event_name == 'workflow_dispatch'
      )
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
          token: ${{ secrets.FERRFLOW_TOKEN }}
      - uses: dtolnay/rust-toolchain@nightly
      - uses: Swatinem/rust-cache@v2
      - name: Configure git
        run: git remote set-url origin https://x-access-token:${{ secrets.FERRFLOW_TOKEN }}@github.com/${{ github.repository }}
      - name: Build ferrflow
        run: cargo build --release
        env:
          FERRFLOW_HMAC_SECRET: ${{ secrets.FERRFLOW_HMAC_SECRET }}
      - name: Run ferrflow release
        run: ./target/release/ferrflow ${{ inputs.dry_run == 'true' && '--dry-run' || '' }} release
        env:
          FERRFLOW_TOKEN: ${{ secrets.FERRFLOW_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.FERRFLOW_TOKEN }}
      - name: Download benchmark summary
        if: needs.benchmark.result == 'success'
        uses: actions/download-artifact@v8
        with:
          name: benchmark-release-summary
          path: benchmark-summary/
        continue-on-error: true
      - name: Append benchmark results to release
        if: needs.benchmark.result == 'success'
        shell: bash
        run: |
          if [[ ! -f benchmark-summary/release-summary.md ]]; then
            echo "No benchmark summary found, skipping"
            exit 0
          fi
          TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
          if [[ -z "$TAG" ]]; then
            echo "No tag found, skipping"
            exit 0
          fi
          CURRENT_BODY=$(gh release view "$TAG" --json body --jq '.body' 2>/dev/null || echo "")
          BENCH=$(cat benchmark-summary/release-summary.md)
          printf -v NEW_BODY '%s\n\n%s' "$CURRENT_BODY" "$BENCH"
          gh release edit "$TAG" --notes "$NEW_BODY"
        env:
          GH_TOKEN: ${{ secrets.FERRFLOW_TOKEN }}