name: "Loop: Release"

# Generated by `micro loop init`. Cuts the next tag when the default branch has
# new commits since the latest one, and pushes it with a PAT (<< .TokenSecret >>)
# so any tag-triggered release workflow fires. The bump reflects what shipped,
# read from the CHANGELOG [Unreleased] section: new features (Added/Changed) cut
# a MINOR; fixes/docs only cut a PATCH; breaking changes are skipped so a MAJOR
# stays a human decision.
#
# The tag MUST be pushed with a PAT, not the default GITHUB_TOKEN: a tag pushed
# by GITHUB_TOKEN does not trigger other workflows (Actions blocks that recursion).

on:
  workflow_dispatch: {}
  schedule:
    - cron: "<< .ReleaseCron >>"

permissions:
  contents: read

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

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # need full history + all tags
          # Do NOT persist the default GITHUB_TOKEN as a git credential: it would
          # be sent on the PAT push below and override it, so the tag push would
          # authenticate as github-actions[bot] and 403. Letting the PAT in the
          # push URL be the only credential is the whole point.
          persist-credentials: false
      - name: Cut the next patch tag if there are new commits
        env:
          RELEASE_TOKEN: ${{ secrets.<< .TokenSecret >> }}
          REPO: ${{ github.repository }}
        run: |
          if [ -z "$RELEASE_TOKEN" ]; then
            echo "<< .TokenSecret >> is not set — skipping."
            exit 0
          fi
          git fetch --tags --force

          LATEST=$(git tag --list '<< .TagPrefix >>*.*.*' --sort=-v:refname | head -1)
          if [ -z "$LATEST" ]; then
            echo "no << .TagPrefix >>MAJOR.MINOR.PATCH tag found — aborting so nothing weird gets tagged."
            exit 1
          fi
          echo "latest tag: $LATEST"

          COUNT=$(git rev-list --count "$LATEST"..HEAD)
          echo "commits since $LATEST: $COUNT"
          if [ "$COUNT" -eq 0 ]; then
            echo "no new commits since $LATEST — no release."
            exit 0
          fi

          ver="${LATEST#<< .TagPrefix >>}"
          major="${ver%%.*}"
          rest="${ver#*.}"
          minor="${rest%%.*}"
          patch="${rest#*.}"
          case "$major.$minor.$patch" in
            [0-9]*.[0-9]*.[0-9]*) ;;
            *) echo "unexpected tag shape: $LATEST" ; exit 1 ;;
          esac

          # Choose the bump from what actually shipped, read from the CHANGELOG
          # [Unreleased] section (kept current by the coherence role):
          #   new features (### Added / ### Changed) -> MINOR
          #   fixes/docs only                        -> PATCH
          #   breaking (### Removed / "(breaking)")  -> skip; a major is a human call
          UNRELEASED=""
          if [ -f CHANGELOG.md ]; then
            UNRELEASED=$(awk '/^## \[Unreleased\]/{f=1; next} /^## \[/{f=0} f' CHANGELOG.md)
          fi
          if printf '%s\n' "$UNRELEASED" | grep -qiE '^### Removed|^### Changed \(breaking\)|BREAKING'; then
            echo "CHANGELOG [Unreleased] contains breaking changes — a major release is a human decision. Skipping."
            exit 0
          elif printf '%s\n' "$UNRELEASED" | grep -qE '^### (Added|Changed)'; then
            NEXT="<< .TagPrefix >>${major}.$((minor + 1)).0"
            KIND="minor (new features)"
          else
            NEXT="<< .TagPrefix >>${major}.${minor}.$((patch + 1))"
            KIND="patch (fixes/docs only)"
          fi
          echo "cutting: $NEXT — $KIND ($COUNT commits since $LATEST)"

          git config user.name "loop release bot"
          git config user.email "noreply@users.noreply.github.com"
          git tag -a "$NEXT" -m "Release $NEXT — automated $KIND ($COUNT commits since $LATEST)"
          git push "https://x-access-token:${RELEASE_TOKEN}@github.com/${REPO}.git" "$NEXT"
          echo "Pushed $NEXT."
