Skip to content

Stable Release

Stable Release #6

Workflow file for this run

name: Stable Release
# Fires when a vX.Y.Z tag is pushed, or can be triggered
# manually with a tag.
on:
push:
tags:
- 'v[0-9]*.[0-9]*.[0-9]*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to release (e.g. v1.4.0)'
required: true
jobs:
release:
name: Stable Release
runs-on: ubuntu-latest
permissions:
contents: write # Required for creating the GitHub Release
steps:
# ──────────────────────────────────────────────────────────────────────
# 1. CHECKOUT
# ──────────────────────────────────────────────────────────────────────
- name: Checkout
uses: actions/checkout@v6
with:
# For push:tags: github.ref is the tag ref (e.g. refs/tags/v1.4.0).
# For workflow_dispatch: github.ref is the branch (e.g. refs/heads/main).
# We never use the tag input here because the tag may not exist yet —
# it is created in a later step.
ref: ${{ github.ref }}
fetch-depth: 0
# ──────────────────────────────────────────────────────────────────────
# 2. RESOLVE VERSION
# ──────────────────────────────────────────────────────────────────────
- name: Resolve version from tag
id: meta
run: |
REF="${{ github.event.inputs.tag || github.ref_name }}"
VERSION="${REF#v}"
echo "ref=${REF}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Releasing version: ${VERSION}"
# ──────────────────────────────────────────────────────────────────────
# 3. CREATE TAG (if it doesn't exist yet)
#
# When triggered via workflow_dispatch the tag may not exist yet.
# This step creates and pushes it so the GitHub Release can reference it.
# ──────────────────────────────────────────────────────────────────────
- name: Create and push tag if missing
run: |
REF="${{ steps.meta.outputs.ref }}"
if git rev-parse --verify --quiet "refs/tags/${REF}" > /dev/null; then
echo "Tag ${REF} already exists; skipping creation."
else
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "${REF}"
git push origin "${REF}"
echo "Created and pushed tag ${REF}."
fi
# ──────────────────────────────────────────────────────────────────────
# 4. BUILD & VERSION
# ──────────────────────────────────────────────────────────────────────
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '21'
cache: maven
- name: Set release version in POM
run: |
mvn -B -ntp versions:set \
-DnewVersion="${{ steps.meta.outputs.version }}" \
-DgenerateBackupPoms=false
- name: Build
run: mvn -B -ntp -DskipTests package
# ──────────────────────────────────────────────────────────────────────
# 5. EXTRACT CHANGELOG
#
# Reads the section for this version from CHANGELOG.md.
# Falls back to a plain message when the section is missing.
# ──────────────────────────────────────────────────────────────────────
- name: Extract changelog for this version
id: changelog
run: |
VERSION="${{ steps.meta.outputs.version }}"
HEADER="## [${VERSION}]"
# Fixed-string match via index() avoids regex issues with dots in versions.
NOTES=$(awk \
-v hdr="$HEADER" \
'/^## \[/ && p { exit }
index($0, hdr) == 1 { p=1; next }
p { print }' \
CHANGELOG.md)
if [[ -z "$NOTES" ]]; then
NOTES="No changelog entry found for ${VERSION}."
fi
printf '%s\n' "$NOTES" > release-notes.md
echo "=== Release notes preview ===" && cat release-notes.md
# ──────────────────────────────────────────────────────────────────────
# 6. GITHUB RELEASE
# ──────────────────────────────────────────────────────────────────────
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ steps.meta.outputs.ref }}" \
--title "EzCountdown ${{ steps.meta.outputs.version }}" \
--notes-file release-notes.md \
"target/EzCountdown-${{ steps.meta.outputs.version }}.jar"
# ──────────────────────────────────────────────────────────────────────
# 7. MODRINTH RELEASE
# ──────────────────────────────────────────────────────────────────────
- name: Release to Modrinth (stable)
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
run: |
VERSION="${{ steps.meta.outputs.version }}"
JAR="target/EzCountdown-${VERSION}.jar"
JAR_NAME="$(basename "$JAR")"
# Resolve all MC release versions >= 1.13 from Modrinth
GAME_VERSIONS=$(python3 -c "import json,urllib.request; r=urllib.request.urlopen('https://api.modrinth.com/v2/tag/game_version'); vs=json.loads(r.read()); K=lambda v:[int(x) for x in v.split('.') if x.isdigit()]; print(json.dumps(sorted([v['version'] for v in vs if v['version_type']=='release' and K(v['version'])>=[1,13]],key=K)))")
# Build version metadata JSON
jq -n \
--arg name "EzCountdown ${VERSION}" \
--arg ver "${VERSION}" \
--rawfile changelog release-notes.md \
--arg jar_name "${JAR_NAME}" \
--argjson game_versions "${GAME_VERSIONS}" \
'{
name: $name,
version_number: $ver,
changelog: $changelog,
dependencies: [],
game_versions: $game_versions,
version_type: "release",
loaders: ["bukkit","spigot","paper","purpur","folia"],
featured: true,
project_id: "rCku0I4l",
primary_file: $jar_name,
file_parts: [$jar_name]
}' > /tmp/version_data.json
# Upload to Modrinth v2 API
HTTP_CODE=$(curl -s \
-o /tmp/modrinth_resp.json \
-w "%{http_code}" \
-X POST "https://api.modrinth.com/v2/version" \
-H "Authorization: ${MODRINTH_TOKEN}" \
-F "data=@/tmp/version_data.json;type=application/json" \
-F "${JAR_NAME}=@${JAR};type=application/java-archive")
if [[ "$HTTP_CODE" -lt 200 || "$HTTP_CODE" -ge 300 ]]; then
echo "::error::Modrinth publish failed (HTTP ${HTTP_CODE}):"
cat /tmp/modrinth_resp.json
exit 1
fi
echo "Published to Modrinth (HTTP ${HTTP_CODE})."
python3 -c "import json; d=json.load(open('/tmp/modrinth_resp.json')); print(f' ID: {d.get(\"id\",\"?\")} | {d.get(\"name\",\"?\")} | {len(d.get(\"game_versions\",[]))} game version(s)')"
# ──────────────────────────────────────────────────────────────────────
# 8. DISCORD NOTIFICATION
# ──────────────────────────────────────────────────────────────────────
- name: Post Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.EZCOUNTDOWN_RELEASE_DISCORD_WEBHOOK }}
run: |
REPO="${{ github.repository }}"
VERSION="${{ steps.meta.outputs.version }}"
GH_URL="https://github.com/${REPO}/releases/tag/${{ steps.meta.outputs.ref }}"
# Truncate changelog to fit Discord's 4096-char embed description limit.
CHANGELOG=$(head -c 3800 release-notes.md || true)
MODRINTH_URL="https://modrinth.com/plugin/ezcountdown/version/${VERSION}"
PAYLOAD=$(jq -n \
--arg version "$VERSION" \
--arg gh_url "$GH_URL" \
--arg modrinth_url "$MODRINTH_URL" \
--arg changelog "$CHANGELOG" \
'{
embeds: [{
title: ("🚀 EzCountdown " + $version + " — Stable Release"),
url: $gh_url,
color: 3447003,
description: $changelog,
fields: [
{ name: "GitHub Release", value: ("[View on GitHub](" + $gh_url + ")"), inline: true },
{ name: "Modrinth", value: ("[Download on Modrinth](" + $modrinth_url + ")"), inline: true }
],
footer: { text: "Stable release" }
}]
}')
curl --fail -s -X POST "$DISCORD_WEBHOOK" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"