8.2 KiB
8.2 KiB
整体框架
数据文件
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 项目 ID(8 位 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_server、client_only、server_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. 解析项目
后端收到选中配置后:
- 读取
{project_dir}/{project_properties_path},提取mod_version_field对应的值 →version - 扫描
{project_dir}/{minecraft_properties_dir}/目录,获取所有${mc_version}.properties文件名 →mc_version[] - 对
mc_version[]排序后计算每个版本的兼容范围:- 非最后一个版本:范围为
[当前版本, 下一个版本),如"1.19.1"→"1.19.1-1.19.2" - 最后一个版本:
< 26的版本用~匹配次版本号(如1.21.x);≥ 26的版本用^匹配主版本号(如26.x),截止版本由用户在下拉框中选择
- 非最后一个版本:范围为
- 遍历每个
mc_version:- 用
version和mc_version填入filename_format模板,生成构建产物文件名 - 用
version和mc_version填入source_filename_format模板,生成源码文件名 - 检查
{project_dir}/build/libs/下是否存在对应文件
- 用
- 将所有匹配结果返回前端,由 VersionTable 展示(MC 版本 | 兼容范围 | 构建产物 | 源码)
4. 版本确认与发布
第一页:版本选择
- 用户点击
[一键发布],弹出 PublishModal - 前端请求两个平台的已有版本列表:
- Modrinth:
GET /project/{id}/version - CurseForge:通过对应 API 获取
- Modrinth:
- 后端比对已有版本与待发布版本(按
version模板生成的值匹配) - 前端左右两栏展示(待发布版本与已有版本合并后,按
mod_version降序、mc_version降序排列):- 顶部绿色行 = 待发布版本,复选框默认勾选
- 下方灰色行 = 已有版本,复选框禁用
- 已存在的待发布版本显示为已有版本颜色,复选框禁用
- 用户调整勾选后点击"下一步"
第二页:发布设置
- 用户填写 changelog(Markdown,Code / Preview 切换)
- 选择 version_type(默认
release) - 点击"确认发布";可点击"上一步"返回修改
5. 执行发布
- 前端调用
trpc.publish.useMutation()发起发布 - 前端通过
trpc.progress.useSubscription()监听进度,实时更新进度条 - 进度条格式:
{平台图标} 平台名: [======== ] 0 / N,仅显示有勾选任务的平台 - 单个平台内按 MC 版本顺序逐个提交,不可并行;两个平台之间可以并行执行
- 每个版本的发布流程:
- 解析模板:用
version和mc_version填入各平台version_name、version模板(含递归解析) - Modrinth:构造 multipart/form-data 请求,含
dataJSON + 构建产物文件 + 源码文件 - CurseForge:构造 multipart/form-data 请求,含
metadataJSON + 文件 - 通过 tRPC subscription 推送进度更新
- 解析模板:用
- 任一请求失败则取消当前平台的后续所有任务,不影响另一个平台
- 全部完成或失败后,在第 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。