Skip to content

Conversation

@saymanlal
Copy link

@saymanlal saymanlal commented Oct 20, 2025

Title

Add Automated Leaderboard Generation via GitHub Actions

Description

This pull request introduces a new GitHub Action that automatically creates and updates a leaderboard.json file whenever a pull request is merged into the main branch or the workflow is manually triggered.

It dynamically showcases contributors, making the project more community-driven and engaging.

Resolved issue #5885


Why This Matters

  • Keeps leaderboard data current after every contribution.
  • Recognizes contributors and promotes transparency.
  • Reduces maintainer workload through automation.
  • Improves visibility, especially on the GitHub mobile app.

Quality Check Metadata

forge link: https://github.com/gofiber/fiber
pkg.go.dev: https://pkg.go.dev/github.com/gofiber/fiber/v2
goreportcard.com: https://goreportcard.com/report/github.com/gofiber/fiber
coverage: https://codecov.io/gh/gofiber/fiber

Summary by CodeRabbit

  • New Features

    • Automated contributors leaderboard generation and a public contributors leaderboard page with responsive UI.
  • Documentation

    • README updated to link to the live Contributors Leaderboard; contribution guide link adjusted to internal section.
  • Tests

    • New tests validating site rendering and leaderboard JSON generation to ensure leaderboard and index output are produced.

@github-actions
Copy link

github-actions bot commented Oct 20, 2025

Automated Quality Checks (from CONTRIBUTING minimum standards)

  • Repo: OK
  • pkg.go.dev: OK
  • goreportcard: OK (grade unknown)
  • coverage: OK

These checks are a best-effort automation and do not replace human review.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Walkthrough

Adds an automated GitHub Actions workflow to update a contributors leaderboard, implements leaderboard generation and contributors page rendering in the static site (new Contributor type, JSON output, template rendering), adds a contributors template and README/CONTRIBUTING docs updates, and updates tests to validate leaderboard generation and rendering.

Changes

Cohort / File(s) Summary
CI/CD Workflow
.github/workflows/update-leaderboard.yml
New GitHub Actions workflow "Update Contributors & Leaderboard" triggered on PR close to main and manual dispatch. Checks out main, sets up Go 1.22, builds the static site via go run main.go, and commits/pushes leaderboard.json and out/contributors/index.html using stefanzweifel/git-auto-commit-action.
Site generation & leaderboard logic
main.go, main_test.go
Adds Contributor type and functions generateLeaderboardJSON and renderContributorsPage; integrates leaderboard generation into the static site build flow with non-fatal error handling; extends template FuncMap (e.g., min/add); adjusts index rendering to pass empty Contributors slice to templates. Tests updated: replaced previous tests with TestRenderIndex and TestLeaderboardGeneration that verify static output and leaderboard JSON.
Templates
tmpl/contributors.tmpl.html, tmpl/index.tmpl.html
New contributors.tmpl.html template rendering Top3/Top10/Top100/Other contributor sections and avatars; updated tmpl/index.tmpl.html asset and favicon paths from /assets/... to /tmpl/assets/....
Documentation
CONTRIBUTING.md, README.md
Updated contribution link in CONTRIBUTING.md to point to internal ./README.md/#contribution. Added/adjusted Contribution section in README.md to reference the Contributors Leaderboard (badge/paragraph insertion).

Sequence Diagram(s)

sequenceDiagram
    participant GH as GitHub Event (PR merge / manual)
    participant Workflow as Update Contributors Workflow
    participant Runner as Actions Runner (ubuntu-latest)
    participant Go as Go 1.22
    participant Site as Static Site (main.go)
    participant GitAuto as git-auto-commit-action
    GH->>Workflow: PR merged to main / workflow_dispatch
    activate Workflow
    Workflow->>Runner: spin up runner
    Runner->>Workflow: checkout `main` (fetch-depth:0)
    Workflow->>Go: setup Go 1.22
    Workflow->>Site: run `go run main.go`
    activate Site
    Site->>Site: generateLeaderboardJSON (call GitHub API, paginate)
    Site->>Site: write `leaderboard.json`
    Site->>Site: renderContributorsPage (apply `contributors.tmpl.html`)
    Site->>Site: write `out/contributors/index.html`
    deactivate Site
    Workflow->>GitAuto: commit `leaderboard.json` & `out/contributors/index.html`
    GitAuto->>Workflow: push to `main` as bot
    deactivate Workflow
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Reason: multiple areas touched (CI workflow, core generation logic, template, tests) with new control flow and substantial test changes requiring careful review.

Poem

🐰 I dug a little tunnel in code so neat,
Collected contributors, one JSON treat,
Templated cards that hop into view,
Actions push changes so the board stays new,
Hooray for the herd — thump, thump, hooray!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Add Automated Leaderboard Generation via GitHub Actions" clearly and accurately summarizes the main objective of the changeset. The title captures the primary feature being added (automated leaderboard generation) and specifies the mechanism (GitHub Actions workflow automation). The changes across all modified files—including the workflow file, Go code changes for leaderboard logic, new contributors template, and supporting documentation updates—all contribute directly to implementing this automated leaderboard feature. The title is concise, specific, and avoids vague language, making it clear to someone reviewing the commit history that this PR introduces leaderboard automation functionality.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@saymanlal
Copy link
Author

