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, extracts the matching changelog block from CHANGELOG.md, # and attaches everything to the 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 # Craftimizer/bin/x64/Release derived from find) or pinned URLs to goatcorp # / gitea. Nothing from a webhook event payload (issue/PR titles, commit # messages, etc.) flows into a run-step. on: push: tags: - 'v*' # Manual recovery trigger. Use Gitea's "Run workflow" UI and select the # tag (e.g. v0.1.0) from the Ref dropdown - not main. The Validate tag # ref step below hard-fails if a non-tag ref is selected, because # 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: - 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) # -p:Platform=x64 is required: csproj declares x64 # and win-x64, but project-level # dotnet build defaults to AnyCPU and emits to bin/Release/ instead of # bin/x64/Release/. The Locate latest.zip step below expects the latter. run: dotnet build Craftimizer/Craftimizer.csproj --configuration Release -p:Platform=x64 - name: Locate latest.zip id: locate run: | zip="$(find Craftimizer/bin/x64/Release -name latest.zip -print -quit)" if [ -z "$zip" ]; then echo "latest.zip not found under Craftimizer/bin/x64/Release" >&2 exit 1 fi echo "Found: $zip" echo "path=$zip" >> "$GITHUB_OUTPUT" # Extract the changelog block for the tagged version from CHANGELOG.md. # Convention: each release block starts with `## vX.Y.Z` and ends at # the next `## v` or the `---` trailer at the bottom. Fails the # workflow if no block exists for the tagged version, which is the # automated counterpart to the "csproj + repo.json + CHANGELOG kept # in sync" rule. - name: Generate release body env: TAG_NAME: ${{ github.ref_name }} run: | set -euo pipefail tag="$TAG_NAME" if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::Refusing to generate release body for non-semver tag: $tag" exit 1 fi version="${tag#v}" changelog="CHANGELOG.md" # awk extracts the block starting at "## vX.Y.Z" and stops at the # next "## v" or the "---" trailer. block="$(awk -v ver="## v${version}" ' $0 ~ "^"ver" " { take=1; print; next } take && /^## v[0-9]+\./ { exit } take && /^---$/ { exit } take { print } ' "$changelog")" if [ -z "$block" ]; then echo "::error::No changelog entry for version $version found in $changelog. Update the changelog before tagging a release." exit 1 fi footer="" if [ -f .gitea/release-footer.md ]; then footer="$(cat .gitea/release-footer.md)" fi { echo "$block" echo "" echo "$footer" } > release-body.md echo "Generated release body for $tag:" echo "----------------------------------------" cat release-body.md echo "----------------------------------------" - name: Expose release body for release-action id: body shell: bash run: | { echo 'content<> "$GITHUB_OUTPUT" - 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 }}