feat(toolchain): auto-deploy on PR merge (git pull + pm2 restart) (#43)

- Add auto-deploy logic in _handle_pr_closed after mail notification
- git pull origin main in install dir on merge
- Smart restart: only restart pm2 when src/templates/frontend/*.py changed
- Pure docs changes: pull only, no restart
- Deploy failure logged but does not block mail notification
- Update design doc §23 with auto-deploy section
This commit is contained in:
cfdaily
2026-06-12 13:26:10 +08:00
parent 5474d0a0e8
commit 9bb1e9dc64
2 changed files with 53 additions and 1 deletions
+16 -1
View File
@@ -28,7 +28,7 @@
|---|---|---|---|---|
| E1 | PR 更新(push 新 commit)→ 通知 reviewer | `pull_request.synchronize` | **高** | review 驳回→修改→重 review 的关键闭环 |
| ~~E2~~ | ~~PR 合并通知~~ | ~~已删除~~ | ~~—~~ | ~~和 §22 CD 成功通知重叠,已删~~ |
| E2 | PR 合并 → 通知 PR 作者 | `pull_request` (closed+merged) | **高** | PR #38 恢复:CD 通知语义不同(部署状态 vs 合并信息),文档 PR 无 CD 流程仍需通知 |
| E2 | PR 合并 → 通知 PR 作者 + 自动部署 | `pull_request` (closed+merged) | **高** | PR #38 恢复:CD 通知语义不同(部署状态 vs 合并信息),文档 PR 无 CD 流程仍需通知。PR #43:含自动部署(git pull + pm2 restart |
| E3 | Review 评论(COMMENTED)→ 通知 PR 作者 | `pull_request_review` (COMMENTED) | 中 | reviewer 讨论提问,作者应知道 |
| E4 | PR 上普通评论 → 通知相关人 | `issue_comment` (on PR) | 低 | 非关键路径 |
@@ -136,6 +136,21 @@ async def _handle_pull_request(payload: Dict[str, Any]) -> None:
所以 **`_EVENT_HANDLERS` 不需要修改**,只需修改 handler 内部的 action/state 分发逻辑。
### PR 合并后自动部署(PR #43
**触发**`_handle_pr_closed` 合并事件处理完成后
**逻辑**
1. `git pull origin main`(安装目录)
2. 获取 PR 变更文件列表(复用 `_fetch_pr_files`
3. 判断是否需要重启:文件路径包含 `src/``templates/``frontend/``*.py` 后缀 → 重启
4.`docs/` 变更 → 只 pull 不重启
5. 部署失败仅 log,不影响 Mail 通知
**设计决策**
- 不做优雅等待(sentinel file 方案):daemon 正在执行任务时重启,已 spawn 的子进程独立运行不受影响,最坏情况是当前 tick 中断、下一轮 PM2 拉起后继续
- 部署失败不阻塞通知:Mail 通知和部署解耦,保证信息触达
### 不做的事
| 项 | 理由 |
+37
View File
@@ -481,6 +481,43 @@ async def _handle_pr_closed(payload: Dict[str, Any]) -> None:
title = f"PR 已合并: {pr_title} ({repo}#{pr_number})"
_send_mail(pr_author, title, text)
# 自动部署:git pull + 按需 pm2 restart
try:
import subprocess
import os
install_dir = os.environ.get("SANGUO_PROJECTS_DIR", os.path.expanduser("~/.sanguo_projects"))
repo_dir = os.path.join(install_dir, "sanguo_moziplus_v2")
# git pull
pull_result = subprocess.run(
["git", "pull", "origin", "main"],
cwd=repo_dir, capture_output=True, text=True, timeout=30
)
if pull_result.returncode == 0:
logger.info("Auto-deploy: git pull success for %s", repo)
# 判断是否需要重启:获取 PR 变更文件列表
files = await _fetch_pr_files(repo, pr_number)
needs_restart = any(
f.startswith("src/") or f.startswith("templates/") or f.startswith("frontend/") or f.endswith(".py")
for f in files[0]
)
if needs_restart:
restart_result = subprocess.run(
["pm2", "restart", "sanguo-moziplus-v2"],
capture_output=True, text=True, timeout=15
)
if restart_result.returncode == 0:
logger.info("Auto-deploy: pm2 restart triggered (files: %s)", ", ".join(files[0][:5]))
else:
logger.error("Auto-deploy: pm2 restart failed: %s", restart_result.stderr)
else:
logger.warning("Auto-deploy: git pull failed: %s", pull_result.stderr)
except Exception as e:
logger.error("Auto-deploy: unexpected error: %s", e)
async def _handle_issues(payload: Dict[str, Any]) -> None:
"""处理 issues 事件:assigned → 通知被指派人;opened+部署失败 → 通知运维。"""