mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-08 15:29:52 +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>
103 lines
3.2 KiB
TypeScript
103 lines
3.2 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import {
|
|
fetchGitHubStars,
|
|
formatStarCount,
|
|
resetGitHubStarsFetcherForTests
|
|
} from './github'
|
|
|
|
describe('fetchGitHubStars', () => {
|
|
const savedOverride = process.env.WEBSITE_GITHUB_STARS_OVERRIDE
|
|
|
|
afterEach(() => {
|
|
resetGitHubStarsFetcherForTests()
|
|
vi.restoreAllMocks()
|
|
if (savedOverride === undefined)
|
|
delete process.env.WEBSITE_GITHUB_STARS_OVERRIDE
|
|
else process.env.WEBSITE_GITHUB_STARS_OVERRIDE = savedOverride
|
|
})
|
|
|
|
it('uses the build-time override without calling GitHub', async () => {
|
|
process.env.WEBSITE_GITHUB_STARS_OVERRIDE = '110000'
|
|
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
|
|
|
await expect(fetchGitHubStars('Comfy-Org', 'ComfyUI')).resolves.toBe(110000)
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('fails fast when the build-time override is malformed', async () => {
|
|
process.env.WEBSITE_GITHUB_STARS_OVERRIDE = '110K'
|
|
|
|
await expect(fetchGitHubStars('Comfy-Org', 'ComfyUI')).rejects.toThrow(
|
|
'WEBSITE_GITHUB_STARS_OVERRIDE must be a non-negative integer'
|
|
)
|
|
})
|
|
|
|
it('memoizes concurrent fetches for the same repo to one network call', async () => {
|
|
const fetchImpl = vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ stargazers_count: 110000 }), {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' }
|
|
})
|
|
)
|
|
|
|
const [a, b, c] = await Promise.all([
|
|
fetchGitHubStars('Comfy-Org', 'ComfyUI', fetchImpl as typeof fetch),
|
|
fetchGitHubStars('Comfy-Org', 'ComfyUI', fetchImpl as typeof fetch),
|
|
fetchGitHubStars('Comfy-Org', 'ComfyUI', fetchImpl as typeof fetch)
|
|
])
|
|
|
|
expect(a).toBe(110000)
|
|
expect(b).toBe(110000)
|
|
expect(c).toBe(110000)
|
|
expect(fetchImpl).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('keys the in-flight cache by owner/repo', async () => {
|
|
const fetchImpl = vi.fn(async (url: string | URL | Request) => {
|
|
const href = typeof url === 'string' ? url : url.toString()
|
|
const count = href.includes('other-repo') ? 42 : 110000
|
|
return new Response(JSON.stringify({ stargazers_count: count }), {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' }
|
|
})
|
|
})
|
|
|
|
const [comfy, other] = await Promise.all([
|
|
fetchGitHubStars('Comfy-Org', 'ComfyUI', fetchImpl as typeof fetch),
|
|
fetchGitHubStars('Comfy-Org', 'other-repo', fetchImpl as typeof fetch)
|
|
])
|
|
|
|
expect(comfy).toBe(110000)
|
|
expect(other).toBe(42)
|
|
expect(fetchImpl).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('returns null when GitHub responds non-2xx', async () => {
|
|
const fetchImpl = vi.fn(
|
|
async () => new Response('rate limited', { status: 403 })
|
|
)
|
|
|
|
await expect(
|
|
fetchGitHubStars('Comfy-Org', 'ComfyUI', fetchImpl as typeof fetch)
|
|
).resolves.toBeNull()
|
|
})
|
|
|
|
it('returns null when fetch throws', async () => {
|
|
const fetchImpl = vi.fn(async () => {
|
|
throw new Error('network down')
|
|
})
|
|
|
|
await expect(
|
|
fetchGitHubStars('Comfy-Org', 'ComfyUI', fetchImpl as typeof fetch)
|
|
).resolves.toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('formatStarCount', () => {
|
|
it('formats the visual-test override to match committed snapshots', () => {
|
|
expect(formatStarCount(110000)).toBe('110K')
|
|
})
|
|
})
|