Skip to content

Commit 8dad800

Browse files
authored
Do more fetching with retries (#45)
1 parent fe6b591 commit 8dad800

8 files changed

Lines changed: 67 additions & 36 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ jobs:
99
- uses: actions/checkout@v4
1010
- name: Install dependencies
1111
run: npm install
12+
- name: Type check
13+
run: npx tsc --noEmit
1214
- name: Run tests
1315
run: npx vitest run

env.d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ declare module "*.txt" {
88
export default content;
99
}
1010

11-
interface Env {
12-
DPRINT_PLUGINS_GH_TOKEN?: string;
13-
PLUGIN_CACHE: R2Bucket;
11+
declare namespace Cloudflare {
12+
interface Env {
13+
DPRINT_PLUGINS_GH_TOKEN?: string;
14+
PLUGIN_CACHE: R2Bucket;
15+
}
1416
}

handleRequest.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import { renderHome } from "./home.jsx";
22
import oldMappings from "./old_redirects.json" with { type: "json" };
3-
import {
4-
tryResolveAssetUrl,
5-
tryResolveLatestJson,
6-
tryResolvePluginUrl,
7-
tryResolveSchemaUrl,
8-
} from "./plugins.js";
3+
import { tryResolveAssetUrl, tryResolveLatestJson, tryResolvePluginUrl, tryResolveSchemaUrl } from "./plugins.js";
94
import { readInfoFile } from "./readInfoFile.js";
105
import robotsTxt from "./robots.txt";
116
import styleCSS from "./style.css";
7+
import { fetchWithRetries } from "./utils/fetchWithRetries.js";
128
import { LruCache } from "./utils/LruCache.js";
139
import { getCliInfo } from "./utils/mod.js";
1410
import { r2Get, r2Put } from "./utils/r2Cache.js";
@@ -94,7 +90,7 @@ export function createRequestHandler() {
9490
}
9591

9692
if (url.pathname === "/") {
97-
return renderHome().then((home) =>
93+
return renderHome(url.origin).then((home) =>
9894
new Response(home, {
9995
headers: {
10096
"content-type": contentTypes.html,
@@ -130,14 +126,16 @@ export function createRequestHandler() {
130126
githubUrl: string,
131127
ctx?: ExecutionContext,
132128
): Promise<{ body: ArrayBuffer | ReadableStream | null; status: number; contentType: string }> {
133-
// L1: in-memory cache (already rewritten)
129+
// L1: in-memory cache
134130
const cached = memoryCache.get(githubUrl);
135131
if (cached != null) {
136132
return { body: cached.body, status: 200, contentType: cached.contentType };
137133
}
138134

139135
const result = await fetchBody(githubUrl, ctx);
140-
if (result.status === 200 && result.body instanceof ArrayBuffer && result.body.byteLength <= MAX_MEM_CACHE_BODY_SIZE) {
136+
if (
137+
result.status === 200 && result.body instanceof ArrayBuffer && result.body.byteLength <= MAX_MEM_CACHE_BODY_SIZE
138+
) {
141139
memoryCache.set(githubUrl, { body: result.body, contentType: result.contentType });
142140
}
143141

@@ -160,7 +158,11 @@ export function createRequestHandler() {
160158
}
161159

162160
// L3: fetch from GitHub
163-
const response = await fetchWithRetries(githubUrl);
161+
const response = await fetchWithRetries(githubUrl, {
162+
// don't need the github token here because these assets
163+
// are not part of the github api
164+
headers: { "user-agent": "dprint-plugins" },
165+
});
164166
if (!response.ok) {
165167
if (response.status !== 404) {
166168
console.error(`GitHub fetch error: ${response.status} ${response.statusText} for ${githubUrl}`);
@@ -227,27 +229,12 @@ function githubUrlToAssetPath(githubUrl: string) {
227229
return `/${username}/${repo}/${tag}/asset/${fileName}`;
228230
}
229231

230-
231232
function contentTypeForUrl(url: string) {
232233
if (url.endsWith(".wasm")) return contentTypes.wasm;
233234
if (url.endsWith(".json") || url.endsWith(".exe-plugin")) return contentTypes.json;
234235
return contentTypes.octetStream;
235236
}
236237

237-
async function fetchWithRetries(url: string, retries = 3): Promise<Response> {
238-
for (let i = 0; i <= retries; i++) {
239-
const response = await fetch(url, {
240-
headers: { "user-agent": "dprint-plugins" },
241-
});
242-
if (response.status < 500 || i === retries) {
243-
return response;
244-
}
245-
console.error(`GitHub fetch attempt ${i + 1} failed: ${response.status} for ${url}`);
246-
await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * 2 ** i, 2500)));
247-
}
248-
throw new Error("unreachable");
249-
}
250-
251238
function create404Response() {
252239
return new Response(null, {
253240
status: 404,

home.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { renderToString } from "preact-render-to-string";
22
import { PluginData, PluginsData, readInfoFile } from "./readInfoFile.js";
33

4-
export async function renderHome() {
5-
const content = await renderContent();
4+
export async function renderHome(origin: string) {
5+
const content = await renderContent(origin);
66
return `<!DOCTYPE html>
77
<html lang="en">
88
<head>
@@ -34,8 +34,8 @@ export async function renderHome() {
3434
`;
3535
}
3636

37-
async function renderContent() {
38-
const pluginsData = await readInfoFile();
37+
async function renderContent(origin: string) {
38+
const pluginsData = await readInfoFile(origin);
3939
const section = (
4040
<section id="content">
4141
<h1>Plugins</h1>

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"devDependencies": {
1515
"@cloudflare/vitest-pool-workers": "^0.13.4",
1616
"@cloudflare/workers-types": "^4.20260317.1",
17+
"typescript": "^6.0.2",
1718
"vitest": "~4.1.1",
1819
"wrangler": "^4.77.0"
1920
}

utils/fetchWithRetries.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export async function fetchWithRetries(
2+
url: string,
3+
init?: RequestInit,
4+
retries = 3,
5+
): Promise<Response> {
6+
for (let i = 0; i <= retries; i++) {
7+
const response = await fetch(url, init);
8+
if (response.status < 500 || i === retries) {
9+
return response;
10+
}
11+
console.error(`Fetch attempt ${i + 1} failed: ${response.status} for ${url}`);
12+
await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * 2 ** i, 2500)));
13+
}
14+
throw new Error("unreachable");
15+
}

utils/github.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { env } from "cloudflare:workers";
2+
import { fetchWithRetries } from "./fetchWithRetries.js";
23
import { LazyExpirableValue } from "./LazyExpirableValue.js";
34
import { LruCache, LruCacheWithExpiry } from "./LruCache.js";
45
import { createSynchronizedActioner } from "./synchronizedActioner.js";
@@ -170,17 +171,18 @@ const synchronizedActioner = createSynchronizedActioner();
170171
function makeGitHubGetRequest(url: string, method: "GET" | "HEAD") {
171172
console.log(`Making request to ${url}`);
172173
return synchronizedActioner.doActionWithTimeout((signal) => {
173-
return fetch(url, {
174+
return fetchWithRetries(url, {
174175
method,
175176
headers: getGitHubHeaders(),
176177
signal,
177-
});
178+
}, /* retries */ 1);
178179
}, 10_000);
179180
}
180181

181-
function getGitHubHeaders() {
182+
// headers for GitHub API requests (not raw asset downloads,
183+
// which don't need auth for public repos)
184+
function getGitHubDownloadHeaders() {
182185
const headers: Record<string, string> = {
183-
"accept": "application/vnd.github.v3+json",
184186
"user-agent": "dprint-plugins",
185187
};
186188
const token = env.DPRINT_PLUGINS_GH_TOKEN;
@@ -189,3 +191,10 @@ function getGitHubHeaders() {
189191
}
190192
return headers;
191193
}
194+
195+
function getGitHubHeaders() {
196+
return {
197+
...getGitHubDownloadHeaders(),
198+
"accept": "application/vnd.github.v3+json",
199+
};
200+
}

0 commit comments

Comments
 (0)