From a7ce9b1a62cf6fc88f07f78776d3c22da28f831c Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 18 Jul 2026 15:14:18 +0200 Subject: [PATCH] ci(release): make test draft releases traceable and self-cleaning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every PR and master build created a draft named "Release v" with tag test-, so 70+ identical drafts piled up and PR drafts were untraceable (for pull_request events github.sha is the ephemeral merge-commit SHA that resolves to nothing in the repo). - Title test drafts as "v — PR # @ [test]" / "v — master @ [test]"; tag releases keep "Release v" - Prepend a context header (PR, head commit, workflow run links) to the auto-generated release notes - Use the PR head SHA and pass target_commitish so generated notes actually cover the PR commits - Use stable tags (test-pr-, test-master) so action-gh-release updates one rolling draft in place instead of creating a new one per push - Mark all non-tag drafts as prerelease - Cancel superseded in-progress PR builds via a concurrency group - Delete a PR's rolling draft when the PR closes (new workflow) Co-Authored-By: Claude Fable 5 --- .github/workflows/build-and-make.yaml | 64 ++++++++++++++++++++++++-- .github/workflows/cleanup-pr-draft.yml | 27 +++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/cleanup-pr-draft.yml diff --git a/.github/workflows/build-and-make.yaml b/.github/workflows/build-and-make.yaml index bd0abee32..dce6e1c83 100644 --- a/.github/workflows/build-and-make.yaml +++ b/.github/workflows/build-and-make.yaml @@ -11,6 +11,10 @@ on: - master workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: name: Build on ${{ matrix.os }} ${{ matrix.arch }} @@ -797,13 +801,67 @@ jobs: id: package-version run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + # Test drafts (PR/master) get a self-describing title plus a context + # header linking the PR, real head commit, and workflow run. A stable + # tag per PR (test-pr-) / branch (test-master) makes the action + # update one rolling draft in place instead of piling up a new draft + # for every push. PR builds must not use github.sha here: that is the + # ephemeral merge-commit SHA, which resolves to nothing in the repo. + - name: Compose release metadata + id: release-meta + shell: bash + env: + VERSION: ${{ steps.package-version.outputs.version }} + EVENT_NAME: ${{ github.event_name }} + IS_TAG_BUILD: ${{ startsWith(github.ref, 'refs/tags/') }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_URL: ${{ github.event.pull_request.html_url }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + SOURCE_BRANCH: ${{ github.head_ref || github.ref_name }} + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + + SHORT_SHA="${HEAD_SHA:0:7}" + SAFE_BRANCH="${SOURCE_BRANCH//\//-}" + + if [ "${IS_TAG_BUILD}" = "true" ]; then + NAME="Release v${VERSION}" + TAG="${GITHUB_REF_NAME}" + BODY="" + elif [ "${EVENT_NAME}" = "pull_request" ]; then + NAME="v${VERSION} — PR #${PR_NUMBER} @ ${SHORT_SHA} [test]" + TAG="test-pr-${PR_NUMBER}" + BODY="$(printf '🧪 Test build for PR [#%s](%s) — %s\n\nCommit [`%s`](%s/commit/%s) · branch `%s` · [workflow run](%s)' \ + "${PR_NUMBER}" "${PR_URL}" "${PR_TITLE}" \ + "${SHORT_SHA}" "${REPO_URL}" "${HEAD_SHA}" "${SOURCE_BRANCH}" "${RUN_URL}")" + else + NAME="v${VERSION} — ${SAFE_BRANCH} @ ${SHORT_SHA} [test]" + TAG="test-${SAFE_BRANCH}" + BODY="$(printf '🧪 Test build from `%s` — commit [`%s`](%s/commit/%s) · [workflow run](%s)' \ + "${SOURCE_BRANCH}" "${SHORT_SHA}" "${REPO_URL}" "${HEAD_SHA}" "${RUN_URL}")" + fi + + { + echo "name=${NAME}" + echo "tag=${TAG}" + echo "commitish=${HEAD_SHA}" + echo "body<> "${GITHUB_OUTPUT}" + - name: Create Draft Release uses: softprops/action-gh-release@v2 with: draft: true - prerelease: ${{ github.event_name == 'pull_request' }} - name: Release v${{ steps.package-version.outputs.version }} - tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('test-{0}', github.sha) }} + prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }} + name: ${{ steps.release-meta.outputs.name }} + tag_name: ${{ steps.release-meta.outputs.tag }} + target_commitish: ${{ steps.release-meta.outputs.commitish }} + body: ${{ steps.release-meta.outputs.body }} generate_release_notes: true files: | artifacts/macos-x64-artifacts/*-x64.dmg diff --git a/.github/workflows/cleanup-pr-draft.yml b/.github/workflows/cleanup-pr-draft.yml new file mode 100644 index 000000000..661892ec3 --- /dev/null +++ b/.github/workflows/cleanup-pr-draft.yml @@ -0,0 +1,27 @@ +name: Cleanup PR Draft Release + +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + delete-draft: + name: Delete PR draft release + runs-on: ubuntu-latest + steps: + # Draft releases have no real git tag, so a lookup via + # releases/tags/ returns 404. List releases and match the + # draft by its stored tag_name (test-pr-) instead. + - name: Delete draft release for closed PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + + gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" --paginate \ + --jq ".[] | select(.draft and .tag_name == \"test-pr-${PR_NUMBER}\") | .id" | + xargs -r -n1 -I{} gh api -X DELETE "repos/${GITHUB_REPOSITORY}/releases/{}"