grr-cli 0.13.3

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: Auto release

# The software publishes itself. The changelog generator computes the next
# version from the conventional-commit history since the last tag; this
# workflow acts on that recommendation by opening the version-bump pull
# request with auto-merge enabled. When the PR merges, tag-release.yml sees
# the new version on main and pushes the tag — which fires the binary
# release (release-binaries.yml, 5 platforms) and crates.io publish.
#
# Every push to main triggers a run: shipping immediately beats batching,
# and the concurrent group below serialises races. After a release lands,
# the next push's recommendation is "none" (docs/chore only), so the loop
# converges on its own. The Friday cron is a backstop for weeks with no
# pushes; workflow_dispatch(force) exists for a deliberate re-check.
#
# Why a PR and not a direct push: the repository GITHUB_TOKEN cannot push
# to a protected branch (GH006), but it CAN open and auto-merge a PR —
# the same pattern every other automation here (changelog, discovery,
# benchmark, demo) already uses.
#
# Safety: a bump PR only opens when the recommendation is a real bump
# (feat -> minor, fix/perf -> patch, breaking -> major). "none" exits clean.

on:
  push:
    branches: [main]
  schedule:
    # 04:31 UTC Fridays — a backstop, not the cadence: releases ship on
    # every meaningful push above.
    - cron: "31 4 * * 5"
  workflow_dispatch:
    inputs:
      force:
        description: "Open the bump PR even when the recommendation is 'none'"
        type: boolean
        default: false

permissions:
  contents: read

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

