mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-05 20:54:56 +00:00
*PR Created by the Glary-Bot Agent*
---
## Problem
The GitHub star badge silently disappears from the comfy.org navigation.
Verified by curl-ing the live homepage:
```
props="{..."github-stars":[0,""]}"
```
`SiteNav.vue` only renders the badge when `githubStars` is truthy, so an
empty string hides it.
## Root cause
`apps/website/src/layouts/BaseLayout.astro` `await`s
`fetchGitHubStars('Comfy-Org', 'ComfyUI')` in its frontmatter. Astro
evaluates layout frontmatter **per rendered page** in SSG. With 379
pages (46 source `.astro` files × locales/dynamic routes), the
unauthenticated GitHub REST endpoint is called hundreds of times per
build, blasting past the 60 req/h anonymous rate limit. Once GitHub
returns 403 the existing `try/catch` returns `null`, `githubStars`
becomes `''`, and the badge vanishes — with no log line to indicate why.
## Fix
Cache the in-flight promise in a module-scope `Map` keyed by
`${owner}/${repo}` so every page in a single build shares one request.
Already-resolved counts stay cached, and the existing
`WEBSITE_GITHUB_STARS_OVERRIDE` env-var escape hatch still
short-circuits first.
While in the file:
- Pass an injectable `fetchImpl` so tests can stub without
`vi.spyOn(globalThis, 'fetch')`.
- Replace the implicit-`any` `data.stargazers_count ?? null` with a
narrow `readStargazerCount(data: unknown)` guard.
- In `BaseLayout.astro`, change `rawStars ? ...` to `rawStars !== null ?
...` so a hypothetical 0-star repo wouldn't be hidden (the old check
treated 0 as missing).
## Verification
- `pnpm --filter @comfyorg/website test:unit` → 89/89 pass (5 new test
cases: memoization, per-key isolation, non-2xx → null, throw → null,
override).
- `pnpm typecheck:website` → 0 errors.
- `pnpm format:check` → clean.
- `pnpm --filter @comfyorg/website build` → 379 pages built; with no
override set, output HTML contains `"github-stars":[0,"115K"]` (the live
count) on every page; with `WEBSITE_GITHUB_STARS_OVERRIDE=110000`, it
contains `"110K"` and `fetch` is never called.
- Playwright on the local preview confirms the badge renders at the
top-right of the nav with `aria-label="ComfyUI on GitHub — 110K stars"`.
## Scope
102 lines changed across 3 files (40 non-test). Deliberately leaves the
broader "snapshot fallback / build-data source" refactor to the existing
`codex/website-github-stars-once` branch — this PR just unblocks the
user-visible symptom.
## Screenshots

---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
74 lines
1.9 KiB
TypeScript
74 lines
1.9 KiB
TypeScript
const inflight = new Map<string, Promise<number | null>>()
|
|
|
|
export function resetGitHubStarsFetcherForTests(): void {
|
|
inflight.clear()
|
|
}
|
|
|
|
export async function fetchGitHubStars(
|
|
owner: string,
|
|
repo: string,
|
|
fetchImpl: typeof fetch = fetch
|
|
): Promise<number | null> {
|
|
const override = readGitHubStarsOverride()
|
|
if (override !== undefined) return override
|
|
|
|
const key = `${owner}/${repo}`
|
|
const cached = inflight.get(key)
|
|
if (cached) return cached
|
|
|
|
const request = doFetch(owner, repo, fetchImpl)
|
|
inflight.set(key, request)
|
|
return request
|
|
}
|
|
|
|
async function doFetch(
|
|
owner: string,
|
|
repo: string,
|
|
fetchImpl: typeof fetch
|
|
): Promise<number | null> {
|
|
try {
|
|
const res = await fetchImpl(
|
|
`https://api.github.com/repos/${owner}/${repo}`,
|
|
{ headers: { Accept: 'application/vnd.github.v3+json' } }
|
|
)
|
|
if (!res.ok) return null
|
|
const data: unknown = await res.json()
|
|
return readStargazerCount(data)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function readStargazerCount(data: unknown): number | null {
|
|
if (data === null || typeof data !== 'object') return null
|
|
if (!('stargazers_count' in data)) return null
|
|
const count = data.stargazers_count
|
|
return typeof count === 'number' ? count : null
|
|
}
|
|
|
|
export function formatStarCount(count: number): string {
|
|
if (count >= 1_000_000) {
|
|
const m = count / 1_000_000
|
|
return `${m >= 10 ? Math.round(m) : m.toFixed(1).replace(/\.0$/, '')}M`
|
|
}
|
|
if (count >= 1_000) {
|
|
const k = count / 1_000
|
|
return `${k >= 10 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, '')}K`
|
|
}
|
|
return count.toString()
|
|
}
|
|
|
|
function readGitHubStarsOverride(): number | undefined {
|
|
const rawCount = process.env.WEBSITE_GITHUB_STARS_OVERRIDE
|
|
if (rawCount === undefined || rawCount === '') return undefined
|
|
|
|
const count = Number(rawCount)
|
|
if (!Number.isSafeInteger(count) || count < 0) {
|
|
throw new Error(
|
|
'WEBSITE_GITHUB_STARS_OVERRIDE must be a non-negative integer'
|
|
)
|
|
}
|
|
|
|
return count
|
|
}
|