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

326 lines
13 KiB
PowerShell

# ----------------------------------------------------------------
# Scenario 1: Community Operations Automation
# Flow: Issue auto-classify -> Assign responsible -> Weekly report -> Release notes
# ----------------------------------------------------------------
#Requires -Version 5.1
param(
[string]$Owner = "",
[string]$Repo = "",
[int]$WeeksAgo = 0,
[switch]$DryRun,
[switch]$Help
)
$ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force
if ($Help) {
Write-Host "Usage: powershell 01-community-ops.ps1 -Owner OWNER -Repo REPO [-WeeksAgo N] [-DryRun]"
exit 0
}
Check-Auth
$r = Resolve-OwnerRepo $Owner $Repo
$Owner = $r.Owner; $Repo = $r.Repo
$BugKw = @('bug','error','crash','fault','fix')
$FeatureKw = @('feature','enhancement','add','support','request')
$QuestionKw = @('how','question','help')
$DocsKw = @('doc','readme','guide','tutorial','example')
# ----------------------------------------------------------------
Log-Title "Phase 1: Issue Auto-Classification"
# ----------------------------------------------------------------
Log-Step "Fetching open issues..."
$issuesJson = Invoke-GLCheck "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100"
if (-not $issuesJson) { Log-Err "Failed to fetch issues"; exit 1 }
$issues = @($issuesJson.data.issues)
$issueCount = $issues.Count
Log-Ok "Found $issueCount open issues"
$BugIds = @(); $FeatureIds = @(); $QuestionIds = @(); $DocsIds = @()
if ($issueCount -gt 0) {
Log-Step "Classifying issues by content..."
foreach ($issue in $issues) {
$id = $issue.id
$title = if ($issue.subject) { $issue.subject } elseif ($issue.title) { $issue.title } else { "" }
$desc = if ($issue.description) { $issue.description } else { "" }
$combined = "$title $desc".ToLower()
$classified = $false
foreach ($kw in $BugKw) {
if ($combined -match [regex]::Escape($kw)) { $BugIds += $id; Log-Info " #$id -> BUG: $title"; $classified = $true; break }
}
if (-not $classified) {
foreach ($kw in $FeatureKw) {
if ($combined -match [regex]::Escape($kw)) { $FeatureIds += $id; Log-Info " #$id -> FEATURE: $title"; $classified = $true; break }
}
}
if (-not $classified) {
foreach ($kw in $QuestionKw) {
if ($combined -match [regex]::Escape($kw)) { $QuestionIds += $id; Log-Info " #$id -> QUESTION: $title"; $classified = $true; break }
}
}
if (-not $classified) {
foreach ($kw in $DocsKw) {
if ($combined -match [regex]::Escape($kw)) { $DocsIds += $id; Log-Info " #$id -> DOCS: $title"; $classified = $true; break }
}
}
if (-not $classified) { Log-Info " #$id -> UNCATEGORIZED: $title" }
}
Divider
Log-Info "Classification summary:"
Log-Info " Bugs: $($BugIds.Count)"
Log-Info " Features: $($FeatureIds.Count)"
Log-Info " Questions: $($QuestionIds.Count)"
Log-Info " Docs: $($DocsIds.Count)"
$labelGroups = @(
@{ Ids = $BugIds; Label = "bug" },
@{ Ids = $FeatureIds; Label = "feature" },
@{ Ids = $QuestionIds; Label = "question" },
@{ Ids = $DocsIds; Label = "documentation" }
)
foreach ($g in $labelGroups) {
if ($g.Ids.Count -gt 0) {
Log-Step "Labeling $($g.Label) issues..."
foreach ($id in $g.Ids) {
Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Repo, "--number", $id, "--labels", $g.Label | Out-Null
}
Log-Ok "Labeled $($g.Ids.Count) $($g.Label) issues"
}
}
}
# ----------------------------------------------------------------
Log-Title "Phase 2: Assign Responsible Persons"
# ----------------------------------------------------------------
Log-Step "Fetching repo members..."
$membersJson = Invoke-GL "repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "50"
$members = @()
if ($membersJson) {
$md = $membersJson.data
if ($md.members) { $members = @($md.members) }
elseif ($md -is [array]) { $members = $md }
}
$memberLogins = @()
foreach ($m in $members) {
$login = if ($m.login) { $m.login } elseif ($m.username) { $m.username } else { $null }
if ($login) { $memberLogins += $login }
}
if ($memberLogins.Count -gt 0) {
$assignIds = @($BugIds + $FeatureIds)
if ($assignIds.Count -gt 0) {
Log-Step "Assigning issues to members (round-robin)..."
$idx = 0
foreach ($id in $assignIds) {
$assignee = $memberLogins[$idx % $memberLogins.Count]
$bodyJson = "{`"assigned_to_id`": `"$assignee`"}"
Invoke-GL "api", "PATCH", "/v1/$Owner/$Repo/issues/$id", "--body", $bodyJson | Out-Null
Log-Info " Assigned #$id -> @$assignee"
$idx++
}
Log-Ok "Assignment complete"
}
} else {
Log-Warn "No repo members found, skipping assignment"
}
# ----------------------------------------------------------------
Log-Title "Phase 3: Generate Community Weekly Report"
# ----------------------------------------------------------------
$weekStart = (Get-Date).AddDays(-$WeeksAgo * 7).ToString("yyyy-MM-dd")
$weekEnd = Get-DateToday
Log-Step "Collecting weekly data (week of $weekStart)..."
$closedJson = Invoke-GL "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100"
$closedCount = if ($closedJson) { @($closedJson.data.issues).Count } else { 0 }
$mergedJson = Invoke-GL "pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100"
$mergedData = @()
$mergedCount = 0
if ($mergedJson) {
$d = $mergedJson.data
if ($d.issues) { $mergedData = @($d.issues) }
elseif ($d.pulls) { $mergedData = @($d.pulls) }
elseif ($d -is [array]) { $mergedData = $d }
$mergedCount = $mergedData.Count
}
$newIssuesCount = $issueCount
$totalClassified = $BugIds.Count + $FeatureIds.Count + $QuestionIds.Count + $DocsIds.Count
$reportTitle = "Community Weekly Report: $weekStart ~ $weekEnd"
$reportBody = "# $reportTitle" + "`n`n"
$reportBody += "## 概览" + "`n"
$reportBody += "- 仓库: **$Owner/$Repo**" + "`n"
$reportBody += "- 当前开放 Issue: **$newIssuesCount**" + "`n"
$reportBody += "- 已关闭 Issue: **$closedCount**" + "`n"
$reportBody += "- 已合并 PR: **$mergedCount**" + "`n`n"
# 分类汇总
$reportBody += "## Issue 分类汇总" + "`n"
$reportBody += "| 类型 | 数量 |" + "`n"
$reportBody += "|------|------|" + "`n"
$reportBody += "| Bug | $($BugIds.Count) |" + "`n"
$reportBody += "| Feature | $($FeatureIds.Count) |" + "`n"
$reportBody += "| Question | $($QuestionIds.Count) |" + "`n"
$reportBody += "| Docs | $($DocsIds.Count) |" + "`n`n"
# 开放 Issue 清单(含分类标记)
$reportBody += "## 当前开放 Issue 清单" + "`n"
$reportBody += "| # | 标题 | 分类 | 创建时间 |" + "`n"
$reportBody += "|---|------|------|----------|" + "`n"
foreach ($issue in $issues) {
$id = $issue.id
$title = if ($issue.subject) { $issue.subject } elseif ($issue.title) { $issue.title } else { "-" }
$title = ($title -replace '\|', '\\|')
$cat = if ($BugIds -contains $id) { "Bug" }
elseif ($FeatureIds -contains $id) { "Feature" }
elseif ($QuestionIds -contains $id) { "Question" }
elseif ($DocsIds -contains $id) { "Docs" }
else { "-" }
$created = if ($issue.created_at) { $issue.created_at } else { "-" }
$reportBody += "| #$id | $title | $cat | $created |" + "`n"
}
$reportBody += "`n"
# 已关闭 Issue 清单
$reportBody += "## 近期已关闭 Issue" + "`n"
if ($closedCount -gt 0) {
$closedIssues = @($closedJson.data.issues)
$closedLimit = [Math]::Min($closedCount, 15)
$reportBody += "| # | 标题 |" + "`n"
$reportBody += "|---|------|" + "`n"
for ($i = 0; $i -lt $closedLimit; $i++) {
$it = $closedIssues[$i]
$cid = if ($it.id) { $it.id } elseif ($it.number) { $it.number } else { "-" }
$ctitle = if ($it.subject) { $it.subject } elseif ($it.title) { $it.title } else { "-" }
$ctitle = ($ctitle -replace '\|', '\\|')
$reportBody += "| #$cid | $ctitle |" + "`n"
}
if ($closedCount -gt $closedLimit) {
$reportBody += "| ... | 还有 $($closedCount - $closedLimit) 条 |`n"
}
} else {
$reportBody += "_本周无关闭记录_" + "`n"
}
$reportBody += "`n"
# 已合并 PR 清单(含作者)
$reportBody += "## 近期已合并 PR" + "`n"
if ($mergedCount -gt 0) {
$prLimit = [Math]::Min($mergedCount, 15)
$reportBody += "| # | 标题 | 作者 |" + "`n"
$reportBody += "|---|------|------|" + "`n"
for ($i = 0; $i -lt $prLimit; $i++) {
$pr = $mergedData[$i]
$prId = if ($pr.id) { $pr.id } elseif ($pr.number) { $pr.number } else { "-" }
$ptitle = if ($pr.subject) { $pr.subject } elseif ($pr.title) { $pr.title } else { "-" }
$ptitle = ($ptitle -replace '\|', '\\|')
$pauthor = if ($pr.author -and $pr.author.login) { $pr.author.login } elseif ($pr.user -and $pr.user.login) { $pr.user.login } else { "-" }
$reportBody += "| #$prId | $ptitle | @$pauthor |" + "`n"
}
if ($mergedCount -gt $prLimit) {
$reportBody += "| ... | 还有 $($mergedCount - $prLimit) 条合并 PR |`n"
}
} else {
$reportBody += "_本周无合并记录_" + "`n"
}
$reportBody += "`n"
# 贡献者排行(按合并 PR 数)
$reportBody += "## 贡献者排行(按合并 PR 数)" + "`n"
if ($mergedCount -gt 0) {
$contributorMap = @{}
foreach ($pr in $mergedData) {
$login = if ($pr.author -and $pr.author.login) { $pr.author.login } elseif ($pr.user -and $pr.user.login) { $pr.user.login } else { $null }
if ($login) {
if ($contributorMap.ContainsKey($login)) { $contributorMap[$login]++ }
else { $contributorMap[$login] = 1 }
}
}
$reportBody += "| 排名 | 贡献者 | 合并 PR 数 |" + "`n"
$reportBody += "|------|--------|------------|" + "`n"
$rank = 1
foreach ($kv in ($contributorMap.GetEnumerator() | Sort-Object Value -Descending)) {
$reportBody += "| $rank | @$($kv.Name) | $($kv.Value) |" + "`n"
$rank++
}
} else {
$reportBody += "_本周无合并记录_" + "`n"
}
$reportBody += "`n"
$reportBody += "## 本周自动化执行" + "`n"
$reportBody += "- 自动分类并打标 Issue: **$totalClassified** 条" + "`n"
$reportBody += "- 已为 Bug/Feature 类 Issue 指派负责人" + "`n`n"
$reportBody += "---" + "`n"
$reportBody += "*Auto-generated by gitlink-cli community-ops workflow*"
Log-Ok "Weekly report generated"
Write-Host ""
Write-Host $reportBody
Log-Step "Publishing weekly report to Wiki..."
$wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $reportTitle, "--content", $reportBody
if ($wikiResult -and $wikiResult.ok) { Log-Ok "Weekly report published to Wiki" } else { Log-Warn "Wiki publish may have failed" }
# ----------------------------------------------------------------
Log-Title "Phase 4: Auto-Publish Release Notes"
# ----------------------------------------------------------------
Log-Step "Collecting recent changes for release notes..."
$tagName = "weekly-$(Get-Date -Format 'yyyyMMdd')"
$releaseName = "Weekly Release $(Get-Date -Format 'yyyy-MM-dd')"
$releaseBody = "# Release Notes - $(Get-Date -Format 'yyyy-MM-dd')" + "`n`n"
$releaseBody += "## Merged PRs ($mergedCount)"
if ($mergedCount -gt 0) {
$limit = [Math]::Min($mergedCount, 10)
for ($i = 0; $i -lt $limit; $i++) {
$prTitle = if ($mergedData[$i].subject) { $mergedData[$i].subject } elseif ($mergedData[$i].title) { $mergedData[$i].title } else { "" }
$prNum = if ($mergedData[$i].id) { $mergedData[$i].id } elseif ($mergedData[$i].number) { $mergedData[$i].number } else { "" }
$releaseBody += "`n- #$prNum $prTitle"
}
}
$releaseBody += "`n`n## Closed Issues ($closedCount)"
if ($closedCount -gt 0) {
$closedIssues = @($closedJson.data.issues)
$limit = [Math]::Min($closedCount, 10)
for ($i = 0; $i -lt $limit; $i++) {
$issueTitle = if ($closedIssues[$i].subject) { $closedIssues[$i].subject } elseif ($closedIssues[$i].title) { $closedIssues[$i].title } else { "" }
$issueNum = if ($closedIssues[$i].number) { $closedIssues[$i].number } elseif ($closedIssues[$i].id) { $closedIssues[$i].id } else { "" }
if ($issueTitle) { $releaseBody += "`n- #$issueNum $issueTitle" }
}
}
$releaseBody += "`n`n---`n*Auto-generated by gitlink-cli community-ops workflow*"
Log-Step "Creating release: $tagName..."
$releaseResult = Invoke-GL "release", "+create", "--owner", $Owner, "--repo", $Repo, "--tag", $tagName, "--name", $releaseName, "--body", $releaseBody
if ($releaseResult -and $releaseResult.ok) { Log-Ok "Release $tagName created successfully" } else { Log-Warn "Release creation may have failed (tag might already exist)" }
# ----------------------------------------------------------------
Log-Title "Community Operations Complete"
# ----------------------------------------------------------------
Write-Host " Issues classified: $totalClassified" -ForegroundColor Green
Write-Host " Closed this week: $closedCount" -ForegroundColor Green
Write-Host " Merged PRs: $mergedCount" -ForegroundColor Green
Write-Host " Weekly report: Published to Wiki" -ForegroundColor Green
Write-Host " Release notes: $tagName" -ForegroundColor Green