forked from Gitlink/gitlink-cli
328 lines
13 KiB
Bash
328 lines
13 KiB
Bash
#!/usr/bin/env bash
|
||
# ================================================================
|
||
# Scenario 1: Community Operations (定时批量链路, 48h周期)
|
||
# Flow: 收集周期数据 → 分类 → 生成 Release Notes → 发布 Release + Wiki
|
||
#
|
||
# 遵循 gitlink-changelog skill 的收集→分类→发布流程
|
||
# 条目格式: - 描述 (#编号) (@作者)
|
||
# ================================================================
|
||
set -o pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
source "$SCRIPT_DIR/lib/common.sh"
|
||
|
||
PERIOD_HOURS=48
|
||
RELEASE_VERSION=""
|
||
OWNER=""
|
||
REPO=""
|
||
DRY_RUN=false
|
||
|
||
usage() {
|
||
echo "Usage: $0 [--owner O] [--repo R] [--period-hours N] [--release-version TAG] [--dry-run]"
|
||
echo ""
|
||
echo " 定时链路 — 收集周期内数据,按 gitlink-changelog skill 生成 Release Notes 和社区周报。"
|
||
exit 1
|
||
}
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--owner) OWNER="$2"; shift 2 ;;
|
||
--repo) REPO="$2"; shift 2 ;;
|
||
--period-hours) PERIOD_HOURS="$2"; shift 2 ;;
|
||
--release-version) RELEASE_VERSION="$2"; shift 2 ;;
|
||
--dry-run) DRY_RUN="true"; shift ;;
|
||
--help|-h) usage ;;
|
||
*) log_err "Unknown arg: $1"; usage ;;
|
||
esac
|
||
done
|
||
|
||
check_auth
|
||
require_owner_repo
|
||
|
||
# ================================================================
|
||
# Phase 1: 确定时间窗口和基线
|
||
# ================================================================
|
||
log_title "Phase 1: Time Window"
|
||
|
||
PERIOD_START=$(date -d "$PERIOD_HOURS hours ago" +"%Y-%m-%d %H:%M" 2>/dev/null || date -v-${PERIOD_HOURS}H +"%Y-%m-%d %H:%M")
|
||
PERIOD_END=$(date +"%Y-%m-%d %H:%M")
|
||
PERIOD_START_TS=$(date -d "$PERIOD_START" +%s 2>/dev/null || date -j -f "%Y-%m-%d %H:%M" "$PERIOD_START" +%s)
|
||
|
||
# 找上一版本
|
||
log_step "Finding previous release..."
|
||
PREV_TAG=""
|
||
PREV_DATE=""
|
||
RELEASES_JSON=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 5 2>/dev/null || true)
|
||
if [[ -n "$RELEASES_JSON" ]]; then
|
||
PREV_TAG=$(echo "$RELEASES_JSON" | jq -r '(.data.releases // .data // [])[0].tag_name // ""' 2>/dev/null || echo "")
|
||
PREV_DATE=$(echo "$RELEASES_JSON" | jq -r '(.data.releases // .data // [])[0].created_at // ""' 2>/dev/null || echo "")
|
||
fi
|
||
|
||
if [[ -z "$PREV_TAG" || "$PREV_TAG" == "null" ]]; then
|
||
log_info "No previous release — this will be the first"
|
||
PREV_TAG="initial"
|
||
fi
|
||
|
||
NEW_VERSION="${RELEASE_VERSION:-weekly-$(date +%Y%m%d)}"
|
||
log_ok "Period: ${PERIOD_START} ~ ${PERIOD_END}"
|
||
log_ok "Release: ${PREV_TAG} → ${NEW_VERSION}"
|
||
|
||
# ================================================================
|
||
# Phase 2: 收集数据 (changelog skill: collect-data)
|
||
# ================================================================
|
||
log_title "Phase 2: Collect Data"
|
||
|
||
# ── Commits ──
|
||
log_step "Collecting commits..."
|
||
COMMIT_LIST=""
|
||
COMMIT_COUNT=0
|
||
if [[ "$PREV_TAG" != "initial" ]]; then
|
||
COMPARE_JSON=$(gl_run api GET "/v1/$OWNER/$REPO/compare/$PREV_TAG...master" 2>/dev/null || true)
|
||
if [[ -n "$COMPARE_JSON" ]]; then
|
||
COMMIT_COUNT=$(echo "$COMPARE_JSON" | jq '.data.total_commits // 0' 2>/dev/null || echo "0")
|
||
COMMIT_LIST=$(echo "$COMPARE_JSON" | jq -r '[.data.commits[]? | "- \(.commit.message | split("\n")[0]) (\(.commit.author.name // "unknown"))"] | .[:50] | join("\n")' 2>/dev/null || echo "")
|
||
fi
|
||
fi
|
||
[[ -z "$COMMIT_LIST" ]] && COMMIT_LIST="(无 commit 数据)"
|
||
log_ok "Commits: $COMMIT_COUNT"
|
||
|
||
# ── Merged PRs (用临时文件避免 pipefail 静默退出) ──
|
||
log_step "Collecting merged PRs..."
|
||
PR_ITEMS=""
|
||
PR_COUNT=0
|
||
PR_CONTRIBUTORS=""
|
||
PR_TMP=$(mktemp)
|
||
PRS_JSON=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state merged --limit 100 2>/dev/null || true)
|
||
if [[ -n "$PRS_JSON" ]]; then
|
||
echo "$PRS_JSON" | jq -r '(.data.issues // .data.pulls // .data // [])[]? | "\(.name // .subject // .title // "N/A")\t\(.pull_request_number // .number // .id)\t\(.author_login // .author.login // "?")"' 2>/dev/null > "$PR_TMP" || true
|
||
while IFS=$'\t' read -r pr_title pr_num pr_author || [[ -n "$pr_title" ]]; do
|
||
PR_ITEMS+="- ${pr_title} (#${pr_num}) (@${pr_author})"$'\n'
|
||
PR_CONTRIBUTORS+="@${pr_author} "
|
||
PR_COUNT=$((PR_COUNT + 1)) || true
|
||
done < "$PR_TMP"
|
||
fi
|
||
rm -f "$PR_TMP"
|
||
[[ -z "$PR_ITEMS" ]] && PR_ITEMS="(本周期无 PR 合并)"
|
||
log_ok "Merged PRs in period: $PR_COUNT"
|
||
|
||
# ── Issues (新增 + 关闭) — 用临时文件避免子 shell 变量丢失 ──
|
||
log_step "Collecting issues..."
|
||
|
||
ISS_FEAT_FILE=$(mktemp)
|
||
ISS_BUG_FILE=$(mktemp)
|
||
ISS_DOC_FILE=$(mktemp)
|
||
ISS_OTHER_FILE=$(mktemp)
|
||
ISS_AUTHORS_FILE=$(mktemp)
|
||
ISS_COUNT_FILE=$(mktemp)
|
||
echo "0" > "$ISS_COUNT_FILE"
|
||
|
||
for state in open closed; do
|
||
ISS_JSON=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state "$state" --limit 100 2>/dev/null || true)
|
||
[[ -z "$ISS_JSON" ]] && continue
|
||
echo "$ISS_JSON" | jq -c '(.data.issues // .data // [])[]' 2>/dev/null | while read -r issue; do
|
||
NUM=$(echo "$issue" | jq -r '.project_issues_index // .number // .id')
|
||
TITLE=$(echo "$issue" | jq -r '.subject // .title // "N/A"')
|
||
DESC=$(echo "$issue" | jq -r '.description // ""')
|
||
AUTHOR=$(echo "$issue" | jq -r '.author.login // .author.username // "?"')
|
||
CREATED=$(echo "$issue" | jq -r '.created_at // ""')
|
||
CLOSED=$(echo "$issue" | jq -r '.closed_at // ""')
|
||
STATE=$(echo "$issue" | jq -r '.status.name // .state // "?"')
|
||
|
||
# 时间筛选:created 或 closed >= PERIOD_START
|
||
IN_PERIOD=false
|
||
if [[ -n "$CREATED" ]]; then CT_TS=$(date -d "$CREATED" +%s 2>/dev/null || echo "0"); [[ "$CT_TS" -ge "$PERIOD_START_TS" ]] && IN_PERIOD=true; fi
|
||
[[ "$IN_PERIOD" != "true" && -n "$CLOSED" ]] && { CL_TS=$(date -d "$CLOSED" +%s 2>/dev/null || echo "0"); [[ "$CL_TS" -ge "$PERIOD_START_TS" ]] && IN_PERIOD=true; }
|
||
[[ "$IN_PERIOD" != "true" ]] && continue
|
||
|
||
LINE="- ${TITLE} (#${NUM}) (@${AUTHOR})"
|
||
[[ "$STATE" =~ 关闭|closed ]] && LINE+=" [已关闭]"
|
||
|
||
# 关键词分类 — 标题 + 描述 都参与匹配, 10 个标准类别
|
||
COMBINED=$(echo "$TITLE $DESC" | tr '[:upper:]' '[:lower:]')
|
||
if echo "$COMBINED" | grep -qiE 'bug|error|crash|fault|fix|缺陷|错误|异常|崩溃|修复|故障'; then
|
||
CATEGORY="缺陷"
|
||
echo "${LINE} [缺陷]" >> "$ISS_BUG_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'feature|enhancement|add|新增|建议|功能|特性|新功能|支持|request'; then
|
||
CATEGORY="功能"
|
||
echo "${LINE} [功能]" >> "$ISS_FEAT_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'doc|readme|guide|wiki|tutorial|文档|说明|教程|手册'; then
|
||
CATEGORY="文档"
|
||
echo "${LINE} [文档]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'test|测试|用例|覆盖|验证'; then
|
||
CATEGORY="测试"
|
||
echo "${LINE} [测试]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'duplicate|重复|重复的|和.*一样'; then
|
||
CATEGORY="重复"
|
||
echo "${LINE} [重复]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'question|疑问|不确定|讨论|澄清|是否|可否'; then
|
||
CATEGORY="疑问"
|
||
echo "${LINE} [疑问]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'help|协助|帮助|协作|请求帮助|互助'; then
|
||
CATEGORY="协助"
|
||
echo "${LINE} [协助]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'postpone|wontfix|暂缓|搁置|低优|不重要|不紧急|暂不|delay'; then
|
||
CATEGORY="搁置"
|
||
echo "${LINE} [搁置]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'task|todo|任务|待办|计划|安排'; then
|
||
CATEGORY="任务"
|
||
echo "${LINE} [任务]" >> "$ISS_DOC_FILE"
|
||
elif echo "$COMBINED" | grep -qiE 'support|兼容|环境|依赖|平台|适配'; then
|
||
CATEGORY="支持"
|
||
echo "${LINE} [支持]" >> "$ISS_DOC_FILE"
|
||
else
|
||
echo "${LINE} [其他]" >> "$ISS_OTHER_FILE"
|
||
fi
|
||
echo "@${AUTHOR}" >> "$ISS_AUTHORS_FILE"
|
||
# 计数
|
||
CNT=$(cat "$ISS_COUNT_FILE"); echo $((CNT + 1)) > "$ISS_COUNT_FILE"
|
||
done
|
||
done
|
||
|
||
ISSUE_FEATURES=$(cat "$ISS_FEAT_FILE" 2>/dev/null || echo "")
|
||
ISSUE_BUGS=$(cat "$ISS_BUG_FILE" 2>/dev/null || echo "")
|
||
ISSUE_DOCS=$(cat "$ISS_DOC_FILE" 2>/dev/null || echo "")
|
||
ISSUE_OTHER=$(cat "$ISS_OTHER_FILE" 2>/dev/null || echo "")
|
||
ISSUE_COUNT=$(cat "$ISS_COUNT_FILE" 2>/dev/null || echo "0")
|
||
ISSUE_CONTRIBUTORS=$(sort -u "$ISS_AUTHORS_FILE" 2>/dev/null | tr '\n' ' ' || echo "")
|
||
rm -f "$ISS_FEAT_FILE" "$ISS_BUG_FILE" "$ISS_DOC_FILE" "$ISS_OTHER_FILE" "$ISS_AUTHORS_FILE" "$ISS_COUNT_FILE"
|
||
|
||
log_ok "Issues in period: $ISSUE_COUNT"
|
||
|
||
# ── 贡献者汇总 ──
|
||
ALL_CONTRIBUTORS=$(echo "$PR_CONTRIBUTORS $ISSUE_CONTRIBUTORS" | tr ' ' '\n' | sort -u | grep -v '^$' | sed 's/^/- /' | tr '\n' ' ')
|
||
[[ -z "$ALL_CONTRIBUTORS" ]] && ALL_CONTRIBUTORS="(无活跃贡献者)"
|
||
|
||
# ================================================================
|
||
# Phase 3: 生成 Release Notes (changelog skill: 标准模板)
|
||
# ================================================================
|
||
log_title "Phase 3: Generate Release Notes"
|
||
|
||
FEAT_SECTION="${ISSUE_FEATURES:-_无_}"
|
||
BUG_SECTION="${ISSUE_BUGS:-_无_}"
|
||
DOC_SECTION="${ISSUE_DOCS:-_无_}"
|
||
OTHER_SECTION="${ISSUE_OTHER:-_无_}"
|
||
|
||
FEAT_COUNT=$(echo "$ISSUE_FEATURES" | grep -c '^-' 2>/dev/null || echo "0")
|
||
BUG_COUNT=$(echo "$ISSUE_BUGS" | grep -c '^-' 2>/dev/null || echo "0")
|
||
|
||
RELEASE_BODY="# 🎉 Release ${NEW_VERSION}
|
||
|
||
## 📊 变更统计
|
||
- **周期**: ${PERIOD_START} ~ ${PERIOD_END}
|
||
- **已合并 PR**: ${PR_COUNT} 个
|
||
- **Issue 活动**: ${ISSUE_COUNT} 条(新增/关闭)
|
||
- **Commits**: ${COMMIT_COUNT} 条
|
||
|
||
## 📝 Issue 活动
|
||
|
||
### 🐛 缺陷 / Bug
|
||
${BUG_SECTION}
|
||
|
||
### ✨ 新功能 / Feature
|
||
${FEAT_SECTION}
|
||
|
||
### 📖 文档
|
||
${DOC_SECTION}
|
||
|
||
### 💡 其他
|
||
${OTHER_SECTION}
|
||
|
||
## 🔀 已合并 PR
|
||
${PR_ITEMS}
|
||
|
||
## 🙏 贡献者
|
||
${ALL_CONTRIBUTORS}
|
||
|
||
---
|
||
**完整变更日志**: https://www.gitlink.org.cn/${OWNER}/${REPO}/compare/${PREV_TAG}...${NEW_VERSION}
|
||
|
||
*Auto-generated by gitlink-cli community-ops workflow (gitlink-changelog skill)*"
|
||
|
||
echo ""
|
||
echo "$RELEASE_BODY"
|
||
echo ""
|
||
|
||
# ================================================================
|
||
# Phase 4: 发布 Release
|
||
# ================================================================
|
||
log_title "Phase 4: Publish Release"
|
||
|
||
if [[ "$DRY_RUN" == "true" ]]; then
|
||
log_warn "[DRY RUN] Would create release: $NEW_VERSION"
|
||
else
|
||
log_step "Creating release $NEW_VERSION..."
|
||
RELEASE_RESULT=$(gl_run release +create \
|
||
--owner "$OWNER" --repo "$REPO" \
|
||
--tag "$NEW_VERSION" \
|
||
--name "Release $NEW_VERSION" \
|
||
--body "$RELEASE_BODY" 2>&1) || true
|
||
|
||
if echo "$RELEASE_RESULT" | jq -e '.ok == true' &>/dev/null; then
|
||
log_ok "Release $NEW_VERSION published!"
|
||
log_info "View: https://www.gitlink.org.cn/$OWNER/$REPO/releases"
|
||
else
|
||
log_warn "Release may have failed (tag might exist):"
|
||
echo "$RELEASE_RESULT" | jq -r '.error.message // "unknown"' 2>/dev/null || true
|
||
fi
|
||
fi
|
||
|
||
# ================================================================
|
||
# Phase 5: 发布社区周报到 Wiki
|
||
# ================================================================
|
||
log_title "Phase 5: Publish Weekly Report"
|
||
|
||
WIKI_TITLE="社区周报"
|
||
WIKI_BODY="# 社区周报 - ${OWNER}/${REPO}
|
||
|
||
**${PERIOD_START} ~ ${PERIOD_END}**
|
||
|
||
---
|
||
|
||
## 📊 数据总览
|
||
- 已合并 PR: **${PR_COUNT}** 个
|
||
- Issue 活动: **${ISSUE_COUNT}** 条
|
||
- 活跃贡献者: $(echo "$ALL_CONTRIBUTORS" | tr '\n' ' ')
|
||
|
||
## 🔀 合并的 PR
|
||
${PR_ITEMS}
|
||
|
||
## 📝 Issue 动态
|
||
${BUG_SECTION}
|
||
${FEAT_SECTION}
|
||
${DOC_SECTION}
|
||
${OTHER_SECTION}
|
||
|
||
---
|
||
|
||
*Auto-generated by gitlink-cli | ${PERIOD_START} ~ ${PERIOD_END} | Next report in ~${PERIOD_HOURS}h*"
|
||
|
||
if [[ "$DRY_RUN" == "true" ]]; then
|
||
log_warn "[DRY RUN] Would publish Wiki"
|
||
else
|
||
log_step "Publishing to Wiki..."
|
||
# 先尝试覆盖更新,页面不存在则新建
|
||
WIKI_RESULT=$(gl_run wiki +update --owner "$OWNER" --repo "$REPO" \
|
||
--title "$WIKI_TITLE" --cover "$WIKI_BODY" 2>&1) || true
|
||
if echo "$WIKI_RESULT" | jq -e '.ok == true' &>/dev/null; then
|
||
log_ok "Weekly report updated on Wiki"
|
||
else
|
||
# 页面可能还不存在,新建
|
||
log_info "Page not found, creating new..."
|
||
WIKI_RESULT=$(gl_run wiki +create --owner "$OWNER" --repo "$REPO" \
|
||
--title "$WIKI_TITLE" --content "$WIKI_BODY" 2>&1) || true
|
||
if echo "$WIKI_RESULT" | jq -e '.ok == true' &>/dev/null; then
|
||
log_ok "Weekly report created on Wiki"
|
||
else
|
||
log_warn "Wiki publish failed"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# ================================================================
|
||
log_title "Complete"
|
||
echo -e "${GREEN} Release: $NEW_VERSION${NC}"
|
||
echo -e "${GREEN} PRs merged: $PR_COUNT${NC}"
|
||
echo -e "${GREEN} Issues: $ISSUE_COUNT${NC}"
|
||
echo -e "${GREEN} Contributors: $(echo "$ALL_CONTRIBUTORS" | wc -w)${NC}"
|
||
echo -e "${GREEN} Wiki: ${WIKI_TITLE}${NC}"
|