ci: add stable release creation workflow #78

Merged
abart27 merged 1 commit from stable-release-ci into main 2026-04-26 17:05:07 +00:00
abart27 commented 2026-04-25 18:42:50 +00:00 (Migrated from github.com)
No description provided.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-04-25 18:45:26 +00:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

Adds a manually triggered GitHub Actions workflow to create draft “stable” releases from the latest main build artifact, with an auto-generated changelog.

Changes:

  • Introduces .github/workflows/release.yml workflow_dispatch pipeline that checks out main, downloads the latest build artifact, and drafts a GitHub Release.
  • Generates a release body using git-cliff based on the previous stable release tag.
  • Uploads the amalgamated Lua outputs as release assets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

## Pull request overview Adds a manually triggered GitHub Actions workflow to create draft “stable” releases from the latest main build artifact, with an auto-generated changelog. **Changes:** - Introduces `.github/workflows/release.yml` workflow_dispatch pipeline that checks out `main`, downloads the latest `build` artifact, and drafts a GitHub Release. - Generates a release body using `git-cliff` based on the previous stable release tag. - Uploads the amalgamated Lua outputs as release assets. --- 💡 <a href="/mupen64/ugui/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
@ -0,0 +25,4 @@
- name: Guard against non-main branches
run: |
if [[ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]]; then
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-25 18:45:25 +00:00

The branch guard is likely to fail because actions/checkout typically leaves the repo in a detached HEAD state (so git rev-parse --abbrev-ref HEAD returns HEAD, not main). Consider enforcing main via a job/step if: github.ref_name == 'main' check, or compare the checked-out commit to origin/main, or explicitly create a local main branch after checkout instead of relying on --abbrev-ref.

          if [[ "${{ github.ref_name }}" != "main" ]]; then
The branch guard is likely to fail because `actions/checkout` typically leaves the repo in a detached HEAD state (so `git rev-parse --abbrev-ref HEAD` returns `HEAD`, not `main`). Consider enforcing main via a job/step `if: github.ref_name == 'main'` check, or compare the checked-out commit to `origin/main`, or explicitly create a local `main` branch after checkout instead of relying on `--abbrev-ref`. ```suggestion if [[ "${{ github.ref_name }}" != "main" ]]; then ```
@ -0,0 +35,4 @@
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: main.yml
branch: main
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-25 18:45:25 +00:00

