Deploying to Cloudflare Pages
Everything in the repository is ready. The remaining steps need your Cloudflare and GitHub authentication, so they have to be run by you — an agent cannot authenticate as you, and should not.
Total time: about ten minutes, most of it waiting for the first build.
What is already done
| Item | State |
|---|---|
| Repository | 515FazzyBear/CATS, private, pushed |
| Build script | tool/pages_build.sh — bootstraps Deno, regenerates everything, assembles dist/ |
| Site source | web/ — 34 files, builds clean |
| Feedback API | functions/api/feedback.ts |
| Database schema | migrations/0001_init.sql |
| Cloudflare config | wrangler.toml — needs one value pasted in (step 3) |
| Line endings | .gitattributes forces LF, which the Linux build depends on |
Step 1 — Connect the repository to Cloudflare Pages
In the Cloudflare dashboard: Workers & Pages → Create → Pages → Connect to Git.
Authorise the Cloudflare GitHub app against 515FazzyBear/CATS. A private repository is fine — the app gets read access.
Then set:
| Setting | Value |
|---|---|
| Project name | cats-rpg |
| Production branch | main |
| Build command | bash tool/pages_build.sh |
| Build output directory | dist |
| Root directory | (leave blank) |
The build command lives in the dashboard, not in
wrangler.toml.wrangler.tomlonly declares the output directory and the database binding. Changing the repo alone will not change what CI runs — this catches people out.
The first build takes 2-3 minutes, most of it downloading Deno. Cloudflare's build image does not ship Deno and offers no version variable, so pages_build.sh fetches a pinned release itself. That is expected, not a workaround for something broken.
At this point the site is live at cats-rpg.pages.dev and every push to main redeploys it. The feedback button will render but return a 503 until step 3.
Step 2 — Set up API access (wrangler does not work here)
The wrangler CLI cannot run on this machine. It depends on
workerd, which publishes no Windows ARM64 build, and this box has no Node, no working WSL (Wsl/CallMsi/Install/ REGDB_E_CLASS_NOT_REG), and no Docker to fall back to. Everynpx wranglerinstruction you will find in Cloudflare's documentation is unusable here.Use
tool/cf.tsinstead. It talks to the Cloudflare REST API over HTTPS, so architecture is irrelevant, and it runs on Deno — this project's only toolchain.
Create a token: Cloudflare dashboard → My Profile → API Tokens → Create Token. Either use the Edit Cloudflare Workers template, or build a custom token with:
| Scope | Resource | Permission |
|---|---|---|
| Account | D1 | Edit |
| Account | Cloudflare Pages | Edit |
Then put it in your environment. It is a credential — never commit it:
$env:CLOUDFLARE_API_TOKEN = "<the token>"
$env:CLOUDFLARE_ACCOUNT_ID = "<account id, shown on the dashboard sidebar>"
Step 3 — Create the feedback database
deno task cf d1 create cats-rpg
It prints the uuid. Uncomment the binding in wrangler.toml and paste it in:
[[d1_databases]]
binding = "CATS_DB"
database_name = "cats-rpg"
database_id = "<the uuid>"
Leave the block commented out until the database exists. Pages validates the UUID at deploy time and fails the entire deployment on a bad one, so a placeholder takes the whole site down rather than degrading one endpoint. This is not hypothetical — it cost the first deploy.
Then create the table and push:
deno task cf d1 exec cats-rpg migrations/0001_init.sql
git add wrangler.toml; git commit -m "Wire up the D1 database"; git push
The migration is optional in practice — functions/api/feedback.ts calls ensureSchema() on every request and creates the table itself. Running it explicitly just means the first request is not the one paying for it.
Step 4 — Set the admin token
Invent a long random string. This gates reading and triaging feedback.
deno task cf pages secret cats-rpg CATS_ADMIN_TOKEN
It reads the value from stdin rather than from the command line, so the secret never lands in shell history or in the process list. Do not commit it. The triage console at /feedback.html asks for it and keeps it in browser local storage.
Redeploy for a new secret to take effect.
Useful checks
deno task cf pages deployments cats-rpg # build status without opening the dashboard
deno task cf d1 query cats-rpg "SELECT count(*) AS n FROM feedback"
Step 5 — Verify
curl -X POST https://cats-rpg.pages.dev/api/feedback \
-H "content-type: application/json" \
-d '{"message":"deploy smoke test","category":"other"}'
A 201 with an id means the whole chain works. Then open /feedback.html, enter the admin token, and confirm the note appears.
Step 6 — Decide who can see it
The site is public the moment it deploys, regardless of the repository being private. Cloudflare Pages does not inherit GitHub permissions.
If it should not be public yet, add Cloudflare Access in front of it: Zero Trust → Access → Applications → Add a self-hosted application over cats-rpg.pages.dev, with a policy allowing specific email addresses. Collaborators then get a one-time code by email.
That is worth doing deliberately rather than by default — the whole point of the feedback widget is that people can disagree with the design in writing, which needs them to reach it.
Working from here
deno task build # regenerate analysis and site
deno task serve # preview at localhost:8817
git push # deploy
dist/ is generated and gitignored. Never edit it; edits vanish on the next build.
Things that will bite
| Problem | Cause |
|---|---|
Unsupported platform: win32 arm64 | You tried to run wrangler. It cannot work here — see step 2 and use deno task cf |
Invalid database UUID and the whole site fails to deploy | A placeholder or wrong database_id in wrangler.toml. Comment the block out rather than leaving a placeholder |
| Build fails with a parse error on a script | Windows PowerShell 5.1 writes CRLF. .gitattributes forces LF for a reason — never remove it |
| Build fails hunting for Deno | Someone removed the bootstrap from pages_build.sh |
| Feedback returns 503 | database_id is still the placeholder, or migrations were not applied with --remote |
| Feedback returns 403 on read | Admin token missing or mismatched |
| Charts show stale numbers | Something was hand-edited in docs/analysis/ or web/. Both are generated — change the model in tools/lib/ instead |
| Docs missing from the site | Every .md under docs/ is picked up automatically; check the file has an # H1 |