Skip to content

feat: add github stars, github issues & created at to comparison page (#2460)#2479

Open
t128n wants to merge 16 commits intonpmx-dev:mainfrom
t128n:feat/compare
Open

feat: add github stars, github issues & created at to comparison page (#2460)#2479
t128n wants to merge 16 commits intonpmx-dev:mainfrom
t128n:feat/compare

Conversation

@t128n
Copy link
Copy Markdown
Contributor

@t128n t128n commented Apr 11, 2026

🔗 Linked issue

Resolves #2460

🧭 Context

In #2460, additional facets were requested including:

  • GitHub Issues
  • GitHub Stars
  • Package Creation Date

This PR implements these facets.

📚 Description

  1. The Created at facet was implemented using the information already available information in pkgData.
  2. The GitHub Stars facet was implemented by introducing another fetch call in the existing Promise.all for additional data fetching for the comparison. It uses the parseRepositoryInfo function from shared/utils/git-providers.ts to ensure that the fetch is only run for GitHub repositories. It then extracts the owner and repo name from the URL stored in the package.json of the package being compared. Afterwards, it makes a direct fetch to ungh.cc/repos/owner/repo and extracts { repo: { stars: number } } from the returned payload.
  3. The GitHub Issues facet was implemented by introducing another API endpoint in server/api/github/issues/[owner]/[repo].get.ts. First, with help from parseRepositoryInfo, the provider is checked, then a fetch against that API endpoint is made. The API endpoint itself queries GitHub’s REST API to get the number of open issues from https://api.github.com/search/issues?q=repo:${owner}/${repo}%20is:issue is:open&per_page=1. Those issues are then returned to the Promise.all and used.

Localization keys were added to en.json, and tests were implemented for the newly introduced facets.

📷 Screenshots

image image

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Apr 12, 2026 1:02pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Apr 12, 2026 1:02pm
npmx-lunaria Ignored Ignored Apr 12, 2026 1:02pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 11, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
i18n/locales/en.json Source changed, localizations will be marked as outdated.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@t128n t128n changed the title feat: add poc for github stars, github issues & created at comparison (#2460) [WIP] feat: add github stars, github issues & created at to comparison page (#2460) [WIP] Apr 11, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 11, 2026

Codecov Report

❌ Patch coverage is 65.71429% with 12 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/utils/compare-scatter-chart.ts 0.00% 6 Missing and 2 partials ⚠️
app/composables/useFacetSelection.ts 0.00% 2 Missing ⚠️
app/composables/usePackageComparison.ts 92.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@graphieros
Copy link
Copy Markdown
Contributor

graphieros commented Apr 12, 2026

@t128n

Very nice addition.
A few remarks while this is a draft:

  • consider having a neutral color for bars for the stars and issues in the table (perhaps it is already the case)
  • the createdAt field should not be displayed with a background bar in the table
  • I recommend you pull the last updates from main, since the quadrant chart was replaced by a scatter chart, where facets can be graphed (see feat: replace quadrant with scatter with selectable axes #2472). Stars and issues can be added as part of chartable_scatter facets :)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 75906978-a7d4-4c22-94cb-6198acb98734

📥 Commits

Reviewing files that changed from the base of the PR and between 9cb87b1 and e29ae1f.

📒 Files selected for processing (1)
  • server/utils/github.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/utils/github.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Compare packages by GitHub stars, open issues and package creation date; added health facets and a cached server-backed issue-count endpoint.
    • Improved GitHub data fetching with robust retry behaviour for repository stats.
  • Tests

    • Added comprehensive tests for GitHub metadata handling and the createdAt facet (positive, negative and non‑GitHub cases).
  • Documentation

    • Added translations and schema entries for the new comparison facets.

Walkthrough

Adds three comparison facets—githubStars, githubIssues, and createdAt—with i18n/schema entries, facet metadata, package metadata population (stars/issues/createdAt), scatter-chart numeric support, a cached server endpoint for issue counts, a GitHub fetch-with-retries util, and tests.

Changes

Cohort / File(s) Summary
Types & facet registry
shared/types/comparison.ts
Added githubStars, githubIssues, createdAt to ComparisonFacet and FACET_INFO (category: health).
Facet selection & labels
app/composables/useFacetSelection.ts
Extended facetLabels with the three new facets; githubStars/githubIssues set chartable/chartable_scatter and provide a numeric formatter; createdAt added (non-chartable).
Package comparison logic
app/composables/usePackageComparison.ts
Extended PackageComparisonData.metadata with createdAt and github stats; detect GitHub repos, fetch repo stars/issues (optional), and added facet value cases for githubStars, githubIssues, createdAt.
Scatter-chart numeric extraction
app/utils/compare-scatter-chart.ts
getNumericFacetValue now supports githubStars and githubIssues, returning numeric values from packageData.metadata.github or null.
Server API: issue count
server/api/github/issues/[owner]/[repo].get.ts
New cached endpoint that returns { owner, repo, issues } using GitHub search API; validates params and maps non-numeric counts to null.
Server GitHub utils
server/utils/github.ts
New fetchGitHubWithRetries util with exponential backoff and maxAttempts, plus related GitHubFetchOptions typing; used by server endpoints.
Contributors endpoint
server/api/github/contributors-evolution/[owner]/[repo].get.ts
Replaced local retry loop and manual timeout with fetchGitHubWithRetries and simplified response handling.
i18n & schema
i18n/locales/en.json, i18n/schema.json
Added translations and schema entries for compare.facets.items.githubStars, githubIssues, and createdAt.
Tests
test/nuxt/components/compare/FacetSelector.spec.ts, test/nuxt/composables/use-package-comparison.spec.ts
Updated test facet labels; added tests for GitHub stars/issues metadata (positive, malformed, non-GitHub) and createdAt facet behaviour.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (composable)
  participant Registry as npm Registry
  participant Server as Nuxt Server (cached API)
  participant GitHub as GitHub API
  participant Cache as Server Cache

  Client->>Registry: fetch package metadata (pkgData)
  Registry-->>Client: pkgData (includes repository URL, time.created)
  Client->>Client: detect GitHub repo from pkgData.repository
  alt repo is GitHub
    Client->>GitHub: fetch repo details (stars) or Server->>GitHub: via util
    GitHub-->>Client: repo data (stars) or null
    Client->>Server: GET /api/github/issues/{owner}/{repo}
    Server->>Cache: lookup 'github-issue-count' for owner/repo
    alt cache miss or stale
      Server->>GitHub: search issues (fetchGitHubWithRetries, retry/backoff)
      GitHub-->>Server: total_count or null
      Server->>Cache: store result
    end
    Server-->>Client: { owner, repo, issues }
  else non-GitHub
    Client-->>Client: set github metadata to null/undefined
  end
  Client-->>Client: populate PackageComparisonData.metadata (createdAt, github.{stars,issues})
  Client->>Client: compute facet values (githubStars, githubIssues, createdAt)
Loading

Possibly related PRs

Suggested reviewers

  • serhalp
  • graphieros
  • danielroe
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main changes: adding GitHub stars, GitHub issues, and created-at facets to the comparison page.
Description check ✅ Passed The description provides relevant context, implementation details for all three facets, and references the linked issue (#2460) that the PR resolves.
Linked Issues check ✅ Passed The PR fully implements all three requirements from issue #2460: GitHub stars, open GitHub issues, and package creation date, with proper facet integration and charting support.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the three requested facets; the GitHub utility refactoring in github.ts supports the new GitHub API integrations and is within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@t128n t128n changed the title feat: add github stars, github issues & created at to comparison page (#2460) [WIP] feat: add github stars, github issues & created at to comparison page (#2460) Apr 12, 2026
@t128n t128n marked this pull request as ready for review April 12, 2026 08:25
Copy link
Copy Markdown
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: 3

🧹 Nitpick comments (2)
test/nuxt/composables/use-package-comparison.spec.ts (1)

196-233: Add a regression test for malformed GitHub payloads.

Current coverage validates happy-path numeric responses, but not missing/invalid stars/issues fields. Add one case asserting the facet value is null (not 0) when the upstream payload shape is incomplete.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt/composables/use-package-comparison.spec.ts` around lines 196 - 233,
Add a regression test in use-package-comparison.spec.ts that uses
usePackageComparisonInComponent and the existing vi.stubGlobal('$fetch') pattern
but returns malformed GitHub payloads (e.g., responses missing repo.stars or
issues fields / non-numeric values) for the GitHub endpoints; after awaiting
status 'success' call getFacetValues('githubStars')[0] and
getFacetValues('githubIssues')[0] and assert each facet's value is null (not 0)
and the facet status is the expected fallback (e.g., neutral/invalid) to ensure
missing/invalid upstream shapes produce null facet values.
test/nuxt/components/compare/FacetSelector.spec.ts (1)

35-37: Keep facet fixture labels aligned with real locale strings.

The new fixture text drifts from production copy (notably createdAt: “Created” vs “Created At”). This makes selector text assertions less representative of real UI output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt/components/compare/FacetSelector.spec.ts` around lines 35 - 37, The
facet fixture labels in the test drift from production locale copy—update the
fixture entry keys (githubStars, githubIssues, createdAt) so their label values
exactly match the real locale strings (in particular change createdAt's label
from "Created" to "Created At") and verify any other fixture labels mirror the
app's locale source; locate these keys in the FacetSelector.spec.ts fixture and
adjust the label values accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/composables/usePackageComparison.ts`:
- Around line 147-155: The current .then handlers in usePackageComparison.ts
coerce missing/malformed GitHub metrics to 0 (via `|| 0`), which hides unknown
values; update the .then callbacks on the stars and issues $fetch calls so they
return null when the API payload doesn't include a numeric value (e.g., change
the arrow results for the repo stars and issues to return null for missing
values instead of 0), and keep the existing .catch(() => null). Locate the two
$fetch chains guarded by isGitHub and repoInfo (the calls that currently use
`res?.repo.stars || 0` and `res?.issues || 0`) and replace those expressions
with a check that yields null for absent/non-numeric metrics (so downstream
comparison logic can handle "unknown" rather than 0).

In `@i18n/schema.json`:
- Around line 3985-4019: The added facet keys use snake_case and must be renamed
to match the project's camelCase convention to avoid lookup mismatches: change
"github_stars" -> "githubStars", "github_issues" -> "githubIssues", and
"created_at" -> "createdAt" in the schema object (keeping their inner structure
and additionalProperties: false intact); also search for any other references to
these snake_case keys and update them to the camelCase identifiers used at
runtime so labels/descriptions resolve correctly.

In `@server/api/github/issues/`[owner]/[repo].get.ts:
- Around line 35-37: The call to $fetch<GitHubSearchResponse>(url, { headers:
GITHUB_HEADERS }) must include authentication and a request timeout to avoid
GitHub Search API rate limits; update the $fetch invocation that returns data to
add an Authorization header (bearer token read from environment/config and
merged into GITHUB_HEADERS) and a reasonable timeout option (e.g., a few
seconds) so requests fail fast and are retried/handled upstream; ensure the
token usage is conditional (fall back safely if absent) and propagate
errors/timeouts from this call so callers of this endpoint can handle
403/timeout responses.

---

Nitpick comments:
In `@test/nuxt/components/compare/FacetSelector.spec.ts`:
- Around line 35-37: The facet fixture labels in the test drift from production
locale copy—update the fixture entry keys (githubStars, githubIssues, createdAt)
so their label values exactly match the real locale strings (in particular
change createdAt's label from "Created" to "Created At") and verify any other
fixture labels mirror the app's locale source; locate these keys in the
FacetSelector.spec.ts fixture and adjust the label values accordingly.

In `@test/nuxt/composables/use-package-comparison.spec.ts`:
- Around line 196-233: Add a regression test in use-package-comparison.spec.ts
that uses usePackageComparisonInComponent and the existing
vi.stubGlobal('$fetch') pattern but returns malformed GitHub payloads (e.g.,
responses missing repo.stars or issues fields / non-numeric values) for the
GitHub endpoints; after awaiting status 'success' call
getFacetValues('githubStars')[0] and getFacetValues('githubIssues')[0] and
assert each facet's value is null (not 0) and the facet status is the expected
fallback (e.g., neutral/invalid) to ensure missing/invalid upstream shapes
produce null facet values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d78fa7b1-bac9-47b1-a5ad-cecade8af8dc

📥 Commits

Reviewing files that changed from the base of the PR and between 5cb6bc6 and 3865fc1.

📒 Files selected for processing (9)
  • app/composables/useFacetSelection.ts
  • app/composables/usePackageComparison.ts
  • app/utils/compare-scatter-chart.ts
  • i18n/locales/en.json
  • i18n/schema.json
  • server/api/github/issues/[owner]/[repo].get.ts
  • shared/types/comparison.ts
  • test/nuxt/components/compare/FacetSelector.spec.ts
  • test/nuxt/composables/use-package-comparison.spec.ts

@graphieros graphieros added the needs review This PR is waiting for a review from a maintainer label Apr 12, 2026
description: t(`compare.facets.items.created_at.description`),
chartable: false,
chartable_scatter: false,
formatter: v => new Date(v).toLocaleDateString(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The formatter for createdAt is unused and should be removed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in f64fd04

Copy link
Copy Markdown
Contributor

@graphieros graphieros left a comment

Choose a reason for hiding this comment

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

LGTM 🌿

@graphieros graphieros requested a review from serhalp April 12, 2026 09:35
Comment on lines +35 to +47
const maxAttempts = 3
let delayMs = 1000

for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
try {
const response = await $fetch.raw<GitHubSearchResponse>(url, {
headers: GITHUB_HEADERS,
timeout: 10000,
})

if (response.status === 200) {
return {
owner,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that the logic is essentially the same as server/api/github/contributors-evolution/[owner]/[repo].get.ts. Maybe it worse to create reusable wrapper?

Looks like the only diff is that here an error is thrown, and there just return []. So probably the current logic works better and for contributors-evolution it will be used as something like:

try {
  const data = await fetchWithRetries({ url, options, retries: 6 })
  return Array.isArray(data) ? data : []
} catch {
  return [];
}

const GITHUB_HEADERS = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'npmx',
'X-GitHub-Api-Version': '2022-11-28',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need to fixate on this specific version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. Honestly, I can't recall whether there was a specific reason I chose that version over the latest version... probably stale docs that I checked yesterday. I will address it. 👍🏻

Copy link
Copy Markdown
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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/api/github/contributors-evolution/`[owner]/[repo].get.ts:
- Around line 30-32: The fetch call to GitHub uses
fetchGitHubWithRetries<GitHubContributorStats[]> but omits a request timeout, so
update the options passed to fetchGitHubWithRetries (the call that assigns to
data) to include a timeout (e.g. timeout: 10000) alongside maxAttempts: 6; this
ensures each retry attempt will fail fast instead of potentially blocking
indefinitely. Target the invocation of fetchGitHubWithRetries in the handler for
contributors-evolution ([owner]/[repo].get.ts) and add the timeout property to
the NitroFetchOptions object.

In `@server/utils/github.ts`:
- Around line 12-13: After destructuring maxAttempts from options (const {
maxAttempts = 3, ...fetchOptions } = options) ensure it is clamped/validated to
at least 1 before any retry loop runs (e.g. replace or follow the destructuring
with something like maxAttempts = Math.max(1, Number(maxAttempts || 0))). Apply
the same guard to the other destructurings that expose maxAttempts (the
occurrences near the other two diffs) so the retry loops using delayMs and
maxAttempts cannot be skipped when maxAttempts <= 0.
- Around line 23-28: The current merge in the $fetch.raw call loses headers when
fetchOptions.headers is a Headers instance or array-of-tuples; update the header
merge logic used before calling $fetch.raw (where defaultHeaders and
fetchOptions.headers are combined) to normalize HeadersInit into a single
Headers and then copy entries so all forms are supported: create a Headers from
defaultHeaders, then if fetchOptions.headers is a Headers, iterate its entries
and set them, if it's an array of tuples iterate and set, and if it's a plain
object iterate its keys and set; finally pass the resulting Headers (or a plain
object built from it) into $fetch.raw so callers using Headers, tuple arrays, or
objects all have their headers preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0be4d085-3032-4fcb-96d0-0c61ce803df9

📥 Commits

Reviewing files that changed from the base of the PR and between f64fd04 and 9cb87b1.

📒 Files selected for processing (3)
  • server/api/github/contributors-evolution/[owner]/[repo].get.ts
  • server/api/github/issues/[owner]/[repo].get.ts
  • server/utils/github.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/api/github/issues/[owner]/[repo].get.ts

Comment on lines +30 to +32
const data = await fetchGitHubWithRetries<GitHubContributorStats[]>(url, {
maxAttempts: 6,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the file under review
fd "contributors-evolution" -t f

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Search for the fetchGitHubWithRetries function definition
rg "fetchGitHubWithRetries" -t ts -t tsx --max-count=20 -A 5 -B 2

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Look for function implementation and type definitions
rg "function fetchGitHubWithRetries|const fetchGitHubWithRetries" -t ts -t tsx -A 15

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Find the specific file being reviewed
find . -name "*.get.ts" -path "*contributors-evolution*"

Repository: npmx-dev/npmx.dev

Length of output: 125


🏁 Script executed:

# Search for fetchGitHubWithRetries without file type flags
rg "fetchGitHubWithRetries" -A 10 -B 2

Repository: npmx-dev/npmx.dev

Length of output: 2684


🏁 Script executed:

# Also search for timeout-related configuration in GitHub API utilities
rg "timeout" --max-count=30

Repository: npmx-dev/npmx.dev

Length of output: 6996


🏁 Script executed:

# Read the full implementation of fetchGitHubWithRetries
head -100 server/utils/github.ts

Repository: npmx-dev/npmx.dev

Length of output: 1485


🏁 Script executed:

# Look for GitHubFetchOptions type definition
rg "GitHubFetchOptions" -A 8 -B 2

Repository: npmx-dev/npmx.dev

Length of output: 941


Add an explicit timeout for the GitHub call.

The fetchGitHubWithRetries function supports timeout via NitroFetchOptions. 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 with timeout: 10000 (see server/api/github/issues/[owner]/[repo].get.ts).

Suggested fix
       const data = await fetchGitHubWithRetries<GitHubContributorStats[]>(url, {
         maxAttempts: 6,
+        timeout: 10_000,
       })
📝 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
const data = await fetchGitHubWithRetries<GitHubContributorStats[]>(url, {
maxAttempts: 6,
})
const data = await fetchGitHubWithRetries<GitHubContributorStats[]>(url, {
maxAttempts: 6,
timeout: 10_000,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/api/github/contributors-evolution/`[owner]/[repo].get.ts around lines
30 - 32, The fetch call to GitHub uses
fetchGitHubWithRetries<GitHubContributorStats[]> but omits a request timeout, so
update the options passed to fetchGitHubWithRetries (the call that assigns to
data) to include a timeout (e.g. timeout: 10000) alongside maxAttempts: 6; this
ensures each retry attempt will fail fast instead of potentially blocking
indefinitely. Target the invocation of fetchGitHubWithRetries in the handler for
contributors-evolution ([owner]/[repo].get.ts) and add the timeout property to
the NitroFetchOptions object.

Comment on lines +23 to +28
const response = await $fetch.raw(url, {
...fetchOptions,
headers: {
...defaultHeaders,
...fetchOptions.headers,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "github.ts" | grep -E "(server|utils)" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 85


🏁 Script executed:

git ls-files | grep -E "server/utils/github" | head -10

Repository: 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.ts

Repository: npmx-dev/npmx.dev

Length of output: 86


🏁 Script executed:

cat -n server/utils/github.ts | head -50

Repository: npmx-dev/npmx.dev

Length of output: 1665


🏁 Script executed:

rg -t ts "type NitroFetchOptions" --max-count 5

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

fd -t json "*.json" | xargs grep -l "nitropack" 2>/dev/null | head -3

Repository: npmx-dev/npmx.dev

Length of output: 232


🌐 Web query:

GitHub API version 2026-03-10 compatibility documentation

💡 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 -3

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

cat -n server/utils/github.ts | sed -n '1,57p' | tail -10

Repository: 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 -5

Repository: npmx-dev/npmx.dev

Length of output: 43


🌐 Web query:

NitroFetchOptions headers type definition Nitro

💡 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.json

Repository: npmx-dev/npmx.dev

Length of output: 5687


Fix headers merge to support all NitroFetchOptions header types (plain objects, tuples, and Headers instances).

At lines 25–28, object spread only works reliably for plain objects. If fetchOptions.headers is a Headers instance or array of tuples (both valid HeadersInit types per NitroFetchOptions spec), entries will be lost and caller-provided headers may not be sent.

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
Verify each finding against the current code and only fix it if needed.

In `@server/utils/github.ts` around lines 23 - 28, The current merge in the
$fetch.raw call loses headers when fetchOptions.headers is a Headers instance or
array-of-tuples; update the header merge logic used before calling $fetch.raw
(where defaultHeaders and fetchOptions.headers are combined) to normalize
HeadersInit into a single Headers and then copy entries so all forms are
supported: create a Headers from defaultHeaders, then if fetchOptions.headers is
a Headers, iterate its entries and set them, if it's an array of tuples iterate
and set, and if it's a plain object iterate its keys and set; finally pass the
resulting Headers (or a plain object built from it) into $fetch.raw so callers
using Headers, tuple arrays, or objects all have their headers preserved.

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

Labels

needs review This PR is waiting for a review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/compare: GitHub stars, issues, created (from npm trends)

3 participants