Skip to content

Deployment ​

Everything is branch-driven. There is no deploy button and no manual promotion: what is on master is production, what is on dev is QA, and a feature branch is checked and nothing else.

Branch to environment ​

The two environments are genuinely separate down to the database: QA has its own D1 instance, its own Google OAuth client and its own JWT secret. A bad migration on dev cannot reach a real player's league.

→ Deploy Strategy · Dev Branch Deployment

What runs where ​

The order a deploy happens in ​

Migrations first, then the Worker, then the frontend. The order is not negotiable: a Worker deployed against a schema it expects but D1 has not received yet is a production error for as long as the gap lasts.

The Worker bundles model/ and dto/ from the repository root, so the deploy installs the root dependencies before running Wrangler, esbuild resolves those imports from the root node_modules, not the backend's.

The target that is not deployed here ​

The backend runs on either of two stores, and this page describes only one of them. No Cloudflare deployment runs on MongoDB: production and preview are D1, and the MongoDB target runs locally, against a replica set.

Keeping it that way took more than intent. composition.ts names both targets, so the driver is reachable from the Worker's entry point, and making the import dynamic does not help, esbuild follows dynamic imports and inlines them. The dry run measured it: 1.5MB of driver shipped with a Worker that can never use it, and the build failed without nodejs_compat, because the driver imports net, tls and child_process.

So wrangler.jsonc aliases the driver to a stub, which brings the production bundle back to within 8 KiB gzipped of its pre-MongoDB size and leaves the D1 deployments configured exactly as they were. The alias is a top-level key with no per-environment form, which is why the MongoDB run has a wrangler config of its own rather than a fifth environment, and a configuration that can never be deployed does not belong in the file that deploys.

bash
./gradlew devMongo   # the frontend, and the Worker on MongoDB

That config also names a different entry module, which is what gives the MongoDB run username/password sign-in and the deployed Worker none of it. → Persistence Targets · Auth Modes

The nightly scoring run ​

Scoring is the one scheduled job that lives outside Cloudflare, and it runs the published container image rather than the sources.

  • It targets production only. QA is not scored.
  • It runs the image publish-images.yml pushed to GHCR for master, so the night scores exactly the artefact that was published, no checkout, no JDK.
  • Concurrency is keyed on the date, so two runs never score the same day at once. Ingest is idempotent anyway, but overlapping runs waste Wikimedia budget.
  • A repository variable, SCORING_RUNNER=external, hands the nightly over to something else without a code change. A manual dispatch always runs regardless , clicking Run is an explicit instruction, and it is how a missed day gets backfilled mid-handover.
  • An unrecognised value for that variable runs the job anyway, deliberately: scoring twice costs Wikimedia budget, scoring never costs a missing day, so a typo fails toward the cheap mistake.

→ Nightly Scoring Pipeline

Secrets, and who holds them ​

SecretHeld byWhat it opens
CLOUDFLARE_API_TOKEN · CLOUDFLARE_ACCOUNT_IDThe deploy jobWrangler and Pages
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRETThe Worker, per environmentSign-in
JWT_SECRETThe Worker, per environmentSession signing
SCORING_INGEST_SECRETThe Worker and the nightly job/internal/*
GH_APP_PRIVATE_KEYThe WorkerFiling problem reports as the bot
CLOUDFLARE_D1_MIGRATION_RUNNER_SECRETThe migration jobReplaying migrations
GITHUB_TOKENActions, automaticallyGHCR, and publishing this site

The GitHub App key is optional on purpose: a deploy still succeeds before it is set, and the report form answers 502 until it is. A feature that is not configured yet should degrade, not block a release.

The collector holds exactly two credentials, the ingest secret and a Wikimedia user agent, and no database access at all. It is the least privileged thing in the system despite being the one that runs unattended.

Running the whole stack locally ​

./gradlew noGenie brings up the frontend, the Worker and a local D1 with no credentials to obtain: the compose file wires the dev sign-in route, which mints a normal session and refuses to exist outside the local environment. Three sibling tasks, up, demo, demoNoGenie, add the Article Genie, the seeded demo league, or neither.

→ Running FantasyWiki in Docker · Local Development Setup

Built from the repository's own documentation. Source on GitHub.