ModReleaser/docs/workflow.md
2026-07-18 10:04:16 +08:00

8.2 KiB
Raw Permalink Blame History

整体框架

数据文件

secrets.json

存放 API 密钥,位于项目根目录。

{
  "curseforge": "<CurseForge API Token>",
  "modrinth": "<Modrinth Personal Access Token>"
}

configs/{config_name}.json

存放单个 mod 项目的配置,位于 configs/ 目录下。文件名即为配置名,用户通过 WebUI 下拉选择。

{
  "name": "模组名称",
  "modrinth": {
    "project_id": "AABBCCDD",
    "version_name": "ModName v${version} for Minecraft ${mc_version_range}",
    "version": "${version}-mc${mc_version}",
    "loaders": ["fabric"],
    "environment": "client_and_server",
    "dependencies": [
      { "dependency_type": "required", "project_id": "P7dR8mSH" }
    ]
  },
  "curseforge": {
    "project_id": 123456,
    "version_name": "#{filename_format}",
    "environment": ["Client", "Server"],
    "loaders": ["fabric"],
    "relations": {
      "projects": []
    }
  },
  "project_dir": "/path/to/project",
  "minecraft_properties_dir": "./properties",  // 目录内为 ${mc_version}.properties 文件,文件名即对应游戏版本
  "project_properties_path": "./gradle.properties",         // 项目版本配置文件路径
  "mod_version_field": "mod_version",             // 版本号在 project_properties_path 文件中的键名
  "filename_format": "modname-${version}-mc${mc_version}.jar",
  "source_filename_format": "modname-${version}-mc${mc_version}-sources.jar"
}
字段 类型 说明
name string 配置文件显示名称(模组名称)
modrinth object Modrinth 配置
modrinth.project_id string Modrinth 项目 ID8 位 base62
modrinth.version_name string 版本名称模板,${mc_version_range}"1.19""1.20-1.20.2"
modrinth.version string 版本号模板
modrinth.loaders string[] 加载器列表
modrinth.environment string 运行环境,必填。可选值:client_and_serverclient_onlyserver_only
modrinth.dependencies object[] 依赖列表,无依赖填 []
curseforge object CurseForge 配置
curseforge.project_id number CurseForge 项目 ID
curseforge.version_name string 版本名称模板,如 "#{filename_format}"
curseforge.environment string[] 运行环境
curseforge.loaders string[] 加载器列表
curseforge.relations object 关联项目,格式 { projects: [...] }
project_dir string 项目目录绝对路径
minecraft_properties_dir string 相对项目目录的 MC 版本配置目录,默认 ./properties。目录内为 ${mc_version}.properties 文件,文件名即为可用游戏版本
project_properties_path string 相对项目目录的项目版本配置文件路径,默认 ./gradle.properties
mod_version_field string 版本号在 project_properties_path 文件中的键名,默认 "mod_version"
filename_format string 构建产物文件名模板,用于反向解析 ${version}${mc_version}
source_filename_format string 源码 jar 文件名模板,用于正向生成文件名

递归解析规则:所有含占位符的模板字符串,正向 format 或反向解析后,若结果仍包含占位符,则需继续解析,直至结果中不再有占位符为止。

  • #{...} — 引用占位符,如 #{filename_format} 表示此处将引用解析后的值
  • ${...} — 模板变量,由运行时填入实际值,如 ${version}${mc_version}

运行时流程

1. 启动

  • 启动 Next.js 开发服务器
  • 后端读取 configs/ 目录,列出所有可用配置文件
  • 后端读取 secrets.json,加载两个平台的 API 密钥到内存

2. 选择配置

  • 前端 ConfigSelector 下拉框展示所有配置(按 name 显示)
  • 用户选择一个配置后,前端请求后端解析项目信息

3. 解析项目

