# Releases. release-bot keeps one pull request open from the `release` branch, "chore(release): vX.Y.Z": the next # version and its CHANGELOG.md entry, which git-cliff (cliff.toml) works out from the conventional commits on main # since the last tag. Merging it makes release-bot tag vX.Y.Z on main and publish the release notes; the tag builds the # image (ci.yml) and Komodo deploys it to prod (dhkatz/infra). Only release-bot may create v* tags. It acts with its # own token (the org secret RELEASE_TOKEN), not this workflow's: Gitea starts no workflows for what that one does, so # the release pull request's checks and the tag's build would never run. name: Release on: push: branches: [main] concurrency: group: release jobs: release: runs-on: ubuntu-latest env: CLIFF_VERSION: 2.14.2 CLIFF_SHA512: 95b0c55ab3e351ca7c0285fbbde8f1e8c42906466a173e6884f5c7ca814523bda44cbd4ab26b0aa28cace4c02c5ee6b553e9742bd9d7df47a73b9ef58ea429a9 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - name: Install git-cliff run: | archive="git-cliff-$CLIFF_VERSION-x86_64-unknown-linux-musl.tar.gz" curl -fsSLO "https://github.com/orhun/git-cliff/releases/download/v$CLIFF_VERSION/$archive" echo "$CLIFF_SHA512 $archive" | sha512sum -c - mkdir -p "$HOME/.local/bin" tar -xzf "$archive" --strip-components=1 -C "$HOME/.local/bin" "git-cliff-$CLIFF_VERSION/git-cliff" rm "$archive" echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Tag a merged release, or update the release pull request env: TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | set -euo pipefail api="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY" host="${GITHUB_SERVER_URL#https://}" git config user.name release-bot git config user.email "release-bot@${host#git.}" basic=$(printf 'release-bot:%s' "$TOKEN" | base64 -w0) push() { git -c http.extraHeader="Authorization: Basic $basic" push "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" "$@"; } gitea() { curl -fsS -H "Authorization: token $TOKEN" -H "Content-Type: application/json" "$@"; } # The release pull request was merged: tag its commit and publish the release. if [[ "$(git log -1 --format=%s)" =~ ^chore\(release\):\ (v[0-9]+\.[0-9]+\.[0-9]+) ]]; then tag="${BASH_REMATCH[1]}" if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then echo "$tag exists already"; exit 0; fi git tag "$tag" push "refs/tags/$tag" jq -n --arg tag "$tag" --arg body "$(git cliff --latest --strip all)" '{tag_name: $tag, name: $tag, body: $body}' | gitea -o /dev/null -X POST "$api/releases" -d @- echo "Released $tag" exit 0 fi # Otherwise: anything to release since the last tag? (Nothing printed when no commit counts.) last=$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true) next=$(git cliff --bumped-version 2>/dev/null || true) next="v${next#v}" if [ "$next" = v ] || [ "$next" = "$last" ]; then echo "Nothing to release since ${last:-the start}"; exit 0; fi notes=$(git cliff --unreleased --bump --strip all) git switch -C release git cliff --bump --output CHANGELOG.md git add CHANGELOG.md git commit -q -m "chore(release): $next" push --force HEAD:refs/heads/release title="chore(release): $next" body=$(printf 'Merging this releases **%s**: release-bot tags it on main, CI builds its image, and Komodo deploys it to prod. It updates itself as pull requests are merged.\n\n%s\n' "$next" "$notes") payload=$(jq -n --arg title "$title" --arg body "$body" '{title: $title, body: $body}') open=$(gitea "$api/pulls?state=open&limit=50" | jq -r '[.[] | select(.head.ref == "release")][0].number // empty') if [ -n "$open" ]; then gitea -o /dev/null -X PATCH "$api/pulls/$open" -d "$payload" echo "Updated the release pull request #$open: $title" else jq '. + {head: "release", base: "main"}' <<<"$payload" | gitea -o /dev/null -X POST "$api/pulls" -d @- echo "Opened the release pull request: $title" fi