diff options
| author | Joe Mou <dev@mou.fo> | 2026-04-25 13:35:17 -0400 |
|---|---|---|
| committer | Joe Mou <dev@mou.fo> | 2026-04-28 02:07:40 -0400 |
| commit | cfe74db6c177151da3d1bc8549732e3d39448040 (patch) | |
| tree | 49359a8587e87b2bc857d0a7b7fc1c3c396cde86 | |
| parent | dbe8141dce07559c1e16f17800dfd505b0ecdb46 (diff) | |
Support redirects
| -rw-r--r-- | cgithub/src/index.ts | 5 | ||||
| -rw-r--r-- | cgithub/src/scraper.test.ts | 26 | ||||
| -rw-r--r-- | cgithub/src/scraper.ts | 21 |
3 files changed, 50 insertions, 2 deletions
diff --git a/cgithub/src/index.ts b/cgithub/src/index.ts index 59c98ee..eb3a0ee 100644 --- a/cgithub/src/index.ts +++ b/cgithub/src/index.ts @@ -14,6 +14,7 @@ import { getGitHubRepo, getGitHubTree, GitHubHTTPError, + RedirectError, } from "./scraper.ts"; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); @@ -32,7 +33,9 @@ async function tryRender<T extends object>(c: Context, template: string, promise try { data = await promise; } catch (e) { - if (e instanceof GitHubHTTPError) { + if (e instanceof RedirectError) { + return c.redirect(e.location); + } else if (e instanceof GitHubHTTPError) { c.status(e.status as StatusCode); const message = `GitHub responded with HTTP ${e.status} ${e.message}`; return c.html(await eta.renderAsync("error.eta", { title: e.message, message })); diff --git a/cgithub/src/scraper.test.ts b/cgithub/src/scraper.test.ts index 4432231..c20a5ff 100644 --- a/cgithub/src/scraper.test.ts +++ b/cgithub/src/scraper.test.ts @@ -9,6 +9,7 @@ import { getGitHubRepo, getGitHubTree, GitHubHTTPError, + RedirectError, } from "./scraper.ts"; describe("GitHub scraper", () => { @@ -245,5 +246,30 @@ describe("GitHub scraper", () => { return true; }); }); + + it("should redirect when fetching a directory as a blob", async () => { + await assert.rejects( + getGitHubBlob("badlogic", "pi-mono", "main", "packages/agent"), + (err) => { + assert(err instanceof RedirectError, "error should be a RedirectError"); + assert.strictEqual(err.location, "/badlogic/pi-mono/tree/main/packages/agent"); + return true; + }, + ); + }); + + it("should redirect when fetching a file as a tree", async () => { + await assert.rejects( + getGitHubTree("badlogic", "pi-mono", "main", "packages/agent/package.json"), + (err) => { + assert(err instanceof RedirectError, "error should be a RedirectError"); + assert.strictEqual( + err.location, + "/badlogic/pi-mono/blob/main/packages/agent/package.json", + ); + return true; + }, + ); + }); }); }); diff --git a/cgithub/src/scraper.ts b/cgithub/src/scraper.ts index 2dcd1d0..04a685b 100644 --- a/cgithub/src/scraper.ts +++ b/cgithub/src/scraper.ts @@ -13,6 +13,16 @@ export class GitHubHTTPError extends Error { } } +export class RedirectError extends Error { + location: string; + + constructor(location: string) { + super(`Redirect to ${location}`); + this.name = "RedirectError"; + this.location = location; + } +} + interface TreeItem { contentType: "directory" | "file"; name: string; @@ -286,7 +296,16 @@ async function fetchGitHubPage(path: string): Promise<string> { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", }; - const response = await fetch(`https://github.com/${path}`, { headers }); + const response = await fetch(`https://github.com/${path}`, { headers, redirect: "manual" }); + if (response.status === 301 || response.status === 302) { + const location = response.headers.get("location"); + if (location) { + const url = new URL(location); + if (url.hostname === "github.com") { + throw new RedirectError(url.pathname); + } + } + } if (!response.ok) throw new GitHubHTTPError(response.status, response.statusText); return response.text(); } |
