-
-
Notifications
You must be signed in to change notification settings - Fork 408
feat: add github stars, github issues & created at to comparison page (#2460) #2479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
144e806
e4886a5
8d2cff7
3a99631
9c6f4d8
b517792
0127286
8434189
3865fc1
2693e16
3e4fccd
9df70fe
f64fd04
85c59be
9cb87b1
e29ae1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { CACHE_MAX_AGE_ONE_HOUR } from '#shared/utils/constants' | ||
|
|
||
| interface GitHubSearchResponse { | ||
| total_count: number | ||
| } | ||
|
|
||
| export interface GithubIssueCountResponse { | ||
| owner: string | ||
| repo: string | ||
| issues: number | null | ||
| } | ||
|
|
||
| export default defineCachedEventHandler( | ||
| async (event): Promise<GithubIssueCountResponse> => { | ||
| const owner = getRouterParam(event, 'owner') | ||
| const repo = getRouterParam(event, 'repo') | ||
|
|
||
| if (!owner || !repo) { | ||
| throw createError({ | ||
| statusCode: 400, | ||
| statusMessage: 'Owner and repo are required parameters.', | ||
| }) | ||
| } | ||
|
|
||
| const query = `repo:${owner}/${repo} is:issue is:open` | ||
| const url = `https://api.github.com/search/issues?q=${encodeURIComponent(query)}&per_page=1` | ||
|
|
||
| try { | ||
| const data = await fetchGitHubWithRetries<GitHubSearchResponse>(url, { | ||
| timeout: 10000, | ||
| }) | ||
|
|
||
| return { | ||
| owner, | ||
| repo, | ||
| issues: typeof data?.total_count === 'number' ? data.total_count : null, | ||
| } | ||
| } catch { | ||
| throw createError({ | ||
| statusCode: 500, | ||
| statusMessage: 'Failed to fetch issue count from GitHub', | ||
| }) | ||
| } | ||
| }, | ||
| { | ||
| maxAge: CACHE_MAX_AGE_ONE_HOUR, | ||
| swr: true, | ||
| name: 'github-issue-count', | ||
| getKey: event => { | ||
| const owner = getRouterParam(event, 'owner') | ||
| const repo = getRouterParam(event, 'repo') | ||
| return `${owner}/${repo}` | ||
| }, | ||
| }, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { setTimeout } from 'node:timers/promises' | ||
|
|
||
| export interface GitHubFetchOptions extends NonNullable<Parameters<typeof $fetch.raw>[1]> { | ||
| maxAttempts?: number | ||
| } | ||
|
|
||
| export async function fetchGitHubWithRetries<T>( | ||
| url: string, | ||
| options: GitHubFetchOptions = {}, | ||
| ): Promise<T | null> { | ||
| const { maxAttempts = 3, ...fetchOptions } = options | ||
| let delayMs = 1000 | ||
alexdln marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const defaultHeaders = { | ||
| 'Accept': 'application/vnd.github+json', | ||
| 'User-Agent': 'npmx', | ||
| 'X-GitHub-Api-Version': '2026-03-10', | ||
| } | ||
|
|
||
| for (let attempt = 0; attempt < maxAttempts; attempt += 1) { | ||
| try { | ||
| const response = await $fetch.raw(url, { | ||
| ...fetchOptions, | ||
| headers: { | ||
| ...defaultHeaders, | ||
| ...fetchOptions.headers, | ||
| }, | ||
|
Comment on lines
+22
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "github.ts" | grep -E "(server|utils)" | head -5Repository: npmx-dev/npmx.dev Length of output: 85 🏁 Script executed: git ls-files | grep -E "server/utils/github" | head -10Repository: npmx-dev/npmx.dev Length of output: 83 🏁 Script executed: fd -t f "github.ts" server/Repository: npmx-dev/npmx.dev Length of output: 83 🏁 Script executed: wc -l server/utils/github.tsRepository: npmx-dev/npmx.dev Length of output: 86 🏁 Script executed: cat -n server/utils/github.ts | head -50Repository: npmx-dev/npmx.dev Length of output: 1665 🏁 Script executed: rg -t ts "type NitroFetchOptions" --max-count 5Repository: npmx-dev/npmx.dev Length of output: 43 🏁 Script executed: fd -t json "*.json" | xargs grep -l "nitropack" 2>/dev/null | head -3Repository: npmx-dev/npmx.dev Length of output: 232 🌐 Web query:
💡 Result: The GitHub REST API version 2026-03-10 was released on March 10, 2026, and is now available as the latest version. It is fully supported alongside the previous version 2022-11-28, which remains supported for at least 24 months. To use it, specify the header: X-GitHub-Api-Version: 2026-03-10 Example: curl --header "X-GitHub-Api-Version: 2026-03-10" https://api.github.com/zen Requests without the header default to 2022-11-28. Official documentation is available with a version picker at https://docs.github.com/en/rest?apiVersion=2026-03-10. Select the version in the docs to view endpoint-specific details. Breaking changes in v2026-03-10 (upgrade guidance): - Remove deprecated 'rate' property from GET /rate_limit (use resources.core instead). - Remove deprecated 'permission' property from team creation requests. - Updates to "Get repository content" for submodules. - SARIF upload response changes. - Remove 'javascript' and 'typescript' from code scanning languages enum (use 'javascript-typescript'). - Various attestation endpoint changes. - Other deprecations like authorizations_url, has_downloads, etc. Full list and migration details: https://docs.github.com/en/rest/about-the-rest-api/breaking-changes?apiVersion=2026-03-10 Additive changes are available in all versions. Review breaking changes before upgrading, update the header, and test. API versions info: https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2026-03-10 Citations:
🏁 Script executed: find . -type f -name "*.d.ts" | xargs grep -l "NitroFetchOptions" 2>/dev/null | head -3Repository: npmx-dev/npmx.dev Length of output: 43 🏁 Script executed: cat -n server/utils/github.ts | sed -n '1,57p' | tail -10Repository: npmx-dev/npmx.dev Length of output: 369 🏁 Script executed: cat package.json | grep -A 5 -B 5 "nitropack"Repository: npmx-dev/npmx.dev Length of output: 43 🏁 Script executed: find . -path ./node_modules -prune -o -type f -name "*.d.ts" -print | xargs grep -l "export.*NitroFetchOptions" 2>/dev/null | head -5Repository: npmx-dev/npmx.dev Length of output: 43 🌐 Web query:
💡 Result: The NitroFetchOptions interface extends FetchOptions from ofetch and adds Nitro-specific typing for API route calls. Key definition from Nitro source discussions and type proposals: ts interface NitroFetchOptions<R extends NitroFetchRequest, M extends AvailableRouterMethod = AvailableRouterMethod> extends FetchOptions { method?: Uppercase | M; body?: HandlerBodyType<R, M>; query?: HandlerQueryType<R, M>; } The headers property inherits directly from FetchOptions, which defines it as: - headers?: MaybeRefOrGetter<Record<string, string> | [key: string, value: string][] | Headers> This allows headers to be a plain object { [key: string]: string }, array of tuples, or Headers instance, and supports reactive refs or getters. Usage example from community wrappers: ts import type { NitroFetchOptions, NitroFetchRequest } from 'nitropack' const $api = async <T = unknown, R extends NitroFetchRequest = NitroFetchRequest, O extends NitroFetchOptions = NitroFetchOptions>( url: R, options?: O ) => $fetch(url, { ...options, headers: { ...useRequestHeaders(['cookie']), ...options?.headers } }) Official Nitro docs reference $fetch options matching ofetch FetchOptions, confirming standard web Fetch API headers support with Nitro route typing enhancements. Types are auto-generated in .nitro/types/nitro-routes.d.ts for IDE inference. Citations:
🏁 Script executed: cat package.jsonRepository: npmx-dev/npmx.dev Length of output: 5687 Fix headers merge to support all At lines 25–28, object spread only works reliably for plain objects. If Suggested fix- const response = await $fetch.raw(url, {
- ...fetchOptions,
- headers: {
- ...defaultHeaders,
- ...fetchOptions.headers,
- },
- })
+ const headers = new Headers(defaultHeaders)
+ for (const [key, value] of new Headers(fetchOptions.headers)) {
+ headers.set(key, value)
+ }
+
+ const response = await $fetch.raw(url, {
+ ...fetchOptions,
+ headers,
+ })Per coding guidelines, "Ensure you write strictly type-safe code". 🤖 Prompt for AI Agents |
||
| }) | ||
|
|
||
| if (response.status === 200) { | ||
| return (response._data as T) ?? null | ||
| } | ||
|
|
||
| if (response.status === 204) { | ||
| return null | ||
| } | ||
|
|
||
| if (response.status === 202) { | ||
| if (attempt === maxAttempts - 1) break | ||
| await setTimeout(delayMs) | ||
| delayMs = Math.min(delayMs * 2, 16_000) | ||
| continue | ||
| } | ||
|
|
||
| break | ||
| } catch (error: unknown) { | ||
| if (attempt === maxAttempts - 1) { | ||
| throw error | ||
| } | ||
| await setTimeout(delayMs) | ||
| delayMs = Math.min(delayMs * 2, 16_000) | ||
| } | ||
| } | ||
|
|
||
| throw new Error(`Failed to fetch from GitHub after ${maxAttempts} attempts`) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 43
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 88
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 88
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 125
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 2684
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 6996
🏁 Script executed:
# Read the full implementation of fetchGitHubWithRetries head -100 server/utils/github.tsRepository: npmx-dev/npmx.dev
Length of output: 1485
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 941
Add an explicit timeout for the GitHub call.
The
fetchGitHubWithRetriesfunction supports timeout viaNitroFetchOptions. Line 30–32 configures 6 retry attempts but lacks a request timeout, leaving each attempt vulnerable to indefinite blocking. The same pattern is used elsewhere in the codebase withtimeout: 10000(seeserver/api/github/issues/[owner]/[repo].get.ts).Suggested fix
const data = await fetchGitHubWithRetries<GitHubContributorStats[]>(url, { maxAttempts: 6, + timeout: 10_000, })📝 Committable suggestion
🤖 Prompt for AI Agents