gitlink-cli/workflows/04-multi-repo-collab.sh

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 ""