grr-cli 0.12.0

Google tools from the terminal, at maximum performance: 401 methods across 14 Google APIs in one generated command tree, zero-config, over HTTP/3
Documentation
name: Tag release

# The other half of self-publishing: when a version-bump PR merges, the
# version in Cargo.toml no longer has a matching tag. This workflow sees
# that, creates and pushes the tag — and the tag is the trigger the binary
# release and crates.io publish both listen for.
#
# Tags are not branch-protected, so the repository GITHUB_TOKEN can push
# them (unlike main — see auto-release.yml for that half of the design).

on:
  push:
    branches: [main]
    paths:
      - Cargo.toml
  # The merge poller (automation-merge.yml) and auto-release dispatch this
  # workflow after a bot merge, because a GITHUB_TOKEN push starts no runs.
  # Without the trigger those dispatches failed with HTTP 422 and the tag
  # never appeared — the exact gap that left v0.8.0 sitting merged and
  # untagged until a human push landed. The body is idempotent (it no-ops
  # when the tag exists), so dispatch is always safe.
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: tag-release
  cancel-in-progress: false

jobs:
  tag:
    name: tag the merged version
    runs-on: ubuntu-26.04
    timeout-minutes: 5
    permissions:
      contents: write
      # gh workflow run below — dispatching release-binaries and publish.
      actions: write
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
        with:
          fetch-tags: true

      - name: Tag when the version has no tag
        timeout-minutes: 3
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
        run: |
          # Read the version bare, whatever is in the manifest. A previous
          # release bump wrote `version = "v0.6.0"` into Cargo.toml, and this
          # line then produced the orphan `vv0.6.0` tag. auto-release.yml now
          # strips the prefix, but this step is the one that creates a public
          # tag and fires the release — it must not trust its input blindly.
          RAW="$(grep -m1 '^version' Cargo.toml | sed 's/version = "\(.*\)"/\1/')"
          VERSION="${RAW#v}"
          if [ "$VERSION" != "$RAW" ]; then
            echo "::error::Cargo.toml version '$RAW' carries a leading 'v'. Cargo needs a bare semver; refusing to tag a release from a malformed manifest."
            exit 1
          fi
          if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then
            echo "::error::Cargo.toml version '$VERSION' is not a bare x.y.z semver; refusing to tag."
            exit 1
          fi
          TAG="v$VERSION"
          # Ask the remote, not just the local clone: a tag that exists upstream
          # but was not fetched would skip a local-only guard and then fail the
          # `git push` with "already exists", turning a no-op release check into
          # a red run. One extra round trip buys a deterministic outcome.
          if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null \
             || git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
            echo "$TAG already exists — nothing to do."
            exit 0
          fi
          echo "Cargo.toml is at $VERSION with no $TAG — tagging $GITHUB_SHA."
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git tag "$TAG" "$GITHUB_SHA"
          git push origin "$TAG"
          # A tag pushed with GITHUB_TOKEN starts no workflows (push events
          # from the token are not among the documented exceptions), so the
          # native `tags: ["v*"]` triggers on release-binaries.yml and
          # publish.yml stay silent for this automated path. workflow_dispatch
          # is the exception — fire the two consumers here instead.
          #
          # These dispatches are the ONLY trigger on the automated path, and
          # nothing retries them (the merge poller re-dispatches tag-release
          # only while the TAG is missing — by here it exists). A swallowed
          # failure would leave a tagged version with no binaries and no
          # crates.io publish, green. Fail loud instead.
          failed=0
          gh workflow run release-binaries.yml --ref "$TAG" || failed=1
          gh workflow run publish.yml --ref "$TAG" || failed=1
          [ "$failed" = 0 ] || { echo "::error::dispatch failed for $TAG; re-run this workflow once the API recovers (it no-ops when the tag exists)."; exit 1; }
          echo "Pushed $TAG and dispatched release-binaries.yml + publish.yml at it."