forked from Gitlink/gitlink-cli
266 lines
11 KiB
Bash
266 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Scenario 4: Multi-Repo Collaboration
|
|
# Flow: Cross-repo issue tracking → PR status dashboard → Coordinated release
|
|
#
|
|
# Commands/Skills chained:
|
|
# 1. repo +list -- list all repos in org
|
|
# 2. issue +list -- fetch issues from each repo
|
|
# 3. pr +list -- fetch PRs from each repo
|
|
# 4. pr +view -- get PR details for dashboard
|
|
# 5. release +list -- check release status across repos
|
|
# 6. release +create -- coordinated release
|
|
# 7. Generate HTML dashboard
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/lib/common.sh"
|
|
|
|
usage() {
|
|
echo "Usage: $0 --org ORG [--repos REPO1,REPO2,...] [--release TAG] [--dry-run]"
|
|
echo ""
|
|
echo " --org ORG Organization name"
|
|
echo " --repos REPO1,REPO2 Comma-separated repo list (default: all repos in org)"
|
|
echo " --release TAG Coordinated release tag to create"
|
|
echo " --output FILE Output HTML dashboard file (default: dashboard.html)"
|
|
echo " --dry-run Preview actions without executing"
|
|
exit 1
|
|
}
|
|
|
|
DRY_RUN=false
|
|
ORG=""
|
|
REPOS=""
|
|
RELEASE_TAG=""
|
|
OUTPUT_FILE="dashboard.html"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--org) ORG="$2"; shift 2 ;;
|
|
--repos) REPOS="$2"; shift 2 ;;
|
|
--release) RELEASE_TAG="$2"; shift 2 ;;
|
|
--output) OUTPUT_FILE="$2"; shift 2 ;;
|
|
--dry-run) DRY_RUN="true"; shift ;;
|
|
--help|-h) usage ;;
|
|
*) log_err "Unknown arg: $1"; usage ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$ORG" ]]; then
|
|
log_err "Missing required parameter: --org"
|
|
usage
|
|
fi
|
|
|
|
check_auth
|
|
|
|
# ── Step 1: List Repositories ────────────────────────────────────────
|
|
log_title "Multi-Repo Collaboration Dashboard"
|
|
|
|
log_step "Fetching repositories for org: $ORG..."
|
|
REPOS_JSON=$(gl_check repo +list --user "$ORG" --limit 100)
|
|
# Response may have .data.projects[] or .data[]
|
|
ALL_REPO_COUNT=$(echo "$REPOS_JSON" | jq '(.data.projects // .data | if type == "array" then . else [] end) | length')
|
|
log_ok "Found $ALL_REPO_COUNT repositories"
|
|
|
|
# Filter repos if --repos specified
|
|
REPO_LIST=()
|
|
if [[ -n "$REPOS" ]]; then
|
|
IFS=',' read -ra REPO_LIST <<< "$REPOS"
|
|
log_info "Filtering to specified repos: ${REPO_LIST[*]}"
|
|
else
|
|
REPOS_DATA_PATH='(.data.projects // .data | if type == "array" then . else [] end)'
|
|
for i in $(seq 0 $((ALL_REPO_COUNT - 1))); do
|
|
RNAME=$(echo "$REPOS_JSON" | jq -r "$REPOS_DATA_PATH[$i].name // $REPOS_DATA_PATH[$i].identifier // empty")
|
|
[[ -n "$RNAME" ]] && REPO_LIST+=("$RNAME")
|
|
done
|
|
fi
|
|
|
|
log_ok "Will process ${#REPO_LIST[@]} repositories"
|
|
|
|
# ── Step 2-3: Collect Issues and PRs from each repo ──────────────────
|
|
log_title "Collecting Data Across Repos"
|
|
|
|
# Data arrays for dashboard
|
|
DASHBOARD_ROWS=""
|
|
TOTAL_ISSUES=0
|
|
TOTAL_PRS=0
|
|
TOTAL_OPEN_ISSUES=0
|
|
TOTAL_OPEN_PRS=0
|
|
|
|
for repo in "${REPO_LIST[@]}"; do
|
|
divider
|
|
log_step "Processing $ORG/$repo..."
|
|
|
|
# Fetch open issues
|
|
ISSUES_JSON=$(gl_run issue +list --owner "$ORG" --repo "$repo" --state open --limit 50)
|
|
OPEN_ISSUES=$(echo "$ISSUES_JSON" | jq '.data.issues | length' 2>/dev/null || echo "0")
|
|
|
|
# Fetch closed issues (recent)
|
|
CLOSED_JSON=$(gl_run issue +list --owner "$ORG" --repo "$repo" --state closed --limit 50)
|
|
CLOSED_ISSUES=$(echo "$CLOSED_JSON" | jq '.data.issues | length' 2>/dev/null || echo "0")
|
|
|
|
# Fetch open PRs
|
|
PRS_JSON=$(gl_run pr +list --owner "$ORG" --repo "$repo" --state open --limit 50)
|
|
OPEN_PRS=$(echo "$PRS_JSON" | jq '(.data.issues // .data.pulls // .data | if type == "array" then . else [] end) | length' 2>/dev/null || echo "0")
|
|
|
|
# Fetch merged PRs (recent)
|
|
MERGED_JSON=$(gl_run pr +list --owner "$ORG" --repo "$repo" --state merged --limit 50)
|
|
MERGED_PRS=$(echo "$MERGED_JSON" | jq '(.data.issues // .data.pulls // .data | if type == "array" then . else [] end) | length' 2>/dev/null || echo "0")
|
|
|
|
# Fetch latest release
|
|
RELEASES_JSON=$(gl_run release +list --owner "$ORG" --repo "$repo" --limit 1)
|
|
LATEST_RELEASE=$(echo "$RELEASES_JSON" | jq -r '.data.releases[0].tag_name // .data.releases[0].name // "none"' 2>/dev/null)
|
|
|
|
log_ok "$repo: Issues(open:$OPEN_ISSUES closed:$CLOSED_ISSUES) PRs(open:$OPEN_PRS merged:$MERGED_PRS) Release:$LATEST_RELEASE"
|
|
|
|
# Get PR details for open PRs
|
|
PR_DETAILS=""
|
|
PR_DATA_PATH='(.data.issues // .data.pulls // .data | if type == "array" then . else [] end)'
|
|
if [[ "$OPEN_PRS" -gt 0 ]] && [[ "$OPEN_PRS" != "null" ]]; then
|
|
for pi in $(seq 0 $((OPEN_PRS > 5 ? 4 : OPEN_PRS - 1))); do
|
|
PR_ID=$(echo "$PRS_JSON" | jq -r "$PR_DATA_PATH[$pi].pull_request_number // $PR_DATA_PATH[$pi].number // $PR_DATA_PATH[$pi].id // empty")
|
|
PR_TITLE=$(echo "$PRS_JSON" | jq -r "$PR_DATA_PATH[$pi].subject // $PR_DATA_PATH[$pi].title // empty")
|
|
PR_AUTHOR=$(echo "$PRS_JSON" | jq -r "$PR_DATA_PATH[$pi].author_login // $PR_DATA_PATH[$pi].author.login // \"unknown\"")
|
|
PR_DETAILS+="<tr><td>#$PR_ID</td><td>$PR_TITLE</td><td>@$PR_AUTHOR</td><td>open</td></tr>"
|
|
done
|
|
fi
|
|
|
|
# Accumulate totals
|
|
TOTAL_ISSUES=$((TOTAL_ISSUES + OPEN_ISSUES + CLOSED_ISSUES))
|
|
TOTAL_OPEN_ISSUES=$((TOTAL_OPEN_ISSUES + OPEN_ISSUES))
|
|
TOTAL_PRS=$((TOTAL_PRS + OPEN_PRS + MERGED_PRS))
|
|
TOTAL_OPEN_PRS=$((TOTAL_OPEN_PRS + OPEN_PRS))
|
|
|
|
# Add to dashboard rows
|
|
STATUS_COLOR="green"
|
|
[[ "$OPEN_ISSUES" -gt 10 ]] && STATUS_COLOR="orange"
|
|
[[ "$OPEN_ISSUES" -gt 20 ]] && STATUS_COLOR="red"
|
|
|
|
DASHBOARD_ROWS+="<tr>
|
|
<td><a href=\"https://gitlink.org.cn/$ORG/$repo\">$repo</a></td>
|
|
<td>$OPEN_ISSUES</td>
|
|
<td>$CLOSED_ISSUES</td>
|
|
<td>$OPEN_PRS</td>
|
|
<td>$MERGED_PRS</td>
|
|
<td>$LATEST_RELEASE</td>
|
|
<td style=\"color:$STATUS_COLOR;font-weight:bold;\">$(
|
|
[[ "$OPEN_ISSUES" -le 5 ]] && echo "Healthy" || \
|
|
[[ "$OPEN_ISSUES" -le 15 ]] && echo "Moderate" || echo "Needs Attention"
|
|
)</td>
|
|
</tr>"
|
|
done
|
|
|
|
# ── Step 4: Generate HTML Dashboard ──────────────────────────────────
|
|
log_title "Generating Dashboard"
|
|
|
|
log_step "Creating HTML dashboard..."
|
|
|
|
cat > "$OUTPUT_FILE" << 'HTMLEOF'
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Multi-Repo Collaboration Dashboard</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f5; padding: 20px; }
|
|
.container { max-width: 1200px; margin: 0 auto; }
|
|
h1 { color: #333; margin-bottom: 20px; }
|
|
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 30px; }
|
|
.card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
.card h3 { color: #666; font-size: 14px; margin-bottom: 8px; }
|
|
.card .value { font-size: 32px; font-weight: bold; color: #333; }
|
|
.card.blue .value { color: #2196F3; }
|
|
.card.green .value { color: #4CAF50; }
|
|
.card.orange .value { color: #FF9800; }
|
|
.card.purple .value { color: #9C27B0; }
|
|
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
th { background: #2196F3; color: white; padding: 12px 16px; text-align: left; }
|
|
td { padding: 12px 16px; border-bottom: 1px solid #eee; }
|
|
tr:hover { background: #f9f9f9; }
|
|
a { color: #2196F3; text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
.timestamp { color: #999; font-size: 14px; margin-bottom: 20px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Multi-Repo Collaboration Dashboard</h1>
|
|
<p class="timestamp">Generated: TIMESTAMP_PLACEHOLDER | Organization: ORG_PLACEHOLDER</p>
|
|
<div class="summary">
|
|
<div class="card blue"><h3>Total Repos</h3><div class="value">REPOS_COUNT</div></div>
|
|
<div class="card orange"><h3>Open Issues</h3><div class="value">OPEN_ISSUES_COUNT</div></div>
|
|
<div class="card purple"><h3>Open PRs</h3><div class="value">OPEN_PRS_COUNT</div></div>
|
|
<div class="card green"><h3>Total Activity</h3><div class="value">TOTAL_ACTIVITY</div></div>
|
|
</div>
|
|
<table>
|
|
<thead><tr><th>Repository</th><th>Open Issues</th><th>Closed Issues</th><th>Open PRs</th><th>Merged PRs</th><th>Latest Release</th><th>Health</th></tr></thead>
|
|
<tbody>DASHBOARD_ROWS_PLACEHOLDER</tbody>
|
|
</table>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
HTMLEOF
|
|
|
|
# Replace placeholders using temp file approach for complex content
|
|
TEMP_HTML=$(mktemp)
|
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
while IFS= read -r line; do
|
|
line="${line//TIMESTAMP_PLACEHOLDER/$TIMESTAMP}"
|
|
line="${line//ORG_PLACEHOLDER/$ORG}"
|
|
line="${line//REPOS_COUNT/${#REPO_LIST[@]}}"
|
|
line="${line//OPEN_ISSUES_COUNT/$TOTAL_OPEN_ISSUES}"
|
|
line="${line//OPEN_PRS_COUNT/$TOTAL_OPEN_PRS}"
|
|
line="${line//TOTAL_ACTIVITY/$TOTAL_ISSUES}"
|
|
line="${line//DASHBOARD_ROWS_PLACEHOLDER/$DASHBOARD_ROWS}"
|
|
echo "$line"
|
|
done < "$OUTPUT_FILE" > "$TEMP_HTML"
|
|
|
|
mv "$TEMP_HTML" "$OUTPUT_FILE"
|
|
|
|
log_ok "Dashboard saved to: $OUTPUT_FILE"
|
|
|
|
# ── Step 5: Coordinated Release ──────────────────────────────────────
|
|
if [[ -n "$RELEASE_TAG" ]]; then
|
|
log_title "Coordinated Release: $RELEASE_TAG"
|
|
|
|
RELEASE_BODY="# Coordinated Release: $RELEASE_TAG
|
|
|
|
## Repos Included
|
|
"
|
|
|
|
for repo in "${REPO_LIST[@]}"; do
|
|
log_step "Creating release for $ORG/$repo..."
|
|
RELEASE_BODY+="- $ORG/$repo"$'\n'
|
|
|
|
RELEASE_RESULT=$(gl_run release +create --owner "$ORG" --repo "$repo" \
|
|
--tag "$RELEASE_TAG" --name "Release $RELEASE_TAG" \
|
|
--body "Coordinated release $RELEASE_TAG for $ORG/$repo" 2>&1) || true
|
|
|
|
if [[ "$(json_ok "$RELEASE_RESULT")" == "true" ]]; then
|
|
log_ok "Release $RELEASE_TAG created for $repo"
|
|
else
|
|
log_warn "Release creation failed for $repo (tag may already exist)"
|
|
fi
|
|
done
|
|
|
|
RELEASE_BODY+=$'\n'"---"$'\n'"*Coordinated release by gitlink-cli multi-repo-collab workflow*"
|
|
fi
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
log_title "Multi-Repo Dashboard Complete"
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
echo -e "${GREEN}Summary:${NC}"
|
|
echo " Repos processed: ${#REPO_LIST[@]}"
|
|
echo " Total issues: $TOTAL_ISSUES (open: $TOTAL_OPEN_ISSUES)"
|
|
echo " Total PRs: $TOTAL_PRS (open: $TOTAL_OPEN_PRS)"
|
|
echo " Dashboard: $OUTPUT_FILE"
|
|
[[ -n "$RELEASE_TAG" ]] && echo " Coordinated release: $RELEASE_TAG"
|
|
echo ""
|
|
echo -e "${CYAN}Open dashboard:${NC}"
|
|
echo " xdg-open $OUTPUT_FILE # Linux"
|
|
echo " open $OUTPUT_FILE # macOS"
|
|
echo ""
|