saymanlal commented Oct 20, 2025

Hello @avelino, @dukex, @dmitshur, @matrixik, @joeybloggs, @kirillDanshin, @felipeweb, @appleboy, @cassiobotaro, @jtemporal, @ceriath, @panjf2000, @phanirithvij, and @yassinebenaid

I’ve opened this pull request to introduce an automated leaderboard generation system powered by GitHub Actions.
This enhancement dynamically updates a leaderboard.json file on each PR merge to main, ensuring contributors are highlighted consistently and automatically — improving transparency, engagement, and visibility across both desktop and GitHub mobile.

This pull request resolves discussion #5885, which proposed adding a contributors’ direct showcase section to the project and automating its updates through GitHub Actions.

Kindly review this implementation and share your feedback or suggestions for improvement.
If everything looks good, please consider merging this PR so the automation can start keeping contributor data up to date.

Thank you all for maintaining this amazing project and for taking the time to review my contribution.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/update-leaderboard.yml (1)

3-12: Consider optimizing trigger efficiency.

The workflow triggers on every PR closed to main, then filters by the merged status on line 12. This results in unnecessary workflow queue entries for closed non-merged PRs. For a cleaner approach, consider triggering on push to main instead, which naturally occurs only on merged PRs:

on:
  push:
    branches:
      - main
  workflow_dispatch:

This eliminates the conditional check and reduces wasted workflow runs. If the current approach is intentional for logging or audit purposes, this optimization can be deferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0002f63 and 786df07.

📒 Files selected for processing (3)
  • .github/workflows/update-leaderboard.yml (1 hunks)
  • CONTRIBUTING.md (1 hunks)
  • README.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Keep documentation in English

Files:

  • CONTRIBUTING.md
  • README.md
README.md

📄 CodeRabbit inference engine (AGENTS.md)

README.md: When modifying the Awesome list, keep categories with at least three entries
Descriptions in the Awesome list must end with a period
Avoid promotional copy; keep descriptions concise and neutral in the Awesome list
Do not remove existing list content unless removal is requested and justified

Files:

  • README.md
{README.md,COVERAGE.md}

📄 CodeRabbit inference engine (AGENTS.md)

Align rendered documentation (README.md, COVERAGE.md, etc.) with behavior changes in main.go or helper packages

Files:

  • README.md
🧠 Learnings (1)
📚 Learning: 2025-08-14T23:46:18.545Z
Learnt from: elymanuba
PR: avelino/awesome-go#5762
File: README.md:0-0
Timestamp: 2025-08-14T23:46:18.545Z
Learning: When reviewing contributions to the awesome-go repository, maintain a relaxed and less pedantic approach. Users may find overly strict feedback about formatting, alphabetical ordering, or description tone to be unnecessary. Focus on major issues rather than minor stylistic preferences.

Applied to files:

  • CONTRIBUTING.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (4)
.github/workflows/update-leaderboard.yml (2)

24-31: Node.js setup and caching look good.

Using Node.js 20 with npm caching is an appropriate choice that optimizes workflow runtime. No concerns here.


33-36: Verify that the update script exists and handles errors gracefully.

The workflow executes npx ts-node scripts/update-leaderboard.ts, which assumes this script exists at the repository root. If the script fails (e.g., API errors, network issues), the workflow will continue to the commit step even if leaderboard data wasn't updated. Consider adding a check to ensure the script succeeds before committing, or ensure the script exits with a non-zero code on error so the workflow fails appropriately.

Verify that:

  1. scripts/update-leaderboard.ts exists and is included in this PR
  2. The script handles errors and exits with appropriate status codes (non-zero on failure)
  3. The script behaves correctly when invoked with the GITHUB_TOKEN environment variable
CONTRIBUTING.md (1)

4-4: Verify that the README.md anchor target exists.

The link ./README.md/#contribution assumes a corresponding anchor or heading with id contribution exists in README.md. Per the PR summary, a Contributors badge section should be present. Ensure the anchor is correctly named and that the link resolves properly when viewed on GitHub.

Following the learnings from prior reviews, I'm keeping feedback focused on functional correctness rather than stylistic preferences. This change appropriately directs contributors to the internal README section.

Confirm that README.md contains a section with id #contribution (or equivalent heading that GitHub will auto-link to).

README.md (1)

3788-3795: Contributors badge looks great

The new badge + avatar strip render cleanly, link to the repo’s contributors graph, and reinforce the project’s community focus. Nice touch!

Comment on lines 38 to 46
- name: "Commit and push changes"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs(leaderboard): update leaderboard.json"
file_pattern: "apps/web/public/leaderboard.json"
branch: main
commit_user_name: "github-actions[bot]"
commit_user_email: "github-actions[bot]@users.noreply.github.com"
commit_author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>" No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Third-party action configuration is sound.