jobs:
  bump:
    name: open the version-bump PR
    runs-on: ubuntu-26.04
    timeout-minutes: 15
    permissions:
      contents: write
      pull-requests: write
      # gh workflow run below — waking the merge poller.
      actions: write
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
        with:
          # Full history: the version recommendation diffs commits since
          # the last tag, and the changelog needs the tag list.
          fetch-depth: 0
          fetch-tags: true

      - uses: actions/setup-node@v7
        timeout-minutes: 5
        with:
          node-version: "26"

      - name: Compute the next version
        id: version
        timeout-minutes: 5
        # One run, one capture: the generator both prints the key=value
        # recommendation lines (stdout) and bakes CHANGELOG.md + the site
        # changelog.json — files the bump PR carries. The lock is no longer
        # tracked: builds resolve the latest semver-compatible versions, so
        # only Cargo.toml is bumped.
        run: |
          OUT="$(node scripts/generate-changelog.ts 2>&1)"
          echo "$OUT" | grep -E '^changelog:' || true
          BUMP="$(echo "$OUT" | grep -E 'changelog:recommended-bump=' | head -1 | cut -d= -f2)"
          NEXT="$(echo "$OUT" | grep -E 'changelog:recommended-version=' | head -1 | cut -d= -f2)"
          CURRENT="$(grep -m1 '^version' Cargo.toml | sed 's/version = "\(.*\)"/\1/')"
          # Race guard: tag-release cuts the tag for this same push, so the
          # recommendation can name the version main is ALREADY at (next
          # v0.8.0, current 0.8.0) — the bump PR would carry an empty diff.
          # Nothing to do; the tag workflow owns this version.
          if [ "${NEXT#v}" = "$CURRENT" ]; then
            echo "::notice::main is already at ${NEXT#v}; the tag workflow owns this version."
            BUMP="none"
          fi
          echo "bump=${BUMP:-none}" >> "$GITHUB_OUTPUT"
          echo "next=${NEXT:-$CURRENT}" >> "$GITHUB_OUTPUT"
          echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
          echo "recommendation: bump=${BUMP:-none} next=${NEXT:-$CURRENT} current=$CURRENT"

      - name: Bump Cargo.toml
        if: steps.version.outputs.bump != 'none' || inputs.force == true
        timeout-minutes: 2
        env:
          NEXT: ${{ steps.version.outputs.next }}
        run: |
          # NEXT arrives with a leading "v" (v0.7.0) because that is how the
          # generator names a version. Cargo wants a bare semver: `version =
          # "v0.7.0"` is a hard parse error, and it is precisely what produced
          # the orphan `vv0.6.0` tag, because tag-release.yml builds its tag as
          # "v$VERSION" and the "v" in the manifest became a second one.
          BARE="${NEXT#v}"
          sed -i "s/^version = \".*\"/version = \"$BARE\"/" Cargo.toml
          # Fail here, loudly, rather than letting a malformed manifest reach
          # the tag step and the release three workflows downstream.
          if ! grep -q "^version = \"$BARE\"$" Cargo.toml; then
            echo "::error::Cargo.toml version did not land as '$BARE'"
            exit 1
          fi
          grep -m1 '^version' Cargo.toml

      - name: Verify the other generated artifacts are current
        if: steps.version.outputs.bump != 'none' || inputs.force == true
        timeout-minutes: 5
        run: |
          node scripts/generate-commands.ts --check
          node scripts/generate-skills.ts --check
          node scripts/generate-coverage.ts --check

      - name: Open the bump PR with auto-merge
        if: steps.version.outputs.bump != 'none' || inputs.force == true
        timeout-minutes: 5
        env:
          NEXT: ${{ steps.version.outputs.next }}
          GH_TOKEN: ${{ github.token }}
        run: |
          # NEXT already carries the leading "v" (v0.7.0); prefixing another
          # one produced the malformed chore/release-vv0.7.0.
          BRANCH="chore/release-$NEXT"
          # A push that lands while the bump PR is still open must not
          # force-push the branch out from under its own CI run: every
          # recreate restarts the merge clock. If a PR for this exact
          # version is already open, there is nothing to do.
          if gh pr list --state open --head "$BRANCH" --json number --jq 'length' | grep -q '^[1-9]'; then
            echo "::notice::$BRANCH already has an open PR; leaving it to merge."
            exit 0
          fi
          git config user.name "debanjanbasu"
          git config user.email "10209115+debanjanbasu@users.noreply.github.com"
          git checkout -b "$BRANCH"
          # A previous run for this same version can leave the throwaway
          # branch behind (an abandoned PR). Drop it first so the push is a
          # clean create instead of a non-fast-forward rejection, which would
          # otherwise make the weekly release unretryable.
          git push origin --delete "$BRANCH" 2>/dev/null || true
          # Cargo.toml only: the changelog files belong to the changelog
          # workflow (see the compute step), and the lock is untracked.
          git add Cargo.toml
          git commit -m "chore(release): grr $NEXT"
          git push origin "$BRANCH"
          # No `|| true` here: the open-PR guard above already exits when this
          # exact branch has a PR, so an `already exists` failure cannot be the
          # benign case — any failure now is real and must be visible.
          gh pr create \
            --title "chore(release): grr $NEXT" \
            --body "Automated version bump — computed by the changelog generator from the conventional-commit history; opened by auto-release.yml. Merging publishes $NEXT: tag-release.yml tags, release-binaries.yml builds the 5 platforms, publish.yml ships to crates.io." \
            --base main --head "$BRANCH" --label dependencies
          # Auto-merge: the GITHUB_TOKEN cannot push to protected main, but
          # it can enable GitHub's native auto-merge, which completes when
          # required checks pass.
          gh pr merge "$BRANCH" --squash --auto || true
          # The poller's cron adds up to 10 minutes before it approves the
          # bump PR's CI runs (token-authored PRs land in action_required);
          # waking it now keeps the release chain snappy. Idempotent.
          gh workflow run automation-merge.yml --ref main || echo "::warning::poller dispatch failed"
          gh pr view "$BRANCH" --json url --jq .url

      - name: Nothing further
        if: ${{ steps.version.outputs.bump == 'none' && inputs.force != true }}
        run: 'echo "Nothing to release (recommendation: none)."'