drizzle 0.1.13

A type-safe SQL query builder for Rust
Documentation
name: Publish

on:
  workflow_dispatch:
  pull_request:
    types: [closed]
    branches: [main]

permissions:
  contents: write
  issues: write
  pull-requests: write
  # Needed for the "Trigger binary release" step to dispatch release.yml.
  # Tags created by release-plz are pushed using GITHUB_TOKEN, and pushes
  # made with that token do NOT trigger tag-push workflows. We work around
  # this by explicitly dispatching release.yml after publishing.
  actions: write

concurrency:
  group: publish
  cancel-in-progress: false

jobs:
  publish:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    if: >-
      github.event_name == 'workflow_dispatch' || (
        github.event.pull_request.merged == true &&
        contains(github.event.pull_request.labels.*.name, 'release') &&
        github.event.pull_request.user.login == 'github-actions[bot]'
      )
    steps:
      - name: Clone repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Verify CI passed on the release PR
        # Belt-and-suspenders: refuse to publish unless every check on the
        # release PR was green. We can't use `gh pr checks` directly because
        # it includes the currently-running publish workflow as a "pending"
        # check (self-reference), so we query the GitHub API for the PR head
        # commit's check-runs and filter out the publish workflow itself.
        #
        # workflow_dispatch is exempt — that path is an explicit human
        # override, e.g. when recovering from a partial publish failure.
        if: github.event_name == 'pull_request'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          PR_NUMBER="${{ github.event.pull_request.number }}"
          PR_HEAD="${{ github.event.pull_request.head.sha }}"
          REPO="${{ github.repository }}"
          THIS_RUN_ID="${{ github.run_id }}"
          echo "Verifying CI on PR #$PR_NUMBER (head: $PR_HEAD)..."

          # Pull check-runs for the PR head commit, excluding publish checks.
          # A commit can have historical check-runs from previous attempts, so
          # keep only the latest run for each check name.
          # Returns one line per check: "<conclusion> <name>". Conclusions:
          # success / failure / cancelled / timed_out / skipped / neutral /
          # action_required / null (running).
          CHECKS=$(gh api \
            "repos/$REPO/commits/$PR_HEAD/check-runs?per_page=100" \
            --jq ".check_runs
              | map(select(.name != \"publish\"))
              | map(select(((.details_url // \"\") | contains(\"/actions/runs/$THIS_RUN_ID/\")) | not))
              | map({
                  name,
                  conclusion: (.conclusion // \"running\"),
                  timestamp: (.completed_at // .started_at // \"\")
                })
              | sort_by(.name, .timestamp)
              | group_by(.name)
              | map(last)
              | .[]
              | \"\(.conclusion) \(.name)\"")

          if [[ -z "$CHECKS" ]]; then
            echo "::error::No CI checks found for PR #$PR_NUMBER head $PR_HEAD."
            echo "::error::CI must run on release PRs before merge."
            exit 1
          fi

          echo "Latest checks (excluding publish runs):"
          echo "$CHECKS" | sed 's/^/  /'

          # Any non-green conclusion is a hard refusal. `skipped` is OK
          # (some matrix jobs may legitimately skip); `neutral` is OK
          # (advisory checks).
          BAD=$(echo "$CHECKS" | awk '$1 != "success" && $1 != "skipped" && $1 != "neutral"' || true)
          if [[ -n "$BAD" ]]; then
            echo "::error::PR #$PR_NUMBER has non-green checks:"
            echo "$BAD" | sed 's/^/::error::  /'
            exit 1
          fi

          for required in "Lint" "Documentation" "MSRV Check"; do
            if ! echo "$CHECKS" | awk -v required="$required" '$1 == "success" { $1=""; sub(/^ /, ""); if ($0 == required) found = 1 } END { exit !found }'; then
              echo "::error::Missing successful required check: $required"
              exit 1
            fi
          done

          if ! echo "$CHECKS" | awk '$1 == "success" && $0 ~ /test::sqlite \(/ && $0 !~ /\$\{\{/ { found = 1 } END { exit !found }'; then
            echo "::error::Missing successful SQLite test matrix check."
            exit 1
          fi

          if ! echo "$CHECKS" | awk '$1 == "success" && $0 ~ /test::pg \(/ && $0 !~ /\$\{\{/ { found = 1 } END { exit !found }'; then
            echo "::error::Missing successful PostgreSQL test matrix check."
            exit 1
          fi

          echo "All checks on PR #$PR_NUMBER are green — proceeding to publish."

      - name: Get Rust version
        id: rust-version
        run: |
          RUST_VERSION=$(grep '^rust-version = ' Cargo.toml | sed 's/rust-version = "\(.*\)"/\1/')
          echo "version=$RUST_VERSION" >> $GITHUB_OUTPUT

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ steps.rust-version.outputs.version }}

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: msrv

      - name: Install release-plz
        uses: taiki-e/install-action@v2
        with:
          tool: release-plz

      - name: Capture pre-release tag
        id: pre
        run: |
          PRE_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo none)
          echo "tag=$PRE_TAG" >> $GITHUB_OUTPUT
          echo "Pre-release latest tag: $PRE_TAG"

      - name: Run release-plz
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: |
          release-plz release --git-token "$GITHUB_TOKEN" || {
            echo "::error::release-plz release failed - some crates may have been partially published"
            exit 1
          }

      - name: Trigger binary release
        # Tag pushes by GITHUB_TOKEN don't trigger workflows, so dispatch
        # release.yml explicitly. workflow_dispatch IS one of the two events
        # that DOES fire when triggered by GITHUB_TOKEN.
        if: success()
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Fetch tags release-plz just pushed
          git fetch --tags --force
          NEW_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo none)
          echo "Post-release latest tag: $NEW_TAG"
          if [[ "$NEW_TAG" == "${{ steps.pre.outputs.tag }}" ]]; then
            echo "No new tag created — skipping binary release."
            exit 0
          fi
          if [[ "$NEW_TAG" != v[0-9]* ]]; then
            echo "Latest tag '$NEW_TAG' is not a versioned release — skipping."
            exit 0
          fi
          echo "Dispatching release.yml for $NEW_TAG"
          gh workflow run release.yml --ref "$NEW_TAG"

      - name: Summary
        run: echo "Successfully published crates to crates.io" >> $GITHUB_STEP_SUMMARY