The git-auto-commit-action setup is appropriate, with:

  • Specific version pin (v5) avoiding unexpected breaking changes
  • File pattern limiting scope to leaderboard.json
  • Proper commit metadata and bot attribution
  • Correct branch target (main)

Ensure that stefanzweifel/git-auto-commit-action@v5 remains maintained and is compatible with your GitHub Actions environment. Consider periodically checking for updates, but the current pinned version provides stability.


🌐 Web query:

Is stefanzweifel/git-auto-commit-action@v5 currently maintained and is it the latest stable version?

💡 Result:

No — @v5 is not the latest. The action is actively maintained (recent releases/commits) and the current major/stable release is v6 (latest release v6.0.1). Use stefanzweifel/git-auto-commit-action@v6 to track the current stable series. [1][2]

Sources:
[1] GitHub repo (stefanzweifel/git-auto-commit-action).
[2] Release summary showing latest release v6.0.1.


Update git-auto-commit-action to v6 (current stable release).

The action is actively maintained, but v6 is the current major/stable release (v6.0.1 is the latest). Update stefanzweifel/git-auto-commit-action@v5 to @v6 to stay on the current stable series.

The rest of the configuration (file pattern, commit metadata, bot attribution, branch) remains sound.

🤖 Prompt for AI Agents
.github/workflows/update-leaderboard.yml lines 38-46: update the GitHub Action
reference from stefanzweifel/git-auto-commit-action@v5 to
stefanzweifel/git-auto-commit-action@v6; keep the existing inputs
(commit_message, file_pattern, branch, commit_user_name, commit_user_email,
commit_author) unchanged so the behaviour and metadata remain the same while
moving to the current stable major release.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.go (1)

271-278: Close sitemap file and return any close error

Prevent descriptor leaks and ensure data is flushed.

Apply this diff:

 func renderSitemap(categories map[string]Category) error {
     f, err := os.Create(outSitemapFile)
     if err != nil {
         return fmt.Errorf("create sitemap file `%s`: %w", outSitemapFile, err)
     }
     fmt.Printf("Render Sitemap to: %s\n", outSitemapFile)
-    return tpl.Lookup("sitemap.tmpl.xml").Execute(f, categories)
+    if err := tpl.Lookup("sitemap.tmpl.xml").Execute(f, categories); err != nil {
+        _ = f.Close()
+        return err
+    }
+    return f.Close()
 }
♻️ Duplicate comments (1)
.github/workflows/update-leaderboard.yml (1)

35-35: Use git-auto-commit-action v6 (current stable) instead of v5

This mirrors prior feedback; keep inputs unchanged.

Apply this diff:

-      - name: Commit and push leaderboard + contributors page
-        uses: stefanzweifel/git-auto-commit-action@v5
+      - name: Commit and push leaderboard + contributors page
+        uses: stefanzweifel/git-auto-commit-action@v6
🧹 Nitpick comments (3)
.github/workflows/update-leaderboard.yml (1)

10-18: Optional: add concurrency to avoid races on rapid merges

Guard against concurrent runs pushing overlapping commits.

Example:

 jobs:
   build-site:
+    concurrency:
+      group: update-leaderboard
+      cancel-in-progress: false
tmpl/contributors.tmpl.html (1)

137-151: Optional a11y: add aria-labels for contributor links

Improve screen-reader context: aria-label="{{ .Login }}: {{ .Contributions }} contributions".

main.go (1)

189-201: Fallback avatar for anonymous contributors may 404

