The v2.0.0 tag built fine and then died on its last step: gitea.com's release-action declares `using: go`, the runner has to compile it, and act cannot -- exec: "go": executable file not found, exit 127, after a green build. The zip existed and never got attached, so the Discord announcement went out while the download link pointed at nothing. This is a known failure. It was diagnosed on another repo in June and the note from then says in as many words that this repo carries the same pattern and should migrate before its next release. It did not, and here we are. The publish step is a plain curl call against the Gitea API now, running in the job image with curl and python3, independent of go, the action cache, and whatever @main happens to point at. Idempotent by design: a re-run reuses an existing release and replaces the asset rather than failing on the duplicate, which is exactly the state a recovery run finds. MessagePack moves from the 3.1.4 floor to 3.1.7, which is what the trivy scan was failing on. The range already allowed it -- NuGet resolves the lower bound of a range, and trivy reads it the same way, so the floor is the version that counts. The advisories are recursion depth in Skip and an LZ4 decompression fault, both reachable only through crafted input; this plugin serialises its own payloads and reads back its own bytes from a local database, so the practical exposure is someone who already has write access to the file. Lifted because it costs nothing and a scan that stays red for a known-harmless reason is how a real finding gets missed later.
208 lines
9.1 KiB
YAML
208 lines
9.1 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: the release
|
|
# name and body are both derived from the tag, so a branch ref would
|
|
# publish a release named after a branch.
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
release:
|
|
name: Build and attach release ZIP
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
|
|
steps:
|
|
# Validate up-front so a manual dispatch from a branch ref fails loud
|
|
# here instead of burning a full build before the publish step notices.
|
|
- 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@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # 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 "----------------------------------------"
|
|
|
|
# The tag comes from GITHUB_REF, the body from the step above. Posted with
|
|
# curl rather than gitea.com/actions/release-action, which declares
|
|
# `using: go` and has to be compiled by the runner -- act cannot do that
|
|
# here and the step dies with exec: "go": executable file not found, exit
|
|
# 127, after a build that otherwise succeeded. This runs as a plain shell
|
|
# step in the job image, which has curl and python3.
|
|
#
|
|
# Idempotent on purpose: a re-run against an existing release reuses it and
|
|
# replaces the asset instead of failing on the duplicate.
|
|
- name: Attach to Gitea release
|
|
shell: bash
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
ZIP_PATH: ${{ steps.locate.outputs.path }}
|
|
TAG_NAME: ${{ github.ref_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
|
|
auth="Authorization: token ${GITEA_TOKEN}"
|
|
|
|
# Existing release for this tag, or create one.
|
|
rel_id="$(curl -sf -H "$auth" "$api/releases/tags/${TAG_NAME}" \
|
|
| python3 -c 'import sys,json; print(json.load(sys.stdin).get("id",""))' 2>/dev/null || true)"
|
|
|
|
if [ -z "$rel_id" ]; then
|
|
payload="$(python3 -c '
|
|
import json, os, sys
|
|
body = open("release-body.md", encoding="utf-8").read()
|
|
json.dump({"tag_name": os.environ["TAG_NAME"], "name": os.environ["TAG_NAME"],
|
|
"body": body, "draft": False, "prerelease": False}, sys.stdout)
|
|
')"
|
|
rel_id="$(printf '%s' "$payload" \
|
|
| curl -sf -X POST -H "$auth" -H "Content-Type: application/json" -d @- "$api/releases" \
|
|
| python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')"
|
|
echo "Created release $rel_id for ${TAG_NAME}"
|
|
else
|
|
echo "Reusing release $rel_id for ${TAG_NAME}"
|
|
fi
|
|
|
|
# Drop a same-named asset from an earlier attempt, or the upload 409s.
|
|
old_id="$(curl -sf -H "$auth" "$api/releases/${rel_id}/assets" \
|
|
| python3 -c 'import sys,json; print(next((a["id"] for a in json.load(sys.stdin) if a["name"]=="latest.zip"), ""))' 2>/dev/null || true)"
|
|
if [ -n "$old_id" ]; then
|
|
curl -sf -X DELETE -H "$auth" "$api/releases/${rel_id}/assets/${old_id}"
|
|
echo "Replaced existing latest.zip (asset $old_id)"
|
|
fi
|
|
|
|
curl -sf -X POST -H "$auth" \
|
|
-F "attachment=@${ZIP_PATH};filename=latest.zip" \
|
|
"$api/releases/${rel_id}/assets?name=latest.zip" \
|
|
| python3 -c 'import sys,json; a=json.load(sys.stdin); print("Attached", a["name"], a["size"], "bytes")'
|