Skip to content

Clean up the deploy workflow and add preprod + PR previews #98

Open

Description

@davd-gzl

Every merge to main goes straight to the live site, there is no preprod to check a build on first, and pull requests have no preview link. The four workflows have also grown overlapping steps and timing-based glue. This issue lists what is wrong today and proposes a cleaner flow.

What's wrong today

No preprod, no previews

  • deploy.yml publishes every push to main directly to production (GitHub Pages). There is nowhere to look at a build before users get it.
  • Pull requests have no preview URL. Testing Add Interrail as a pass and read timetables from train-api #83 meant building locally, because a repo's GitHub Pages can only host one site.
  • vite.config.ts hardcodes base: "/MAX-Finder/" for web builds, so the same build cannot be served from another host or path (a preview domain, a staging subfolder).

CI and deploy duplicate each other and drift

  • test.yml and deploy.yml each run install, build, prerender, generate-pages, verify and e2e on their own. On a push to main both run in parallel and the deploy does not wait for CI.
  • They already differ: the deploy skips the unit tests (npm test), and it ships a build made with freshly fetched data, while CI tested the committed snapshot. What gets deployed is not exactly what was tested.

Data refresh and code deploy are tangled

  • update-data.yml commits a new snapshot to main every day (chore(data): update tgvmax snapshot [skip ci]), which fills the history with bot commits.
  • Because bot pushes don't trigger workflows, deploy.yml needs its own cron (13:30 UTC) timed 30 minutes after the data cron (13:00 UTC). GitHub delays scheduled runs heavily: the last scheduled deploys actually started between 17:55 and 21:10 UTC, so the ordering only works by luck.
  • The e2e and render gates read live data relative to today's date. When the snapshot changes, a code-unrelated test can fail and block every deploy. That is what happened on 10 and 11 October: the scheduled deploy failed and the site stayed on the 9 October build until Tests: nightly checks pick a day the snapshot has trains for #95 landed today (Fix the daily deploy: run the scroll e2e test on a busy date #96 also targets the same failure and is still open).

Fragile deploy step

  • The Pages deploy is wrapped in a "try, sleep 90, retry" to work around Pages' one-deployment-in-flight limit.

Proposed flow

PR opened/updated ──► CI (build once, test, upload dist) ──► preview deploy (per-PR URL, commented on the PR)
merge to main ─────► CI ──► staging deploy (preprod URL)  ──► manual "promote" ──► production
daily data job ────► fetch data ──► data sanity check ──► rebuild staging + production from the current release
  1. One build, reused everywhere. Turn test.yml into a reusable workflow (workflow_call) that builds once, runs every gate (unit, verify, e2e) and uploads dist as an artifact. Preview, staging and production deploys download that artifact instead of rebuilding.
  2. Three environments using GitHub Environments:
    • preview: one URL per PR, removed when the PR closes.
    • staging (preprod): updated automatically on every merge to main.
    • production: deployed only by promoting a build that is already on staging (manual approval on the environment, or publishing a release/tag).
  3. Hosting for previews and staging. GitHub Pages can't do per-PR sites. Cloudflare Pages or Netlify (both free for this size, both give per-branch preview URLs) would cover previews and staging; production can stay on GitHub Pages or move too. This needs the base path to come from an env var (e.g. VITE_BASE) instead of being hardcoded.
  4. Decouple data from code.
    • Make the e2e and render gates use a pinned fixture snapshot and a fixed "today", so a data refresh can never fail a code check.
    • Keep a small, separate data sanity check (file parses, enough trains, dates in range) that gates the data refresh only.
    • The daily data job then redeploys the current production release with the new data directly, instead of committing to main and relying on a second cron. Whether the snapshot is still committed (for the APK/F-Droid offline fallback) can move to a less frequent commit or a dedicated data branch.
  5. Drop the sleep-and-retry. Put the concurrency group on the deploy job per environment, so a deploy waits for the previous one instead of failing and retrying.

Done when

  • Every PR gets a preview link posted on it.
  • Merging to main updates a preprod URL, not production.
  • Production changes only by promoting a build that passed on preprod.
  • CI builds once and the deploys reuse that artifact.
  • Test gates no longer depend on today's date or the live snapshot.
  • The daily data refresh no longer needs a second, timed cron to reach the live site.

Activity

  1. self-assigned this
    on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions