culvert-async 0.2.0

Async typed access to the HDMI 2.1 SCDC register map
Documentation
name: Release Tag

on:
  workflow_dispatch:

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
  tag:
    name: Promote to main and tag
    runs-on: ubuntu-latest
    permissions:
      contents: write
      actions: write  # `gh workflow run publish.yml` (workflow_dispatch) needs write
      pull-requests: write
    steps:
      # Always operates on develop (see checkout below). Fail loudly when started
      # from another branch, whose copy of this workflow may be outdated. (A modified
      # copy could remove this check; it only guards against mistakes.)
      - name: Require develop
        if: github.ref != 'refs/heads/develop'
        run: |
          echo "::error::Run this workflow from develop (started from ${GITHUB_REF})."
          exit 1

      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: develop
          fetch-depth: 0

      - name: Read version from Cargo.toml
        id: version
        run: |
          VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "Tag will be: v${VERSION}"

      - name: Verify release commit is on develop
        run: |
          VERSION="${{ steps.version.outputs.version }}"
          # The release PR's commit, or its squash merge ("Release vX.Y.Z (#N)").
          RELEASE_SHA=$(git log -E --grep="^Release v${VERSION//./\\.}( \(#[0-9]+\))?$" \
            --format="%H" | head -1)
          if [ -z "$RELEASE_SHA" ]; then
            echo "::error::No 'Release v${VERSION}' commit found on develop. Has the release PR been merged into develop?"
            exit 1
          fi

      # CI starts on every push to develop; wait for it after merging the release PR.
      - name: Verify CI is green on develop
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          SHA=$(git rev-parse HEAD)
          RESULT=$(gh run list \
            --branch develop \
            --workflow ci.yml \
            --json headSha,status,conclusion \
            --limit 20 \
            | jq -r --arg sha "$SHA" '
                [.[] | select(.headSha == $sha)] as $runs
                | ([$runs[] | select(.status == "completed")] | first | .conclusion)
                  // (if ($runs | length) > 0 then "still-running" else "not-found" end)')
          case "$RESULT" in
            success ) echo "ci.yml on develop@${SHA}: success" ;;
            still-running )
              echo "::error::ci.yml on develop@${SHA} has not finished yet — re-run this workflow once it has."
              exit 1 ;;
            * )
              echo "::error::CI conclusion on develop@${SHA} is '${RESULT}' — must be 'success' before tagging."
              exit 1 ;;
          esac

      - name: Check tag does not already exist
        run: |
          VERSION="${{ steps.version.outputs.version }}"
          if git ls-remote --tags origin "refs/tags/v${VERSION}" | grep -q .; then
            echo "::error::Tag v${VERSION} already exists — was this release already published?"
            exit 1
          fi

      - name: Configure git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Promote develop → main via PR
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ steps.version.outputs.version }}"
          BRANCH="release-promote/v${VERSION}"
          git fetch origin main
          git merge origin/main --allow-unrelated-histories -s ours \
            -m "Release v${VERSION}: promote develop to main"
          git push origin "HEAD:refs/heads/${BRANCH}"
          PR_URL=$(gh pr create \
            --base main \
            --head "${BRANCH}" \
            --title "Release v${VERSION}" \
            --body "Promote release v${VERSION} from develop to main.")
          MERGED=false
          for attempt in 1 2 3; do
            if gh pr merge "$PR_URL" --merge; then
              MERGED=true
              break
            fi
            [ "$attempt" -lt 3 ] && sleep 10
          done
          if [ "$MERGED" != true ]; then
            echo "::error::Could not merge $PR_URL into main — not tagging."
            exit 1
          fi
          git push origin --delete "${BRANCH}" || true

      - name: Create and push tag
        run: |
          VERSION="${{ steps.version.outputs.version }}"
          git fetch origin main
          # Release tags cannot be moved or deleted (ruleset), so refuse to tag
          # unless main now holds exactly the tree that was promoted.
          if [ "$(git rev-parse 'origin/main^{tree}')" != "$(git rev-parse 'HEAD^{tree}')" ]; then
            echo "::error::origin/main does not match the promoted release commit — not tagging v${VERSION}."
            exit 1
          fi
          git checkout main
          git tag "v${VERSION}" -m "Release v${VERSION}"
          git push origin "v${VERSION}"

      - name: Trigger publish workflow
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ steps.version.outputs.version }}"
          gh workflow run publish.yml --ref "v${VERSION}"
          echo "Triggered Publish workflow for v${VERSION}."