name: Cleanup PR Draft Release on: pull_request: types: [closed] # contents: write — delete the draft release; actions: write — cancel the # closed PR's still-running build workflow before deleting. permissions: actions: write contents: write jobs: delete-draft: name: Delete PR draft release # Fork PRs never get a draft (the release job skips them) and their # GITHUB_TOKEN is read-only regardless of the permissions block, so # there is nothing to cancel or delete. if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest steps: # A closed PR can be reopened while this job is still queued or # waiting; a reopened PR's fresh build must not be cancelled and # its draft must not be deleted. Check the live state up front # (and again right before deleting below). - name: Check PR is still closed id: pr-state env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -euo pipefail echo "state=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.state')" >> "${GITHUB_OUTPUT}" # A build for this PR may still be running and would recreate the # rolling draft after we delete it. Cancel those runs (dead work # for a closed PR anyway) and wait for them to wind down. The # release job additionally re-checks the live PR state, so this # wait is defense in depth, not the only guard. - name: Cancel in-progress builds for the closed PR if: steps.pr-state.outputs.state == 'closed' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} HEAD_BRANCH: ${{ github.event.pull_request.head.ref }} run: | set -euo pipefail # --event pull_request: a manually dispatched build on the # same branch is not this PR's work and must not be cancelled. list_active_runs() { gh run list --repo "${GITHUB_REPOSITORY}" \ --workflow 'Build and Make Electron App' \ --branch "${HEAD_BRANCH}" \ --event pull_request \ --json databaseId,status \ --jq '.[] | select(.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "requested" or .status == "pending") | .databaseId' } for run_id in $(list_active_runs); do echo "Cancelling run ${run_id}" gh run cancel "${run_id}" --repo "${GITHUB_REPOSITORY}" || true done # Cancellation is asynchronous; poll until the runs settle. for _ in $(seq 1 18); do if [ -z "$(list_active_runs)" ]; then break fi sleep 10 done # 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. The PR state # is re-checked one last time right before deleting, in case the # PR was reopened during the cancellation wait above. - name: Delete draft release for closed PR if: steps.pr-state.outputs.state == 'closed' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -euo pipefail if [ "$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.state')" != "closed" ]; then echo "PR #${PR_NUMBER} was reopened; keeping its draft." exit 0 fi 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/{}"