-
Notifications
You must be signed in to change notification settings - Fork 0
Web Deploy Workflow
This page covers the end-to-end release process for proton-pulse-web -- from local changes through staging review to production.
- Branch and issue workflow
- Overview
- Before you start
- Step 1: make changes and pre-push
- Step 2: commit and push to staging
- Step 3: deploy to staging site
- Step 4: review on staging
- Step 5: open a PR and merge to main
- Step 6: promote to production
- Full pipeline deploy
- Staging deploy paths (pick the smallest one that fits)
- CI workflows
- Skipping staging
- Emergency overrides
Every change needs a GitHub issue. This is the paper trail and the anchor for staging/prod comments.
All in-progress code goes to the staging branch. Production (main) only receives code through a reviewed and approved PR.
- Create a GitHub issue:
gh issue create --repo mdeguzis/proton-pulse-web --title "..." --label enhancement - Create a feature branch off
staging:git checkout staging && git checkout -b feature/<short-name> - Do all work on the feature branch. Reference the issue in commits:
feat: ... (closes #N) - Merge the feature branch into
stagingwhen ready:git checkout staging && git merge feature/<short-name> && git push origin staging - Go through the staging review flow. Open a PR
staging->mainonce satisfied. - After the issue closes, delete the branch:
git push origin --delete feature/<short-name>
- Create a GitHub issue:
gh issue create --repo mdeguzis/proton-pulse-web --title "..." --label bug - Commit the fix to the
stagingbranch (or afix/<short-name>branch merged intostaging). - Reference the issue in the commit:
fix: ... (closes #N)
Branch naming: feature/<short-name> for features, fix/<short-name> for standalone bug branches.
All changes follow this path:
local changes -> pre-push checks -> push to staging -> staging deploy -> staging review -> PR staging->main -> production
Production (main) is never touched until staging has been reviewed and the PR has been approved. Branch protection on main requires 1 approved review and a passing test CI check before any merge can proceed.
The site was migrated off GitHub Pages onto Cloudflare Pages. Two projects:
| Environment | CF Pages project | Custom domain |
|---|---|---|
| Staging | proton-pulse-web-staging |
staging.proton-pulse.com |
| Production | proton-pulse-web |
www.proton-pulse.com |
Per-game data buckets (~187k files) live in the R2 bucket proton-pulse-data, served at data.proton-pulse.com. The Pages projects only host the shell + a few dozen small top-level JSON files.
Every push to staging or main that touches a user-facing file (*.html, css/**, js/**, gh-pages-manifest.txt, scripts/publish-cloudflare.sh, scripts/preserve-cert-monitor.sh, or .github/workflows/publish-shell.yml) auto-triggers publish-shell.yml, which deploys the shell to the matching CF Pages project within ~1-2 minutes. No manual step required for typical UI changes.
The retired GitHub Pages deploy path (gh-pages branch + pages-build-deployment) is kept as a rollback option only; see Rollback to GitHub Pages.
The web app lives at:
~/src/decky-proton-pulse-project/proton-pulse-web/
All deploy targets require gh (GitHub CLI) and a valid session. Confirm before starting:
gh auth statusMake sure you are on the staging branch before making changes:
git checkout stagingAfter editing JS or CSS, run:
make pre-pushThis runs three things in sequence:
-
cache-bust (
make build) - recomputes?v=hashes on all JS/CSS imports based on file content. Every file that changed gets a new hash; unchanged files keep theirs. -
Jest tests (
make coverage) - unit tests, manifest completeness check, and coverage gate. -
Smoke render (
make smoke) - headless render check that catches missing imports before they hit CI.
If any step fails, fix it before committing. Do not commit with a failing make pre-push.
Two extra steps are required when adding a new file:
- Add it to
gh-pages-manifest.txt-- the pages-only deploy only copies files listed there. A missing entry means the import 404s silently on production. - Add it to every relevant deploy step in
.github/workflows/update-data.yml.
The manifest test in Jest catches rule 1 automatically -- npm test will fail if a manifest JS file imports something not listed in the manifest.
git add <files>
git commit -m "feat: ..."
git push origin stagingCommit messages must follow Conventional Commits: feat:, fix:, chore:, ci:, docs:, etc.
On push, CI runs automatically:
-
CI (
ci.yml) - Jest tests with coverage, Supabase schema/RLS integration tests -
Sync staging repo (
sync-staging-main.yml) - mirrors changes toproton-pulse-web-stagingrepo
Watch CI at: https://github.com/mdeguzis/proton-pulse-web/actions
Nothing to run. The push to staging in Step 2 auto-triggers publish-shell.yml for any user-facing file change. Within ~1-2 minutes the shell lands on staging.proton-pulse.com.
Check the run via the Deployments admin tab (admin.html) or:
gh run list --repo mdeguzis/proton-pulse-web --workflow="Publish Shell to Cloudflare Pages" --limit 3Live staging: https://staging.proton-pulse.com/
If you need to force a re-deploy without a new commit (e.g. after fixing a CF secret out-of-band):
make cf-staging # dispatches publish-shell.yml against stagingmake gh-staging still exists and deploys to the retired mdeguzis.github.io/proton-pulse-web-staging GitHub Pages repo (that URL now 301-redirects, so the deploy has no visible effect). Kept as a rollback path only -- see Rollback to GitHub Pages.
Open the staging site and verify:
- The change you made works as expected
- No regressions on pages you did not touch (home, app, admin if relevant)
- No console errors in browser DevTools
Post the staging run URL and staging preview URL as a comment on the relevant GitHub issue.
Do not make further commits or trigger another staging deploy until the user confirms the staging looks correct. One staging deploy per iteration.
This step must always be performed by a human. Never merge the PR yourself.
Once staging is confirmed good, open a PR from staging to main:
gh pr create --base main --head staging --repo mdeguzis/proton-pulse-web --title "..." --body "..."Branch protection on main requires:
- At least 1 approved review
- Passing
testCI status check
The user reviews the PR, approves it, and merges it. Only after the merge does production get updated.
Nothing to run. The merge into main from Step 5 is itself a push that auto-triggers publish-shell.yml against main. The shell lands on www.proton-pulse.com within ~1-2 minutes.
Check the run via the admin Deployments tab or:
gh run list --repo mdeguzis/proton-pulse-web --workflow="Publish Shell to Cloudflare Pages" --limit 3Live site: https://www.proton-pulse.com/
Confirm on the about page that the SHA matches main's tip. Post the run URL as a follow-up comment on the issue and close it.
scripts/publish-cloudflare.sh fetches the live version.json before deploying and compares the deployed_at timestamp to the git commit's ISO (normalized to UTC via date -d -u). If the commit is OLDER than what is already live, the deploy is SKIPPED with exit 0. This catches the pipeline-with-stale-checkout race where a long finalize run finishes after a fresh shell push and would otherwise overwrite it.
If you need to dispatch prod manually (e.g. after fixing a CF secret and no new commit is available):
make cf-prod # dispatches publish-shell.yml against mainmake gh-pages-only still exists and deploys to the gh-pages branch of the main repo. GitHub Pages will still build that branch, but the DNS for www.proton-pulse.com now points at CF Pages so the gh-pages deploy is only reachable via the mdeguzis.github.io/proton-pulse-web/ URL. Kept as a rollback option -- see Rollback to GitHub Pages.
Use this when game data also needs refreshing (new pipeline features, data format changes, or a scheduled full refresh):
make gh-runThis runs the full data pipeline (Steam catalog fetch, ProtonDB probe, finalize, search index) and then deploys to production. It also requires the staging site to be in sync with origin/staging.
Staging has three deploy targets. Pick the smallest one that actually exercises the change so verification stays fast.
flowchart TD
A[Change on staging] --> B{Touches JS / CSS / HTML only?}
B -- yes --> C[make gh-staging<br/>~2 min<br/>shell only, staging reads prod data]
B -- no --> D{Touches finalize.py or<br/>search-index / recent-reports /<br/>stats / most_played shape only?}
D -- yes --> E[make gh-staging-finalize<br/>~5 min<br/>skip probe + build,<br/>restore prod chunk state,<br/>rerun finalize + stats,<br/>push to staging repo]
D -- no --> F[make gh-staging-pipeline<br/>~30 min<br/>full pipeline against staging,<br/>push to staging repo]
C --> G[Verify at staging URL]
E --> G
F --> G
G --> H{Looks right?}
H -- yes --> I[Open PR staging to main]
H -- no --> A
Rules for picking:
- If the change is UI-only,
gh-stagingis enough. Staging falls back to prod data throughfetchDataWithProdFallback, so cards render exactly as they will on prod. - If the change adds or renames a field in a pipeline JSON output (search-index column, recent-reports field, stats bucket),
gh-staging-finalizeregenerates the affected files against prod chunk state. This is the right target for schema-only shape changes. - Reach for
gh-staging-pipelineonly when the change needs fresh probe data (new backfill logic, changed probe cadence, a new signal source).
The staging_with_finalize mode reuses the sharded chunk state saved to gh-pages/.pipeline-state/ (see Data-Pipeline). No probing happens on this path, which is why it lands in about the wall time of a normal finalize step.
| Workflow | Trigger | What it does |
|---|---|---|
CI (ci.yml) |
push to main/staging, daily | Jest + coverage, Supabase schema/RLS tests |
Publish Shell to Cloudflare Pages (publish-shell.yml) |
push to main/staging (path-filtered), manual dispatch | Fast shell-only CF Pages deploy (~1-2 min). Handles the day-to-day "my push is now live" path. |
Update ProtonDB Data (update-data.yml) |
manual dispatch, cron, staging/prod deploy targets | Full data pipeline. Deploys to CF Pages by default (deploy_target=cloudflare) via publish-cloudflare.sh. ghpages option kept as rollback. |
Deploy Cloudflare Workers (deploy-worker.yml) |
push to workers/**, manual dispatch | Wrangler deploy of the pp-edge-status worker |
Deploy Cloudflare Functions (deploy-functions.yml) |
push to supabase/functions/**, manual dispatch | Deploys Supabase edge functions |
Cert Monitor (cert-monitor.yml) |
every 6 hours | Reads the served TLS cert, writes cert-status.json + cert-history.json to gh-pages (still the source of truth; publish-cloudflare.sh copies these into the CF Pages deploy) |
Content Moderation (content-moderation.yml) |
scheduled | Scans submitted reports for banned phrases |
Sync staging repo (sync-staging-main.yml) |
push to main/staging, daily | Mirrors branch to the retired staging repo (kept as rollback) |
| pages-build-deployment | automatic on gh-pages push | GitHub Pages build for the retired live site (rollback only) |
publish-shell.yml and the finalize job in update-data.yml share a concurrency group cf-pages-<project> with cancel-in-progress: false. Deploys to the same CF Pages project queue behind each other rather than racing.
Badges for all workflows are in the README.
tests/supabaseSchema.test.js runs against the live Supabase Management API using SUPABASE_URL and SUPABASE_TOKEN secrets. Tests use describeIfCreds and skip automatically when credentials are absent (forked PRs). The tests include 2-attempt retry logic with 3s backoff for transient Supabase 5xx responses.
Staging review may be skipped only for:
- Typo-only README or docs changes
- CI/workflow-only changes with no site impact
- Hotfixes the user has already verified on the live site
For everything else, go through staging.
If you need to promote to production without going through the staging sync check (e.g. a hotfix that was already verified live):
FORCE_DEPLOY=1 make gh-pages-onlyUse this sparingly. It bypasses the staging SHA check entirely.
The GitHub Pages deploy path is preserved as a rollback if Cloudflare Pages goes down or a CF-side regression forces us to revert. Steps:
-
Dispatch the pipeline with
deploy_target=ghpagesvia workflow_dispatch (Actions -> Update ProtonDB Data -> Run workflow). This deploys shell + data to thegh-pagesbranch of the main repo. -
Repoint DNS at Cloudflare so
www.proton-pulse.com-> the GitHub Pages IPs (185.199.108-111.153) instead of the CF Pages worker route. - Set Cloudflare SSL/TLS mode back to Full (non-strict). Full (Strict) will 526 against GitHub Pages' Let's Encrypt cert because Cloudflare cannot validate it while proxied.
- If the GH Pages cert has expired during the CF-only window, follow the retired
GitHub-Pages-Cert-Renewalwalkthrough (kept in the wiki for exactly this scenario).
Reverse the steps to return to CF Pages. Keep the rollback window short -- staying on GH Pages long-term reintroduces the origin-cert saga that motivated the migration.