ThinkAdmin/.github/generate-release-notes.py
Anyon 1ddee14de6 ci(release): 完善双分支自动拆分与发布流程
为 ThinkAdmin 主仓库补强 v6-dev 与 v8-dev 的自动化处理机制,避免两个开发分支在插件仓库分支、发布 Tag 与 Release Notes 生成时互相覆盖。

主要内容:

- 拆分工作流仅允许 v6-dev 与 v8-dev,按同名分支推送各插件仓库。

- 发布工作流按 v6.* / v8.* Tag 自动选择对应开发分支,并要求 Tag 指向对应分支最新提交。

- 发布脚本同步推送插件分支和同名 Tag,遇到冲突 Tag 会拒绝覆盖。

- 新增中文 Release Notes 生成器,按提交前缀自动汇总本次变更内容。

- 静态分析步骤兼容 v6-dev 未定义 analyse 脚本的情况,避免旧分支发布失败。
2026-05-08 15:56:49 +08:00

119 lines
3.6 KiB
Python
Executable File
Raw 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.

#!/usr/bin/env python3
from __future__ import annotations
import os
import re
import subprocess
from collections import OrderedDict
from pathlib import Path
CURRENT_TAG = os.environ["CURRENT_TAG"]
PREVIOUS_TAG = os.environ.get("PREVIOUS_TAG", "")
RELEASE_BRANCH = os.environ.get("RELEASE_BRANCH", "")
GITHUB_REPOSITORY = os.environ.get("GITHUB_REPOSITORY", "zoujingli/ThinkAdmin")
GROUPS = OrderedDict([
("feat", "新增功能"),
("fix", "问题修复"),
("refactor", "重构调整"),
("perf", "性能优化"),
("pref", "性能优化"),
("style", "样式调整"),
("docs", "文档更新"),
("test", "测试质量"),
("build", "构建发布"),
("ci", "持续集成"),
("chore", "工程维护"),
("other", "其他变更"),
])
PREFIX_RE = re.compile(r"^(?P<type>[a-z]+)(?:\((?P<scope>[^)]+)\))?[:]\s*(?P<title>.+)$")
def git_lines(*args: str) -> list[str]:
out = subprocess.check_output(["git", *args], text=True)
return [line for line in out.splitlines() if line]
def release_range() -> str:
if PREVIOUS_TAG:
return f"{PREVIOUS_TAG}..{CURRENT_TAG}"
return CURRENT_TAG
def commits() -> list[tuple[str, str]]:
lines = git_lines("log", "--pretty=format:%H%x01%s", release_range())
result: list[tuple[str, str]] = []
for line in lines:
sha, subject = line.split("\x01", 1)
result.append((sha, subject))
return result
def normalize(subject: str) -> tuple[str, str]:
match = PREFIX_RE.match(subject)
if not match:
return "other", subject
typ = match.group("type")
title = match.group("title")
if typ not in GROUPS:
typ = "other"
scope = match.group("scope")
if scope:
title = f"{scope}{title}"
return typ, title
def main() -> None:
grouped: dict[str, list[str]] = {key: [] for key in GROUPS}
all_commits = commits()
for sha, subject in all_commits:
typ, title = normalize(subject)
grouped[typ].append(f"- {title} ({sha[:8]})")
lines: list[str] = []
lines.append(f"## Release {CURRENT_TAG}")
lines.append("")
if RELEASE_BRANCH:
lines.append(f"- 发布分支:`{RELEASE_BRANCH}`")
if PREVIOUS_TAG:
compare_url = f"https://github.com/{GITHUB_REPOSITORY}/compare/{PREVIOUS_TAG}...{CURRENT_TAG}"
lines.append(f"- 对比范围:[`{PREVIOUS_TAG}...{CURRENT_TAG}`]({compare_url})")
else:
lines.append("- 对比范围:首次发布标签")
lines.append(f"- 提交数量:{len(all_commits)}")
lines.append("")
lines.append("## 本次变更摘要")
lines.append("")
summary = [f"{label} {len(items)}" for key, label in GROUPS.items() if (items := grouped[key])]
lines.append("".join(summary) if summary else "- 本次发布没有检测到提交变更。")
lines.append("")
lines.append("## 变更明细")
lines.append("")
for key, label in GROUPS.items():
items = grouped[key]
if not items:
continue
lines.append(f"### {label}")
lines.extend(items)
lines.append("")
lines.append("## 发布说明")
lines.append("")
lines.append("- 主仓库 Release 由 GitHub Actions 自动创建。")
lines.append("- 插件仓库会同步推送同名分支和同名 TagPackagist 可通过 GitHub Hook 自动刷新。")
lines.append("- v6 与 v8 使用不同主版本号 Tag避免两个开发分支发布互相覆盖。")
lines.append("")
out = Path("log") / f"{CURRENT_TAG}.md"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text("\n".join(lines), encoding="utf-8")
print(out)
if __name__ == "__main__":
main()