Skip to content

Release Build

Release Build #29

Workflow file for this run

name: Release Build
on:
workflow_dispatch:
inputs:
publish_discord:
description: "Send Discord notification"
required: true
type: boolean
default: true
publish_github:
description: "Publish to GitHub Releases"
required: true
type: boolean
default: true
publish_curseforge:
description: "Publish to CurseForge"
required: true
type: boolean
default: true
publish_modrinth:
description: "Publish to Modrinth"
required: true
type: boolean
default: true
confirm:
description: "Type RELEASE to confirm"
required: true
type: string
concurrency:
group: release-${{ github.ref_name || github.run_id }}
cancel-in-progress: false
permissions:
contents: read
jobs:
validate-release:
name: Validate Release Target
runs-on: ubuntu-latest
outputs:
is_version_branch: ${{ steps.branch_check.outputs.is_version_branch }}
target_branch: ${{ steps.branch_check.outputs.target_branch }}
publish_discord: ${{ steps.branch_check.outputs.publish_discord }}
publish_github: ${{ steps.branch_check.outputs.publish_github }}
publish_curseforge: ${{ steps.branch_check.outputs.publish_curseforge }}
publish_modrinth: ${{ steps.branch_check.outputs.publish_modrinth }}
steps:
- name: Resolve trigger inputs
id: branch_check
shell: bash
env:
TARGET_BRANCH: ${{ github.ref_name }}
INPUT_PUBLISH_DISCORD: ${{ inputs.publish_discord }}
INPUT_PUBLISH_GITHUB: ${{ inputs.publish_github }}
INPUT_PUBLISH_CURSEFORGE: ${{ inputs.publish_curseforge }}
INPUT_PUBLISH_MODRINTH: ${{ inputs.publish_modrinth }}
INPUT_CONFIRM: ${{ inputs.confirm }}
run: |
target="$TARGET_BRANCH"
publish_discord="$INPUT_PUBLISH_DISCORD"
publish_github="$INPUT_PUBLISH_GITHUB"
publish_curseforge="$INPUT_PUBLISH_CURSEFORGE"
publish_modrinth="$INPUT_PUBLISH_MODRINTH"
if [[ "$INPUT_CONFIRM" != "RELEASE" ]]; then
echo "Manual confirmation failed. Type RELEASE in the confirm input."
exit 1
fi
# Accept branch names like 26.1, 1.21.1, 1.20.2, etc.
if [[ "$target" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
echo "is_version_branch=true" >> "$GITHUB_OUTPUT"
echo "Release target branch '$target' accepted."
else
echo "is_version_branch=false" >> "$GITHUB_OUTPUT"
echo "Release target branch '$target' is not a version branch. Workflow will skip."
fi
echo "target_branch=$target" >> "$GITHUB_OUTPUT"
echo "publish_discord=$publish_discord" >> "$GITHUB_OUTPUT"
echo "publish_github=$publish_github" >> "$GITHUB_OUTPUT"
echo "publish_curseforge=$publish_curseforge" >> "$GITHUB_OUTPUT"
echo "publish_modrinth=$publish_modrinth" >> "$GITHUB_OUTPUT"
build:
name: Build
needs: validate-release
if: needs.validate-release.outputs.is_version_branch == 'true'
runs-on: ubuntu-latest
outputs:
minecraft_version: ${{ steps.meta.outputs.minecraft_version }}
mod_version: ${{ steps.meta.outputs.mod_version }}
mod_name: ${{ steps.meta.outputs.mod_name }}
release_tag: ${{ steps.meta.outputs.release_tag }}
changelog: ${{ steps.meta.outputs.changelog }}
java_version: ${{ steps.meta.outputs.java_version }}
loader: ${{ steps.meta.outputs.loader }}
steps:
- name: Checkout target branch
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref }}
- name: Read project metadata
id: meta
shell: bash
run: |
minecraft_version="$(awk -F= '/^minecraft_version=/{print $2}' gradle.properties | tr -d '\r')"
mod_version="$(awk -F= '/^mod_version=/{print $2}' gradle.properties | tr -d '\r')"
mod_name="$(awk -F= '/^mod_name=/{print $2}' gradle.properties | tr -d '\r')"
release_tag="${minecraft_version}-${mod_version}"
changelog_file="CHANGELOGS/${release_tag}.md"
java_version="$(grep -oE 'JavaLanguageVersion\.of\([0-9]+\)' build.gradle.kts | head -n1 | grep -oE '[0-9]+' || true)"
if [[ -z "$java_version" ]]; then
java_version="21"
fi
if [[ ! -f "$changelog_file" ]]; then
echo "Missing changelog file: $changelog_file"
exit 1
fi
if grep -q 'id("net.neoforged.moddev")' build.gradle.kts; then
loader="neoforge"
elif grep -q 'id("net.minecraftforge.gradle")' build.gradle.kts; then
loader="forge"
else
loader="neoforge"
fi
echo "minecraft_version=$minecraft_version" >> "$GITHUB_OUTPUT"
echo "mod_version=$mod_version" >> "$GITHUB_OUTPUT"
echo "mod_name=$mod_name" >> "$GITHUB_OUTPUT"
echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT"
echo "java_version=$java_version" >> "$GITHUB_OUTPUT"
echo "loader=$loader" >> "$GITHUB_OUTPUT"
{
echo "changelog<<EOF"
cat "$changelog_file"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ steps.meta.outputs.java_version }}
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Ensure gradlew is executable
run: chmod +x ./gradlew
- name: Build
shell: bash
run: |
exclude_args=()
if ./gradlew tasks --all --no-daemon --console=plain | grep -qE '^copyJar\s'; then
exclude_args=(-x copyJar)
fi
./gradlew build "${exclude_args[@]}" --no-daemon --console=plain
env:
CI: true
BUILD_VERSION: ${{ steps.meta.outputs.release_tag }}
CHANGELOG: ${{ steps.meta.outputs.changelog }}
PUBLISH: true
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: release-build
path: build/libs/*.jar
publish-github:
name: Publish to GitHub Releases
needs: [validate-release, build]
if: needs.build.result == 'success' && needs.validate-release.outputs.publish_github == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
release_url: ${{ steps.github_release.outputs.url }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: release-build
path: build/libs
- name: Publish to GitHub Releases
id: github_release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ needs.build.outputs.release_tag }}
target_commitish: ${{ github.sha }}
name: ${{ needs.build.outputs.mod_name }} - ${{ needs.build.outputs.release_tag }}
body: ${{ needs.build.outputs.changelog }}
files: build/libs/*.jar
fail_on_unmatched_files: true
publish-curseforge:
name: Publish to CurseForge
needs: [validate-release, build]
if: needs.build.result == 'success' && needs.validate-release.outputs.publish_curseforge == 'true' && vars.CURSEFORGE_PROJECT_ID != ''
runs-on: ubuntu-latest
env:
CURSEFORGE_TOKEN: ${{ secrets.TOKEN_CURSEFORGE }}
outputs:
release_url: ${{ steps.curseforge_publish.outputs.curseforge-url }}
steps:
- name: Validate CurseForge config
id: curseforge_config
shell: bash
run: |
if [[ -z "$CURSEFORGE_TOKEN" ]]; then
echo "CurseForge token is not configured. Skipping publish."
echo "enabled=false" >> "$GITHUB_OUTPUT"
else
echo "enabled=true" >> "$GITHUB_OUTPUT"
fi
- name: Download artifacts
if: steps.curseforge_config.outputs.enabled == 'true'
uses: actions/download-artifact@v4
with:
name: release-build
path: build/libs
- name: Publish to CurseForge
if: steps.curseforge_config.outputs.enabled == 'true'
id: curseforge_publish
uses: Kira-NT/mc-publish@v3.3.0
with:
name: ${{ needs.build.outputs.mod_name }} - ${{ needs.build.outputs.release_tag }}
version: ${{ needs.build.outputs.release_tag }}
files: build/libs/*.jar
changelog: ${{ needs.build.outputs.changelog }}
loaders: ${{ needs.build.outputs.loader }}
game-versions: ${{ needs.build.outputs.minecraft_version }}
java: ${{ needs.build.outputs.java_version }}
curseforge-id: ${{ vars.CURSEFORGE_PROJECT_ID }}
curseforge-token: ${{ env.CURSEFORGE_TOKEN }}
publish-modrinth:
name: Publish to Modrinth
needs: [validate-release, build]
if: needs.build.result == 'success' && needs.validate-release.outputs.publish_modrinth == 'true' && vars.MODRINTH_PROJECT_ID != ''
runs-on: ubuntu-latest
env:
MODRINTH_TOKEN: ${{ secrets.TOKEN_MODRINTH }}
outputs:
release_url: ${{ steps.modrinth_publish.outputs.modrinth-url }}
steps:
- name: Validate Modrinth config
id: modrinth_config
shell: bash
run: |
if [[ -z "$MODRINTH_TOKEN" ]]; then
echo "Modrinth token is not configured. Skipping publish."
echo "enabled=false" >> "$GITHUB_OUTPUT"
else
echo "enabled=true" >> "$GITHUB_OUTPUT"
fi
- name: Download artifacts
if: steps.modrinth_config.outputs.enabled == 'true'
uses: actions/download-artifact@v4
with:
name: release-build
path: build/libs
- name: Publish to Modrinth
if: steps.modrinth_config.outputs.enabled == 'true'
id: modrinth_publish
uses: Kira-NT/mc-publish@v3.3.0
with:
name: ${{ needs.build.outputs.mod_name }} - ${{ needs.build.outputs.release_tag }}
version: ${{ needs.build.outputs.release_tag }}
files: build/libs/*.jar
changelog: ${{ needs.build.outputs.changelog }}
loaders: ${{ needs.build.outputs.loader }}
game-versions: ${{ needs.build.outputs.minecraft_version }}
java: ${{ needs.build.outputs.java_version }}
modrinth-id: ${{ vars.MODRINTH_PROJECT_ID }}
modrinth-token: ${{ env.MODRINTH_TOKEN }}
notify-discord:
name: Notify Discord
needs: [validate-release, build, publish-github, publish-curseforge, publish-modrinth]
if: always() && needs.validate-release.outputs.is_version_branch == 'true' && needs.validate-release.outputs.publish_discord == 'true' && needs.build.result == 'success'
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
DISCORD_ROLE_ID: ${{ vars.DISCORD_RELEASE_ROLE_ID }}
TARGET_BRANCH: ${{ needs.validate-release.outputs.target_branch }}
MINECRAFT_VERSION: ${{ needs.build.outputs.minecraft_version }}
MOD_VERSION: ${{ needs.build.outputs.mod_version }}
RELEASE_TAG: ${{ needs.build.outputs.release_tag }}
MOD_NAME: ${{ needs.build.outputs.mod_name }}
CHANGELOG: ${{ needs.build.outputs.changelog }}
BUILD_RESULT: ${{ needs.build.result }}
GITHUB_RESULT: ${{ needs.publish-github.result }}
GITHUB_URL: ${{ needs.publish-github.outputs.release_url }}
CURSEFORGE_RESULT: ${{ needs.publish-curseforge.result }}
CURSEFORGE_URL: ${{ needs.publish-curseforge.outputs.release_url }}
MODRINTH_RESULT: ${{ needs.publish-modrinth.result }}
MODRINTH_URL: ${{ needs.publish-modrinth.outputs.release_url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
ACTOR: ${{ github.actor }}
REPOSITORY: ${{ github.repository }}
steps:
- name: Validate Discord config
id: discord_config
shell: bash
run: |
if [[ -z "$DISCORD_WEBHOOK_URL" ]]; then
echo "Discord webhook is not configured. Skipping notification."
echo "enabled=false" >> "$GITHUB_OUTPUT"
else
echo "enabled=true" >> "$GITHUB_OUTPUT"
fi
- name: Send Discord notification
if: steps.discord_config.outputs.enabled == 'true'
shell: bash
run: |
payload=$(python3 - <<'PY'
import json
import os
def link_line(label, url, result):
if url:
return f"{label}: {url}"
if result == "skipped":
return f"{label}: Skipped"
if result == "success":
return f"{label}: Published"
if result == "failure":
return f"{label}: Failed"
if result == "cancelled":
return f"{label}: Cancelled"
return f"{label}: {result}"
role_id = os.environ.get("DISCORD_ROLE_ID", "").strip()
greeting = f"Hey <@&{role_id}>" if role_id else "Hey"
release_name = f"LogisticsNetwork-{os.environ['MINECRAFT_VERSION']}-{os.environ['MOD_VERSION']}"
lines = [
greeting,
"",
f"New release: {release_name}",
"## Changelog",
"",
]
changelog = os.environ.get("CHANGELOG", "").strip()
if changelog:
lines.append(changelog)
else:
lines.append("No changelog provided.")
lines.extend([
"",
link_line("Curseforge", os.environ.get("CURSEFORGE_URL", "").strip(), os.environ.get("CURSEFORGE_RESULT", "").strip()),
link_line("Modrinth", os.environ.get("MODRINTH_URL", "").strip(), os.environ.get("MODRINTH_RESULT", "").strip()),
link_line("Github", os.environ.get("GITHUB_URL", "").strip(), os.environ.get("GITHUB_RESULT", "").strip()),
"",
"Thank you all for downloading,",
"AlmanaX21",
])
content = "\n".join(lines)
if len(content) > 1900:
static_lines = [
greeting,
"",
f"New release: {release_name}",
"## Changelog",
"",
]
footer_lines = [
"",
link_line("Curseforge", os.environ.get("CURSEFORGE_URL", "").strip(), os.environ.get("CURSEFORGE_RESULT", "").strip()),
link_line("Modrinth", os.environ.get("MODRINTH_URL", "").strip(), os.environ.get("MODRINTH_RESULT", "").strip()),
link_line("Github", os.environ.get("GITHUB_URL", "").strip(), os.environ.get("GITHUB_RESULT", "").strip()),
"",
"Thank you all for downloading,",
"AlmanaX21",
]
static_prefix = "\n".join(static_lines)
static_suffix = "\n".join(footer_lines)
available = 1900 - len(static_prefix) - len(static_suffix) - len("\n...\n")
trimmed = changelog[:max(0, available)].rstrip()
content = "\n".join(static_lines + [trimmed, "..."] + footer_lines)
payload = {"content": content}
if role_id:
payload["allowed_mentions"] = {"parse": [], "roles": [role_id]}
print(json.dumps(payload))
PY
)
curl \
--fail \
--silent \
--show-error \
-H "Content-Type: application/json" \
-d "$payload" \
"$DISCORD_WEBHOOK_URL"