关于 minecraft-ci-release
面向 Minecraft 模组和 Paper 插件项目的 CI/CD 与发布管理技能,提供工作流、版本和发布流程指导。内容涵盖 GitHub Release、构建检查、依赖更新及安全处理密钥,并区分不同 Minecraft 版本的 Java 要求。
主要功能
- 01CI 构建与测试工作流
- 02GitHub Release 配置
- 03版本标签与制品校验
- 04发布密钥安全处理
- 05工作流片段验证
适用场景
- 01为 Fabric 和 NeoForge 模组设置构建流程
- 02为 Paper 插件配置 CI
- 03检查版本号与 Git 标签是否匹配
- 04发布前验证工作流片段
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jahrome907/ minecraft-agent-skills --skill minecraft-ci-release安装后在 Claude Code 中输入/minecraft-ci-release确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/publishing-gradle.mdscripts/validate-workflow-snippets.shscripts/vendor/js-yaml.min.cjsLICENSE.js-yaml详细介绍
Minecraft CI / Release 技能
路由边界
Use when:任务涉及 CI/CD 流水线、制品发布、版本管理或发行管理。Do not use when:任务涉及游戏玩法或插件实现(minecraft-modding、minecraft-plugin-dev或minecraft-datapack)。Do not use when:任务涉及服务端运行时操作或基础设施调优(minecraft-server-admin)。
发行设置
保留每个仓库现有的发行目标和审批政策。标签可以构建制品并创建 GitHub Release,而不发布到 Modrinth 或 CurseForge。仅当项目已经使用发布工具,或用户提出要求时,才添加发布工具。切勿将令牌添加到已提交的文件中。
对于 Minecraft 26.x,使用 Java 25,并在工作流标签 和示例中注明。旧版 Minecraft 1.21.x 项目继续使用 Java 21;保留其自身的 加载器和 Gradle 约定,不要机械地更改其版本。
在受保护的工作流中使用不可变的 GitHub Action 修订版本。以下引用于 2026-09-04 根据上游标签验证;有意升级某个 Action 前, 请从上游标签重新获取并确认:
actions/[email protected] 3d3c42e5aac5ba805825da76410c181273ba90b1
actions/[email protected] dd06d9cba3e5552c54d9f8ea23572deb30010f7c
gradle/actions/[email protected] 9c971963bec38e04b3d30dcc455b5382be2fdbfb
actions/[email protected] 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
softprops/[email protected] efb35369e0ad2afab669f228072c1b0d510eae64
版本与标签约定
Minecraft 模组版本格式为:{mod_version}+{mc_version}
1.0.0+26.2 ← mod 1.0.0 for MC 26.2
1.2.3+26.2
2.0.0+26.2
Git 标签使用不带游戏版本后缀的发行版本;如果项目采用该约定,则在制品版本中保留 Minecraft 版本:
mod_version: 1.2.3
project/artifact version: 1.2.3+26.2
tag: v1.2.3
发行工作流必须在构建前验证 v1.2.3 与 mod_version=1.2.3 是否匹配。不要根据标签覆盖 mod_version,因为这样会掩盖不匹配的问题。
Gradle 任务和安全提取更新日志的方法见
发布参考。
核心 CI 工作流(NeoForge + Fabric)
这是 Minecraft 26.x / Java 25 的示例。添加下方测试任务后,显示的检查名称为
Build / Build (fabric)、Build / Build (neoforge) 和 Build / Test。首次拉取请求成功后,将 GitHub 显示的确切名称复制到分支保护设置中;重命名工作流或任务
会更改必需检查的上下文。
.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@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Java 25
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c
with:
java-version: "25"
distribution: "temurin"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
with:
cache-read-only: ${{ github.event_name == 'pull_request' }}
- 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: mod-${{ matrix.platform }}-${{ github.sha }}
path: ${{ matrix.platform }}/build/libs/*.jar
if-no-files-found: error
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Java 25
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c
with:
distribution: temurin
java-version: "25"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
- name: Run tests
run: ./gradlew test --no-daemon
带标签的 GitHub Release
.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 tagged source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Java 25
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c
with:
java-version: "25"
distribution: "temurin"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Validate tag and build
env:
MOD_VERSION: ${{ github.ref_name }}
run: |
MOD_VERSION="${MOD_VERSION#v}"
./gradlew verifyReleaseVersion build --no-daemon \
-PreleaseModVersion="$MOD_VERSION"
- name: Select release artifacts
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
mkdir -p release-artifacts
shopt -s nullglob
select_primary() {
local loader="$1"; shift
local matches=( "$@" )
if (( ${#matches[@]} != 1 )); then
printf 'Expected one %s primary JAR, found %s: %s\n' \
"$loader" "${#matches[@]}" "${matches[*]:-none}" >&2
exit 1
fi
cp "${matches[0]}" "release-artifacts/${loader}-${RELEASE_TAG}.jar"
}
select_primary fabric fabric/build/libs/*-fabric.jar
select_primary neoforge neoforge/build/libs/*-neoforge.jar
- name: Create GitHub Release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64
with:
files: |
release-artifacts/fabric-${{ github.ref_name }}.jar
release-artifacts/neoforge-${{ github.ref_name }}.jar
fail_on_unmatched_files: true
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}
此工作流仅创建 GitHub Release。它要求有一个主要 Fabric JAR 和 一个主要 NeoForge JAR,且两者的名称能区分加载器。在项目构建中配置这些分类器;如果命名约定不同,则同时修改两种匹配模式。 选择步骤在匹配结果为零个或多个时会失败,并复制为不同的发行名称,以防意外覆盖或部分发行。只有当目标范围包含特定发布目标时,才在版本验证后添加项目专用的发布工具。
Paper 插件 CI
.github/workflows/build.yml(插件)
name: Build
on:
push:
branches: ["main"]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c
with:
java-version: "25"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
- run: chmod +x gradlew
- run: ./gradlew build --no-daemon
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: plugin-${{ github.sha }}
path: build/libs/*.jar
if-no-files-found: error
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c
with:
java-version: "25"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
- run: ./gradlew test --no-daemon
发布与更新日志详情
仅当项目发布到 Modrinth 或 CurseForge 时,才阅读发布参考。其中包括当前插件版本、
26.x 的 jar 选择、旧版 Loom 的明确 remapJar 指南、版本验证,以及在缺少预期更新日志标题时会失败的解析器。
gradle.properties 密钥模式
切勿硬编码令牌。请从环境变量中读取:
# gradle.properties (committed)
mod_id=mymod
mod_version=1.0.0
minecraft_version=26.2
modrinth_project_id=AABBCCDD
curseforge_project_id=123456
# DO NOT commit tokens
# Set these as GitHub repo secrets:
# MODRINTH_TOKEN, CURSEFORGE_TOKEN
模组的语义化版本控制
| 更改 | 版本变更 |
|---|---|
| 新功能,无破坏性更改 | 次版本:1.1.0 |
| 仅修复缺陷 | 修订版本:1.0.1 |
| API/配置存在破坏性更改 | 主版本:2.0.0 |
| Minecraft 版本更新 | 保持模组版本不变,更改 +26.2 后缀 |
| 预发行版 | 1.0.0-beta.1、1.0.0-rc.1 |
CHANGELOG.md 约定
# Changelog
## [1.1.0] — 2025-06-01
### Added
- New `/kit` command
- PDC-based kill tracker
### Fixed
- Death message not appearing on Paper 26.2
## [1.0.0] — 2025-05-01
### Added
- Initial release
发布参考按 mod_version 提取此部分;如果缺少预期标题,则会清楚地报告失败。
Dependabot 配置
.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"
构建缓存最佳实践
# In all workflow jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb
with:
# Read-only cache on PRs, read-write on main
cache-read-only: ${{ github.event_name == 'pull_request' }}
分支保护 + 必需检查
建议为 main 配置以下 GitHub 分支保护:
- 要求通过以下观察到的检查:
Build / Build (fabric)、Build / Build (neoforge)和Build / Test - 要求保持线性历史(使用 squash/rebase 合并)
- 要求提交签名(可选,但建议用于发布工作流)
标签和发布脚本
#!/usr/bin/env bash
# scripts/release.sh <mod-version>
set -euo pipefail
VERSION="${1:?Usage: release.sh <mod-version>}"
REMOTE="${RELEASE_REMOTE:-origin}"
BRANCH="${RELEASE_BRANCH:-main}"
EXPECTED_REMOTE_URL="${RELEASE_REMOTE_URL:?Set RELEASE_REMOTE_URL to the expected push URL}"
git diff --check
[[ -z "$(git status --porcelain=v1 --untracked-files=all)" ]] \
|| { echo "Working tree contains staged, unstaged, or untracked files." >&2; exit 1; }
[[ "$(git branch --show-current)" == "$BRANCH" ]] \
|| { echo "Release must start from branch $BRANCH." >&2; exit 1; }
mapfile -t PUSH_URLS < <(git remote get-url --push --all "$REMOTE")
[[ ${#PUSH_URLS[@]} -eq 1 && "${PUSH_URLS[0]}" == "$EXPECTED_REMOTE_URL" ]] \
|| { echo "Remote $REMOTE must have one expected push URL." >&2; exit 1; }
! git rev-parse --verify --quiet "refs/tags/v${VERSION}" >/dev/null \
|| { echo "Local tag v${VERSION} already exists." >&2; exit 1; }
set +e
git ls-remote --exit-code --tags "$EXPECTED_REMOTE_URL" "refs/tags/v${VERSION}" >/dev/null
REMOTE_TAG_STATUS=$?
set -e
case "$REMOTE_TAG_STATUS" in
0) echo "Remote tag v${VERSION} already exists." >&2; exit 1 ;;
2) ;;
*) echo "Could not verify remote tag v${VERSION}." >&2; exit "$REMOTE_TAG_STATUS" ;;
esac
./gradlew verifyReleaseVersion --no-daemon -PreleaseModVersion="$VERSION"
git tag --annotate "v${VERSION}" --message "Release v${VERSION}"
git push "$REMOTE" "HEAD:refs/heads/${BRANCH}"
git push "$REMOTE" "refs/tags/v${VERSION}"
运行此脚本前,请更新并验证版本/变更日志文件,然后为该发布提交打标签。为目标发布分支和确切的推送 URL 设置 RELEASE_BRANCH、RELEASE_REMOTE 和 RELEASE_REMOTE_URL。未经明确授权,请勿强制推送或重新标记已发布的版本。
工作流片段验证器
从已安装的 minecraft-ci-release 技能目录运行捆绑的验证器:
# Run from the installed skill directory:
./scripts/validate-workflow-snippets.sh --root .
# Strict mode treats warnings as failures:
./scripts/validate-workflow-snippets.sh --root . --strict
此验证器随附且自包含。可从复制的 .agents/、.codex/ 或 .claude/ minecraft-ci-release 技能目录运行,无需依赖仓库根目录下的 node_modules。
它会验证符合工作流格式的 YAML、未解析的占位符、工作流密钥文档,以及固定到完整提交 SHA 的远程 uses: 引用。本地操作(./...)和 Docker 操作(docker://...)有意不纳入 SHA 要求。它只读取此技能的 SKILL.md:不会验证项目的 .github/workflows 文件或 Gradle 任务。正式发布前,请检查项目生成的制品、运行 verifyReleaseVersion,并审查确切的工作流差异和配置的发布目标。