Skip to content

Commit 44a1f98

Browse files
authored
Merge pull request #5 from cazala/party-assets
Deploy assets for reverse proxy
2 parents a488e73 + 12b52ef commit 44a1f98

4 files changed

Lines changed: 231 additions & 3 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,21 @@ jobs:
5252
run: pnpm --filter @cazala/party exec -- rollup --version
5353

5454
- name: Build
55-
run: pnpm --filter=@cazala/party build && pnpm --filter=@cazala/playground build
55+
run: |
56+
set -euo pipefail
57+
58+
pnpm --filter=@cazala/party build
59+
60+
# Typecheck once, then build twice:
61+
# - root build (default base="/") for party.caza.la
62+
# - subpath build (base="/party/") for caza.la/party via the edge Worker
63+
pnpm --filter=@cazala/playground type-check
64+
65+
# Root build (existing)
66+
pnpm --filter=@cazala/playground exec -- vite build
67+
68+
# Subpath build (separate output dir) - used as upstream for caza.la/party reverse-proxy
69+
VITE_PUBLIC_BASE=/party/ pnpm --filter=@cazala/playground exec -- vite build --outDir dist-party-assets
5670
5771
- name: Install Wrangler
5872
run: npm install -g wrangler@3
@@ -64,12 +78,21 @@ jobs:
6478
export CLOUDFLARE_ACCOUNT_ID="${{ secrets.CLOUDFLARE_ACCOUNT_ID }}"
6579
6680
# Check if project exists, create if it doesn't
67-
if ! wrangler pages project list 2>/dev/null | grep -q "party"; then
81+
# NOTE: avoid substring matches (e.g. "party-assets" should not satisfy "party")
82+
if ! wrangler pages project list 2>/dev/null | grep -qE '(^|[[:space:]])party([[:space:]]|$)'; then
6883
echo "Creating Cloudflare Pages project 'party'..."
6984
wrangler pages project create party --production-branch=main || true
7085
else
7186
echo "Project 'party' already exists"
7287
fi
88+
89+
# Second project for the subpath build (used as upstream for caza.la/party reverse proxy).
90+
if ! wrangler pages project list 2>/dev/null | grep -qE '(^|[[:space:]])party-assets([[:space:]]|$)'; then
91+
echo "Creating Cloudflare Pages project 'party-assets'..."
92+
wrangler pages project create party-assets --production-branch=main || true
93+
else
94+
echo "Project 'party-assets' already exists"
95+
fi
7396
env:
7497
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
7598
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
@@ -87,6 +110,7 @@ jobs:
87110
wrangler pages deploy packages/playground/dist \
88111
--project-name party \
89112
--branch "pr-${{ github.event.pull_request.number }}" \
113+
--commit-dirty=true \
90114
2>&1 | tee wrangler-deploy.log
91115
92116
PREVIEW_URL="$(grep -Eo 'https://[^ ]+\.pages\.dev[^ ]*' wrangler-deploy.log | head -n 1 || true)"
@@ -161,5 +185,12 @@ jobs:
161185
run: |
162186
wrangler pages deploy packages/playground/dist \
163187
--project-name party \
164-
--branch "${{ github.ref_name }}"
188+
--branch "${{ github.ref_name }}" \
189+
--commit-dirty=true
190+
191+
# Deploy the subpath build to the second Pages project.
192+
wrangler pages deploy packages/playground/dist-party-assets \
193+
--project-name party-assets \
194+
--branch "${{ github.ref_name }}" \
195+
--commit-dirty=true
165196
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# LLM Instructions: Create an “edge” repo with a Cloudflare Worker reverse-proxy for `caza.la/party`
2+
3+
You are implementing a **new GitHub repository** (an “edge” repo) that deploys a **Cloudflare Worker** to the `caza.la` Cloudflare zone.
4+
5+
The Worker must **serve the Party playground at `https://caza.la/party`** by **reverse-proxying** to an upstream origin that already serves the playground:
6+
7+
- **Upstream origin (MUST)**: `https://party-assets.caza.la`
8+
- This is a **second Cloudflare Pages deployment** of the Party playground that is built with Vite `base="/party/"`.
9+
- `party.caza.la` remains the “normal” root build and is **not** the Worker upstream for `caza.la/party`.
10+
11+
## Non‑negotiable requirements
12+
13+
- **No redirect to `*.pages.dev`** at any point.
14+
- The browser URL must remain **`caza.la/party...`** while using the playground.
15+
- Root website must remain at **`https://caza.la/`** (served by the separate `caza.la` repo / its own Cloudflare Pages project).
16+
- The Worker must only handle the `/party` subtree (route-scoped), and must not affect other paths.
17+
18+
## What you are building
19+
20+
### Behavior summary
21+
22+
- Requests to `https://caza.la/party` should 308 to `https://caza.la/party/` (same-host, same-scheme).
23+
This is only a trailing-slash normalization and still satisfies “no redirect to pages.dev”.
24+
- Requests to `https://caza.la/party/*` should be proxied to the upstream by **stripping** the `/party` prefix:
25+
- `GET /party/` → upstream `GET /`
26+
- `GET /party/assets/x.js` → upstream `GET /assets/x.js`
27+
- `GET /party/some/spa/route` → upstream `GET /some/spa/route`
28+
- The Worker must preserve:
29+
- method (GET/POST/etc)
30+
- query string
31+
- request body
32+
- most headers (with a few safe adjustments described below)
33+
- The Worker must return upstream responses mostly as-is (status, headers, body).
34+
35+
### Important dependency (already decided)
36+
37+
This Worker is **route-scoped to `caza.la/party*`**, so the app must request its assets/chunks under `/party/...` on `caza.la`.
38+
39+
That is why the upstream is **`party-assets.caza.la`**, which is built with:
40+
41+
- Vite `base: "/party/"`
42+
- Cloudflare Pages SPA fallback (`/* /index.html 200`)
43+
44+
## Repo deliverables
45+
46+
Create a repo with:
47+
48+
- `src/index.ts` (or `src/worker.ts`): Worker implementation (TypeScript)
49+
- `wrangler.toml`: Wrangler config
50+
- `package.json`: scripts for typecheck + deploy
51+
- `tsconfig.json`
52+
- `README.md`: setup + deploy instructions
53+
- `.github/workflows/deploy.yml`: CI deploy on push to `main`
54+
55+
Prefer minimal dependencies (no frameworks needed).
56+
57+
## Cloudflare + Wrangler configuration
58+
59+
### Worker name
60+
61+
Use a clear name, e.g.:
62+
63+
- `cazala-edge-party`
64+
65+
### Route
66+
67+
The Worker must be attached to the `caza.la` zone with **route**:
68+
69+
- `caza.la/party*`
70+
71+
### Configuration style (recommended)
72+
73+
Use environment variables so upstream can be changed without code changes:
74+
75+
- `UPSTREAM_ORIGIN = "https://party-assets.caza.la"`
76+
77+
In `wrangler.toml`:
78+
79+
- define `vars.UPSTREAM_ORIGIN`
80+
- set a pinned `compatibility_date`
81+
82+
## Worker implementation details (must follow)
83+
84+
### 1) Routing rules
85+
86+
- If path is exactly `/party`, redirect to `/party/` (308).
87+
- If path does not start with `/party/`, return 404 (defensive; route should already constrain it).
88+
- Otherwise proxy:
89+
- Strip the `/party` prefix from `pathname`
90+
- Use upstream host from `UPSTREAM_ORIGIN`
91+
- Keep query string
92+
93+
### 2) Headers handling
94+
95+
- Forward most request headers, but:
96+
- Remove `Host` (it must match the upstream host)
97+
- Consider removing `Accept-Encoding` (Cloudflare will manage compression; leaving it is usually okay, but removing avoids rare edge-cases)
98+
- Add a helpful header for debugging:
99+
- `x-edge-proxy: cazala-edge-party`
100+
101+
### 3) Caching (keep it simple)
102+
103+
Default: do not implement custom caching at first.
104+
105+
Optionally:
106+
107+
- For requests that look like hashed assets (e.g. `/assets/*.js`, `/assets/*.css`), you may set `Cache-Control: public, max-age=31536000, immutable` **only if** upstream doesn’t already do this correctly.
108+
- For `index.html` and SPA routes, keep caching conservative (or unchanged).
109+
110+
### 4) Content rewriting (do NOT do unless necessary)
111+
112+
Do not attempt HTML rewriting unless tests show it’s needed.
113+
The primary mechanism should be path stripping + upstream build base configuration.
114+
115+
### 5) Error handling
116+
117+
- If upstream fetch fails, return a 502 with a short message.
118+
- Never leak secrets.
119+
120+
## Acceptance tests (must pass)
121+
122+
Assume the Worker is deployed and route is active.
123+
124+
- Visiting `https://caza.la/party` loads the playground and ends at `https://caza.la/party/` (same host).
125+
- Hard refresh on a nested route works (SPA):
126+
- Open `https://caza.la/party/some/deep/route`
127+
- Refresh page → it still loads (no 404).
128+
- Static assets load from `/party/assets/...` (verify in DevTools network).
129+
- `https://caza.la/` and other non-`/party` routes continue to work and are not served by this Worker.
130+
- There is **no** redirect to `party-assets.caza.la`, `party.caza.la`, or any `*.pages.dev` URL.
131+
132+
## GitHub Actions deploy
133+
134+
Implement `.github/workflows/deploy.yml` that:
135+
136+
- runs on push to `main`
137+
- installs deps
138+
- typechecks (or `tsc --noEmit`)
139+
- deploys with `wrangler deploy`
140+
141+
Use secrets:
142+
143+
- `CLOUDFLARE_API_TOKEN`
144+
- `CLOUDFLARE_ACCOUNT_ID`
145+
146+
Do NOT require interactive login.
147+
148+
## Suggested file contents (outline)
149+
150+
### `package.json`
151+
152+
- scripts:
153+
- `typecheck`: `tsc --noEmit`
154+
- `deploy`: `wrangler deploy`
155+
156+
### `wrangler.toml`
157+
158+
- `name = "cazala-edge-party"`
159+
- `main = "src/index.ts"`
160+
- `compatibility_date = "YYYY-MM-DD"`
161+
- `routes = [{ pattern = "caza.la/party*", zone_name = "caza.la" }]`
162+
- `vars = { UPSTREAM_ORIGIN = "https://party-assets.caza.la" }`
163+
164+
### `src/index.ts`
165+
166+
- implement the routing + proxy described above
167+
168+
## Notes / pitfalls
169+
170+
- The Worker route must be **path-scoped**. Do not bind it to all of `caza.la/*`.
171+
- Be careful when stripping paths:
172+
- `/party/` must become `/` (not empty)
173+
- Don’t accidentally proxy `/party` without normalizing slash; relative URLs can behave differently.
174+
- Don’t rely on `*.pages.dev` anywhere (it may change).
175+
176+
## Definition of done
177+
178+
- Repo can be cloned and deployed by CI with only the two Cloudflare secrets.
179+
- Worker is deployed and mounted on `caza.la/party*`.
180+
- All acceptance tests above pass.
181+
182+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/* /index.html 200
2+

packages/playground/vite.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,21 @@ import { defineConfig } from "vite";
22
import react from "@vitejs/plugin-react";
33
import { fileURLToPath, URL } from "node:url";
44

5+
function normalizeBase(base) {
6+
if (!base) return "/";
7+
// Ensure leading slash.
8+
let b = base.startsWith("/") ? base : `/${base}`;
9+
// Ensure trailing slash.
10+
if (!b.endsWith("/")) b = `${b}/`;
11+
return b;
12+
}
13+
514
export default defineConfig({
615
plugins: [react()],
16+
// Default to "/" for local dev & normal Pages hosting.
17+
// For subpath hosting (e.g. https://caza.la/party/), set:
18+
// VITE_PUBLIC_BASE=/party/
19+
base: normalizeBase(process.env.VITE_PUBLIC_BASE),
720
resolve: {
821
alias: {
922
"@cazala/party": fileURLToPath(new URL("../core/src", import.meta.url)),

0 commit comments

Comments
 (0)