Skip to content

deploy-prod.mjs

Builds the site, deploys the production Cloudflare Worker, and binds grafted-wars.com and its www alias to it.

Operator toolingdeploy-prod.mjs

What it does

Production takes a proven sha and nothing else (ADR-0018): the working tree must be clean, HEAD must be on origin/dev, and CI must have marked that sha green. That rule is judged first, before the arming, and a sha that fails it exits 4 armed or not. Then it refuses to run unless armed with both --flip and a matching GW_FLIP_ACK environment variable, so an ordinary npm run deploy:prod never touches production. Builds the site, stamps a deploy-time version into src/server/version.mjs, and restores that file afterward whether the deploy succeeds or fails. Deploys the worker via wrangler deploy, then binds grafted-wars.com and www.grafted-wars.com to it through the Cloudflare API. A deploy that ends green fast-forwards main to the sha: main is what production runs.

Usage

The minimal deploy, once both arming signals are set:

bash
GW_FLIP_ACK=grafted-wars.com npm run deploy:prod -- --flip

Options

FlagValueWhat it doesDefault
--flipno valueRead from this script's own command line. One of the two required arming signals; without it the script refuses with exit code 3.Unset; required to deploy
--dry-runno valueRead from this script's own command line; GW_DEPLOY_DRY=1 is the same mode. Judges the sha, skips the arming and the Cloudflare credentials, builds, and prints what it would have deployed and where it would have moved main. Nothing is deployed.Unset
--namegrafted-wars-stagingPassed by this script to the underlying wrangler deploy call, naming the Cloudflare Worker being deployed. Not read from this script's own command line — the value is fixed in the source.grafted-wars-staging (fixed; this is production's worker name, not staging's)
--varADMIN_CLAIM_HASH:<64-char hex digest>Passed to wrangler deploy when an owner token has been minted (see owner-claim.mjs), setting the ADMIN_CLAIM_HASH var the deployed worker checks a claim link against.Omitted when no token has been minted — the deployed worker's claim route then answers 404

Inputs and outputs

Reads:

  • The local git branch, short sha and current time, to stamp src/server/version.mjs.
  • git status and a fresh git fetch origin dev, to tell whether the tree is clean and HEAD is on the Forge's dev; then the Forge's commit status for that sha, for CI's verdict.
  • The owner token at ~/.grafted-wars/owner-token, through owner-claim.mjs, or an ADMIN_CLAIM_HASH environment override.
  • CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID, from the environment or the repository's gitignored .env.
  • Optionally CLOUDFLARE_ZONE_ID, falling back to a zone id built into the script when unset.

Writes src/server/version.mjs for the duration of the deploy and restores its original content afterward — always, even on failure, since the restore runs in a finally block. Calls wrangler deploy to upload the built worker, then issues one PUT to https://api.cloudflare.com/client/v4/accounts/{account}/workers/domains per hostname. After both bindings succeed it pushes origin <sha>:refs/heads/main — a fast-forward only; if git refuses the push, production is live at the sha, main is left where it was, and the script exits 1 without repeating the push, since a fast-forward that failed once fails the same way again. Recovery is a revert on dev, proven green, deployed with this script — the proven revert fast-forwards main.

On success, it prints the worker and binding summary, then a curl command for checking the live edge. That command is named but not run, since local DNS can still answer the old address for a while.

Exit codeMeaning
0Deployed and main moved — or, with --dry-run / GW_DEPLOY_DRY=1, printed what it would have done without touching anything
1npm run build failed, wrangler deploy failed, a domain binding call failed, or the fast-forward of main was refused
2CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID is missing
3Arming refused — --flip, GW_FLIP_ACK=grafted-wars.com, or both, are missing
4Sha not proven — the tree is dirty, HEAD is not on origin/dev, or CI has not marked it green; judged before the arming

Examples

See whether HEAD would be allowed, and the exact wrangler invocation and domain bindings this run would make, without arming, deploying or needing Cloudflare credentials:

bash
npm run deploy:prod -- --dry-run

Deploy for real, once staging has been checked:

bash
GW_FLIP_ACK=grafted-wars.com npm run deploy:prod -- --flip

Notes

The worker this script deploys is named grafted-wars-staging in Cloudflare — the actual staging deployment is the separate worker grafted-wars-true-staging, deployed by deploy-staging.mjs. Check a deploy by the hostname it answers on, not by the worker name in the Cloudflare dashboard.

ADMIN_EMAIL is a secret, not a --var, set independently with npx wrangler secret put ADMIN_EMAIL --name grafted-wars-staging; this script never sets it, and a worker without it refuses every request. An ordinary deploy through this script preserves an existing secret. The secret is lost only if the worker itself is deleted and recreated in Cloudflare — after that, the universe refuses every request again until the secret is put back.

If wrangler deploy succeeds but a domain binding call fails partway, with one hostname bound and the other not, the script exits 1. It leaves the domains in a mixed state and does not undo a binding that already succeeded.

Rerunning the same command is safe. The redeploy re-uploads the same build, and the binding call is a PUT with override_existing_origin: true, so repeating it changes nothing that was already correct.

There is no rollback command in this tooling. Undoing a bad deploy means committing a revert on dev, letting CI prove it green, and running this script again — not checking out an older commit, since main only moves forward and a push of an older sha is not a fast-forward. The proven revert is a descendant of what is live, so the same script fast-forwards main to it.

Source

scripts/deploy-prod.mjs — part of Operator tooling