name: Build and Release on: push: tags: - 'v*' permissions: contents: write jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - name: Make gradlew executable run: chmod +x ./gradlew - name: Run Forge data generation run: | echo "=== 运行 Forge 数据生成 ===" ./gradlew runData --no-daemon continue-on-error: false - name: Build with Gradle run: ./gradlew build --no-daemon - name: Prepare release files run: | mkdir -p release-files # 收集所有模块的构建产物 echo "=== 收集 common 模块构建产物 ===" if [ -d "common/build/libs" ]; then cp common/build/libs/*.jar release-files/ 2>/dev/null || echo "common 模块没有 jar 文件" fi echo "=== 收集 fabric 模块构建产物 ===" if [ -d "fabric/build/libs" ]; then cp fabric/build/libs/*-dev.jar release-files/ 2>/dev/null || true # 排除dev jar cp fabric/build/libs/*-sources.jar release-files/ 2>/dev/null || true cp fabric/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true # 只复制主jar(没有sources/javadoc/dev classifier的jar) find fabric/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" ! -name "*-dev.jar" -exec cp {} release-files/ \; fi echo "=== 收集 forge 模块构建产物 ===" if [ -d "forge/build/libs" ]; then cp forge/build/libs/*-sources.jar release-files/ 2>/dev/null || true cp forge/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true # 只复制主jar(没有sources/javadoc classifier的jar) find forge/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" -exec cp {} release-files/ \; fi echo "=== 准备发布的文件 ===" ls -la release-files/ - name: Upload release artifacts uses: actions/upload-artifact@v4 with: name: release-files path: release-files/ retention-days: 7 release: runs-on: ubuntu-latest needs: build if: startsWith(github.ref, 'refs/tags/v') steps: - name: Checkout with full history uses: actions/checkout@v4 with: fetch-depth: 0 - name: Determine version type id: version_type run: | if [[ "${{ github.ref_name }}" == *"alpha"* ]]; then echo "type=alpha" >> $GITHUB_OUTPUT elif [[ "${{ github.ref_name }}" == *"beta"* ]]; then echo "type=beta" >> $GITHUB_OUTPUT elif [[ "${{ github.ref_name }}" == *"rc"* ]]; then echo "type=beta" >> $GITHUB_OUTPUT else echo "type=release" >> $GITHUB_OUTPUT fi - name: Download artifacts uses: actions/download-artifact@v4 with: name: release-files path: ./dist - name: Extract version info id: version_info run: | # 从tag中提取版本号(去掉v前缀) VERSION="${GITHUB_REF_NAME#v}" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "minecraft_version=$(grep "^minecraft_version=" gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT # 从 gradle.properties 提取 mod_id,如果没有则尝试从文件名推断 MOD_ID=$(grep "^mod_id=" gradle.properties | cut -d'=' -f2 || echo "") if [ -z "$MOD_ID" ]; then # 尝试从现有的 jar 文件名提取 mod_id SAMPLE_JAR=$(ls dist/ | grep -m 1 -E ".*-(fabric|forge)-.*\.jar" || echo "") if [ -n "$SAMPLE_JAR" ]; then MOD_ID=$(echo "$SAMPLE_JAR" | sed -E 's/-(fabric|forge)-.*//') else MOD_ID="mymod" # 默认值,请根据实际情况修改 fi fi echo "mod_id=$MOD_ID" >> $GITHUB_OUTPUT # 从 gradle.properties 提取 mod_name(用于显示) MOD_NAME=$(grep "^mod_name=" gradle.properties | cut -d'=' -f2 || echo "My Mod") echo "mod_name=$MOD_NAME" >> $GITHUB_OUTPUT # 从 gradle.properties 提取 modrinth_id MODRINTH_ID=$(grep "^modrinth_id=" gradle.properties | cut -d'=' -f2 || echo "") echo "modrinth_id=$MODRINTH_ID" >> $GITHUB_OUTPUT # 从 gradle.properties 提取 curseforge_id CURSEFORGE_ID=$(grep "^curseforge_id=" gradle.properties | cut -d'=' -f2 || echo "") echo "curseforge_id=$CURSEFORGE_ID" >> $GITHUB_OUTPUT # Java版本 - 使用简单格式,不用JSON JAVA_VERSIONS=$(grep "^java_versions=" gradle.properties | cut -d'=' -f2- || echo "21,17") # 清理格式,移除无效字符 JAVA_VERSIONS=$(echo "$JAVA_VERSIONS" | sed 's/\[//g; s/\]//g; s/"//g; s/ //g; s/21a/21/g' | tr -d '\r') echo "java_versions=$JAVA_VERSIONS" >> $GITHUB_OUTPUT # 读取发布控制布尔值(默认都为 true) PUBLISH_GITHUB=$(grep "^publish_github=" gradle.properties | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ' ' || echo "true") if [ "$PUBLISH_GITHUB" = "true" ] || [ "$PUBLISH_GITHUB" = "1" ] || [ "$PUBLISH_GITHUB" = "yes" ]; then echo "publish_github=true" >> $GITHUB_OUTPUT else echo "publish_github=false" >> $GITHUB_OUTPUT fi PUBLISH_MODRINTH=$(grep "^publish_modrinth=" gradle.properties | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ' ' || echo "true") if [ "$PUBLISH_MODRINTH" = "true" ] || [ "$PUBLISH_MODRINTH" = "1" ] || [ "$PUBLISH_MODRINTH" = "yes" ]; then echo "publish_modrinth=true" >> $GITHUB_OUTPUT else echo "publish_modrinth=false" >> $GITHUB_OUTPUT fi PUBLISH_CURSEFORGE=$(grep "^publish_curseforge=" gradle.properties | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ' ' || echo "true") if [ "$PUBLISH_CURSEFORGE" = "true" ] || [ "$PUBLISH_CURSEFORGE" = "1" ] || [ "$PUBLISH_CURSEFORGE" = "yes" ]; then echo "publish_curseforge=true" >> $GITHUB_OUTPUT else echo "publish_curseforge=false" >> $GITHUB_OUTPUT fi # 读取依赖配置 - 使用简单字符串,不用JSON FABRIC_MODRINTH_DEPS=$(grep "^fabric_modrinth_dependencies=" gradle.properties | cut -d'=' -f2- || echo "") echo "fabric_modrinth_dependencies=$FABRIC_MODRINTH_DEPS" >> $GITHUB_OUTPUT FORGE_MODRINTH_DEPS=$(grep "^forge_modrinth_dependencies=" gradle.properties | cut -d'=' -f2- || echo "") echo "forge_modrinth_dependencies=$FORGE_MODRINTH_DEPS" >> $GITHUB_OUTPUT FABRIC_CURSEFORGE_DEPS=$(grep "^fabric_curseforge_dependencies=" gradle.properties | cut -d'=' -f2- || echo "") echo "fabric_curseforge_dependencies=$FABRIC_CURSEFORGE_DEPS" >> $GITHUB_OUTPUT FORGE_CURSEFORGE_DEPS=$(grep "^forge_curseforge_dependencies=" gradle.properties | cut -d'=' -f2- || echo "") echo "forge_curseforge_dependencies=$FORGE_CURSEFORGE_DEPS" >> $GITHUB_OUTPUT - name: Generate CZ-compliant changelog id: generate_changelog run: | CURRENT_TAG="${{ github.ref_name }}" PREV_TAG=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "") # 创建临时文件 TEMP_FILE=$(mktemp) echo "# 🚀 版本 $CURRENT_TAG 发布" > $TEMP_FILE echo "" >> $TEMP_FILE echo "## 📋 变更摘要" >> $TEMP_FILE echo "" >> $TEMP_FILE if [ -z "$PREV_TAG" ]; then echo "### 初始版本发布" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "这是项目的第一个正式版本。" >> $TEMP_FILE echo "" >> $TEMP_FILE # 获取所有提交并按类型分组 git log --pretty=format:"%s" --reverse | while read -r line; do echo "- $line" >> $TEMP_FILE done else echo "### 从 $PREV_TAG 到 $CURRENT_TAG 的变更" >> $TEMP_FILE echo "" >> $TEMP_FILE # 定义符合CZ规范的提交类型映射 declare -A commit_types commit_types=( ["✨ 新功能"]="^(feat|feature)(\(.*\))?:" ["🐛 修复"]="^(fix|bugfix)(\(.*\))?:" ["📝 文档"]="^(docs|documentation)(\(.*\))?:" ["🎨 样式"]="^(style)(\(.*\))?:" ["🔨 重构"]="^(refactor)(\(.*\))?:" ["⚡️ 性能"]="^(perf|performance)(\(.*\))?:" ["✅ 测试"]="^(test)(\(.*\))?:" ["🔧 构建"]="^(build)(\(.*\))?:" ["👷 CI"]="^(ci)(\(.*\))?:" ["📦 依赖"]="^(chore|deps)(\(.*\))?:" ["⏪ 回退"]="^(revert)(\(.*\))?:" ["🛠 合并"]="^Merge " ) # 获取所有提交 COMMITS=$(git log --pretty=format:"%s" $PREV_TAG..HEAD) # 处理每种类型的提交 for type_name in "${!commit_types[@]}"; do pattern="${commit_types[$type_name]}" # 提取匹配的提交 matched_commits=$(echo "$COMMITS" | grep -E "$pattern" || true) if [ -n "$matched_commits" ]; then echo "#### $type_name" >> $TEMP_FILE echo "" >> $TEMP_FILE # 处理每条提交,提取scope和subject echo "$matched_commits" | while read -r commit; do # 解析scope和subject if [[ $commit =~ ^[a-z]+\((.*)\):\ (.*) ]]; then scope="${BASH_REMATCH[1]}" subject="${BASH_REMATCH[2]}" echo "- **$scope**: $subject" >> $TEMP_FILE elif [[ $commit =~ ^[a-z]+:\ (.*) ]]; then subject="${BASH_REMATCH[1]}" echo "- $subject" >> $TEMP_FILE else echo "- $commit" >> $TEMP_FILE fi done echo "" >> $TEMP_FILE fi done # 处理破坏性变更(BREAKING CHANGE) breaking_changes=$(git log --pretty=format:"%b" $PREV_TAG..HEAD | grep -i "BREAKING CHANGE" || true) if [ -n "$breaking_changes" ]; then echo "#### ⚠️ 破坏性变更" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "$breaking_changes" | while read -r line; do echo "- $line" >> $TEMP_FILE done echo "" >> $TEMP_FILE fi # 处理未分类的提交 uncategorized="$COMMITS" for pattern in "${commit_types[@]}"; do uncategorized=$(echo "$uncategorized" | grep -v -E "$pattern" || true) done if [ -n "$uncategorized" ]; then echo "#### 📝 其他更改" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "$uncategorized" | while read -r commit; do echo "- $commit" >> $TEMP_FILE done echo "" >> $TEMP_FILE fi fi echo "## 📊 统计信息" >> $TEMP_FILE echo "" >> $TEMP_FILE if [ -z "$PREV_TAG" ]; then TOTAL_COMMITS=$(git rev-list --count HEAD) echo "- 总提交数: $TOTAL_COMMITS" >> $TEMP_FILE echo "- 首次发布" >> $TEMP_FILE else COMMITS=$(git rev-list --count $PREV_TAG..HEAD) echo "- 本次发布提交数: $COMMITS" >> $TEMP_FILE echo "- 上一个版本: $PREV_TAG" >> $TEMP_FILE fi echo "- 发布日期: $(date '+%Y年%m月%d日')" >> $TEMP_FILE echo "- 当前版本: $CURRENT_TAG" >> $TEMP_FILE echo "- Minecraft版本: ${{ steps.version_info.outputs.minecraft_version }}" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "---" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "### 📜 详细提交历史" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "
" >> $TEMP_FILE echo "点击展开查看完整提交历史" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "\`\`\`" >> $TEMP_FILE if [ -z "$PREV_TAG" ]; then # 使用 while 循环确保每条提交独立一行 git log --pretty=format:"%h %s - %an (%ad)" --date=short --reverse | while IFS= read -r line; do echo "$line" >> $TEMP_FILE done else git log --pretty=format:"%h %s - %an (%ad)" --date=short $PREV_TAG..HEAD | while IFS= read -r line; do echo "$line" >> $TEMP_FILE done fi # 确保文件末尾有换行 echo "" >> $TEMP_FILE echo "\`\`\`" >> $TEMP_FILE echo "
" >> $TEMP_FILE # 将文件内容输出到变量 CHANGELOG_CONTENT=$(cat $TEMP_FILE) echo "changelog<> $GITHUB_OUTPUT echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Create Release if: steps.version_info.outputs.publish_github == 'true' uses: ncipollo/release-action@v1 with: artifacts: | dist/*.jar tag: ${{ github.ref_name }} name: "${{ steps.version_info.outputs.minecraft_version }} - ${{ github.ref_name }}" body: ${{ steps.generate_changelog.outputs.changelog }} draft: false prerelease: ${{ contains(github.ref_name, 'rc') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') }} token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true removeArtifacts: true # Fabric 发布到 Modrinth 和 CurseForge - name: Publish Fabric to Modrinth & CurseForge uses: Kir-Antipov/mc-publish@v3.3 if: success() && (steps.version_info.outputs.publish_modrinth == 'true' || steps.version_info.outputs.publish_curseforge == 'true') continue-on-error: true with: # 文件匹配规则 - 只匹配 fabric 的文件 files: | dist/${{ steps.version_info.outputs.mod_id }}-fabric-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}.jar dist/${{ steps.version_info.outputs.mod_id }}-fabric-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-javadoc.jar dist/${{ steps.version_info.outputs.mod_id }}-fabric-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-sources.jar # 版本信息 name: ${{ steps.version_info.outputs.mod_name }} ${{ steps.version_info.outputs.version }} (Fabric/${{ steps.version_info.outputs.minecraft_version }}) version: "${{ steps.version_info.outputs.minecraft_version }}-fabric-${{ steps.version_info.outputs.version }}" # 更新日志 changelog: ${{ steps.generate_changelog.outputs.changelog }} # 版本类型 version-type: ${{ steps.version_type.outputs.type }} # 只指定 Fabric 加载器 loaders: fabric # 游戏版本 game-versions: | ${{ steps.version_info.outputs.minecraft_version }} # Java版本 java: | ${{ steps.version_info.outputs.java_versions }} # Modrinth 配置 modrinth-id: ${{ steps.version_info.outputs.modrinth_id }} modrinth-token: ${{ secrets.MODRINTH_TOKEN }} modrinth-featured: true modrinth-unfeature-mode: any modrinth-dependencies: ${{ steps.version_info.outputs.fabric_modrinth_dependencies }} # CurseForge 配置 curseforge-id: ${{ steps.version_info.outputs.curseforge_id }} curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} curseforge-dependencies: ${{ steps.version_info.outputs.fabric_curseforge_dependencies }} # 失败处理 fail-mode: skip # Forge 发布到 Modrinth 和 CurseForge - name: Publish Forge to Modrinth & CurseForge uses: Kir-Antipov/mc-publish@v3.3 if: success() && (steps.version_info.outputs.publish_modrinth == 'true' || steps.version_info.outputs.publish_curseforge == 'true') continue-on-error: true with: # 文件匹配规则 - 只匹配 forge 的文件 files: | dist/${{ steps.version_info.outputs.mod_id }}-forge-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}.jar dist/${{ steps.version_info.outputs.mod_id }}-forge-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-javadoc.jar dist/${{ steps.version_info.outputs.mod_id }}-forge-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-sources.jar # 版本信息 name: ${{ steps.version_info.outputs.mod_name }} ${{ steps.version_info.outputs.version }} (Forge/${{ steps.version_info.outputs.minecraft_version }}) version: "${{ steps.version_info.outputs.minecraft_version }}-forge-${{ steps.version_info.outputs.version }}" # 更新日志 changelog: ${{ steps.generate_changelog.outputs.changelog }} # 版本类型 version-type: ${{ steps.version_type.outputs.type }} # 只指定 Forge 加载器 loaders: forge # 游戏版本 game-versions: | ${{ steps.version_info.outputs.minecraft_version }} # Java版本 java: | ${{ steps.version_info.outputs.java_versions }} # Modrinth 配置 modrinth-id: ${{ steps.version_info.outputs.modrinth_id }} modrinth-token: ${{ secrets.MODRINTH_TOKEN }} modrinth-featured: true modrinth-unfeature-mode: any modrinth-dependencies: ${{ steps.version_info.outputs.forge_modrinth_dependencies }} # CurseForge 配置 curseforge-id: ${{ steps.version_info.outputs.curseforge_id }} curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} curseforge-dependencies: ${{ steps.version_info.outputs.forge_curseforge_dependencies }} # 失败处理 fail-mode: skip # 发布完成后列出结果 - name: Summary if: always() run: | echo "## 发布结果摘要" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### GitHub Release" >> $GITHUB_STEP_SUMMARY echo "- 标签: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "- URL: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Modrinth" >> $GITHUB_STEP_SUMMARY echo "- 项目ID: ${{ steps.version_info.outputs.modrinth_id }}" >> $GITHUB_STEP_SUMMARY echo "- Fabric版本: ${{ steps.version_info.outputs.version }}-fabric" >> $GITHUB_STEP_SUMMARY echo "- Forge版本: ${{ steps.version_info.outputs.version }}-forge" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### CurseForge" >> $GITHUB_STEP_SUMMARY echo "- 项目ID: ${{ steps.version_info.outputs.curseforge_id }}" >> $GITHUB_STEP_SUMMARY echo "- Fabric版本: ${{ steps.version_info.outputs.version }}-fabric" >> $GITHUB_STEP_SUMMARY echo "- Forge版本: ${{ steps.version_info.outputs.version }}-forge" >> $GITHUB_STEP_SUMMARY