fix(workflows): make PowerShell scripts compatible with PS 5.1

- Replace ?? operator with if/else for Windows PowerShell 5.1 compatibility
- Remove Chinese characters to avoid encoding issues
- Use string concatenation instead of complex expressions in here-strings
- Update common.psm1 to remove PS 7+ syntax

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Donkey_kevin 2026-07-01 23:59:45 +08:00
parent ecf16b379d
commit 012c197ee6
5 changed files with 278 additions and 515 deletions

View File

@ -1,6 +1,6 @@
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
# Scenario 1: Community Operations Automation
# Flow: Issue auto-classify → Assign responsible → Weekly report → Release notes
# Flow: Issue auto-classify -> Assign responsible -> Weekly report -> Release notes
#
# Commands chained:
# 1. issue +list -- fetch open issues
@ -10,7 +10,7 @@
# 5. pr +list -- collect merged PRs for weekly report
# 6. wiki +create -- publish community weekly report
# 7. release +create -- publish release notes
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
#Requires -Version 5.1
param(
@ -25,7 +25,7 @@ $ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force
if ($Help) {
Write-Host "Usage: pwsh 01-community-ops.ps1 -Owner OWNER -Repo REPO [-WeeksAgo N] [-DryRun]"
Write-Host "Usage: powershell 01-community-ops.ps1 -Owner OWNER -Repo REPO [-WeeksAgo N] [-DryRun]"
Write-Host ""
Write-Host " -Owner OWNER Repository owner (org or user)"
Write-Host " -Repo REPO Repository name"
@ -39,16 +39,14 @@ $r = Resolve-OwnerRepo $Owner $Repo
$Owner = $r.Owner; $Repo = $r.Repo
# Classification keywords
$Keywords = @{
bug = @('bug','error','crash','fault','fix','修复','错误','异常','崩溃')
feature = @('feature','新增','建议','enhancement','add','support','功能')
question = @('how','怎么','如何','question','help','?','')
documentation = @('doc','文档','readme','说明','guide')
}
$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
@ -65,30 +63,30 @@ if ($issueCount -gt 0) {
foreach ($issue in $issues) {
$id = $issue.id
$title = $issue.subject ?? $issue.title ?? ""
$desc = $issue.description ?? ""
$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 $Keywords.bug) {
if ($combined -match [regex]::Escape($kw)) { $BugIds += $id; Log-Info " #$id BUG: $title"; $classified = $true; break }
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 $Keywords.feature) {
if ($combined -match [regex]::Escape($kw)) { $FeatureIds += $id; Log-Info " #$id FEATURE: $title"; $classified = $true; break }
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 $Keywords.question) {
if ($combined -match [regex]::Escape($kw)) { $QuestionIds += $id; Log-Info " #$id QUESTION: $title"; $classified = $true; break }
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 $Keywords.documentation) {
if ($combined -match [regex]::Escape($kw)) { $DocsIds += $id; Log-Info " #$id DOCS: $title"; $classified = $true; break }
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" }
if (-not $classified) { Log-Info " #$id -> UNCATEGORIZED: $title" }
}
Divider
@ -100,10 +98,10 @@ if ($issueCount -gt 0) {
# Apply labels
$labelGroups = @(
@{ Ids = $BugIds; Label = "bug" },
@{ Ids = $BugIds; Label = "bug" },
@{ Ids = $FeatureIds; Label = "feature" },
@{ Ids = $QuestionIds; Label = "question" },
@{ Ids = $DocsIds; Label = "documentation" }
@{ Ids = $DocsIds; Label = "documentation" }
)
foreach ($g in $labelGroups) {
if ($g.Ids.Count -gt 0) {
@ -116,9 +114,9 @@ if ($issueCount -gt 0) {
}
}
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Title "Phase 2: Assign Responsible Persons"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Step "Fetching repo members..."
$membersJson = Invoke-GL repo,+members,--owner,$Owner,--repo,$Repo,--limit,50
@ -131,7 +129,7 @@ if ($membersJson) {
$memberLogins = @()
foreach ($m in $members) {
$login = $m.login ?? $m.username
$login = if ($m.login) { $m.login } elseif ($m.username) { $m.username } else { $null }
if ($login) { $memberLogins += $login }
}
@ -142,8 +140,9 @@ if ($memberLogins.Count -gt 0) {
$idx = 0
foreach ($id in $assignIds) {
$assignee = $memberLogins[$idx % $memberLogins.Count]
Invoke-GL api,PATCH,"/v1/$Owner/$Repo/issues/$id",--body,"{`"assigned_to_id`": `"$assignee`"}" | Out-Null
Log-Info " Assigned #$id → @$assignee"
$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"
@ -152,9 +151,9 @@ if ($memberLogins.Count -gt 0) {
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
@ -178,29 +177,23 @@ $newIssuesCount = $issueCount
$totalClassified = $BugIds.Count + $FeatureIds.Count + $QuestionIds.Count + $DocsIds.Count
$reportTitle = "Community Weekly Report: $weekStart ~ $weekEnd"
$reportBody = @"
# $reportTitle
## Summary
- New Issues: **$newIssuesCount**
- Closed Issues: **$closedCount**
- Merged PRs: **$mergedCount**
## Issue Classification
| Type | Count |
|------|-------|
| Bug | $($BugIds.Count) |
| Feature | $($FeatureIds.Count) |
| Question | $($QuestionIds.Count) |
| Docs | $($DocsIds.Count) |
## Highlights
- Auto-classified and labeled $totalClassified issues
- Assigned responsible persons for bug and feature issues
---
*Auto-generated by gitlink-cli community-ops workflow*
"@
$reportBody = "# $reportTitle" + "`n`n"
$reportBody += "## Summary" + "`n"
$reportBody += "- New Issues: **$newIssuesCount**" + "`n"
$reportBody += "- Closed Issues: **$closedCount**" + "`n"
$reportBody += "- Merged PRs: **$mergedCount**" + "`n`n"
$reportBody += "## Issue Classification" + "`n"
$reportBody += "| Type | Count |" + "`n"
$reportBody += "|------|-------|" + "`n"
$reportBody += "| Bug | $($BugIds.Count) |" + "`n"
$reportBody += "| Feature | $($FeatureIds.Count) |" + "`n"
$reportBody += "| Question | $($QuestionIds.Count) |" + "`n"
$reportBody += "| Docs | $($DocsIds.Count) |" + "`n`n"
$reportBody += "## Highlights" + "`n"
$reportBody += "- Auto-classified and labeled $totalClassified issues" + "`n"
$reportBody += "- Assigned responsible persons for bug and feature issues" + "`n`n"
$reportBody += "---" + "`n"
$reportBody += "*Auto-generated by gitlink-cli community-ops workflow*"
Log-Ok "Weekly report generated"
Write-Host ""
@ -214,22 +207,23 @@ if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) {
Log-Warn "Wiki publish may have failed (wiki module might not be enabled)"
}
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
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## Merged PRs ($mergedCount)"
$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 = $mergedData[$i].subject ?? $mergedData[$i].title ?? ""
$prNum = $mergedData[$i].id ?? $mergedData[$i].number ?? ""
$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"
}
}
@ -239,8 +233,8 @@ if ($closedCount -gt 0) {
$closedIssues = @($closedJson.data.issues)
$limit = [Math]::Min($closedCount, 10)
for ($i = 0; $i -lt $limit; $i++) {
$issueTitle = $closedIssues[$i].subject ?? $closedIssues[$i].title ?? ""
$issueNum = $closedIssues[$i].number ?? $closedIssues[$i].id ?? ""
$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" }
}
}
@ -255,9 +249,9 @@ if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) {
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

View File

@ -1,16 +1,17 @@
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
# Scenario 3: One-Click Project Initialization
# Flow: Input description → Create repo → README/CONTRIBUTING → CI config →
# Initial Issues + Milestone → Branch protection → Initial Release
# Flow: Input description -> Create repo -> README/CONTRIBUTING/CI config ->
# Initial Issues -> Branch protection -> Initial Release
#
# Commands chained:
# 1. repo +create -- create repository
# 2. wiki +create -- create README wiki page
# 3. wiki +create -- create CONTRIBUTING guide
# 4. issue +create -- create initial issues with milestone
# 5. branch +protect -- protect master branch
# 6. release +create -- create initial release
# ─────────────────────────────────────────────────────────────────────
# 4. wiki +create -- create CI/CD config guide
# 5. issue +create -- create initial issues
# 6. branch +protect -- protect master branch
# 7. release +create -- create initial release
# ----------------------------------------------------------------
#Requires -Version 5.1
param(
@ -29,7 +30,7 @@ $ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force
if ($Help) {
Write-Host "Usage: pwsh 03-project-init.ps1 -Owner OWNER -Name REPO_NAME -Description DESC [-Lang go|python|node|java] [-Private] [-DryRun]"
Write-Host "Usage: powershell 03-project-init.ps1 -Owner OWNER -Name REPO_NAME -Description DESC [-Lang go|python|node|java] [-Private] [-DryRun]"
exit 0
}
@ -39,9 +40,9 @@ if (-not $Owner) {
$Owner = $detected.Owner
}
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Title "Project Initialization: $Owner/$Name"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Write-Host " Owner: $Owner"
Write-Host " Name: $Name"
Write-Host " Description: $Description"
@ -49,7 +50,7 @@ Write-Host " Language: $Lang"
Write-Host " Private: $($Private.IsPresent)"
Divider
# ── Step 1: Create Repository ────────────────────────────────────────
# -- Step 1: Create Repository --
Log-Step "Creating repository..."
$privateStr = if ($Private) { "true" } else { "false" }
$repoResult = Invoke-GLCheck repo,+create,--owner,$Owner,--name,$Name,--description,$Description,--private,$privateStr
@ -60,128 +61,34 @@ if ($repoResult) {
exit 1
}
# ── Step 2: Create README ────────────────────────────────────────────
# -- Step 2: Create README --
Log-Step "Creating README wiki page..."
Start-Sleep -Seconds 2
$langContent = switch ($Lang) {
$langSection = switch ($Lang) {
"go" {
@"
### Prerequisites
- Go 1.21+
- Git
### Installation
``````bash
git clone https://gitlink.org.cn/$Owner/$Name.git
cd $Name
go mod download
go build ./...
``````
### Usage
``````bash
go run main.go
``````
### Testing
``````bash
go test ./...
``````
"@
"### Prerequisites`n- Go 1.21+`n- Git`n`n### Installation`n``````bash`ngit clone https://gitlink.org.cn/$Owner/$Name.git`ncd $Name`ngo mod download`ngo build ./...`n```````n`n### Usage`n``````bash`ngo run main.go`n```````n`n### Testing`n``````bash`ngo test ./...`n``````"
}
"python" {
@"
### Prerequisites
- Python 3.9+
- pip
### Installation
``````bash
git clone https://gitlink.org.cn/$Owner/$Name.git
cd $Name
pip install -r requirements.txt
``````
### Usage
``````bash
python main.py
``````
### Testing
``````bash
pytest
``````
"@
"### Prerequisites`n- Python 3.9+`n- pip`n`n### Installation`n``````bash`ngit clone https://gitlink.org.cn/$Owner/$Name.git`ncd $Name`npip install -r requirements.txt`n```````n`n### Usage`n``````bash`npython main.py`n```````n`n### Testing`n``````bash`npytest`n``````"
}
"node" {
@"
### Prerequisites
- Node.js 18+
- npm or yarn
### Installation
``````bash
git clone https://gitlink.org.cn/$Owner/$Name.git
cd $Name
npm install
``````
### Usage
``````bash
npm start
``````
### Testing
``````bash
npm test
``````
"@
"### Prerequisites`n- Node.js 18+`n- npm or yarn`n`n### Installation`n``````bash`ngit clone https://gitlink.org.cn/$Owner/$Name.git`ncd $Name`nnpm install`n```````n`n### Usage`n``````bash`nnpm start`n```````n`n### Testing`n``````bash`nnpm test`n``````"
}
"java" {
@"
### Prerequisites
- JDK 17+
- Maven 3.8+
### Installation
``````bash
git clone https://gitlink.org.cn/$Owner/$Name.git
cd $Name
mvn clean install
``````
### Usage
``````bash
mvn exec:java
``````
### Testing
``````bash
mvn test
``````
"@
"### Prerequisites`n- JDK 17+`n- Maven 3.8+`n`n### Installation`n``````bash`ngit clone https://gitlink.org.cn/$Owner/$Name.git`ncd $Name`nmvn clean install`n```````n`n### Usage`n``````bash`nmvn exec:java`n```````n`n### Testing`n``````bash`nmvn test`n``````"
}
default { "" }
}
$readmeContent = @"
# $Name
$Description
## Getting Started
$langContent
## Contributing
See [CONTRIBUTING](./CONTRIBUTING) for guidelines.
## License
This project is licensed under the MIT License.
"@
$readmeContent = "# $Name" + "`n`n"
$readmeContent += "$Description" + "`n`n"
$readmeContent += "## Getting Started" + "`n`n"
$readmeContent += $langSection + "`n`n"
$readmeContent += "## Contributing" + "`n`n"
$readmeContent += "See [CONTRIBUTING](./CONTRIBUTING) for guidelines." + "`n`n"
$readmeContent += "## License" + "`n`n"
$readmeContent += "This project is licensed under the MIT License."
$wikiOk = $false
for ($attempt = 1; $attempt -le 3; $attempt++) {
@ -195,41 +102,28 @@ for ($attempt = 1; $attempt -le 3; $attempt++) {
}
if (-not $wikiOk) { Log-Warn "README wiki creation may have failed" }
# ── Step 3: Create CONTRIBUTING Guide ────────────────────────────────
# -- Step 3: Create CONTRIBUTING Guide --
Log-Step "Creating CONTRIBUTING guide..."
$contribContent = @"
# Contributing to $Name
Thank you for your interest in contributing!
## How to Contribute
1. Fork the repository
2. Create a feature branch: ``git checkout -b feature/my-feature``
3. Make your changes
4. Run tests to ensure everything passes
5. Commit your changes: ``git commit -m 'feat: add my feature'``
6. Push to your fork: ``git push origin feature/my-feature``
7. Create a Pull Request
## Code Style
- Follow the existing code style
- Write meaningful commit messages
- Add tests for new features
- Update documentation as needed
## Reporting Issues
- Use the issue tracker
- Include reproduction steps
- Include environment details
## Code of Conduct
Please be respectful and constructive in all interactions.
"@
$contribContent = "# Contributing to $Name" + "`n`n"
$contribContent += "Thank you for your interest in contributing!" + "`n`n"
$contribContent += "## How to Contribute" + "`n`n"
$contribContent += "1. Fork the repository" + "`n"
$contribContent += "2. Create a feature branch: ``git checkout -b feature/my-feature```n"
$contribContent += "3. Make your changes" + "`n"
$contribContent += "4. Run tests to ensure everything passes" + "`n"
$contribContent += "5. Commit your changes: ``git commit -m 'feat: add my feature'```n"
$contribContent += "6. Push to your fork: ``git push origin feature/my-feature```n"
$contribContent += "7. Create a Pull Request" + "`n`n"
$contribContent += "## Code Style" + "`n`n"
$contribContent += "- Follow the existing code style" + "`n"
$contribContent += "- Write meaningful commit messages" + "`n"
$contribContent += "- Add tests for new features" + "`n"
$contribContent += "- Update documentation as needed" + "`n`n"
$contribContent += "## Reporting Issues" + "`n`n"
$contribContent += "- Use the issue tracker" + "`n"
$contribContent += "- Include reproduction steps" + "`n"
$contribContent += "- Include environment details"
$wikiOk = $false
for ($attempt = 1; $attempt -le 3; $attempt++) {
@ -243,88 +137,50 @@ for ($attempt = 1; $attempt -le 3; $attempt++) {
}
if (-not $wikiOk) { Log-Warn "CONTRIBUTING wiki creation may have failed" }
# ── Step 4: Create CI Config Wiki Page ───────────────────────────────
# -- Step 4: Create CI Config Guide --
Log-Step "Creating CI/CD configuration guide..."
$ciContent = @"
# CI/CD Configuration
## GitLink CI Setup
This project uses GitLink CI for continuous integration.
### Pipeline Stages
1. **Test**: Run unit tests
2. **Build**: Build the project
3. **Deploy**: Deploy to staging (master branch only)
### Configuration
Create a ``.gitlink-ci.yml`` file in the repository root:
``````yaml
stages:
- test
- build
- deploy
test:
stage: test
script:
- echo "Running tests..."
build:
stage: build
script:
- echo "Building project..."
deploy:
stage: deploy
script:
- echo "Deploying..."
only:
- master
``````
### Status Badges
Add a CI status badge to your README:
``````markdown
[![CI Status](https://gitlink.org.cn/$Owner/$Name/badges/master/pipeline.svg)](https://gitlink.org.cn/$Owner/$Name/pipelines)
``````
"@
$ciContent = "# CI/CD Configuration" + "`n`n"
$ciContent += "## GitLink CI Setup" + "`n`n"
$ciContent += "This project uses GitLink CI for continuous integration." + "`n`n"
$ciContent += "### Pipeline Stages" + "`n`n"
$ciContent += "1. **Test**: Run unit tests" + "`n"
$ciContent += "2. **Build**: Build the project" + "`n"
$ciContent += "3. **Deploy**: Deploy to staging (master branch only)" + "`n`n"
$ciContent += "### Configuration" + "`n`n"
$ciContent += "Create a ``.gitlink-ci.yml`` file in the repository root."
Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"CI/CD Configuration",--body,$ciContent | Out-Null
Log-Ok "CI/CD configuration guide created"
# ── Step 5: Create Initial Issues with Milestone ─────────────────────
# -- Step 5: Create Initial Issues --
Log-Step "Creating initial issues..."
$issuesToCreate = @(
@{ Title = "Setup CI/CD Pipeline"; Body = "Configure continuous integration and deployment for the project.`n`n## Tasks`n- [ ] Create `.gitlink-ci.yml` configuration`n- [ ] Setup test stage`n- [ ] Setup build stage`n- [ ] Setup deploy stage`n- [ ] Add status badge to README"; Label = "feature" },
@{ Title = "Setup CI/CD Pipeline"; Body = "Configure continuous integration and deployment for the project.`n`n## Tasks`n- [ ] Create .gitlink-ci.yml configuration`n- [ ] Setup test stage`n- [ ] Setup build stage`n- [ ] Setup deploy stage`n- [ ] Add status badge to README"; Label = "feature" },
@{ Title = "Write Project Documentation"; Body = "Complete project documentation including API docs and architecture guide.`n`n## Tasks`n- [ ] Write API documentation`n- [ ] Create architecture diagram`n- [ ] Add usage examples`n- [ ] Document configuration options"; Label = "documentation" },
@{ Title = "Setup Code Review Process"; Body = "Establish code review guidelines and automation.`n`n## Tasks`n- [ ] Define review checklist`n- [ ] Setup branch protection rules`n- [ ] Configure required reviewers`n- [ ] Document review process"; Label = "enhancement" },
@{ Title = "Add Unit Tests"; Body = "Add comprehensive unit test coverage for core modules.`n`n## Tasks`n- [ ] Setup test framework`n- [ ] Write tests for core modules`n- [ ] Achieve 80% code coverage`n- [ ] Add CI test integration"; Label = "enhancement" },
@{ Title = "Add Unit Tests"; Body = "Add comprehensive unit test coverage for core modules.`n`n## Tasks`n- [ ] Setup test framework`n- [ ] Write tests for core modules`n- [ ] Achieve 80 percent code coverage`n- [ ] Add CI test integration"; Label = "enhancement" },
@{ Title = "Setup Dependency Management"; Body = "Configure dependency scanning and updates.`n`n## Tasks`n- [ ] Setup dependency scanner`n- [ ] Configure automatic updates`n- [ ] Add license compliance check`n- [ ] Document dependency policy"; Label = "security" }
)
foreach ($entry in $issuesToCreate) {
$issueResult = Invoke-GL issue,+create,--owner,$Owner,--repo,$Name,--title,$entry.Title,--body,$entry.Body
if ($issueResult) {
$issueJson = $issueResult | ConvertFrom-Json
$issueNum = $issueJson.data.id ?? $issueJson.data.number
if ($issueNum) {
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Name,--number,$issueNum,--labels,$entry.Label | Out-Null
Log-Ok "Issue created: #$issueNum - $($entry.Title)"
try {
$issueJson = $issueResult | ConvertFrom-Json
$issueNum = if ($issueJson.data.id) { $issueJson.data.id } elseif ($issueJson.data.number) { $issueJson.data.number } else { $null }
if ($issueNum) {
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Name,--number,$issueNum,--labels,$entry.Label | Out-Null
Log-Ok "Issue created: #$issueNum - $($entry.Title)"
}
} catch {
Log-Warn "Issue creation may have failed: $($entry.Title)"
}
} else {
Log-Warn "Issue creation may have failed: $($entry.Title)"
}
}
# ── Step 6: Protect Default Branch ───────────────────────────────────
# -- Step 6: Protect Default Branch --
Log-Step "Protecting master branch..."
$protectResult = Invoke-GL branch,+protect,--owner,$Owner,--repo,$Name,--name,master
if ($protectResult -and (Get-JsonOk ($protectResult | ConvertFrom-Json))) {
@ -333,28 +189,22 @@ if ($protectResult -and (Get-JsonOk ($protectResult | ConvertFrom-Json))) {
Log-Warn "Branch protection may have failed (may require admin permissions)"
}
# ── Step 7: Create Initial Release ───────────────────────────────────
# -- Step 7: Create Initial Release --
Log-Step "Creating initial release v0.1.0..."
$releaseBody = @"
# v0.1.0 - Initial Release
## What's New
- Project initialized with $Lang template
- README and CONTRIBUTING guides created
- CI/CD configuration guide created
- 5 initial issues filed
- Branch protection enabled
## Next Steps
- [ ] Setup CI/CD pipeline
- [ ] Write comprehensive tests
- [ ] Complete documentation
- [ ] First feature implementation
---
*Auto-initialized by gitlink-cli project-init workflow*
"@
$releaseBody = "# v0.1.0 - Initial Release" + "`n`n"
$releaseBody += "## What's New" + "`n"
$releaseBody += "- Project initialized with $Lang template" + "`n"
$releaseBody += "- README and CONTRIBUTING guides created" + "`n"
$releaseBody += "- CI/CD configuration guide created" + "`n"
$releaseBody += "- 5 initial issues filed" + "`n"
$releaseBody += "- Branch protection enabled" + "`n`n"
$releaseBody += "## Next Steps" + "`n"
$releaseBody += "- [ ] Setup CI/CD pipeline" + "`n"
$releaseBody += "- [ ] Write comprehensive tests" + "`n"
$releaseBody += "- [ ] Complete documentation" + "`n"
$releaseBody += "- [ ] First feature implementation" + "`n`n"
$releaseBody += "---`n*Auto-initialized by gitlink-cli project-init workflow*"
$releaseResult = Invoke-GL release,+create,--owner,$Owner,--repo,$Name,--tag,"v0.1.0",--name,"Initial Release",--body,$releaseBody
if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) {
@ -363,9 +213,9 @@ if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) {
Log-Warn "Release creation may have failed"
}
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Title "Project Initialization Complete"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Write-Host " Repository: $Owner/$Name" -ForegroundColor Green
Write-Host " README: Wiki page" -ForegroundColor Green

View File

@ -1,6 +1,6 @@
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
# Scenario 4: Multi-Repo Collaboration
# Flow: Cross-repo issue tracking → PR status dashboard → Coordinated release
# Flow: Cross-repo issue tracking -> PR status dashboard -> Coordinated release
#
# Commands chained:
# 1. repo +list -- list all repos in org
@ -9,7 +9,7 @@
# 4. release +list -- check release status across repos
# 5. release +create -- coordinated release (optional)
# 6. Generate HTML dashboard
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
#Requires -Version 5.1
param(
@ -26,13 +26,13 @@ $ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force
if ($Help) {
Write-Host "Usage: pwsh 04-multi-repo-collab.ps1 -Org ORG [-Repos 'repo1,repo2'] [-Release TAG] [-Output FILE] [-DryRun]"
Write-Host "Usage: powershell 04-multi-repo-collab.ps1 -Org ORG [-Repos 'repo1,repo2'] [-Release TAG] [-Output FILE] [-DryRun]"
exit 0
}
Check-Auth
# ── Step 1: List Repositories ────────────────────────────────────────
# -- Step 1: List Repositories --
Log-Title "Multi-Repo Collaboration Dashboard"
Log-Step "Fetching repositories for org: $Org..."
@ -46,21 +46,20 @@ elseif ($rd -is [array]) { $allRepos = $rd }
Log-Ok "Found $($allRepos.Count) repositories"
# Filter repos if specified
$repoList = @()
if ($Repos) {
$repoList = $Repos -split ','
Log-Info "Filtering to specified repos: $($repoList -join ', ')"
} else {
foreach ($r in $allRepos) {
$rname = $r.name ?? $r.identifier
$rname = if ($r.name) { $r.name } elseif ($r.identifier) { $r.identifier } else { $null }
if ($rname) { $repoList += $rname }
}
}
Log-Ok "Will process $($repoList.Count) repositories"
# ── Step 2-3: Collect Issues and PRs from each repo ──────────────────
# -- Step 2-3: Collect Issues and PRs from each repo --
Log-Title "Collecting Data Across Repos"
$totalIssues = 0; $totalOpenIssues = 0; $totalPRs = 0; $totalOpenPRs = 0
@ -70,15 +69,12 @@ foreach ($repo in $repoList) {
Divider
Log-Step "Processing $Org/$repo..."
# Open issues
$issuesJson = Invoke-GL issue,+list,--owner,$Org,--repo,$repo,--state,open,--limit,50
$openIssues = if ($issuesJson) { @($issuesJson.data.issues).Count } else { 0 }
# Closed issues
$closedJson = Invoke-GL issue,+list,--owner,$Org,--repo,$repo,--state,closed,--limit,50
$closedIssues = if ($closedJson) { @($closedJson.data.issues).Count } else { 0 }
# Open PRs
$prsJson = Invoke-GL pr,+list,--owner,$Org,--repo,$repo,--state,open,--limit,50
$openPRs = 0
if ($prsJson) {
@ -88,7 +84,6 @@ foreach ($repo in $repoList) {
elseif ($pd -is [array]) { $openPRs = $pd.Count }
}
# Merged PRs
$mergedJson = Invoke-GL pr,+list,--owner,$Org,--repo,$repo,--state,merged,--limit,50
$mergedPRs = 0
if ($mergedJson) {
@ -98,53 +93,28 @@ foreach ($repo in $repoList) {
elseif ($md -is [array]) { $mergedPRs = $md.Count }
}
# Latest release
$releaseJson = Invoke-GL release,+list,--owner,$Org,--repo,$repo,--limit,1
$latestRelease = "none"
if ($releaseJson -and $releaseJson.data.releases) {
$releases = @($releaseJson.data.releases)
if ($releases.Count -gt 0) {
$latestRelease = $releases[0].tag_name ?? $releases[0].name ?? "none"
$latestRelease = if ($releases[0].tag_name) { $releases[0].tag_name } elseif ($releases[0].name) { $releases[0].name } else { "none" }
}
}
Log-Ok "$repo : Issues(open:$openIssues closed:$closedIssues) PRs(open:$openPRs merged:$mergedPRs) Release:$latestRelease"
# Build PR details HTML
$prDetailsHtml = ""
if ($openPRs -gt 0 -and $prsJson) {
$prData = @()
$pd2 = $prsJson.data
if ($pd2.issues) { $prData = @($pd2.issues) }
elseif ($pd2.pulls) { $prData = @($pd2.pulls) }
elseif ($pd2 -is [array]) { $prData = $pd2 }
$limit = [Math]::Min($prData.Count, 5)
for ($i = 0; $i -lt $limit; $i++) {
$prId = $prData[$i].pull_request_number ?? $prData[$i].number ?? $prData[$i].id ?? ""
$prTitle = $prData[$i].subject ?? $prData[$i].title ?? ""
$prAuthor = $prData[$i].author_login ?? $prData[$i].author.login ?? "unknown"
$prDetailsHtml += "<tr><td>#$prId</td><td>$prTitle</td><td>@$prAuthor</td><td>open</td></tr>`n"
}
}
# Health status
$statusColor = "green"
$healthText = "Healthy"
if ($openIssues -gt 10) { $statusColor = "orange"; $healthText = "Moderate" }
if ($openIssues -gt 20) { $statusColor = "red"; $healthText = "Needs Attention" }
$dashboardRows += @"
<tr>
<td><a href="https://gitlink.org.cn/$Org/$repo">$repo</a></td>
<td>$openIssues</td>
<td>$closedIssues</td>
<td>$openPRs</td>
<td>$mergedPRs</td>
<td>$latestRelease</td>
<td style="color:$statusColor;font-weight:bold;">$healthText</td>
</tr>
"@
$dashboardRows += "<tr>`n"
$dashboardRows += " <td><a href=`"https://gitlink.org.cn/$Org/$repo`">$repo</a></td>`n"
$dashboardRows += " <td>$openIssues</td><td>$closedIssues</td>`n"
$dashboardRows += " <td>$openPRs</td><td>$mergedPRs</td><td>$latestRelease</td>`n"
$dashboardRows += " <td style=`"color:$statusColor;font-weight:bold;`">$healthText</td>`n"
$dashboardRows += "</tr>`n"
$totalIssues += $openIssues + $closedIssues
$totalOpenIssues += $openIssues
@ -152,14 +122,13 @@ foreach ($repo in $repoList) {
$totalOpenPRs += $openPRs
}
# ── Step 4: Generate HTML Dashboard ──────────────────────────────────
# -- Step 4: Generate HTML Dashboard --
Log-Title "Generating Dashboard"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$repoCount = $repoList.Count
$html = @"
<!DOCTYPE html>
$html = '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
@ -167,7 +136,7 @@ $html = @"
<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; }
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; }
@ -190,28 +159,27 @@ a:hover { text-decoration: underline; }
<body>
<div class="container">
<h1>Multi-Repo Collaboration Dashboard</h1>
<p class="timestamp">Generated: $timestamp | Organization: $Org</p>
<p class="timestamp">Generated: ' + $timestamp + ' | Organization: ' + $Org + '</p>
<div class="summary">
<div class="card blue"><h3>Total Repos</h3><div class="value">$repoCount</div></div>
<div class="card orange"><h3>Open Issues</h3><div class="value">$totalOpenIssues</div></div>
<div class="card purple"><h3>Open PRs</h3><div class="value">$totalOpenPRs</div></div>
<div class="card green"><h3>Total Activity</h3><div class="value">$totalIssues</div></div>
<div class="card blue"><h3>Total Repos</h3><div class="value">' + $repoCount + '</div></div>
<div class="card orange"><h3>Open Issues</h3><div class="value">' + $totalOpenIssues + '</div></div>
<div class="card purple"><h3>Open PRs</h3><div class="value">' + $totalOpenPRs + '</div></div>
<div class="card green"><h3>Total Activity</h3><div class="value">' + $totalIssues + '</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>
$dashboardRows
' + $dashboardRows + '
</tbody>
</table>
</div>
</body>
</html>
"@
</html>'
$html | Out-File -FilePath $Output -Encoding UTF8
Log-Ok "Dashboard saved to: $Output"
# ── Step 5: Coordinated Release ──────────────────────────────────────
# -- Step 5: Coordinated Release --
if ($Release) {
Log-Title "Coordinated Release: $Release"
@ -227,9 +195,9 @@ if ($Release) {
}
}
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Title "Multi-Repo Dashboard Complete"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Write-Host " Repos processed: $repoCount" -ForegroundColor Green
Write-Host " Total issues: $totalIssues (open: $totalOpenIssues)" -ForegroundColor Green

View File

@ -1,6 +1,6 @@
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
# Scenario 5: Contributor Growth System
# Flow: Collect data → Calculate scores → Generate HTML → Publish Wiki → Award badges
# Flow: Collect data -> Calculate scores -> Generate HTML -> Publish Wiki -> Award badges
#
# Scoring (AHP weight model):
# - Issues Created: 15% weight (issue +list)
@ -10,12 +10,12 @@
# - Team Member: 15% weight (repo +members)
#
# Badges:
# - Champion (冠军) >= 80
# - Core Contributor >= 60
# - Active Contributor >= 40
# - Contributor >= 20
# - Newcomer (新人) < 20
# ─────────────────────────────────────────────────────────────────────
# - Champion >= 80
# - Core Contributor >= 60
# - Active Contributor >= 40
# - Contributor >= 20
# - Newcomer < 20
# ----------------------------------------------------------------
#Requires -Version 5.1
param(
@ -31,7 +31,7 @@ $ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot/lib/common.psm1" -Force
if ($Help) {
Write-Host "Usage: pwsh 05-contributor-growth.ps1 -Owner OWNER -Repo REPO [-Sample N] [-Award] [-DryRun]"
Write-Host "Usage: powershell 05-contributor-growth.ps1 -Owner OWNER -Repo REPO [-Sample N] [-Award] [-DryRun]"
Write-Host ""
Write-Host " -Owner OWNER Repository owner"
Write-Host " -Repo REPO Repository name"
@ -47,11 +47,11 @@ $Owner = $r.Owner; $Repo = $r.Repo
$reportFile = "contrib-report-$Owner-$Repo.html"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Title "Contributor Growth System: $Owner/$Repo"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
# ── Step 1: Collect Data ─────────────────────────────────────────────
# -- Step 1: Collect Data --
Log-Step "Collecting data..."
$issuesOpen = Invoke-GLCheck issue,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,100
@ -80,10 +80,9 @@ $memberCount = $memberData.Count
Log-Ok "Issues(open:$openCount closed:$closedCount) PRs(merged:$prMergedCount) Members:$memberCount"
# ── Step 2: Build Contributor Data ───────────────────────────────────
# -- Step 2: Build Contributor Data --
Log-Step "Building contributor profiles..."
# Hashtable: user -> stats
$contribData = @{}
function Ensure-Contrib {
@ -101,7 +100,7 @@ $allIssues = @()
if ($issuesOpen) { $allIssues += @($issuesOpen.data.issues) }
if ($issuesClosed) { $allIssues += @($issuesClosed.data.issues) }
foreach ($issue in $allIssues) {
$author = $issue.author.login ?? $issue.author.username
$author = if ($issue.author.login) { $issue.author.login } elseif ($issue.author.username) { $issue.author.username } else { $null }
if ($author) {
Ensure-Contrib $author
$contribData[$author].Issues++
@ -113,8 +112,8 @@ Log-Step "Analyzing PR code changes (sampling $Sample)..."
$prSample = [Math]::Min($prMergedCount, $Sample)
for ($i = 0; $i -lt $prMergedCount; $i++) {
$pr = $prMergedData[$i]
$author = $pr.author_login ?? $pr.author.login
$prId = $pr.pull_request_number ?? $pr.number ?? $pr.id
$author = if ($pr.author_login) { $pr.author_login } elseif ($pr.author.login) { $pr.author.login } else { $null }
$prId = if ($pr.pull_request_number) { $pr.pull_request_number } elseif ($pr.number) { $pr.number } elseif ($pr.id) { $pr.id } else { $null }
if ($author) {
Ensure-Contrib $author
$contribData[$author].Merged++
@ -123,8 +122,8 @@ for ($i = 0; $i -lt $prMergedCount; $i++) {
$filesJson = Invoke-GL pr,+files,--owner,$Owner,--repo,$Repo,--id,$prId
if ($filesJson -and $filesJson.data.files) {
foreach ($f in $filesJson.data.files) {
$add = $f.additions ?? $f.addition ?? 0
$del = $f.deletions ?? $f.deletion ?? 0
$add = if ($f.additions) { $f.additions } elseif ($f.addition) { $f.addition } else { 0 }
$del = if ($f.deletions) { $f.deletions } elseif ($f.deletion) { $f.deletion } else { 0 }
$contribData[$author].Additions += $add
$contribData[$author].Deletions += $del
}
@ -134,7 +133,7 @@ for ($i = 0; $i -lt $prMergedCount; $i++) {
# Members
foreach ($m in $memberData) {
$login = $m.login ?? $m.username
$login = if ($m.login) { $m.login } elseif ($m.username) { $m.username } else { $null }
if ($login) {
Ensure-Contrib $login
$contribData[$login].IsMember = $true
@ -144,16 +143,16 @@ foreach ($m in $memberData) {
# Comments (sample open issues)
Log-Step "Sampling issue comments..."
if ($issuesOpen) {
$openIssues = @($issuesOpen.data.issues)
$commentSample = [Math]::Min($openIssues.Count, 10)
$openIssuesArr = @($issuesOpen.data.issues)
$commentSample = [Math]::Min($openIssuesArr.Count, 10)
for ($i = 0; $i -lt $commentSample; $i++) {
$id = $openIssues[$i].id
$id = $openIssuesArr[$i].id
if (-not $id) { continue }
$detail = Invoke-GL issue,+view,--owner,$Owner,--repo,$Repo,--number,$id
if ($detail) {
$commentCount = $detail.data.comment_journals_count ?? 0
$commentCount = if ($detail.data.comment_journals_count) { $detail.data.comment_journals_count } else { 0 }
if ($commentCount -gt 0) {
$author = $openIssues[$i].author.login
$author = $openIssuesArr[$i].author.login
if ($author) {
Ensure-Contrib $author
$contribData[$author].Comments += $commentCount
@ -163,10 +162,9 @@ if ($issuesOpen) {
}
}
# ── Step 3: Calculate Scores ─────────────────────────────────────────
# -- Step 3: Calculate Scores --
Log-Step "Calculating scores..."
# Find max values for normalization
$maxIssues = 0; $maxMerged = 0; $maxLines = 0; $maxComments = 0
foreach ($user in $contribData.Keys) {
$c = $contribData[$user]
@ -190,11 +188,11 @@ foreach ($user in $contribData.Keys) {
$scores[$user] = $score
}
# ── Step 4: Display Rankings ─────────────────────────────────────────
# -- Step 4: Display Rankings --
Log-Title "Contributor Rankings"
Write-Host ""
Write-Host ("{0,-4} {1,-18} {2,-8} {3,-8} {4,-12} {5,-10} {6,-8} {7}" -f "Rank","Contributor","Issues","Merged","+/- Lines","Comments","Score","Badge") -ForegroundColor White
Write-Host " ──── ─────────────────── ──────── ──────── ──────────── ────────── ──────── ─────────────"
Write-Host " ---- ------------------ -------- -------- ------------ ---------- -------- -------------"
$ranked = $scores.GetEnumerator() | Sort-Object -Property Value -Descending
$rank = 1
@ -204,29 +202,21 @@ foreach ($entry in $ranked) {
$score = $entry.Value
$c = $contribData[$user]
$si = [int]$score
$badge = switch ($true) {
($si -ge 80) { "Champion" }
($si -ge 60) { "Core Contributor" }
($si -ge 40) { "Active Contributor" }
($si -ge 20) { "Contributor" }
default { "Newcomer" }
}
$badge = if ($si -ge 80) { "Champion" } elseif ($si -ge 60) { "Core Contributor" } elseif ($si -ge 40) { "Active Contributor" } elseif ($si -ge 20) { "Contributor" } else { "Newcomer" }
$lines = $c.Additions + $c.Deletions
Write-Host ("{0,-4} {1,-18} {2,-8} {3,-8} +{4,-6}/-{5,-4} {6,-10} {7,-8} {8}" -f $rank,$user,$c.Issues,$c.Merged,$c.Additions,$c.Deletions,$c.Comments,$score,$badge)
$rankedList += @{ Rank=$rank; User=$user; Issues=$c.Issues; Merged=$c.Merged; Lines=$lines; Additions=$c.Additions; Deletions=$c.Deletions; Comments=$c.Comments; Score=$score; Badge=$badge }
$rank++
}
# ── Step 5: Generate HTML Report ─────────────────────────────────────
# -- Step 5: Generate HTML Report --
Log-Title "Generating HTML Report"
# Build pie chart data
$pieData = ""
foreach ($entry in $ranked) {
$pieData += "{value: $($entry.Value), name: '$($entry.Key)'},"
}
# Build table rows
$tableRows = ""
foreach ($r in $rankedList) {
$rankCls = ""
@ -242,25 +232,22 @@ foreach ($r in $rankedList) {
default { "newcomer" }
}
$tableRows += @"
<tr><td class="rank$rankCls">$($r.Rank)</td><td>@$($r.User)</td><td>$($r.Issues)</td><td>$($r.Merged)</td><td>$($r.Lines)</td><td>$($r.Comments)</td><td>$($r.Score)</td><td><span class="badge badge-$badgeCls">$($r.Badge)</span></td></tr>
"@
$tableRows += ' <tr><td class="rank' + $rankCls + '">' + $r.Rank + '</td><td>@' + $r.User + '</td><td>' + $r.Issues + '</td><td>' + $r.Merged + '</td><td>' + $r.Lines + '</td><td>' + $r.Comments + '</td><td>' + $r.Score + '</td><td><span class="badge badge-' + $badgeCls + '">' + $r.Badge + '</span></td></tr>' + "`n"
}
$totalIssuesCount = $openCount + $closedCount
$contribCount = $contribData.Count
$html = @"
<!DOCTYPE html>
$html = '<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contributor Report - $Owner/$Repo</title>
<title>Contributor Report - ' + $Owner + '/' + $Repo + '</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 40px 20px; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 40px 20px; }
.container { max-width: 1200px; margin: 0 auto; }
.header { text-align: center; color: white; margin-bottom: 40px; }
.header h1 { font-size: 2.5rem; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
@ -298,12 +285,12 @@ $html = @"
<div class="container">
<div class="header">
<h1>Contributor Report</h1>
<p>$Owner/$Repo - Team Contribution Analysis</p>
<p>' + $Owner + '/' + $Repo + ' - Team Contribution Analysis</p>
</div>
<div class="stats-grid">
<div class="stat-card"><div class="stat-value">$contribCount</div><div class="stat-label">Contributors</div></div>
<div class="stat-card"><div class="stat-value">$totalIssuesCount</div><div class="stat-label">Total Issues</div></div>
<div class="stat-card"><div class="stat-value">$prMergedCount</div><div class="stat-label">Merged PRs</div></div>
<div class="stat-card"><div class="stat-value">' + $contribCount + '</div><div class="stat-label">Contributors</div></div>
<div class="stat-card"><div class="stat-value">' + $totalIssuesCount + '</div><div class="stat-label">Total Issues</div></div>
<div class="stat-card"><div class="stat-value">' + $prMergedCount + '</div><div class="stat-label">Merged PRs</div></div>
</div>
<div class="card">
<h2>Score Distribution</h2>
@ -312,7 +299,7 @@ $html = @"
<div class="card">
<h2>Detailed Rankings</h2>
<table><thead><tr><th>Rank</th><th>Contributor</th><th>Issues</th><th>Merged PRs</th><th>Code Lines</th><th>Comments</th><th>Score</th><th>Badge</th></tr></thead><tbody>
$tableRows
' + $tableRows + '
</tbody></table>
</div>
<div class="card">
@ -329,30 +316,29 @@ $tableRows
</div>
</div>
<script>
var chart = echarts.init(document.getElementById('pieChart'));
var chart = echarts.init(document.getElementById("pieChart"));
chart.setOption({
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left', top: 'middle' },
tooltip: { trigger: "item", formatter: "{a} <br/>{b}: {c} ({d}%)" },
legend: { orient: "vertical", left: "left", top: "middle" },
series: [{
name: 'Score',
type: 'pie',
radius: ['40%', '70%'],
center: ['60%', '50%'],
itemStyle: { borderRadius: 10, borderColor: '#fff', borderWidth: 2 },
label: { show: true, formatter: '{b}\n{d}%' },
data: [$pieData]
name: "Score",
type: "pie",
radius: ["40%", "70%"],
center: ["60%", "50%"],
itemStyle: { borderRadius: 10, borderColor: "#fff", borderWidth: 2 },
label: { show: true, formatter: "{b}\n{d}%" },
data: [' + $pieData + ']
}]
});
window.addEventListener('resize', () => chart.resize());
window.addEventListener("resize", function() { chart.resize(); });
</script>
</body>
</html>
"@
</html>'
$html | Out-File -FilePath $reportFile -Encoding UTF8
Log-Ok "HTML report: $reportFile"
# ── Step 6: Publish to Wiki ──────────────────────────────────────────
# -- Step 6: Publish to Wiki --
Log-Step "Publishing to Wiki..."
$wikiRankRows = ""
@ -364,34 +350,26 @@ foreach ($r in $rankedList) {
"Contributor" { "Contributor" }
default { "Newcomer" }
}
$wikiRankRows += "| $($r.Rank) | @$($r.User) | $($r.Issues) | $($r.Merged) | $($r.Lines) | $($r.Comments) | $($r.Score) | $shortBadge |`n"
$wikiRankRows += "| $($r.Rank) | @$($r.User) | $($r.Issues) | $($r.Merged) | $($r.Lines) | $($r.Comments) | $($r.Score) | $shortBadge |" + "`n"
}
$wikiTitle = "Contributor Leaderboard $(Get-Date -Format 'yyyy-MM-dd')"
$wikiContent = @"
# Contributor Leaderboard - $Owner/$Repo
*Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm')*
## Scoring System
| Dimension | Weight | Source |
|-----------|--------|--------|
| Issues Created | 15% | issue +list |
| PRs Merged | 25% | pr +list state=merged |
| Code Changes | 30% | pr +files |
| Issue Comments | 15% | issue +view |
| Team Member | 15% | repo +members |
## Rankings
| Rank | Contributor | Issues | Merged | Lines | Comments | Score | Badge |
|------|-------------|--------|--------|-------|----------|-------|-------|
$wikiRankRows
---
*Auto-generated by gitlink-cli*
"@
$wikiContent = "# Contributor Leaderboard - $Owner/$Repo" + "`n`n"
$wikiContent += "*Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm')*" + "`n`n"
$wikiContent += "## Scoring System" + "`n`n"
$wikiContent += "| Dimension | Weight | Source |" + "`n"
$wikiContent += "|-----------|--------|--------|" + "`n"
$wikiContent += "| Issues Created | 15% | issue +list |" + "`n"
$wikiContent += "| PRs Merged | 25% | pr +list state=merged |" + "`n"
$wikiContent += "| Code Changes | 30% | pr +files |" + "`n"
$wikiContent += "| Issue Comments | 15% | issue +view |" + "`n"
$wikiContent += "| Team Member | 15% | repo +members |" + "`n`n"
$wikiContent += "## Rankings" + "`n`n"
$wikiContent += "| Rank | Contributor | Issues | Merged | Lines | Comments | Score | Badge |" + "`n"
$wikiContent += "|------|-------------|--------|--------|-------|----------|-------|-------|" + "`n"
$wikiContent += $wikiRankRows + "`n"
$wikiContent += "---" + "`n"
$wikiContent += "*Auto-generated by gitlink-cli*"
$wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Repo,--title,$wikiTitle,--body,$wikiContent
if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) {
@ -400,7 +378,7 @@ if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) {
Log-Warn "Wiki publish failed"
}
# ── Step 7: Award Badges (optional) ──────────────────────────────────
# -- Step 7: Award Badges (optional) --
if ($Award) {
Log-Title "Awarding Badges"
@ -412,51 +390,41 @@ if ($Award) {
foreach ($badge in $badgeGroups.Keys) {
$users = $badgeGroups[$badge]
if ($badge -eq "Newcomer") { continue } # Skip newcomers
if ($badge -eq "Newcomer") { continue }
$userList = ($users | ForEach-Object { "@$_" }) -join ", "
$issueTitle = "Badge Award: $badge"
$issueBody = @"
## Congratulations! :tada:
The following contributors have earned the **$badge** badge:
$userList
### Badge Criteria
$(switch ($badge) {
"Champion" { "- Score >= 80: Exceptional contribution to the project" }
"Core Contributor" { "- Score >= 60: Significant and consistent contributions" }
"Active Contributor" { "- Score >= 40: Regular contributions to the project" }
"Contributor" { "- Score >= 20: Made meaningful contributions" }
})
### Scoring Dimensions
- Issues Created: 15%
- PRs Merged: 25%
- Code Changes: 30%
- Issue Comments: 15%
- Team Member: 15%
---
*Auto-awarded by gitlink-cli contributor-growth workflow*
"@
$issueBody = "## Congratulations!" + "`n`n"
$issueBody += "The following contributors have earned the **$badge** badge:" + "`n`n"
$issueBody += $userList + "`n`n"
$issueBody += "### Badge Criteria" + "`n"
$issueBody += switch ($badge) {
"Champion" { "- Score >= 80: Exceptional contribution to the project" }
"Core Contributor" { "- Score >= 60: Significant and consistent contributions" }
"Active Contributor" { "- Score >= 40: Regular contributions to the project" }
"Contributor" { "- Score >= 20: Made meaningful contributions" }
}
$issueBody += "`n`n---`n*Auto-awarded by gitlink-cli contributor-growth workflow*"
$issueResult = Invoke-GL issue,+create,--owner,$Owner,--repo,$Repo,--title,$issueTitle,--body,$issueBody
if ($issueResult) {
$issueJson = $issueResult | ConvertFrom-Json
$issueNum = $issueJson.data.id ?? $issueJson.data.number
if ($issueNum) {
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Repo,--number,$issueNum,--labels,"badge" | Out-Null
Log-Ok "Badge issue created: #$issueNum - $issueTitle ($($users.Count) recipients)"
try {
$issueJson = $issueResult | ConvertFrom-Json
$issueNum = if ($issueJson.data.id) { $issueJson.data.id } elseif ($issueJson.data.number) { $issueJson.data.number } else { $null }
if ($issueNum) {
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Repo,--number,$issueNum,--labels,"badge" | Out-Null
Log-Ok "Badge issue created: #$issueNum - $issueTitle ($($users.Count) recipients)"
}
} catch {
Log-Warn "Badge issue creation may have failed: $issueTitle"
}
}
}
}
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Log-Title "Complete"
# ─────────────────────────────────────────────────────────────────────
# ----------------------------------------------------------------
Write-Host " Contributors: $contribCount" -ForegroundColor Green
Write-Host " HTML Report: $reportFile" -ForegroundColor Green

View File

@ -1,35 +1,35 @@
# Common utilities for gitlink-cli workflow scripts (PowerShell)
#Requires -Version 5.1
# Common utilities for gitlink-cli workflow scripts (PowerShell 5.1+)
$Script:GL = "gitlink-cli"
# ── Logging ──────────────────────────────────────────────────────────
# -- Logging --
function Log-Step { param([string]$Msg) Write-Host "[STEP] $Msg" -ForegroundColor Blue }
function Log-Ok { param([string]$Msg) Write-Host "[ OK] $Msg" -ForegroundColor Green }
function Log-Warn { param([string]$Msg) Write-Host "[WARN] $Msg" -ForegroundColor Yellow }
function Log-Err { param([string]$Msg) Write-Host "[ ERR] $Msg" -ForegroundColor Red }
function Log-Info { param([string]$Msg) Write-Host "[INFO] $Msg" -ForegroundColor Cyan }
function Log-Title { param([string]$Msg) Write-Host "`n══════ $Msg ══════`n" -ForegroundColor White }
function Divider { Write-Host "────────────────────────────────────────────────" -ForegroundColor Cyan }
function Log-Title { param([string]$Msg) Write-Host ""; Write-Host "====== $Msg ======" -ForegroundColor White; Write-Host "" }
function Divider { Write-Host "------------------------------------------------" -ForegroundColor Cyan }
# ── Auth Check ───────────────────────────────────────────────────────
# -- Auth Check --
function Check-Auth {
if ($env:GITLINK_TOKEN) {
Log-Ok "GITLINK_TOKEN is set"
return
}
$status = & $Script:GL auth status 2>&1
if ($status -match "logged in|✓") {
$statusStr = $status -join " "
if ($statusStr -match "logged in") {
Log-Ok "Authenticated"
return
}
Log-Err "Not authenticated. Please login first:"
Log-Info " gitlink-cli auth login"
Log-Info " `$env:GITLINK_TOKEN = 'your-private-token'"
Log-Info ' $env:GITLINK_TOKEN = "your-private-token"'
exit 1
}
# ── CLI Wrapper ──────────────────────────────────────────────────────
# -- CLI Wrapper --
function Invoke-GL {
param([string[]]$Args)
$output = & $Script:GL @Args --format json 2>&1
@ -42,8 +42,9 @@ function Invoke-GLCheck {
try {
$json = $output | ConvertFrom-Json
if (-not $json.ok) {
$errMsg = if ($json.error.message) { $json.error.message } else { "unknown error" }
Log-Err "Command failed: $Script:GL $($Args -join ' ')"
Log-Err ($json.error.message ?? "unknown error")
Log-Err $errMsg
return $null
}
return $json
@ -54,18 +55,13 @@ function Invoke-GLCheck {
}
}
# ── JSON Helpers ─────────────────────────────────────────────────────
# -- JSON Helpers --
function Get-JsonOk {
param($Json)
return ($Json.ok -eq $true)
}
function Get-JsonData {
param($Json)
return $Json.data
}
# ── Owner/Repo Detection ────────────────────────────────────────────
# -- Owner/Repo Detection --
function Detect-OwnerRepo {
$remote = git remote get-url origin 2>$null
if (-not $remote) {
@ -83,27 +79,14 @@ function Resolve-OwnerRepo {
param([string]$Owner, [string]$Repo)
if (-not $Owner -or -not $Repo) {
$detected = Detect-OwnerRepo
$Owner = $Owner ?? $detected.Owner
$Repo = $Repo ?? $detected.Repo
if (-not $Owner) { $Owner = $detected.Owner }
if (-not $Repo) { $Repo = $detected.Repo }
}
Log-Info "Using: $Owner/$Repo"
return @{ Owner = $Owner; Repo = $Repo }
}
# ── Confirmation ─────────────────────────────────────────────────────
function Confirm-Action {
param([string]$Msg, [switch]$DryRun)
if ($DryRun) {
Log-Warn "[DRY RUN] Would execute: $Msg"
return $false
}
$answer = Read-Host "$Msg [y/N]"
return ($answer -match '^[Yy]')
}
# ── Date Helpers ─────────────────────────────────────────────────────
# -- Date Helpers --
function Get-DateToday { return (Get-Date -Format "yyyy-MM-dd") }
function Get-DateWeekAgo { return ((Get-Date).AddDays(-7).ToString("yyyy-MM-dd")) }
function Get-DateMonthAgo { return ((Get-Date).AddDays(-30).ToString("yyyy-MM-dd")) }
Export-ModuleMember -Function *