diff --git a/.github/scripts/prepare-release-metadata.sh b/.github/scripts/prepare-release-metadata.sh new file mode 100644 index 0000000..2b8ec70 --- /dev/null +++ b/.github/scripts/prepare-release-metadata.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +release_name="$(jq -r '.release.name // ""' "$GITHUB_EVENT_PATH")" +jq -r '.release.body // ""' "$GITHUB_EVENT_PATH" > release-artifacts/changelog.md + +if [[ -z "${release_name//[[:space:]]/}" ]]; then + release_name="Ascend: Ailments ${RELEASE_TAG}" +fi + +if ! grep -q '[^[:space:]]' release-artifacts/changelog.md; then + echo "::error::The GitHub release needs a non-empty description; it is used as the CurseForge changelog." + exit 1 +fi + +case "${RELEASE_TAG,,}" in + *-alpha*) release_type="alpha" ;; + *) + if [[ "${RELEASE_PRERELEASE,,}" == "true" ]]; then + release_type="beta" + else + release_type="release" + fi + ;; +esac + +delimiter="release_name_$(date +%s%N)" +{ + echo "display_name<<$delimiter" + echo "$release_name" + echo "$delimiter" + echo "release_type=$release_type" +} >> "$GITHUB_OUTPUT" + +echo "Prepared $release_type metadata for $RELEASE_TAG." diff --git a/.github/scripts/publish-curseforge.sh b/.github/scripts/publish-curseforge.sh new file mode 100644 index 0000000..9415763 --- /dev/null +++ b/.github/scripts/publish-curseforge.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +required_variables=( + CHANGELOG_FILE + CURSEFORGE_API_TOKEN + CURSEFORGE_PROJECT_ID + DISPLAY_NAME + JAR_PATH + MINECRAFT_VERSION + RELEASE_TYPE +) + +for variable in "${required_variables[@]}"; do + if [[ -z "${!variable:-}" ]]; then + echo "::error::$variable is required for the CurseForge upload." + exit 1 + fi +done + +if [[ ! "$CURSEFORGE_PROJECT_ID" =~ ^[0-9]+$ ]]; then + echo "::error::CURSEFORGE_PROJECT_ID must be the numeric project ID from the CurseForge project page." + exit 1 +fi +if [[ ! -f "$JAR_PATH" || ! -f "$CHANGELOG_FILE" ]]; then + echo "::error::The release JAR or changelog file is missing." + exit 1 +fi +if [[ "$RELEASE_TYPE" != "alpha" && "$RELEASE_TYPE" != "beta" && "$RELEASE_TYPE" != "release" ]]; then + echo "::error::RELEASE_TYPE must be alpha, beta, or release." + exit 1 +fi + +metadata_file="$(mktemp)" +response_file="$(mktemp)" +trap 'rm -f "$metadata_file" "$response_file"' EXIT + +jq -n \ + --rawfile changelog "$CHANGELOG_FILE" \ + --arg display_name "$DISPLAY_NAME" \ + --arg minecraft_version "$MINECRAFT_VERSION" \ + --arg release_type "$RELEASE_TYPE" \ + '{ + changelog: $changelog, + changelogType: "markdown", + displayName: $display_name, + gameVersionNames: [$minecraft_version, "Forge", "Client", "Server"], + releaseType: $release_type, + relations: { + projects: [ + { + slug: "ascend", + projectID: "1383429", + type: "optionalDependency" + } + ] + } + }' > "$metadata_file" + +upload_url="https://minecraft.curseforge.com/api/projects/${CURSEFORGE_PROJECT_ID}/upload-file" +if ! curl \ + --silent \ + --show-error \ + --fail-with-body \ + --retry 3 \ + --retry-delay 2 \ + --header "X-Api-Token: ${CURSEFORGE_API_TOKEN}" \ + --form "metadata=<${metadata_file};type=application/json" \ + --form "file=@${JAR_PATH};type=application/java-archive" \ + --output "$response_file" \ + "$upload_url"; then + echo "::error::CurseForge rejected the upload. Response:" + cat "$response_file" >&2 + exit 1 +fi + +file_id="$(jq -er '.id' "$response_file")" +echo "Uploaded $(basename "$JAR_PATH") to CurseForge as file ID $file_id." diff --git a/.github/scripts/verify-release-artifact.sh b/.github/scripts/verify-release-artifact.sh new file mode 100644 index 0000000..16dae66 --- /dev/null +++ b/.github/scripts/verify-release-artifact.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +property() { + local key="$1" + awk -F= -v key="$key" '$1 == key { sub(/^[^=]*=/, ""); print; exit }' gradle.properties +} + +mod_id="$(property mod_id)" +mod_version="$(property mod_version)" +minecraft_version="$(property minecraft_version)" + +if [[ -z "$mod_id" || -z "$mod_version" || -z "$minecraft_version" ]]; then + echo "::error::mod_id, mod_version or minecraft_version is missing from gradle.properties." + exit 1 +fi + +expected_tag="v${mod_version}" +if [[ -n "${RELEASE_TAG:-}" && "$RELEASE_TAG" != "$expected_tag" ]]; then + echo "::error::Release tag '$RELEASE_TAG' does not match mod_version '$mod_version'. Expected '$expected_tag'." + exit 1 +fi + +jar_path="build/libs/${mod_id}-${mod_version}.jar" +if [[ ! -f "$jar_path" ]]; then + echo "::error::Expected release artifact '$jar_path' was not produced." + find build/libs -maxdepth 1 -type f -print 2>/dev/null || true + exit 1 +fi + +mods_toml="$(unzip -p "$jar_path" META-INF/mods.toml)" +if ! grep -Fq "modId=\"${mod_id}\"" <<<"$mods_toml"; then + echo "::error::The built JAR does not contain the expected mod ID '$mod_id'." + exit 1 +fi +if ! grep -Fq "version=\"${mod_version}\"" <<<"$mods_toml"; then + echo "::error::The built JAR does not contain the expected version '$mod_version'." + exit 1 +fi + +{ + echo "jar_name=$(basename "$jar_path")" + echo "jar_path=$jar_path" + echo "minecraft_version=$minecraft_version" + echo "mod_version=$mod_version" +} >> "$GITHUB_OUTPUT" + +echo "Verified $jar_path (Minecraft $minecraft_version, version $mod_version)." diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml new file mode 100644 index 0000000..ce8b3e0 --- /dev/null +++ b/.github/workflows/build-and-publish.yml @@ -0,0 +1,115 @@ +name: Build and publish + +env: + CURSEFORGE_PROJECT_ID: "1668190" + +on: + push: + branches: + - "**" + pull_request: + workflow_dispatch: + release: + types: + - published + +permissions: + contents: read + +concurrency: + group: ailments-${{ github.workflow }}-${{ github.event.release.tag_name || github.ref }} + cancel-in-progress: ${{ github.event_name != 'release' }} + +jobs: + build: + name: Test and build Java 17 JAR + runs-on: ubuntu-latest + timeout-minutes: 30 + outputs: + jar_name: ${{ steps.artifact.outputs.jar_name }} + minecraft_version: ${{ steps.artifact.outputs.minecraft_version }} + mod_version: ${{ steps.artifact.outputs.mod_version }} + + steps: + - name: Check out source + uses: actions/checkout@v6 + + - name: Set up Java 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + + - name: Set up and validate Gradle + uses: gradle/actions/setup-gradle@v6 + + - name: Make Gradle wrapper executable + run: chmod +x gradlew + + - name: Run tests and build the reobfuscated JAR + run: ./gradlew clean test build --no-daemon --stacktrace + + - name: Verify version and release artifact + id: artifact + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: bash .github/scripts/verify-release-artifact.sh + + - name: Create SHA-256 checksum + env: + JAR_PATH: ${{ steps.artifact.outputs.jar_path }} + run: | + cd "$(dirname "$JAR_PATH")" + sha256sum "$(basename "$JAR_PATH")" > "$(basename "$JAR_PATH").sha256" + + - name: Upload workflow artifact + uses: actions/upload-artifact@v7 + with: + name: ascend-ailments-${{ github.run_id }} + if-no-files-found: error + retention-days: 30 + path: | + ${{ steps.artifact.outputs.jar_path }} + ${{ steps.artifact.outputs.jar_path }}.sha256 + + publish: + name: Publish GitHub and CurseForge release + if: github.event_name == 'release' && github.event.action == 'published' + needs: build + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + + steps: + - name: Check out released source + uses: actions/checkout@v6 + + - name: Download verified release artifact + uses: actions/download-artifact@v8 + with: + name: ascend-ailments-${{ github.run_id }} + path: release-artifacts + + - name: Prepare release metadata + id: release + env: + RELEASE_PRERELEASE: ${{ github.event.release.prerelease }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: bash .github/scripts/prepare-release-metadata.sh + + - name: Attach JAR and checksum to the GitHub release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$RELEASE_TAG" "release-artifacts/${{ needs.build.outputs.jar_name }}" "release-artifacts/${{ needs.build.outputs.jar_name }}.sha256" --clobber + + - name: Upload JAR to CurseForge + env: + CHANGELOG_FILE: release-artifacts/changelog.md + CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }} + DISPLAY_NAME: ${{ steps.release.outputs.display_name }} + JAR_PATH: release-artifacts/${{ needs.build.outputs.jar_name }} + MINECRAFT_VERSION: ${{ needs.build.outputs.minecraft_version }} + RELEASE_TYPE: ${{ steps.release.outputs.release_type }} + run: bash .github/scripts/publish-curseforge.sh diff --git a/README.md b/README.md index e53508b..d647a22 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Forge 1.20.1 library mod providing Soul Rot, Bleed, Fracture, Fear, Charm, Taunt, Overcharm, and Winded. The mod ID is `ascend_ailments`; Ascend is optional. +Release builds are tested and published through GitHub Actions. The required one-time CurseForge setup and release +process are documented in [docs/RELEASING.md](docs/RELEASING.md). + ## Public API Normal callers should use `net.fretux.ailments.api.AilmentApi`, for example: