Compare commits

..

4 Commits

Author SHA1 Message Date
JonKazama-Hellion 61d5a33683 Merge fix/release-workflow-ref-guard into main
Security / scan (push) Successful in 21s
Build / Build (Release) (push) Successful in 28s
Forge Announce / Post changelog to Hellion Forge (push) Successful in 7s
Release / Build and attach release ZIP (push) Successful in 39s
Guards release.yml against non-tag refs and fixes the silent
ignore of body_path / tag_name that left every Gitea release
since v1.4.1 with an empty body.
2026-05-12 11:50:32 +02:00
JonKazama-Hellion 7ed689587b fix(ci): guard release.yml against non-tag refs and pass body inline
The release-action@main reads GITHUB_REF directly and rejects anything
that doesn't start with refs/tags/. The previous workflow tried to work
around this by passing tag_name as an action input, but the action's
action.yml never declared tag_name (or body_path) - both inputs were
silently ignored, which is why every Gitea release since v1.4.1 was
published with an empty body.

Changes:
- New "Validate tag ref" step fails fast with a clear message when the
  workflow is dispatched from a branch ref instead of a tag ref.
- workflow_dispatch.inputs.tag dropped; recovery now means picking the
  tag from Gitea's Ref dropdown so GITHUB_REF lines up with refs/tags/.
- release-body.md is re-emitted as a step output and passed via body:
  (the input the action actually reads) instead of body_path.
- tag_name input removed from the action call - the action derives the
  tag from GITHUB_REF_NAME on its own.
2026-05-12 11:33:58 +02:00
JonKazama-Hellion 612bf8814f fix(ci): match release + forge-announce parsing to current yaml format
Security / scan (push) Successful in 21s
Build / Build (Release) (push) Successful in 30s
Both workflows looked for "**Hellion Chat <version>" as the changelog
subblock header, but the yaml convention is "**v<version> — <subtitle>"
(matches verify-changelog-sync.sh and the slim-rule grep). Plus the
indent-strip was 2 spaces, but prettier writes the changelog block with
4-space indent. Both regressions silently failed every release-workflow
run since the format change — likely why v1.4.3 was released manually.

Sync header marker to "**v$version " and indent-strip to 4 spaces in
both files.
2026-05-12 11:17:41 +02:00
JonKazama-Hellion be17472cd5 chore(ci): migrate workflows to .gitea/workflows/
Security / scan (push) Successful in 19s
Build / Build (Release) (push) Successful in 42s
Gitea Actions reads exclusively from .gitea/workflows/, not from
.github/workflows/. Since the cutover in v1.4.3 only the security
workflow has been running — release and forge-announce silently sat in
the wrong directory and never fired on any tag push. v1.4.3 must have
been released manually.

Move build, release and forge-announce yamls to .gitea/workflows/. The
.github/forge-posts/ and .github/release-footer.md data files stay where
they are; the workflows reference them by repo-relative path and that
keeps working.

