7ed689587b
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.
184 lines
8.6 KiB
YAML
184 lines
8.6 KiB
YAML
name: Release
|
|
|
|
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
|
# current Dalamud staging branch, locates the latest.zip produced by
|
|
# DalamudPackager and attaches it to the matching Gitea Release.
|
|
#
|
|
# User-controlled inputs touched by this workflow:
|
|
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
|
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
|
# All other values are either repo-controlled (paths under
|
|
# HellionChat/bin/Release derived from find / Get-ChildItem) or pinned
|
|
# URLs to goatcorp / gitea. Nothing from a webhook event payload (issue/PR
|
|
# titles, commit messages, etc.) flows into a run-step.
|
|
#
|
|
# Linux runner: gitea.com Cloud Actions only ships ubuntu-latest. The
|
|
# plugin csproj targets net10.0-windows, `dotnet build` cross-compiles on
|
|
# Linux when the Dalamud staging assemblies sit under $(HOME)/.xlcore/...
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
# 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:
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
release:
|
|
name: Build and attach release ZIP
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
# 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
|
|
|
|
- name: Setup .NET 10
|
|
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
|
with:
|
|
dotnet-version: 10.0.x
|
|
|
|
- name: Download Dalamud staging
|
|
run: |
|
|
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
|
mkdir -p "$hooks"
|
|
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
|
unzip -oq dalamud.zip -d "$hooks"
|
|
|
|
- name: Build (Release)
|
|
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
|
|
|
- name: Locate latest.zip
|
|
id: locate
|
|
run: |
|
|
zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
|
|
if [ -z "$zip" ]; then
|
|
echo "latest.zip not found under HellionChat/bin/Release" >&2
|
|
exit 1
|
|
fi
|
|
echo "Found: $zip"
|
|
echo "path=$zip" >> "$GITHUB_OUTPUT"
|
|
|
|
# Build a release body from the matching changelog block in
|
|
# HellionChat.yaml plus a static install / docs footer. Fails the
|
|
# workflow if no block exists for the tagged version, which is the
|
|
# automated counterpart to the "yaml + repo.json + release body
|
|
# kept in sync" rule.
|
|
#
|
|
# GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
|
|
# tag value is treated as a PowerShell variable, not as inline shell
|
|
# text. The strict regex below rejects anything that is not a clean
|
|
# semver tag before it is used to build a string.
|
|
- name: Generate release body
|
|
shell: pwsh
|
|
env:
|
|
# 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+$') {
|
|
throw "Refusing to generate release body for non-semver tag: $tag"
|
|
}
|
|
$version = $tag.Substring(1)
|
|
|
|
$yamlPath = "HellionChat/HellionChat.yaml"
|
|
$raw = Get-Content -Path $yamlPath -Raw
|
|
|
|
$marker = "changelog: |-"
|
|
$idx = $raw.IndexOf($marker)
|
|
if ($idx -lt 0) { throw "changelog block not found in $yamlPath" }
|
|
|
|
# changelog: is the last top-level key in the manifest, so
|
|
# everything after the marker is the literal block. Strip the
|
|
# 4-space yaml indent (prettier convention) from each line.
|
|
$afterMarker = $raw.Substring($idx + $marker.Length)
|
|
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
|
if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
|
|
}) -join "`n"
|
|
|
|
# Subblock convention: "**vX.Y.Z — <subtitle> (<date>)**"
|
|
# matches verify-changelog-sync.sh and slim-rule grep.
|
|
$header = "**v$version "
|
|
$start = $changelogBody.IndexOf($header)
|
|
if ($start -lt 0) {
|
|
throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
|
|
}
|
|
|
|
$rest = $changelogBody.Substring($start)
|
|
$nextHdr = $rest.IndexOf("`n`n**v", 1)
|
|
$trailer = $rest.IndexOf("`n`n---")
|
|
|
|
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
|
|
$currentBlock = $rest.Substring(0, $nextHdr).TrimEnd()
|
|
} elseif ($trailer -ge 0) {
|
|
$currentBlock = $rest.Substring(0, $trailer).TrimEnd()
|
|
} else {
|
|
$currentBlock = $rest.TrimEnd()
|
|
}
|
|
|
|
# Static install / docs / licence footer is maintained as a
|
|
# separate file so the workflow YAML stays clean (no embedded
|
|
# heredoc that would have to be indented under the run-block).
|
|
$footerPath = ".github/release-footer.md"
|
|
if (-not (Test-Path $footerPath)) {
|
|
throw "Release footer template not found: $footerPath"
|
|
}
|
|
$footer = Get-Content -Path $footerPath -Raw
|
|
|
|
$body = $currentBlock + "`n" + $footer
|
|
$body | Out-File -FilePath release-body.md -Encoding utf8 -NoNewline
|
|
|
|
Write-Host "Generated release body for $tag :"
|
|
Write-Host "----------------------------------------"
|
|
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<<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
|
|
# 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:
|
|
files: ${{ steps.locate.outputs.path }}
|
|
body: ${{ steps.body.outputs.content }}
|
|
api_key: ${{ secrets.GITHUB_TOKEN }}
|