From 7acd6c4d08a11852f1e355913de5616d026affa2 Mon Sep 17 00:00:00 2001 From: Jake <134332787+justjxke@users.noreply.github.com> Date: Sat, 21 Mar 2026 06:32:01 +0000 Subject: [PATCH] fix release fetch and GitHub release URL routing (#324) Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com> --- .../[owner]/[repo]/releases/_latest/page.tsx | 17 +++++++++ .../download/[tag]/[...filename]/route.ts | 2 + .../[repo]/releases/tag/[tag]/page.tsx | 10 +++++ apps/web/src/lib/github-utils.ts | 26 ++++++++++++- apps/web/src/lib/github.ts | 38 ++++++++++++------- packages/chrome-extension/background.js | 11 ++++++ packages/chrome-extension/rules.json | 9 +++++ packages/firefox-extension/background.js | 10 +++++ packages/firefox-extension/rules.json | 9 +++++ 9 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 apps/web/src/app/(app)/repos/[owner]/[repo]/releases/_latest/page.tsx create mode 100644 apps/web/src/app/(app)/repos/[owner]/[repo]/releases/tag/[tag]/page.tsx diff --git a/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/_latest/page.tsx b/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/_latest/page.tsx new file mode 100644 index 0000000..b155745 --- /dev/null +++ b/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/_latest/page.tsx @@ -0,0 +1,17 @@ +import { notFound, redirect } from "next/navigation"; +import { getLatestRepoRelease } from "@/lib/github"; + +export default async function LatestReleaseRedirectPage({ + params, +}: { + params: Promise<{ owner: string; repo: string }>; +}) { + const { owner, repo } = await params; + const release = await getLatestRepoRelease(owner, repo); + + if (!release?.tag_name) { + notFound(); + } + + redirect(`/${owner}/${repo}/releases/${encodeURIComponent(release.tag_name)}`); +} diff --git a/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/download/[tag]/[...filename]/route.ts b/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/download/[tag]/[...filename]/route.ts index 3b85caf..bec91c6 100644 --- a/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/download/[tag]/[...filename]/route.ts +++ b/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/download/[tag]/[...filename]/route.ts @@ -7,6 +7,8 @@ export async function GET( }, ) { const { owner, repo, tag, filename } = await context.params; + + // this route basically redirects users to the real github asset URL, if the extension is on it can loop unless that URL is allowed listed in extension rules const githubUrl = `https://github.com/${owner}/${repo}/releases/download/${encodeURIComponent(tag)}/${filename.join("/")}`; return NextResponse.redirect(githubUrl, { status: 302 }); } diff --git a/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/tag/[tag]/page.tsx b/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/tag/[tag]/page.tsx new file mode 100644 index 0000000..4df3658 --- /dev/null +++ b/apps/web/src/app/(app)/repos/[owner]/[repo]/releases/tag/[tag]/page.tsx @@ -0,0 +1,10 @@ +import { redirect } from "next/navigation"; + +export default async function GitHubStyleReleaseTagPage({ + params, +}: { + params: Promise<{ owner: string; repo: string; tag: string }>; +}) { + const { owner, repo, tag } = await params; + redirect(`/${owner}/${repo}/releases/${encodeURIComponent(tag)}`); +} diff --git a/apps/web/src/lib/github-utils.ts b/apps/web/src/lib/github-utils.ts index bdf490f..cf48f92 100644 --- a/apps/web/src/lib/github-utils.ts +++ b/apps/web/src/lib/github-utils.ts @@ -129,6 +129,9 @@ export function toInternalUrl(htmlUrl: string): string { if (type === "blob" && path) return `${base}/blob/${path}`; if (type === "commits") return `${base}/commits`; if (type === "commit" && path) return `${base}/commits/${path}`; + if (type === "releases") return `${base}/releases`; + if (type === "release" && path) return `${base}/releases/${encodeURIComponent(path)}`; + if (type === "release_latest") return `${base}/releases/_latest`; if (type === "repo") return base; return htmlUrl; @@ -202,7 +205,17 @@ type ParsedGitHubUrl = | { owner: string; repo: string; - type: "repo" | "pull" | "issue" | "tree" | "blob" | "commits" | "commit"; + type: + | "repo" + | "pull" + | "issue" + | "tree" + | "blob" + | "commits" + | "commit" + | "releases" + | "release" + | "release_latest"; number?: number; path?: string; } @@ -286,6 +299,17 @@ export function parseGitHubUrl(htmlUrl: string): ParsedGitHubUrl | null { tag: rest[2], filename: rest.slice(3).join("/"), }; + if (rest[0] === "releases" && rest.length === 1) + return { owner, repo, type: "releases" }; + if (rest[0] === "releases" && rest[1] === "tag" && rest[2]) + return { + owner, + repo, + type: "release", + path: decodeURIComponent(rest.slice(2).join("/")), + }; + if (rest[0] === "releases" && rest[1] === "latest") + return { owner, repo, type: "release_latest" }; if (rest[0] === "actions") { // /actions/runs/:runId or /actions/runs/:runId/jobs/:jobId if (rest[1] === "runs" && rest[2]) { diff --git a/apps/web/src/lib/github.ts b/apps/web/src/lib/github.ts index 6adb4f8..3f8408a 100644 --- a/apps/web/src/lib/github.ts +++ b/apps/web/src/lib/github.ts @@ -2850,7 +2850,7 @@ export async function getRepoReleases(owner: string, repo: string) { const authCtx = await getGitHubAuthContext(); const cacheKey = buildRepoReleasesCacheKey(owner, repo); - return readLocalFirstGitData({ + const releases = await readLocalFirstGitData({ authCtx, cacheKey, cacheType: "repo_releases", @@ -2859,6 +2859,18 @@ export async function getRepoReleases(owner: string, repo: string) { jobPayload: { owner, repo }, fetchRemote: (octokit) => fetchRepoReleasesFromGitHub(octokit, owner, repo), }); + + if (releases.length > 0 || !authCtx?.octokit) return releases; + + try { + const fresh = await fetchRepoReleasesFromGitHub(authCtx.octokit, owner, repo); + upsertGithubCacheEntry(authCtx.userId, cacheKey, "repo_releases", fresh).catch( + () => {}, + ); + return fresh; + } catch { + return releases; + } } export async function getRepoReleasesPage(owner: string, repo: string, page: number) { @@ -2892,18 +2904,6 @@ export async function getRepoReleaseByTag(owner: string, repo: string, tag: stri const authCtx = await getGitHubAuthContext(); if (!authCtx?.octokit) return null; - if (tag === "latest") { - try { - const { data } = await authCtx.octokit.repos.getLatestRelease({ - owner, - repo, - }); - return data; - } catch { - return null; - } - } - try { const { data } = await authCtx.octokit.repos.getReleaseByTag({ owner, repo, tag }); return data; @@ -2912,6 +2912,18 @@ export async function getRepoReleaseByTag(owner: string, repo: string, tag: stri } } +export async function getLatestRepoRelease(owner: string, repo: string) { + const authCtx = await getGitHubAuthContext(); + if (!authCtx?.octokit) return null; + + try { + const { data } = await authCtx.octokit.repos.getLatestRelease({ owner, repo }); + return data; + } catch { + return null; + } +} + export async function getFileContent(owner: string, repo: string, path: string, ref?: string) { const authCtx = await getGitHubAuthContext(); const normalizedRef = normalizeRef(ref); diff --git a/packages/chrome-extension/background.js b/packages/chrome-extension/background.js index 37a98d4..233f6eb 100644 --- a/packages/chrome-extension/background.js +++ b/packages/chrome-extension/background.js @@ -173,6 +173,17 @@ function buildRules(host) { resourceTypes: ["main_frame"], }, }, + // --- Allow release asset download URLs to prevent redirect loops --- + { + id: 251, + priority: 5, + action: { type: "allow" }, + condition: { + regexFilter: + "^https://github\\.com/[^/]+/[^/]+/releases/download/.+", + resourceTypes: ["main_frame"], + }, + }, // --- Specific path rewrites --- { diff --git a/packages/chrome-extension/rules.json b/packages/chrome-extension/rules.json index c1992eb..348da93 100644 --- a/packages/chrome-extension/rules.json +++ b/packages/chrome-extension/rules.json @@ -101,6 +101,15 @@ "resourceTypes": ["main_frame"] } }, + { + "id": 37, + "priority": 5, + "action": { "type": "allow" }, + "condition": { + "regexFilter": "^https://github\\.com/[^/]+/[^/]+/releases/download/.+", + "resourceTypes": ["main_frame"] + } + }, { "id": 12, "priority": 5, diff --git a/packages/firefox-extension/background.js b/packages/firefox-extension/background.js index 0db7614..6b36d7f 100644 --- a/packages/firefox-extension/background.js +++ b/packages/firefox-extension/background.js @@ -132,6 +132,16 @@ function buildRules(host) { resourceTypes: ["main_frame"], }, }, + { + id: 251, + priority: 5, + action: { type: "allow" }, + condition: { + regexFilter: + "^https://github\\.com/[^/]+/[^/]+/releases/download/.+", + resourceTypes: ["main_frame"], + }, + }, { id: 101, diff --git a/packages/firefox-extension/rules.json b/packages/firefox-extension/rules.json index 1a4eeb6..134c2fd 100644 --- a/packages/firefox-extension/rules.json +++ b/packages/firefox-extension/rules.json @@ -101,6 +101,15 @@ "resourceTypes": ["main_frame"] } }, + { + "id": 37, + "priority": 5, + "action": { "type": "allow" }, + "condition": { + "regexFilter": "^https://github\\.com/[^/]+/[^/]+/releases/download/.+", + "resourceTypes": ["main_frame"] + } + }, { "id": 12, "priority": 5,