For the v1.4.4 backfill: workflow_dispatch via the Gitea web UI with
tag=v1.4.4 will run release.yml + forge-announce.yml against the tagged
tree (which doesn't contain this migration). The dispatch yaml itself
is read from the default branch, not the tag, so the missing yamls in
the v1.4.4 tag tree don't matter.
2026-05-12 11:05:52 +02:00
3 changed files with 51 additions and 38 deletions
@@ -101,16 +101,16 @@ jobs:
if ($idx -lt 0) { throw "V5: changelog-Block nicht gefunden in $yamlPath" } if ($idx -lt 0) { throw "V5: changelog-Block nicht gefunden in $yamlPath" }
$afterMarker = $raw.Substring($idx + $marker.Length) $afterMarker = $raw.Substring($idx + $marker.Length)
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object { $changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
if ($_ -match '^ ') { $_.Substring(2) } else { $_ } if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
}) -join "`n" }) -join "`n"
$header = "**Hellion Chat $version" $header = "**v$version "
$start = $changelogBody.IndexOf($header) $start = $changelogBody.IndexOf($header)
if ($start -lt 0) { if ($start -lt 0) {
throw "V5: No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging." throw "V5: No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging."
} }
$rest = $changelogBody.Substring($start) $rest = $changelogBody.Substring($start)
$nextHdr = $rest.IndexOf("`n`n**Hellion Chat ", 1) $nextHdr = $rest.IndexOf("`n`n**v", 1)
$trailer = $rest.IndexOf("`n`n---") $trailer = $rest.IndexOf("`n`n---")
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) { if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
$enBlock = $rest.Substring(0, $nextHdr).TrimEnd() $enBlock = $rest.Substring(0, $nextHdr).TrimEnd()
@@ -20,16 +20,12 @@ on:
push: push:
tags: tags:
- "v*" - "v*"
# Manual recovery trigger. Use when a tag was pushed but the auto-run # Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
# was missed or failed: `gh workflow run release.yml -f tag=v0.6.1`. # tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag
# The tag input is validated against the same semver regex as the # ref step below hard-fails if a non-tag ref is selected, because the
# auto-trigger before any string interpolation happens. # release-action reads GITHUB_REF directly and rejects anything that
# does not start with refs/tags/.
workflow_dispatch: workflow_dispatch:
inputs:
tag:
description: "Existing tag to (re)release, e.g. v0.6.1"
required: true
type: string
permissions: permissions:
contents: write contents: write
@@ -41,14 +37,21 @@ jobs:
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
# On push:tags, github.ref_name is the tag — checkout default works. # release-action@main reads GITHUB_REF directly (its action.yml
# On workflow_dispatch, ref defaults to the branch the action was # does not declare a tag_name input). Validate up-front so manual
# invoked from; we need to explicitly check out the tag the user # dispatches from a branch ref fail loud here instead of burning
# supplied so the build comes from the tagged commit, not main. # 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 - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Setup .NET 10 - name: Setup .NET 10
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
@@ -89,12 +92,11 @@ jobs:
- name: Generate release body - name: Generate release body
shell: pwsh shell: pwsh
env: env:
# workflow_dispatch carries the user-supplied tag in inputs.tag; # github.ref_name is the tag because Validate tag ref above
# push:tags carries it in github.ref_name. Either way the value # already enforced refs/tags/v*. Read via env: so the value
# is treated as a PowerShell variable (env-var pass), not as # is a PowerShell variable, not inline shell text, and gets
# inline shell text, and validated against the semver regex # re-validated against the semver regex below.
# below before any string interpolation. TAG_NAME: ${{ github.ref_name }}
TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }}
run: | run: |
$tag = $env:TAG_NAME $tag = $env:TAG_NAME
if ($tag -notmatch '^v\d+\.\d+\.\d+$') { if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
@@ -111,20 +113,22 @@ jobs:
# changelog: is the last top-level key in the manifest, so # changelog: is the last top-level key in the manifest, so
# everything after the marker is the literal block. Strip the # everything after the marker is the literal block. Strip the
# 2-space yaml indent from each line. # 4-space yaml indent (prettier convention) from each line.
$afterMarker = $raw.Substring($idx + $marker.Length) $afterMarker = $raw.Substring($idx + $marker.Length)
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object { $changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
if ($_ -match '^ ') { $_.Substring(2) } else { $_ } if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
}) -join "`n" }) -join "`n"
$header = "**Hellion Chat $version" # Subblock convention: "**vX.Y.Z — <subtitle> (<date>)**"
# matches verify-changelog-sync.sh and slim-rule grep.
$header = "**v$version "
$start = $changelogBody.IndexOf($header) $start = $changelogBody.IndexOf($header)
if ($start -lt 0) { if ($start -lt 0) {
throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release." throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
} }
$rest = $changelogBody.Substring($start) $rest = $changelogBody.Substring($start)
$nextHdr = $rest.IndexOf("`n`n**Hellion Chat ", 1) $nextHdr = $rest.IndexOf("`n`n**v", 1)
$trailer = $rest.IndexOf("`n`n---") $trailer = $rest.IndexOf("`n`n---")
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) { if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
@@ -152,19 +156,28 @@ jobs:
Write-Host $body Write-Host $body
Write-Host "----------------------------------------" 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<<RELEASE_BODY_EOF'
cat release-body.md
echo 'RELEASE_BODY_EOF'
} >> "$GITHUB_OUTPUT"
# Gitea-native release action. Creates the release if the tag has no # Gitea-native release action. Creates the release if the tag has no
# release yet, or updates the existing one. body_path provides the # release yet, or updates the existing one with latest.zip attached
# generated release body, files attaches latest.zip. The auto-injected # and the generated body. The auto-injected GITHUB_TOKEN on Gitea
# GITHUB_TOKEN on Gitea Actions has Gitea-API scope and is sufficient # Actions has Gitea-API scope and is sufficient for release write.
# for release write.
- name: Attach to Gitea release - name: Attach to Gitea release
uses: https://gitea.com/actions/release-action@main uses: https://gitea.com/actions/release-action@main
with: 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 }} files: ${{ steps.locate.outputs.path }}
body_path: release-body.md body: ${{ steps.body.outputs.content }}
api_key: ${{ secrets.GITHUB_TOKEN }} api_key: ${{ secrets.GITHUB_TOKEN }}