avatars.githubusercontent.com/ isn’t guaranteed for non-user placeholders. Use a known default (e.g., https://github.com/ghost.png).

Apply this diff:

-            if c.AvatarURL == "" {
-                // Use GitHub-style default identicon based on login
-                pageContributors[i].AvatarURL = fmt.Sprintf(
-                    "https://avatars.githubusercontent.com/%s?v=4",
-                    url.PathEscape(pageContributors[i].Login),
-                )
-            }
+            if c.AvatarURL == "" {
+                pageContributors[i].AvatarURL = "https://github.com/ghost.png"
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 786df07 and 43e9eb2.

📒 Files selected for processing (6)
  • .github/workflows/update-leaderboard.yml (1 hunks)
  • README.md (1 hunks)
  • main.go (10 hunks)
  • main_test.go (1 hunks)
  • tmpl/contributors.tmpl.html (1 hunks)
  • tmpl/index.tmpl.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Use standard formatting (gofmt) and idiomatic Go style
Favor small, testable functions in Go
Keep exported Go APIs documented with Go-style comments
Maintain ≥80% coverage for non-data packages and ≥90% for data packages when adding new testable Go code

Files:

  • main_test.go
  • main.go
{**/*.go,.github/scripts/**/*.js}

📄 CodeRabbit inference engine (AGENTS.md)

Use American English spelling in code comments

Files:

  • main_test.go
  • main.go
🧠 Learnings (1)
📚 Learning: 2025-10-08T00:38:13.132Z
Learnt from: CR
PR: avelino/awesome-go#0
File: AGENTS.md:0-0
Timestamp: 2025-10-08T00:38:13.132Z
Learning: Applies to {README.md,COVERAGE.md} : Align rendered documentation (README.md, COVERAGE.md, etc.) with behavior changes in main.go or helper packages

Applied to files:

  • main_test.go
🧬 Code graph analysis (1)
main_test.go (1)
main.go (1)
  • Contributor (40-45)
🪛 actionlint (1.7.8)
.github/workflows/update-leaderboard.yml

27-27: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis

Comment on lines +27 to +29
uses: actions/setup-go@v4
with:
go-version: "1.22"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update actions/setup-go to v5 (per actionlint hint)

actionlint warns v4 is too old for runners. Bump to the current major.

Apply this diff:

-      - name: Set up Go
-        uses: actions/setup-go@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
         with:
           go-version: "1.22"

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 actionlint (1.7.8)

27-27: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In .github/workflows/update-leaderboard.yml around lines 27 to 29, update the
GitHub Actions step using actions/setup-go from v4 to v5 per actionlint's
recommendation; modify the uses value to actions/setup-go@v5 while keeping the
existing with.go-version setting (e.g., "1.22") unchanged so the runner uses the
newer major of the action.

Comment on lines +31 to +33
- name: Build static site
run: go run main.go

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Pass GITHUB_TOKEN to avoid GitHub API rate limits during build

generateLeaderboardJSON reads GITHUB_TOKEN from env, but the step doesn’t export it. Without it, the API calls will be unauthenticated and can hit rate limits on large repos.

Apply this diff:

-      - name: Build static site
-        run: go run main.go
+      - name: Build static site
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: go run main.go
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Build static site
run: go run main.go
- name: Build static site
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go run main.go
🤖 Prompt for AI Agents
In .github/workflows/update-leaderboard.yml around lines 31-33, the "Build
static site" step runs `go run main.go` but does not pass GITHUB_TOKEN, so
generateLeaderboardJSON will make unauthenticated API calls; update that step to
export the token by adding an env mapping GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN
}} (or set env at job level) so the process reads the token from the workflow
secrets and avoids GitHub API rate limits.

Comment on lines +38 to +41
file_pattern: |
leaderboard.json
out/contributors/index.html
branch: main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrong commit path: leaderboard.json lives under out/

main.go writes leaderboardJSON to out/leaderboard.json. The workflow commits leaderboard.json (repo root), so changes won’t be picked up.

Apply this diff:

       with:
         commit_message: "docs: update leaderboard & contributors"
         file_pattern: |
-          leaderboard.json
+          out/leaderboard.json
           out/contributors/index.html
         branch: main
🤖 Prompt for AI Agents
In .github/workflows/update-leaderboard.yml around lines 38 to 41, the workflow
is committing leaderboard.json at the repo root but main.go writes
out/leaderboard.json; update the file_pattern to point to the correct path
(out/leaderboard.json) so the workflow picks up and commits the generated file
(keep out/contributors/index.html entry as-is).

