feat(release): 发布页自动生成「本次更新」说明

- prepare 任务新增 notes 步骤, 两级生成: GLM 总结(每条一句话、30字内、
  合并同类、最多6条) → 无 key/调用失败时约定式提交归类兜底
  (剔除 chore/ci 等噪声与版本号提交, 每条=提交主题, 保证发布页必有说明)
- build 任务 body 模板加「🔧 本次更新」区块, 注入 notes 输出
- prepare 检出改全量+带 tag (兜底需要上一版本区间的 git log)
- 已验证: YAML 合法; 兜底在 v0.3.10..v0.3.11 实际提交上输出正确;
  AI 请求 JSON 对引号/换行/反斜杠健壮
This commit is contained in:
shy3130
2026-10-21 09:29:55 +08:00
parent 59e2772f51
commit ef132f3bf9
+101 -6
View File
@@ -38,10 +38,15 @@ jobs:
outputs:
matrix: ${{ steps.filter.outputs.matrix }}
ver: ${{ steps.filter.outputs.ver }}
notes: ${{ steps.notes.outputs.notes }}
steps:
- name: 检出代码
# 仅为校验版本一致性 (读 frontend/package.json), 浅检出足够
# 版本一致性校验读 frontend/package.json; 生成更新说明需要 tag 区间内的
# git log, 故全量检出并带 tag (浅检出拿不到上一版本区间)
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- id: filter
run: |
@@ -81,11 +86,97 @@ jobs:
break
else:
raise SystemExit(f'未知产物后缀: {name}')
print('matrix=' + json.dumps({'include': selected}))
with open('$GITHUB_OUTPUT', 'a') as f:
f.write('matrix=' + json.dumps({'include': selected}) + '\n')
f.write('ver=' + ver + '\n')
"
print('matrix=' + json.dumps({'include': selected}))
with open('$GITHUB_OUTPUT', 'a') as f:
f.write('matrix=' + json.dumps({'include': selected}) + '\n')
f.write('ver=' + ver + '\n')
"
# 生成「本次更新」说明 (发布页正文), 两级: AI 总结 → 约定式提交归类兜底。
# 设计约束: 总结要精简 —— 每条一句话; 兜底同样一行一条 (提交主题本身即
# 一句话), 保证发布页任何情况下 (无 key / API 失败) 都有更新说明。
- id: notes
env:
GLM_API_KEY: ${{ secrets.GLM_API_KEY }}
REQ_VER: ${{ github.event.inputs.version }}
run: |
set -e
# 上一版本 tag: 版本倒序里第一个不等于本次输入的 tag (重发同版本也成立)
PREV_TAG=$(git tag --sort=-v:refname | awk -v cur="$REQ_VER" '$0 != cur' | head -1)
if [ -n "$PREV_TAG" ]; then
LOG=$(git log --no-merges --pretty=format:'%s' "$PREV_TAG"..HEAD)
else
LOG=$(git log --no-merges -20 --pretty=format:'%s')
fi
export LOG
NOTES=""
if [ -n "$GLM_API_KEY" ]; then
# AI 总结 (glm-4-flash, bigmodel 兼容端点): 每条一句话、合并同类、
# 用户视角。任何失败静默 → 走兜底, 不阻塞发版。
BODY=$(LOG="$LOG" python3 - <<'PY'
import json, os
print(json.dumps({
"model": "glm-4-flash",
"temperature": 0.3,
"messages": [
{"role": "system", "content": (
"你是发版说明撰稿人。根据提交清单写面向用户的中文更新说明。"
"要求: 每条一句话且精简(30字内); 只写用户可感知的变化; "
"合并同类提交; 最多6条; 直接输出 markdown 无序列表"
"(- 开头, 每行一条), 不要标题、代码块或任何解释。")},
{"role": "user", "content": "提交清单:\n" + os.environ["LOG"]},
],
}, ensure_ascii=False))
PY
)
NOTES=$(curl -sS --max-time 60 https://open.bigmodel.cn/api/paas/v4/chat/completions \
-H "Authorization: Bearer $GLM_API_KEY" \
-H 'Content-Type: application/json' \
-d "$BODY" \
| python3 -c 'import json,sys
try:
d = json.load(sys.stdin)
print(d["choices"][0]["message"]["content"].strip())
except Exception:
pass' || true)
# 剥掉模型偶发的 ```markdown 围栏与空行; 结果为空则交给兜底
NOTES=$(printf '%s\n' "$NOTES" \
| sed -e 's/^```[a-zA-Z]*$//' -e '/^```$/d' -e '/^[[:space:]]*$/d')
fi
if [ -z "$NOTES" ]; then
# 兜底: 约定式提交归类 — 剔除噪声类型(chore/ci/build/test/docs)与
# 版本号提交, 其余每条=提交主题(一句话); fix→修复 feat→新增
NOTES=$(LOG="$LOG" python3 - <<'PY'
import os, re
lines = [l.strip() for l in os.environ.get("LOG", "").splitlines() if l.strip()]
ver_bump = re.compile(r"^chore\s*:\s*v?\d+(\.\d+)+", re.I)
noise = re.compile(r"^(chore|ci|build|test|docs)(\([^)]*\))?!?:", re.I)
conv = re.compile(r"^(\w+)(\([^)]*\))?!?:\s*(.+)$")
groups = {"修复": [], "新增": [], "其他": []}
for l in lines:
if ver_bump.match(l) or noise.match(l):
continue
m = conv.match(l)
if not m:
continue
typ, msg = m.group(1).lower(), m.group(3)
kind = "新增" if typ.startswith("feat") else "修复" if typ.startswith(("fix", "perf")) else "其他"
groups[kind].append(msg if kind != "其他" else f"{typ}: {msg}")
out = [f"- {m}" for kind in ("修复", "新增", "其他") for m in groups[kind]]
print("\n".join(out) if out else "- 常规维护与稳定性优化")
PY
)
fi
{
echo 'notes<<NOTES_EOF'
printf '%s\n' "$NOTES"
echo 'NOTES_EOF'
} >> "$GITHUB_OUTPUT"
echo "---- 更新说明预览 ----"
printf '%s\n' "$NOTES"
# 第二步: 各平台并行构建
build:
@@ -244,6 +335,10 @@ jobs:
body: |
## 桌面客户端 ${{ github.event.inputs.version }}
### 🔧 本次更新
${{ needs.prepare.outputs.notes }}
### ⬇️ 下载
| 平台 | 文件 | 下载 |