后端收到选中配置后:

  1. 读取 {project_dir}/{project_properties_path},提取 mod_version_field 对应的值 → version
  2. 扫描 {project_dir}/{minecraft_properties_dir}/ 目录,获取所有 ${mc_version}.properties 文件名 → mc_version[]
  3. mc_version[] 排序后计算每个版本的兼容范围:
    • 非最后一个版本:范围为 [当前版本, 下一个版本),如 "1.19.1""1.19.1-1.19.2"
    • 最后一个版本:< 26 的版本用 ~ 匹配次版本号(如 1.21.x≥ 26 的版本用 ^ 匹配主版本号(如 26.x),截止版本由用户在下拉框中选择
  4. 遍历每个 mc_version
    • versionmc_version 填入 filename_format 模板,生成构建产物文件名
    • versionmc_version 填入 source_filename_format 模板,生成源码文件名
    • 检查 {project_dir}/build/libs/ 下是否存在对应文件
  5. 将所有匹配结果返回前端,由 VersionTable 展示MC 版本 | 兼容范围 | 构建产物 | 源码)

4. 版本确认与发布

第一页:版本选择

  1. 用户点击 [一键发布],弹出 PublishModal
  2. 前端请求两个平台的已有版本列表:
    • ModrinthGET /project/{id}/version
    • CurseForge通过对应 API 获取
  3. 后端比对已有版本与待发布版本(按 version 模板生成的值匹配)
  4. 前端左右两栏展示(待发布版本与已有版本合并后,按 mod_version 降序、mc_version 降序排列):
    • 顶部绿色行 = 待发布版本,复选框默认勾选
    • 下方灰色行 = 已有版本,复选框禁用
    • 已存在的待发布版本显示为已有版本颜色,复选框禁用
  5. 用户调整勾选后点击"下一步"

第二页:发布设置

  • 用户填写 changelogMarkdownCode / Preview 切换)
  • 选择 version_type默认 release
  • 点击"确认发布";可点击"上一步"返回修改

5. 执行发布

  1. 前端调用 trpc.publish.useMutation() 发起发布
  2. 前端通过 trpc.progress.useSubscription() 监听进度,实时更新进度条
  3. 进度条格式:{平台图标} 平台名: [======== ] 0 / N,仅显示有勾选任务的平台
  4. 单个平台内按 MC 版本顺序逐个提交,不可并行;两个平台之间可以并行执行
  5. 每个版本的发布流程:
    • 解析模板:用 versionmc_version 填入各平台 version_nameversion 模板(含递归解析)
    • Modrinth构造 multipart/form-data 请求,含 data JSON + 构建产物文件 + 源码文件
    • CurseForge构造 multipart/form-data 请求,含 metadata JSON + 文件
    • 通过 tRPC subscription 推送进度更新
  6. 任一请求失败则取消当前平台的后续所有任务,不影响另一个平台
  7. 全部完成或失败后,在第 3 步进度页内嵌显示最终状态(成功数 / 失败数 + 各平台失败原因列表)

错误处理与可见性

  • 单个版本上传失败:记入该平台 errors,发出 failed 进度事件(进度条变红),该平台后续版本取消;
  • 平台准备阶段失败(元数据拉取、版本范围计算):记为该平台全部失败,同样发出 failed 事件,并在服务端终端输出 console.error
  • mutation 层兜底:任何未被捕获的平台异常记入 errors 并输出服务端日志;
  • 前端进度页的结果区按平台分行列出所有失败原因。

Dry-run模拟发布

.env.local 中设置 PUBLISH_DRY_RUN=1 后重启开发服务器,发布流程进入模拟模式:

  • 配置解析、版本范围计算、文件读取、multipart 请求组装全部照常执行;
  • 两个平台的上传请求在 ModrinthClient.post / CurseForgeClient.uploadPost 内被拦截,不会发生任何实际上传
  • 每次拦截会把完整请求摘要metadata JSON、各文件字段名/大小)打印到终端,并追加写入日志文件供人工核对,路径见终端输出;可用环境变量 PUBLISH_DRY_RUN_LOG_DIR 指定日志目录,默认为系统临时目录;
  • 平台返回伪造响应以维持调用链类型校验Modrinth 为 { name: "(dry-run)", ... }CurseForge 为 { id: 0 }),并带 1.5 秒模拟延迟,让前端进度条逐个推进。

实现位置:src/lib/utils/dryRun.ts