aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoe Mou <dev@mou.fo>2026-04-25 13:35:17 -0400
committerJoe Mou <dev@mou.fo>2026-04-28 02:07:40 -0400
commit0d8911577ba3d49df3577d686837541a9aee69bd (patch)
tree638be4b23ee14a30fa895d11ccdf2d3779c56b32 /src
parentf179a3f77c6617b4375e6149cc696ae5dab4d171 (diff)
Support redirects
Diffstat (limited to 'src')
-rw-r--r--src/index.ts5
-rw-r--r--src/scraper.test.ts26
-rw-r--r--src/scraper.ts21
3 files changed, 50 insertions, 2 deletions
diff --git a/src/index.ts b/src/index.ts
index 59c98ee..eb3a0ee 100644
--- a/src/index.ts
+++ b/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/src/scraper.test.ts b/src/scraper.test.ts
index 4432231..c20a5ff 100644
--- a/src/scraper.test.ts
+++ b/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/src/scraper.ts b/src/scraper.ts
index 2dcd1d0..04a685b 100644
--- a/src/scraper.ts
+++ b/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();
}