gitlink-cli/workflows/01-community-ops.sh

328 lines
13 KiB
Bash
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 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}"