Skip to content

feat: support get repository release by tag#162

Open
FliPPeDround wants to merge 1 commit into
unjs:mainfrom
FliPPeDround:main
Open

feat: support get repository release by tag#162
FliPPeDround wants to merge 1 commit into
unjs:mainfrom
FliPPeDround:main

Conversation

@FliPPeDround

@FliPPeDround FliPPeDround commented Apr 9, 2026

Copy link
Copy Markdown

#161

Summary by CodeRabbit

New Features

  • Added a new API endpoint for fetching repository releases by specific tag. Returns comprehensive release metadata including author information, creation and publication timestamps, draft and prerelease status indicators, markdown and HTML-formatted content, and detailed information about associated release assets including file size, content type, and download metrics.

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A new API endpoint was added to fetch GitHub repository releases by tag. The implementation includes a Nitro route handler that retrieves release data from GitHub's API, normalizes the response structure, converts release markdown to HTML, and maps asset metadata into a standardized format.

Changes

Cohort / File(s) Summary
API Endpoint Documentation
README.md
Added documentation for the new GET /repos/{owner}/{name}/releases/tags/{tag} endpoint with example URL and JSON response payload structure.
Release Fetching Handler
routes/repos/[owner]/[repo]/releases/tags/[tag].ts
Implemented route handler that extracts owner, repo, and tag from params, fetches release data from GitHub API, maps release fields to GithubRelease object, converts markdown body to HTML via ghMarkdown, and normalizes assets array with content type, size, timestamps, download count, and URL.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as Route Handler
    participant GitHub as GitHub API
    participant Markdown as Markdown Processor
    
    Client->>Handler: GET /repos/{owner}/{repo}/releases/tags/{tag}
    Handler->>GitHub: ghFetch(repos/{owner}/{repo}/releases/tags/{tag})
    GitHub-->>Handler: Release data (id, tag, author, assets, body, etc.)
    Handler->>Markdown: ghMarkdown(release.body, repo, context)
    Markdown-->>Handler: Converted HTML
    Handler->>Handler: Map assets to normalized format
    Handler->>Handler: Construct GithubRelease object
    Handler-->>Client: { release: GithubRelease }
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

🐰 A new release endpoint hops into place,
GitHub tags now fetch with grace,
Assets mapped and markdown too,
HTML rendered, all so true!
The release API, oh what a treat! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: support get repository release by tag' directly and clearly summarizes the main change: adding functionality to fetch a repository release by its tag.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@vercel

vercel Bot commented Apr 9, 2026

Copy link
Copy Markdown

@FliPPeDround is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
README.md (1)

153-167: Include assets in this example payload for contract accuracy.

The handler returns release.assets and GithubRelease requires it, so documenting it here will keep the endpoint example aligned with runtime output.

