diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 4615366..623ac5a 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -20,16 +20,12 @@ on: push: tags: - "v*" - # Manual recovery trigger. Use when a tag was pushed but the auto-run - # was missed or failed: `gh workflow run release.yml -f tag=v0.6.1`. - # The tag input is validated against the same semver regex as the - # auto-trigger before any string interpolation happens. + # Manual recovery trigger. Use Gitea's "Run workflow" UI and select the + # tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag + # ref step below hard-fails if a non-tag ref is selected, because the + # release-action reads GITHUB_REF directly and rejects anything that + # does not start with refs/tags/. workflow_dispatch: - inputs: - tag: - description: "Existing tag to (re)release, e.g. v0.6.1" - required: true - type: string permissions: contents: write @@ -41,14 +37,21 @@ jobs: timeout-minutes: 20 steps: - # On push:tags, github.ref_name is the tag — checkout default works. - # On workflow_dispatch, ref defaults to the branch the action was - # invoked from; we need to explicitly check out the tag the user - # supplied so the build comes from the tagged commit, not main. + # release-action@main reads GITHUB_REF directly (its action.yml + # does not declare a tag_name input). Validate up-front so manual + # dispatches from a branch ref fail loud here instead of burning + # a full build before the final step errors out with "ref X is + # not a tag". + - name: Validate tag ref + run: | + if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then + echo "::error::Release workflow must run on a v*.X.Y tag ref, got ${GITHUB_REF}" + echo "::error::Push a tag, or pick the tag (not main) in the workflow_dispatch Ref dropdown." + exit 1 + fi + - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - ref: ${{ github.event.inputs.tag || github.ref }} - name: Setup .NET 10 uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 @@ -89,12 +92,11 @@ jobs: - name: Generate release body shell: pwsh env: - # workflow_dispatch carries the user-supplied tag in inputs.tag; - # push:tags carries it in github.ref_name. Either way the value - # is treated as a PowerShell variable (env-var pass), not as - # inline shell text, and validated against the semver regex - # below before any string interpolation. - TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }} + # github.ref_name is the tag because Validate tag ref above + # already enforced refs/tags/v*. Read via env: so the value + # is a PowerShell variable, not inline shell text, and gets + # re-validated against the semver regex below. + TAG_NAME: ${{ github.ref_name }} run: | $tag = $env:TAG_NAME if ($tag -notmatch '^v\d+\.\d+\.\d+$') { @@ -154,19 +156,28 @@ jobs: Write-Host $body Write-Host "----------------------------------------" + # release-action@main only declares files/title/body/pre_release/ + # draft/api_key/insecure as inputs (see its action.yml). It silently + # ignores anything else, including body_path and tag_name. The tag + # itself comes from GITHUB_REF, the body must be passed inline via + # body:, so we re-emit release-body.md as a step output first. + - name: Expose release body for release-action + id: body + shell: bash + run: | + { + echo 'content<> "$GITHUB_OUTPUT" + # Gitea-native release action. Creates the release if the tag has no - # release yet, or updates the existing one. body_path provides the - # generated release body, files attaches latest.zip. The auto-injected - # GITHUB_TOKEN on Gitea Actions has Gitea-API scope and is sufficient - # for release write. + # release yet, or updates the existing one with latest.zip attached + # and the generated body. The auto-injected GITHUB_TOKEN on Gitea + # Actions has Gitea-API scope and is sufficient for release write. - name: Attach to Gitea release uses: https://gitea.com/actions/release-action@main with: - # Explicit tag_name so the action targets the correct release in - # both push:tags (auto) and workflow_dispatch (manual recovery) - # modes. Without this, dispatch runs would default to the branch - # ref (main) and fail to find the release. - tag_name: ${{ github.event.inputs.tag || github.ref_name }} files: ${{ steps.locate.outputs.path }} - body_path: release-body.md + body: ${{ steps.body.outputs.content }} api_key: ${{ secrets.GITHUB_TOKEN }}