Comment on lines +45 to 83
func TestLeaderboardGeneration(t *testing.T) {
// Use mock leaderboard for safe testing
os.Setenv("TEST_LEADERBOARD", "1")
defer os.Unsetenv("TEST_LEADERBOARD")

lines := strings.Split(string(input), "\n")
for _, line := range lines {
line = strings.Trim(line, " ")
containsLink = reContainsLink.MatchString(line)
if containsLink {
noDescription = reOnlyLink.MatchString(line)
if noDescription {
continue
}
matched = reLinkWithDescription.MatchString(line)
if !matched {
t.Errorf("expected entry to be in form of `* [link] - description.`, got '%s'", line)
}
}
// Build static site
if err := buildStaticSite(); err != nil {
t.Fatalf("buildStaticSite failed: %v", err)
}
}

func TestRenderIndex(t *testing.T) {
requireNoErr(t, mkdirAll(outDir), "output dir should exists")

err := renderIndex(readmePath, outIndexFile)
requireNoErr(t, err, "html should be rendered")
}
// Check leaderboard.json exists
info, err := os.Stat(leaderboardJSON)
if err != nil {
t.Fatalf("leaderboard.json missing: %v", err)
}
if info.Size() == 0 {
t.Fatal("leaderboard.json is empty")
}

func testList(t *testing.T, list *goquery.Selection) {
list.Find("ul").Each(func(_ int, items *goquery.Selection) {
testList(t, items)
items.RemoveFiltered("ul")
})
t.Run(list.Prev().Text(), func(t *testing.T) {
checkAlphabeticOrder(t, list)
})
}
// Read and validate JSON
data, err := os.ReadFile(leaderboardJSON)
if err != nil {
t.Fatalf("read leaderboard.json failed: %v", err)
}

func checkAlphabeticOrder(t *testing.T, s *goquery.Selection) {
items := s.Find("li > a:first-child").Map(func(_ int, li *goquery.Selection) string {
return strings.ToLower(li.Text())
})
sorted := make([]string, len(items))
copy(sorted, items)
sort.Strings(sorted)
for k, item := range items {
if item != sorted[k] {
t.Errorf("expected '%s' but actual is '%s'", sorted[k], item)
}
var contributors []Contributor
if err := json.Unmarshal(data, &contributors); err != nil {
t.Fatalf("invalid leaderboard JSON: %v", err)
}
if len(contributors) == 0 {
t.Fatal("no contributors found in leaderboard.json")
}
if t.Failed() {
t.Logf("expected order is:\n%s", strings.Join(sorted, "\n"))

// Validate first mock contributor
first := contributors[0]
if first.Login == "" || first.Contributions == 0 {
t.Errorf("first contributor missing required fields: %+v", first)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Tests currently hit the live GitHub API; rely on a mock path instead

As written, this test will flake/ratelimit because generateLeaderboardJSON ignores TEST_LEADERBOARD. With the proposed code change, it becomes hermetic and stable.

Run to confirm TEST_LEADERBOARD isn’t referenced outside tests:

Expected: only matches in main_test.go after the code change adds the check in generateLeaderboardJSON.


🏁 Script executed:

#!/bin/bash
rg -n "TEST_LEADERBOARD" -C2

Length of output: 282



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for generateLeaderboardJSON function implementation
rg -n "func generateLeaderboardJSON|TEST_LEADERBOARD" --type=go -C5

Length of output: 926



---

🏁 Script executed:

```shell
#!/bin/bash
# Extract generateLeaderboardJSON function from main.go
# Find function start at line 152 and show next ~50 lines or until next func
sed -n '152,220p' main.go

Length of output: 1936


generateLeaderboardJSON must check TEST_LEADERBOARD and skip live API calls during testing

The test sets TEST_LEADERBOARD=1 but generateLeaderboardJSON (main.go:152) unconditionally fetches from GitHub API, causing the test to hit the network and flake on rate limits. Add a check at the start of generateLeaderboardJSON:

if os.Getenv("TEST_LEADERBOARD") == "1" {
	// Use mock contributors for testing instead of live API
	// ... load or construct test data ...
	return nil
}

This must be implemented before the HTTP request at line 161.

🤖 Prompt for AI Agents
In main.go around lines 152 to 161, generateLeaderboardJSON currently always
calls the GitHub API; add an early check for TEST_LEADERBOARD
(os.Getenv("TEST_LEADERBOARD") == "1") before the HTTP request and short-circuit
to use or construct mock contributor data and write the same leaderboardJSON
output the test expects (or return the appropriate value) so the function does
not perform live network calls during tests; ensure the mock data matches the
Contributor structure and that leaderboardJSON is produced so
TestLeaderboardGeneration can read and validate it.

Comment on lines +60 to +74
var tpl = template.Must(template.New("").Funcs(template2.FuncMap{
"min": func(a, b int) int {
if a < b {
return a
}
return b
},
"add": func(nums ...int) int {
sum := 0
for _, n := range nums {
sum += n
}
return sum
},
}).ParseFS(tplFs, "tmpl/*.tmpl.html", "tmpl/*.tmpl.xml"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

FuncMap type mismatch: use text/template.FuncMap (or switch fully to html/template)

You pass html/template.FuncMap to text/template.Funcs, which is a different type and won’t compile. Use template.FuncMap here. Optionally, consider moving all HTML templates to html/template for auto-escaping.

Apply this minimal fix:

-var tpl = template.Must(template.New("").Funcs(template2.FuncMap{
+var tpl = template.Must(template.New("").Funcs(template.FuncMap{
   "min": func(a, b int) int {
@@
   },
 }).ParseFS(tplFs, "tmpl/*.tmpl.html", "tmpl/*.tmpl.xml"))

If you’d like, I can propose a follow-up to split HTML (html/template) and XML (text/template) rendering.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var tpl = template.Must(template.New("").Funcs(template2.FuncMap{
"min": func(a, b int) int {
if a < b {
return a
}
return b
},
"add": func(nums ...int) int {
sum := 0
for _, n := range nums {
sum += n
}
return sum
},
}).ParseFS(tplFs, "tmpl/*.tmpl.html", "tmpl/*.tmpl.xml"))
var tpl = template.Must(template.New("").Funcs(template.FuncMap{
"min": func(a, b int) int {
if a < b {
return a
}
return b
},
"add": func(nums ...int) int {
sum := 0
for _, n := range nums {
sum += n
}
return sum
},
}).ParseFS(tplFs, "tmpl/*.tmpl.html", "tmpl/*.tmpl.xml"))
🤖 Prompt for AI Agents
In main.go around lines 60 to 74, the FuncMap passed to template.Funcs uses the
wrong type (html/template.FuncMap via template2); replace that with
template.FuncMap so the types match (or alternatively switch the whole file to
html/template and adjust imports). Concretely, change the FuncMap construction
to use template.FuncMap (and adjust the import aliases if needed) so
template.Funcs receives the correct type and the code compiles.

Comment on lines +150 to 217
// ---------------------- Generate Leaderboard JSON via GitHub API ----------------------

func generateLeaderboardJSON() error {
repoOwner := "avelino"
repoName := "awesome-go"

var contributors []Contributor
page := 1

for {
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/contributors?per_page=100&anon=1&page=%d", repoOwner, repoName, page)
req, _ := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil)

if token := os.Getenv("GITHUB_TOKEN"); token != "" {
req.Header.Set("Authorization", "token "+token)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("fetch contributors: %w", err)
}
if resp.Body != nil {
defer resp.Body.Close()
}

if resp.StatusCode != 200 {
return fmt.Errorf("github api error: status %d", resp.StatusCode)
}

var pageContributors []Contributor
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&pageContributors); err != nil {
return fmt.Errorf("decode github contributors: %w", err)
}

if len(pageContributors) == 0 {
break
}

for i, c := range pageContributors {
if c.Login == "" {
pageContributors[i].Login = "Anonymous Contributor"
pageContributors[i].HTMLURL = "#"
}
if c.AvatarURL == "" {
// Use GitHub-style default identicon based on login
pageContributors[i].AvatarURL = fmt.Sprintf(
"https://avatars.githubusercontent.com/%s?v=4",
url.PathEscape(pageContributors[i].Login),
)
}
}

contributors = append(contributors, pageContributors...)
page++
}

if err := mkdirAll(dir); err != nil {
return fmt.Errorf("create dir: %w", err)
if len(contributors) == 0 {
return errors.New("no contributors found from GitHub API")
}

data, _ := json.MarshalIndent(contributors, "", " ")
if err := os.WriteFile(leaderboardJSON, data, 0644); err != nil {
return fmt.Errorf("write leaderboard.json: %w", err)
}

fmt.Printf("✅ Generated leaderboard.json with %d contributors → %s\n", len(contributors), leaderboardJSON)
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden GitHub API calls: fix response body handling and basic error checks

  • defer Close inside a loop leaks descriptors until function returns.
  • NewRequest error is ignored.
  • Marshal error is ignored.

Apply this diff:

 func generateLeaderboardJSON() error {
@@
-        req, _ := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil)
+        req, err := http.NewRequestWithContext(context.Background(), "GET", apiURL, nil)
+        if err != nil {
+            return fmt.Errorf("build request: %w", err)
+        }
+        req.Header.Set("User-Agent", "awesome-go-leaderboard/1.0")
@@
-        if resp.Body != nil {
-            defer resp.Body.Close()
-        }
+        // Read and close body within the iteration
+        if resp.Body == nil {
+            return fmt.Errorf("empty response body: status %d", resp.StatusCode)
+        }
+        defer func(b io.ReadCloser) { _ = b.Close() }(resp.Body)
@@
-        if err := decoder.Decode(&pageContributors); err != nil {
+        if err := decoder.Decode(&pageContributors); err != nil {
             return fmt.Errorf("decode github contributors: %w", err)
         }
+        _ = resp.Body.Close()
@@
-    data, _ := json.MarshalIndent(contributors, "", "  ")
+    data, err := json.MarshalIndent(contributors, "", "  ")
+    if err != nil {
+        return fmt.Errorf("marshal leaderboard: %w", err)
+    }

Optionally, move http.DefaultClient to a client with a timeout and add basic 403 rate‑limit backoff.

🤖 Prompt for AI Agents
main.go around lines 150 to 217: fix several robustness issues when calling the
GitHub API: check and return the error from http.NewRequestWithContext instead
of ignoring it; ensure resp.Body is closed immediately per-iteration (do not use
a loop-level defer that accumulates — either call resp.Body.Close() explicitly
after reading, or wrap the per-page logic in a small func and defer inside it so
the body is closed each iteration); handle json.MarshalIndent error instead of
discarding it; replace http.DefaultClient with a client that has a reasonable
timeout and add a simple 403-rate-limit backoff/retry (detect StatusCode 403 and
Retry-After or exponential backoff with a few retries) before failing. Ensure
all error messages include context when returned.

Comment on lines +282 to 349
func renderContributorsPage() error {
// Read leaderboard JSON
data, err := os.ReadFile(leaderboardJSON)
if err != nil {
return fmt.Errorf("read leaderboard.json: %w", err)
}

return nil
var contributors []Contributor
if err := json.Unmarshal(data, &contributors); err != nil {
return fmt.Errorf("parse leaderboard.json: %w", err)
}

if contributors == nil {
contributors = []Contributor{}
}

// Split contributors into sections
top3 := contributors
if len(top3) > 3 {
top3 = contributors[:3]
}

top10 := []Contributor{}
if len(contributors) > 3 {
if len(contributors) > 13 {
top10 = contributors[3:13]
} else {
top10 = contributors[3:]
}
}

top100 := []Contributor{}
if len(contributors) > 13 {
if len(contributors) > 113 {
top100 = contributors[13:113]
} else {
top100 = contributors[13:]
}
}

others := []Contributor{}
if len(contributors) > 113 {
others = contributors[113:]
}

// Ensure output directory exists
if err := mkdirAll(outContributorsDir); err != nil {
return fmt.Errorf("create contributors dir: %w", err)
}

// Create HTML file
outFile := filepath.Join(outContributorsDir, "index.html")
f, err := os.Create(outFile)
if err != nil {
return fmt.Errorf("create contributors file: %w", err)
}
defer f.Close()

fmt.Println("Render Contributors Leaderboard →", outFile)

// Execute template
return tpl.Lookup("contributors.tmpl.html").Execute(f, map[string]interface{}{
"Top3": top3,
"Top10": top10,
"Top100": top100,
"Others": others,
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Implement test mode to avoid live network calls in CI

Tests set TEST_LEADERBOARD=1, but generateLeaderboardJSON doesn’t honor it. Provide a small in-process mock path to keep tests hermetic.

Apply this diff near the start of generateLeaderboardJSON:

 func generateLeaderboardJSON() error {
+    if os.Getenv("TEST_LEADERBOARD") == "1" {
+        mock := []Contributor{
+            {Login: "alice", AvatarURL: "https://avatars.githubusercontent.com/u/1?v=4", HTMLURL: "https://github.com/alice", Contributions: 100},
+            {Login: "bob",   AvatarURL: "https://avatars.githubusercontent.com/u/2?v=4", HTMLURL: "https://github.com/bob",   Contributions: 80},
+            {Login: "carol", AvatarURL: "https://avatars.githubusercontent.com/u/3?v=4", HTMLURL: "https://github.com/carol", Contributions: 60},
+            {Login: "dave",  AvatarURL: "https://avatars.githubusercontent.com/u/4?v=4", HTMLURL: "https://github.com/dave",  Contributions: 40},
+        }
+        data, err := json.MarshalIndent(mock, "", "  ")
+        if err != nil {
+            return fmt.Errorf("marshal mock leaderboard: %w", err)
+        }
+        if err := os.WriteFile(leaderboardJSON, data, 0644); err != nil {
+            return fmt.Errorf("write mock leaderboard.json: %w", err)
+        }
+        return nil
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In main.go around lines 282-349, add support for TEST_LEADERBOARD to make
generateLeaderboardJSON hermetic during CI by short-circuiting live network
calls: if os.Getenv("TEST_LEADERBOARD") == "1" then write a small deterministic
mock leaderboard JSON to a local temp/test fixture path (or set leaderboardJSON
to that path) and return that path immediately instead of performing network
requests; ensure generateLeaderboardJSON uses that path so the rest of
renderContributorsPage can read it, and keep normal network behavior when
TEST_LEADERBOARD is not set.

Comment on lines +436 to +440
linkURL, err := url.Parse(href)
if err == nil && linkURL.Fragment != "" && linkURL.Fragment != "contents" {
s.SetAttr("href", linkURL.Fragment)
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Anchor rewriting drops the leading #

Setting href to linkURL.Fragment removes the ‘#’, breaking in-page navigation.

Apply this diff:

-        if err == nil && linkURL.Fragment != "" && linkURL.Fragment != "contents" {
-            s.SetAttr("href", linkURL.Fragment)
-        }
+        if err == nil && linkURL.Fragment != "" && linkURL.Fragment != "contents" {
+            s.SetAttr("href", "#"+linkURL.Fragment)
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
linkURL, err := url.Parse(href)
if err == nil && linkURL.Fragment != "" && linkURL.Fragment != "contents" {
s.SetAttr("href", linkURL.Fragment)
}
})
linkURL, err := url.Parse(href)
if err == nil && linkURL.Fragment != "" && linkURL.Fragment != "contents" {
s.SetAttr("href", "#"+linkURL.Fragment)
}
})
🤖 Prompt for AI Agents
In main.go around lines 436 to 440, the anchor rewriting sets href to
linkURL.Fragment which drops the leading ‘#’ and breaks in-page navigation;
update the assignment to prepend a ‘#’ when setting href (e.g., set href to "#"
+ linkURL.Fragment) so the resulting href remains a valid in-page anchor,
keeping the existing checks for non-empty and non-"contents" fragments.

Comment on lines +146 to +151
<a href="{{ $c.HTMLURL }}" target="_blank" class="card {{if eq $index 0}}top1{{else if eq $index 1}}top2{{else if eq $index 2}}top3{{end}}">
<div class="medal">{{ add $index 1 }}</div>
<img src="{{ $c.AvatarURL }}" alt="{{ $c.Login }}">
<h3>{{ $c.Login }}</h3>
<p>Contributions: {{ $c.Contributions }}</p>
</a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add rel="noopener noreferrer" on external links opened in new tabs

Prevent tabnabbing and reduce window.opener risks.

Apply this diff:

-    <a href="{{ $c.HTMLURL }}" target="_blank" class="card {{if eq $index 0}}top1{{else if eq $index 1}}top2{{else if eq $index 2}}top3{{end}}">
+    <a href="{{ $c.HTMLURL }}" target="_blank" rel="noopener noreferrer" class="card {{if eq $index 0}}top1{{else if eq $index 1}}top2{{else if eq $index 2}}top3{{end}}">
@@
-    <a href="{{ .HTMLURL }}" target="_blank" class="card">
+    <a href="{{ .HTMLURL }}" target="_blank" rel="noopener noreferrer" class="card">
@@
-    <a href="{{ .HTMLURL }}" target="_blank" class="card">
+    <a href="{{ .HTMLURL }}" target="_blank" rel="noopener noreferrer" class="card">
@@
-    <a href="{{ .HTMLURL }}" target="_blank" class="card">
+    <a href="{{ .HTMLURL }}" target="_blank" rel="noopener noreferrer" class="card">

Also applies to: 160-164, 173-177, 186-190

🤖 Prompt for AI Agents
In tmpl/contributors.tmpl.html around lines 146-151 (and also update occurrences
at 160-164, 173-177, 186-190), the anchor tags use target="_blank" without rel
attributes; add rel="noopener noreferrer" to each external link opened in a new
tab to prevent tabnabbing and remove access to window.opener. Modify each <a ...
target="_blank"> to include rel="noopener noreferrer" while preserving existing
classes and attributes.

Comment on lines +20 to +27
<link rel="stylesheet" type="text/css" href="/tmpl/assets/fonts/firasans.css" />
<link rel="stylesheet" type="text/css" href="/tmpl/assets/normalize.css" />
<link rel="stylesheet" type="text/css" href="/tmpl/assets/awesome-go.css" />

<!--ICONS-->
<link rel="icon" href="/assets/favicon/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="/assets/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/assets/favicon/manifest.json" />
<link rel="icon" href="/tmpl/assets/favicon/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="/tmpl/assets/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/tmpl/assets/favicon/manifest.json" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Broken asset paths: use /assets not /tmpl/assets

Templates reference /tmpl/assets/* but the build copies tmpl/assets → out/assets. On the site, /tmpl/assets won’t exist, breaking CSS and icons. Point to /assets/*.

Apply this diff:

-    <link rel="stylesheet" type="text/css" href="/tmpl/assets/fonts/firasans.css" />
-    <link rel="stylesheet" type="text/css" href="/tmpl/assets/normalize.css" />
-    <link rel="stylesheet" type="text/css" href="/tmpl/assets/awesome-go.css" />
+    <link rel="stylesheet" type="text/css" href="/assets/fonts/firasans.css" />
+    <link rel="stylesheet" type="text/css" href="/assets/normalize.css" />
+    <link rel="stylesheet" type="text/css" href="/assets/awesome-go.css" />
@@
-    <link rel="icon" href="/tmpl/assets/favicon/favicon.ico" type="image/x-icon" />
-    <link rel="apple-touch-icon" href="/tmpl/assets/favicon/apple-touch-icon.png" />
-    <link rel="manifest" href="/tmpl/assets/favicon/manifest.json" />
+    <link rel="icon" href="/assets/favicon/favicon.ico" type="image/x-icon" />
+    <link rel="apple-touch-icon" href="/assets/favicon/apple-touch-icon.png" />
+    <link rel="manifest" href="/assets/favicon/manifest.json" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<link rel="stylesheet" type="text/css" href="/tmpl/assets/fonts/firasans.css" />
<link rel="stylesheet" type="text/css" href="/tmpl/assets/normalize.css" />
<link rel="stylesheet" type="text/css" href="/tmpl/assets/awesome-go.css" />
<!--ICONS-->
<link rel="icon" href="/assets/favicon/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="/assets/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/assets/favicon/manifest.json" />
<link rel="icon" href="/tmpl/assets/favicon/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="/tmpl/assets/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/tmpl/assets/favicon/manifest.json" />
<link rel="stylesheet" type="text/css" href="/assets/fonts/firasans.css" />
<link rel="stylesheet" type="text/css" href="/assets/normalize.css" />
<link rel="stylesheet" type="text/css" href="/assets/awesome-go.css" />
<!--ICONS-->
<link rel="icon" href="/assets/favicon/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="/assets/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/assets/favicon/manifest.json" />
🤖 Prompt for AI Agents
In tmpl/index.tmpl.html around lines 20 to 27 the asset references use
/tmpl/assets/* but the build copies tmpl/assets to out/assets so those paths
will 404; update each href/src/manifest path to point to /assets/* (e.g.
/assets/fonts/firasans.css, /assets/normalize.css, /assets/awesome-go.css,
/assets/favicon/favicon.ico, /assets/favicon/apple-touch-icon.png,
/assets/favicon/manifest.json) so the generated site loads the CSS and icons
correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant