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

159 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 整体框架
## 数据文件
### `secrets.json`
存放 API 密钥,位于项目根目录。
```json
{
"curseforge": "<CurseForge API Token>",
"modrinth": "<Modrinth Personal Access Token>"
}
```
### `configs/{config_name}.json`
存放单个 mod 项目的配置,位于 `configs/` 目录下。文件名即为配置名,用户通过 WebUI 下拉选择。
```jsonc
{
"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_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. 解析项目
后端收到选中配置后:
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`
- `version` `mc_version` 填入 `filename_format` 模板生成构建产物文件名
- `version` `mc_version` 填入 `source_filename_format` 模板生成源码文件名
- 检查 `{project_dir}/build/libs/` 下是否存在对应文件
5. 将所有匹配结果返回前端 VersionTable 展示MC 版本 | 兼容范围 | 构建产物 | 源码
### 4. 版本确认与发布
#### 第一页:版本选择
1. 用户点击 `[一键发布]`弹出 PublishModal
2. 前端请求两个平台的已有版本列表
- Modrinth`GET /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. 每个版本的发布流程
- 解析模板 `version` `mc_version` 填入各平台 `version_name`、`version` 模板含递归解析
- 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`。