Commit & Deploy Policy
How code goes from a local edit to live in production, and the rules around it. The guiding principle: production must always be reachable from
main— what's deployed should equal what's committed.
Golden rules
- Commit only when asked. Don't auto-commit work in progress.
- New commits, never
--amend(unless explicitly asked) — a failed hook means the commit didn't happen; amending would rewrite the previous one. - Add specific files by name, not
git add -A/.— avoids sweeping in.env.local, data dumps, or secrets. - Never commit secrets.
.env.localis gitignored and stays that way. - Every commit ends with the
Co-Authored-By: …trailer when AI-assisted. - Deploy ships what's in the worker tree (bundled by esbuild), so the worker tree must be committed before a (non-forced) deploy — see the gate.
The Worker deploy — scripts/deploy-worker.sh
This is the only way the Worker ships. Never run raw wrangler deploy.
scripts/deploy-worker.sh # both accounts (default)
scripts/deploy-worker.sh --fcr # production only (FCR Media)
scripts/deploy-worker.sh --cd # personal dev only
scripts/deploy-worker.sh --force # deploy a dirty tree (emergencies only)
Preflight gates (why deploys are safe):
- Clean-tree gate — refuses to deploy if anything under
worker/is uncommitted. (Scoped toworker/only — dirty root-level docs/data don't block.)--forcebypasses but then "you owe a commit." no-undeflint gate — runseslintwithscripts/eslint-no-undef.config.mjsoverworker/src/. Catches the classic trap: a free variable referenced in a tool branch that the outer try/catch would swallow into{"error":…}. Two such bugs shipped on 2026-05-17 before this gate existed.
Deploy targets:
- FCR Media (production):
wrangler deploy --env fcrwithCF_API_TOKEN_FCR. - Personal (dev):
wrangler deploy --env=""withCF_API_TOKEN_CDandCLOUDFLARE_ACCOUNT_ID(the worker name exists on both accounts — without the account ID, wrangler resolves to FCR and the CD token 401s).
Both tokens are read from .env.local at the repo root.
Postflight: tags every non-forced deploy deploy/worker/<UTCstamp>-<sha> so
each deployed state is a named ref. --force skips the tag (the SHA wouldn't
reflect what shipped). Tags are local until git push --tags.
Recurring footnote in this repo: the deploy gate frequently catches unrelated in-progress edits under
worker/(e.g. someone else's WIP). The clean way past is togit stash push <file>the unrelated change, deploy, thengit stash pop— don't--forcepast it.
The UI deploy (Cloudflare Pages)
The web UI is a separate Pages project — not shipped by deploy-worker.sh.
npm run build # vite build → dist/
CLOUDFLARE_API_TOKEN=$CF_API_TOKEN_FCR \
npx wrangler pages deploy dist --project-name=fcr-dashboard-ui --branch=production
- Project name is
fcr-dashboard-ui(notfcr-dashboard). - Must deploy to
--branch=productionto update the live site. - Uses
CF_API_TOKEN_FCR(production account).
The docs micro-site deploy (Cloudflare Pages)
The architecture/onboarding docs are also published as a shareable HTML site at
https://fcr-dashboard-docs.pages.dev (rendered from docs/ — humans read
HTML, not markdown). Rebuild + redeploy after editing any doc:
node scripts/build-docs-site.mjs # docs/*.md → docs-site/*.html
CLOUDFLARE_API_TOKEN=$CF_API_TOKEN_FCR CLOUDFLARE_ACCOUNT_ID=0481b38f86c96053c528ec08d7d7dddc \
npx wrangler pages deploy docs-site --project-name=fcr-dashboard-docs --branch=production --commit-dirty=true
- The curated page set + order is the
PAGESarray inscripts/build-docs-site.mjs— add a doc there to include it. docs-site/is generated (gitignored); only the build script is committed.
The HubSpot card deploy
The Prospect Advisor card is a HubSpot UI extension, deployed to HubSpot (not CF):
cd hubspot-extension
hs project upload # builds + uploads to HubSpot
Note: card source includes a gitignored
config.local.tsholding the Worker API key — copy it from the example file before first upload.
Rollback
- Worker:
git checkout <deploy/worker/...-sha>→scripts/deploy-worker.sh, or roll back to a previous version in the CF dashboard (Workers → Deployments). Every deploy is tagged, so any prior state is reachable. - Pages: re-deploy a previous build, or use Pages → Deployments → rollback.
Two Cloudflare accounts
| Account | Worker | Used for | |
|---|---|---|---|
| Production | FCR Media (0481b38f…) |
…fcrmedia.workers.dev |
live — Roam, HubSpot card, UI |
| Dev | Personal (1ceb8928…) |
…cathaldempsey.workers.dev |
testing before prod |
Both carry the same bindings + secrets. deploy-worker.sh (no flag) ships to
both; use --fcr to ship prod only.
Typical change → live flow
1. Edit worker/src/handlers/<x>.js (+ import in index.js if new route)
2. git add <those files>; git commit -m "feat(...): … Co-Authored-By: …"
3. scripts/deploy-worker.sh # gates: clean tree + lint; ships both; tags
4. git push origin main; git push --tags # keep GitHub == prod
5. smoke-test the route (curl with x-api-key)
FCR Dashboard documentation · generated from docs/ · keep counts verified, not guessed.