This workflow checks out main at run time but downloads the artifact from the latest main.yml run, which can be for a different commit than the one just checked out (e.g., if a newer commit was pushed but its build is still running/failed). That can produce releases where the changelog/tag and attached artifacts don't correspond to the same SHA. Consider pinning the artifact download to the same commit you intend to release (or obtaining the run-id/SHA of the build you download and checking out that SHA).

      - name: Resolve latest successful main build run
        id: main_run
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          run_id=$(gh api \
            "/repos/${{ github.repository }}/actions/workflows/main.yml/runs?branch=main&status=success&per_page=1" \
            --jq '.workflow_runs[0].id')
          head_sha=$(gh api \
            "/repos/${{ github.repository }}/actions/runs/${run_id}" \
            --jq '.head_sha')

          if [[ -z "${run_id}" || "${run_id}" == "null" || -z "${head_sha}" || "${head_sha}" == "null" ]]; then
            echo "❌ Could not resolve a successful main.yml run on branch main."
            exit 1
          fi

          echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
          echo "head_sha=${head_sha}" >> "$GITHUB_OUTPUT"

      - name: Checkout release source commit
        uses: actions/checkout@v4
        with:
          ref: ${{ steps.main_run.outputs.head_sha }}
          fetch-depth: 0

      - name: Guard against unresolved main build run
        run: |
          if [[ -z "${{ steps.main_run.outputs.run_id }}" || -z "${{ steps.main_run.outputs.head_sha }}" ]]; then
            echo "❌ This workflow must use a resolved successful build from the main branch."
            exit 1
          fi

      - name: Download build artifact from resolved main run
        uses: dawidd6/action-download-artifact@v6
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          run_id: ${{ steps.main_run.outputs.run_id }}
This workflow checks out `main` at run time but downloads the artifact from the *latest* `main.yml` run, which can be for a different commit than the one just checked out (e.g., if a newer commit was pushed but its build is still running/failed). That can produce releases where the changelog/tag and attached artifacts don't correspond to the same SHA. Consider pinning the artifact download to the same commit you intend to release (or obtaining the run-id/SHA of the build you download and checking out that SHA). ```suggestion - name: Resolve latest successful main build run id: main_run env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | run_id=$(gh api \ "/repos/${{ github.repository }}/actions/workflows/main.yml/runs?branch=main&status=success&per_page=1" \ --jq '.workflow_runs[0].id') head_sha=$(gh api \ "/repos/${{ github.repository }}/actions/runs/${run_id}" \ --jq '.head_sha') if [[ -z "${run_id}" || "${run_id}" == "null" || -z "${head_sha}" || "${head_sha}" == "null" ]]; then echo "❌ Could not resolve a successful main.yml run on branch main." exit 1 fi echo "run_id=${run_id}" >> "$GITHUB_OUTPUT" echo "head_sha=${head_sha}" >> "$GITHUB_OUTPUT" - name: Checkout release source commit uses: actions/checkout@v4 with: ref: ${{ steps.main_run.outputs.head_sha }} fetch-depth: 0 - name: Guard against unresolved main build run run: | if [[ -z "${{ steps.main_run.outputs.run_id }}" || -z "${{ steps.main_run.outputs.head_sha }}" ]]; then echo "❌ This workflow must use a resolved successful build from the main branch." exit 1 fi - name: Download build artifact from resolved main run uses: dawidd6/action-download-artifact@v6 with: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ steps.main_run.outputs.run_id }} ```
@ -0,0 +44,4 @@
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag=$(gh release list --exclude-pre-releases --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || true)
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-25 18:45:25 +00:00

When there are no stable releases, the jq expression '.[0].tagName' will evaluate to null, which is non-empty and will make the next step run git cliff with a null..HEAD range. Treat null the same as empty (or change the jq filter to return an empty string when the array is empty) before deciding which git cliff command to run.

          tag=$(gh release list --exclude-pre-releases --limit 1 --json tagName --jq '.[0].tagName // ""' 2>/dev/null || true)
When there are no stable releases, the jq expression `'.[0].tagName'` will evaluate to `null`, which is non-empty and will make the next step run `git cliff` with a `null..HEAD` range. Treat `null` the same as empty (or change the jq filter to return an empty string when the array is empty) before deciding which `git cliff` command to run. ```suggestion tag=$(gh release list --exclude-pre-releases --limit 1 --json tagName --jq '.[0].tagName // ""' 2>/dev/null || true) ```
@ -0,0 +65,4 @@
{
echo 'body<<EOF'
cat CHANGELOG_DRAFT.md
echo EOF
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-25 18:45:26 +00:00

Writing the changelog into $GITHUB_OUTPUT uses a fixed EOF delimiter. If the generated changelog ever contains a line that equals the delimiter, it will corrupt the output value. Use a unique delimiter (e.g., randomly generated) to make the output robust.

          delimiter="$(uuidgen)"
          {
            echo "body<<$delimiter"
            cat CHANGELOG_DRAFT.md
            echo "$delimiter"
Writing the changelog into `$GITHUB_OUTPUT` uses a fixed `EOF` delimiter. If the generated changelog ever contains a line that equals the delimiter, it will corrupt the output value. Use a unique delimiter (e.g., randomly generated) to make the output robust. ```suggestion delimiter="$(uuidgen)" { echo "body<<$delimiter" cat CHANGELOG_DRAFT.md echo "$delimiter" ```
Sign in to join this conversation.
No description provided.