minecraft-ci-release

SKILL.md

Minecraft CI / Release Skill

Workflow Overview

PR opened → build + test checks
main branch push → build artifacts
Tag push (v*) → build + publish to Modrinth + CurseForge + GitHub Releases

Routing Boundaries

  • Use when: the task is CI/CD pipelines, release automation, artifact publishing, versioning, or release governance.
  • Do not use when: the task is implementing gameplay/plugin/mod features (minecraft-modding, minecraft-plugin-dev, minecraft-datapack).
  • Do not use when: the task is server runtime operations and infrastructure tuning (minecraft-server-admin).

Versioning Convention

Minecraft mod versions follow: {mod_version}+{mc_version}

1.0.0+1.21.1   ← mod 1.0.0 for MC 1.21.1
1.2.3+1.21.1
2.0.0+1.21.4

Git tag format: v1.0.0 (mod version only, not MC version in the tag).


Core CI Workflow (NeoForge + Fabric)

.github/workflows/build.yml

name: Build

on:
  push:
    branches: ["main", "develop"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  build:
    name: Build (${{ matrix.platform }})
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform: [neoforge, fabric]
      fail-fast: false

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java 21
        uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          cache-read-only: ${{ github.ref != 'refs/heads/main' }}

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build (${{ matrix.platform }})
        run: ./gradlew :${{ matrix.platform }}:build --no-daemon

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: mod-${{ matrix.platform }}-${{ github.sha }}
          path: ${{ matrix.platform }}/build/libs/*.jar
          if-no-files-found: error

Release Workflow (with Publishing)

.github/workflows/release.yml

name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write      # for creating GitHub releases

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java 21
        uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Extract version from tag
        id: version
        run: echo "MOD_VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

      - name: Build all platforms
        run: ./gradlew build --no-daemon

      - name: Publish to Modrinth & CurseForge
        run: ./gradlew publishMods --no-daemon
        env:
          MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
          CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            fabric/build/libs/*.jar
            neoforge/build/libs/*.jar
          generate_release_notes: true
          draft: false
          prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}

Paper Plugin CI

.github/workflows/build.yml (plugin)

name: Build

on:
  push:
    branches: ["main"]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"
      - uses: gradle/actions/setup-gradle@v3
      - run: chmod +x gradlew
      - run: ./gradlew shadowJar --no-daemon
      - uses: actions/upload-artifact@v4
        with:
          name: plugin-${{ github.sha }}
          path: build/libs/*.jar

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"
      - uses: gradle/actions/setup-gradle@v3
      - run: ./gradlew test --no-daemon

Modrinth Publishing (minotaur)

build.gradle.kts (root or platform-specific)

plugins {
    id("com.modrinth.minotaur") version "2.8.7"
}

// === Fabric subproject ===
modrinth {
    token.set(System.getenv("MODRINTH_TOKEN") ?: "")
    projectId.set("YOUR-PROJECT-ID")    // from modrinth.com project slug or ID

    versionNumber.set("${project.version}")
    versionType.set("release")          // release | beta | alpha

    uploadFile.set(tasks.remapJar)      // the JAR to upload

    gameVersions.addAll("1.21.1")
    loaders.addAll("fabric")

    changelog.set(
        rootProject.file("CHANGELOG.md").readText()
            .substringAfter("## [${project.version}]")
            .substringBefore("\n## [")
            .trim()
    )

    dependencies {
        required.project("fabric-api")
        // optional.project("some-optional-mod")
    }
}

Combined Fabric + NeoForge publish task (root-level)

// root build.gradle.kts
tasks.register("publishMods") {
    dependsOn(":fabric:modrinth", ":neoforge:modrinth")
    dependsOn(":fabric:curseforge", ":neoforge:curseforge")
    group = "publishing"
    description = "Publish all platforms to Modrinth and CurseForge"
}

CurseForge Publishing

build.gradle.kts

plugins {
    id("net.darkhax.curseforgegradle") version "1.1.25"
}

tasks.register<net.darkhax.curseforgegradle.TaskPublishCurseForge>("curseforge") {
    apiToken = System.getenv("CURSEFORGE_TOKEN") ?: ""

    val cf = upload(PROJECT_ID, tasks.named("remapJar"))  // or shadowJar
    cf.changelogType = "markdown"
    cf.changelog = rootProject.file("CHANGELOG.md").readText()
        .substringAfter("## [${project.version}]")
        .substringBefore("\n## [")
        .trim()

    cf.releaseType = "release"
    cf.addGameVersion("1.21.1")
    cf.addModLoader("Fabric")     // "NeoForge" for NeoForge subproject
    cf.addRequirement("fabric-api")
    // cf.addJavaVersion("Java 21")

    // Replace PROJECT_ID with your numeric CurseForge project ID
}

Replace PROJECT_ID with your actual numeric CurseForge project ID (found in project settings).


gradle.properties Secrets Pattern

Never hardcode tokens. Read them from environment:

# gradle.properties (committed)
mod_id=mymod
mod_version=1.0.0
minecraft_version=1.21.1
modrinth_project_id=AABBCCDD
curseforge_project_id=123456

# DO NOT commit tokens
# Set these as GitHub repo secrets:
# MODRINTH_TOKEN, CURSEFORGE_TOKEN

Semantic Versioning for Mods

Change Version bump
New features, no breaking changes Minor: 1.1.0
Bug fixes only Patch: 1.0.1
API/config breaking changes Major: 2.0.0
Minecraft version update Keep mod version, change +1.21.4 suffix
Pre-release 1.0.0-beta.1, 1.0.0-rc.1

CHANGELOG.md Convention

# Changelog

## [1.1.0] — 2025-06-01
### Added
- New `/kit` command
- PDC-based kill tracker

### Fixed
- Death message not appearing on Paper 1.21.11

## [1.0.0] — 2025-05-01
### Added
- Initial release

Automate CHANGELOG parsing in Gradle (as shown above in modrinth block) by extracting the section between version headers.


Dependabot Configuration

.github/dependabot.yml

version: 2
updates:
  - package-ecosystem: "gradle"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      gradle-plugins:
        patterns:
          - "com.gradleup.shadow"
          - "dev.architectury.loom"
          - "com.modrinth.minotaur"
          - "net.darkhax.curseforgegradle"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Build Caching Best Practices

# In all workflow jobs:
- name: Setup Gradle
  uses: gradle/actions/setup-gradle@v3
  with:
    # Read-only cache on PRs, read-write on main
    cache-read-only: ${{ github.event_name == 'pull_request' }}
    # Cache Minecraft assets (speeds up loom tasks by minutes)
    gradle-home-cache-includes: |
      caches
      notifications
      .gradle/loom-cache

Branch Protection + Required Checks

Recommended GitHub branch protection for main:

  • Require status checks: build (fabric), build (neoforge), test
  • Require linear history (squash/rebase merges)
  • Require signed commits (optional but recommended for release workflows)

Tag and Release Script

#!/usr/bin/env bash
# scripts/release.sh <version>
# Usage: ./scripts/release.sh 1.1.0
set -euo pipefail

VERSION="${1:?Usage: release.sh <version>}"

# Update gradle.properties
sed -i "s/^mod_version=.*/mod_version=${VERSION}/" gradle.properties

# Stage and commit
git add gradle.properties
git commit -m "chore: release v${VERSION}"

# Tag
git tag "v${VERSION}"

echo "Created commit and tag v${VERSION}"
echo "Push with: git push && git push --tags"

Workflow Snippet Validator

Use the bundled validator script to keep SKILL.md workflow snippets copy-paste safe:

# Run from the skill directory:
.agents/skills/minecraft-ci-release/scripts/validate-workflow-snippets.sh --root .

# Strict mode treats warnings as failures:
.agents/skills/minecraft-ci-release/scripts/validate-workflow-snippets.sh --root . --strict

What it checks:

  • YAML snippet structure for workflow-like blocks (name, on, jobs)
  • Unresolved placeholder tokens and suspicious glob patterns
  • ${{ secrets.* }} usage stays consistent with secrets documented in this file

References

Weekly Installs
1
GitHub Stars
3
First Seen
6 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1