- mise (see
mise.toml) - Cloudflare account +
wranglerlogin (pnpm exec wrangler loginfrom any worker package) for remote D1 / deploy.
mise install
pnpm install
hk install # git hooks: oxlint --fix + oxfmt --write on staged filespackages/
shared/ Zod schemas shared between server and client
server/ Hono on Workers — API, rooms (DO), matchmaking (DO), daily cron
client/ Vite + React + PixiJS, deployed to Cloudflare Pages
tail-worker/ Cloudflare Tail Worker — forwards errors to Discord
| Local | Production | |
|---|---|---|
| Server worker name | differ-server-local |
differ-server |
| Tail worker name | differ-tail-local |
differ-tail |
| D1 database name | differ-local |
differ |
| Rate-limit ns ids | 1xxx |
2xxx |
The *-local suffix is what makes local Cloudflare dev registry discovery
work — the server's tail_consumers.service matches the tail worker's local
name. Don't deploy the -local names to production.
Rate-limit namespace_id counters are shared across all deployments using the
same id, so prod and dev are split (1xxx vs 2xxx) to avoid leaking dev
test traffic into the prod rate-limit window.
pnpm dev:workers # server (primary, :8787) + tail-worker
pnpm dev:client # vite (:8080), proxies API/WS to :8787The local DB is differ-local (prod is differ). State persists under
packages/server/.wrangler/state.
cd packages/server
pnpm db:migrate:local # apply migrations
pnpm db:seed:local # regenerates puzzles.sql + loads it
pnpm exec wrangler d1 execute differ-local --local --command "SELECT 1"Reset:
rm -rf packages/server/.wrangler/state
pnpm db:migrate:local
pnpm db:seed:localcd packages/tail-worker
pnpm exec wrangler secret put DISCORD_WEBHOOK_URL --env production
pnpm deploy:prodFirst time only — provision D1 and seed:
cd packages/server
pnpm exec wrangler d1 create differ # paste the printed id into wrangler.toml's [env.production] block
pnpm db:migrate:prod
pnpm db:seed:prod
pnpm exec wrangler secret put JWT_SECRET --env production
pnpm exec wrangler secret put TURNSTILE_SECRET --env productionEach deploy:
pnpm db:migrate:prod # if there are new migrations
pnpm deploy:prodThe daily-puzzle cron (5 0 * * * UTC) is wired in production
[env.production.triggers]; lazy-build in getDailyRound covers a missed
firing.
Pages auto-builds from the GitHub repo.