📝 Suggested doc update
   "release": {
@@
-    "html": "..."
+    "html": "...",
+    "assets": [
+      {
+        "contentType": "application/zip",
+        "size": 12345,
+        "createdAt": "2022-11-04T11:41:59Z",
+        "updatedAt": "2022-11-04T11:41:59Z",
+        "downloadCount": 42,
+        "downloadUrl": "https://github.com/owner/repo/releases/download/v3.0.0-rc.13/example.zip"
+      }
+    ]
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 153 - 167, The example JSON payload omits the
required release.assets field used by the handler and declared on the
GithubRelease type; update the example for the release object to include an
assets array (release.assets) with at least the fields the handler/GithubRelease
expect (e.g., asset id, name, content/type/size, and download URL or
browser_download_url) so the documented payload matches runtime output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@routes/repos/`[owner]/[repo]/releases/tags/[tag].ts:
- Line 46: The cache key passed to ghMarkdown is using the wrong release field —
change the key construction in the ghMarkdown call (where html: await
ghMarkdown(i.body, repo, "release-" + i.tag)) to use the release object's
tag_name property instead of i.tag (i.e., "release-" + i.tag_name) so markdown
caching is correctly namespaced per release tag; update any other ghMarkdown
usages that build the same key from i.tag to use i.tag_name as well.
- Around line 32-34: The code unsafely accesses route params via
event.context.params! and interpolates tag raw into the GitHub path causing
failures for tags containing slashes; update access to use safe checks (e.g.,
read params via event.context.params or destructure with optional chaining and
validate presence of owner/repo/tag) and handle missing params (return an error
or throw), and URL-encode the tag when calling ghFetch (use encodeURIComponent
on the tag) so the ghFetch call (repos/${repo}/releases/tags/${...}) is
constructed safely; reference the existing symbols event.context.params, tag,
repo, and ghFetch when making the fix.

---

Nitpick comments:
In `@README.md`:
- Around line 153-167: The example JSON payload omits the required
release.assets field used by the handler and declared on the GithubRelease type;
update the example for the release object to include an assets array
(release.assets) with at least the fields the handler/GithubRelease expect
(e.g., asset id, name, content/type/size, and download URL or
browser_download_url) so the documented payload matches runtime output.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 95ef129e-f4c5-44ad-ad89-a58eb741640d

📥 Commits

Reviewing files that changed from the base of the PR and between c9e3274 and 61cc066.

📒 Files selected for processing (2)
  • README.md
  • routes/repos/[owner]/[repo]/releases/tags/[tag].ts

Comment on lines +32 to +34
const repo = `${event.context.params!.owner}/${event.context.params!.repo}`;
const tag = event.context.params!.tag;
const i = await ghFetch(`repos/${repo}/releases/tags/${tag}`);

Copy link
Copy Markdown

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:

#!/bin/bash
# Verify current param extraction and URL construction in this handler.
rg -n 'event\.context\.params|getRouterParam|releases/tags/\$\{tag\}|encodeURIComponent\(tag\)' -- 'routes/repos/[owner]/[repo]/releases/tags/[tag].ts'

Repository: unjs/ungh

Length of output: 246


🏁 Script executed:

cat -n 'routes/repos/[owner]/[repo]/releases/tags/[tag].ts'

Repository: unjs/ungh

Length of output: 2096


Use safe router-param access and URL-encode the tag in the GitHub path.

Interpolating tag raw can break lookups for valid tags containing / (for example, release/v1). Also, event.context.params! is unsafe and bypasses route-param guards.

💡 Suggested fix
 import { defineRouteMeta, defineHandler } from "nitro";
+import { getRouterParam, HTTPError } from "nitro/h3";
 import { ghFetch, ghMarkdown } from "~/utils/github";
 import type { GithubRelease } from "~types";
@@
 export default defineHandler(async (event) => {
-  const repo = `${event.context.params!.owner}/${event.context.params!.repo}`;
-  const tag = event.context.params!.tag;
-  const i = await ghFetch(`repos/${repo}/releases/tags/${tag}`);
+  const owner = getRouterParam(event, "owner");
+  const repoName = getRouterParam(event, "repo");
+  const tag = getRouterParam(event, "tag");
+  if (!owner || !repoName || !tag) {
+    throw new HTTPError({ statusCode: 400, message: "Missing required route parameters" });
+  }
+  const repo = `${owner}/${repoName}`;
+  const i = await ghFetch(
+    `repos/${encodeURIComponent(owner)}/${encodeURIComponent(repoName)}/releases/tags/${encodeURIComponent(tag)}`,
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@routes/repos/`[owner]/[repo]/releases/tags/[tag].ts around lines 32 - 34, The
code unsafely accesses route params via event.context.params! and interpolates
tag raw into the GitHub path causing failures for tags containing slashes;
update access to use safe checks (e.g., read params via event.context.params or
destructure with optional chaining and validate presence of owner/repo/tag) and
handle missing params (return an error or throw), and URL-encode the tag when
calling ghFetch (use encodeURIComponent on the tag) so the ghFetch call
(repos/${repo}/releases/tags/${...}) is constructed safely; reference the
existing symbols event.context.params, tag, repo, and ghFetch when making the
fix.

createdAt: i.created_at,
publishedAt: i.published_at,
markdown: i.body,
html: await ghMarkdown(i.body, repo, "release-" + i.tag),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix cache-key source field: use i.tag_name instead of i.tag.

i.tag is not the GitHub release field here, so this can become undefined and collapse markdown cache keys across tags in the same repo.

🐛 Suggested fix
-    html: await ghMarkdown(i.body, repo, "release-" + i.tag),
+    html: await ghMarkdown(i.body, repo, `release-${i.tag_name}`),
📝 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
html: await ghMarkdown(i.body, repo, "release-" + i.tag),
html: await ghMarkdown(i.body, repo, `release-${i.tag_name}`),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@routes/repos/`[owner]/[repo]/releases/tags/[tag].ts at line 46, The cache key
passed to ghMarkdown is using the wrong release field — change the key
construction in the ghMarkdown call (where html: await ghMarkdown(i.body, repo,
"release-" + i.tag)) to use the release object's tag_name property instead of
i.tag (i.e., "release-" + i.tag_name) so markdown caching is correctly
namespaced per release tag; update any other ghMarkdown usages that build the
same key from i.tag to use i.tag_name as well.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant