Skip to content

Commit 258d27c

Browse files
feat(cdn): add R2 CDN bucket and /cdn/ Worker routes for WASM wheels
Creates a pywire-cdn R2 bucket and exposes it via two Worker routes: - /cdn/simple/{package}/ — PEP 503 simple index (generated from R2 listing) - /cdn/{key} — serve object from R2 with immutable cache headers This hosts Pyodide WASM wheels (starting with tree-sitter-pywire) so micropip can resolve them by package name without pinning a full URL: micropip.install('tree-sitter-pywire', index_urls=['https://pywire.dev/cdn/simple', 'https://pypi.org/simple'])
1 parent 697b777 commit 258d27c

2 files changed

Lines changed: 52 additions & 8 deletions

File tree

infra/main.tf

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,25 @@ resource "cloudflare_pages_project" "landing" {
5959
}
6060
}
6161

62-
# --- 3. The Router (Worker) ---
62+
# --- 3. CDN Bucket (R2) ---
63+
resource "cloudflare_r2_bucket" "cdn" {
64+
account_id = var.account_id
65+
name = "pywire-cdn"
66+
location = "WNAM"
67+
}
68+
69+
# --- 4. The Router (Worker) ---
6370
resource "cloudflare_workers_script" "router" {
6471
account_id = var.account_id
6572
script_name = "pywire-router"
6673
content_file = "../worker/src/index.js"
6774
content_sha256 = filesha256("../worker/src/index.js")
6875
main_module = "index.js"
76+
77+
r2_buckets = [{
78+
binding = "CDN_BUCKET"
79+
bucket_name = cloudflare_r2_bucket.cdn.name
80+
}]
6981
}
7082

7183
# --- Nightly Environment ---
@@ -91,7 +103,7 @@ resource "cloudflare_dns_record" "vscode_verification" {
91103

92104

93105

94-
# --- 4. The DNS & Routing ---
106+
# --- 5. The DNS & Routing ---
95107
resource "cloudflare_workers_route" "catch_all" {
96108
zone_id = var.zone_id
97109
pattern = "pywire.dev/*"
@@ -104,7 +116,7 @@ resource "cloudflare_workers_route" "nightly" {
104116
script = cloudflare_workers_script.router.script_name
105117
}
106118

107-
# --- 5. Allow AI crawlers to LLM documentation files ---
119+
# --- 6. Allow AI crawlers to LLM documentation files ---
108120
resource "cloudflare_ruleset" "allow_llm_crawlers" {
109121
zone_id = var.zone_id
110122
name = "Allow AI crawlers to LLM txt files"
@@ -123,7 +135,7 @@ resource "cloudflare_ruleset" "allow_llm_crawlers" {
123135
}]
124136
}
125137

126-
# --- 6. Email Routing Setup ---
138+
# --- 7. Email Routing Setup ---
127139

128140
resource "cloudflare_email_routing_settings" "main" {
129141
zone_id = var.zone_id

worker/src/index.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,46 @@ export default {
1111
const landingTarget = isNightly ? "nightly.pywire-landing.pages.dev" : "pywire-landing.pages.dev";
1212
const docsTarget = isNightly ? "nightly.pywire-docs.pages.dev" : "pywire-docs.pages.dev";
1313

14-
// --- 1. HANDLE SHORTCUT REDIRECTS ---
14+
// --- 1. CDN (R2 bucket proxy + PEP 503 simple index) ---
15+
if (path.startsWith('/cdn/simple/')) {
16+
const pkgName = path.slice('/cdn/simple/'.length).replace(/\/$/, '')
17+
if (!pkgName) return new Response('Not Found', { status: 404 })
18+
const listed = await env.CDN_BUCKET.list({ prefix: `${pkgName}/` })
19+
const links = listed.objects
20+
.map(obj => {
21+
const filename = obj.key.split('/').pop()
22+
return `<a href="/cdn/${obj.key}">${filename}</a>`
23+
})
24+
.join('\n')
25+
return new Response(
26+
`<!DOCTYPE html><html><head><title>Links for ${pkgName}</title></head>` +
27+
`<body><h1>Links for ${pkgName}</h1>\n${links}\n</body></html>`,
28+
{ headers: { 'Content-Type': 'text/html; charset=utf-8' } }
29+
)
30+
}
31+
32+
if (path.startsWith('/cdn/')) {
33+
const key = path.slice('/cdn/'.length)
34+
if (!key) return new Response('Not Found', { status: 404 })
35+
const obj = await env.CDN_BUCKET.get(key)
36+
if (!obj) return new Response('Not Found', { status: 404 })
37+
const contentType = obj.httpMetadata?.contentType ?? 'application/octet-stream'
38+
return new Response(obj.body, {
39+
headers: {
40+
'Content-Type': contentType,
41+
'Cache-Control': 'public, max-age=31536000, immutable',
42+
},
43+
})
44+
}
45+
46+
// --- 2. HANDLE SHORTCUT REDIRECTS ---
1547
const redirects = {
1648
// "/discord": "https://discord.gg/pywire", // Update this!
1749
"/github": "https://github.com/pywire/pywire",
1850
};
1951
if (redirects[path]) return Response.redirect(redirects[path], 302);
2052

21-
// --- 2. DEFINE PROXY FUNCTION ---
53+
// --- 3. DEFINE PROXY FUNCTION ---
2254
// This helper strips the 'Host' header so Pages accepts the request
2355
async function proxy(targetOrigin, pathOverride) {
2456
const newUrl = new URL(request.url);
@@ -43,14 +75,14 @@ export default {
4375
return fetch(newRequest);
4476
}
4577

46-
// --- 3. ROUTE TO DOCS ---
78+
// --- 4. ROUTE TO DOCS ---
4779
if (path.startsWith("/docs")) {
4880
// Strip "/docs" so the origin sees "/_astro/..." or "/"
4981
const newPath = path.replace(/^\/docs/, "") || "/";
5082
return proxy(docsTarget, newPath);
5183
}
5284

53-
// --- 4. ROUTE TO LANDING ---
85+
// --- 5. ROUTE TO LANDING ---
5486
return proxy(landingTarget);
5587
},
5688
};

0 commit comments

Comments
 (0)