Appearance
deploy-prod.mjs
Builds the site, deploys the production Cloudflare Worker, and binds grafted-wars.com and its www alias to it.
Operator tooling › deploy-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 -- --flipOptions
| Flag | Value | What it does | Default |
|---|---|---|---|
--flip | no value | Read 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-run | no value | Read 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 |
--name | grafted-wars-staging | Passed 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) |
--var | ADMIN_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 statusand a freshgit fetch origin dev, to tell whether the tree is clean andHEADis on the Forge'sdev; then the Forge's commit status for that sha, for CI's verdict.- The owner token at
~/.grafted-wars/owner-token, throughowner-claim.mjs, or anADMIN_CLAIM_HASHenvironment override. CLOUDFLARE_API_TOKENandCLOUDFLARE_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 code | Meaning |
|---|---|
0 | Deployed and main moved — or, with --dry-run / GW_DEPLOY_DRY=1, printed what it would have done without touching anything |
1 | npm run build failed, wrangler deploy failed, a domain binding call failed, or the fast-forward of main was refused |
2 | CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID is missing |
3 | Arming refused — --flip, GW_FLIP_ACK=grafted-wars.com, or both, are missing |
4 | Sha 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-runDeploy for real, once staging has been checked:
bash
GW_FLIP_ACK=grafted-wars.com npm run deploy:prod -- --flipNotes
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