Skip to content

feat: environment-aware datasette base URL - #609

Open
stefanb wants to merge 1 commit into
mainfrom
feat/env-aware-datasette-url
Open

feat: environment-aware datasette base URL#609
stefanb wants to merge 1 commit into
mainfrom
feat/env-aware-datasette-url

Conversation

@stefanb

@stefanb stefanb commented Aug 18, 2026

Copy link
Copy Markdown
Member

Supersedes #604 (closed; it was stacked on #603 — this PR targets main directly and carries no CLAUDE.md changes).

Problem

Every website environment queried the stage datasette: https://stage-data.podnebnik.org was baked into the JS bundle (hardcoded in two of the three islands). Production served stage data, local instances couldn't use a local datasette, and previews were implicitly tied to stage.

What this does

Shared resolvercode/datasette-base.ts, consumed by all three islands (ERA5 api.ts, legacy ali-je-vroce/constants.ts, temperatura/heatmaps.jsx). Precedence:

  1. VITE_DATASETTE_URL build-time override (compose → 127.0.0.1:8001; manual dev → :8010; preview builds → per-PR)
  2. Fixture mode → the recorded stage base (2,087 URL-keyed fixtures stay valid)
  3. podnebnik.org / stage.podnebnik.orgsame-origin /data — the gateway already routes each host's /data prefix to its own datasette, so the single promoted image reaches prod data on prod and stage data on stage, with no CORS
  4. Fallback → stage base (Node/tests, localhost, previews without their own datasette)

Local — docker compose wires the website dev server to the local datasette container.

Previews (preview-deploy label) — docker-preview.yaml detects whether the PR touches the datasette path set (same list as docker-data.yaml; PR-files API, fail-closed):

  • touches data → builds ghcr.io/podnebnik/data:pr-<N> (db-export generation + multi-arch runtime in one job), bakes /data into the website bundle, and applies the preview-data label after the image push
  • doesn't → bakes the stage base and retracts a stale preview-data label

The Helm chart gains a previewDatasette component (Deployment/Service + /data HTTPRoute rewrite mirroring the live stage/prod route shape; templated rollout revision so each push re-pulls the mutable pr-<N> tag). Chart 1.1.0 → 1.2.0; default render unchanged.

⚠️ Infrastructure prerequisite (sledilnik/infrastructure)

The per-PR-preview half needs the podnebnik-website-preview ApplicationSet to enable the component when the PR carries the preview-data label, e.g. in the generator/template:

previewDatasette:
  enabled: true          # gate on the preview-data PR label
  image:
    tag: "pr-{{ .number }}"
  revision: "{{ .head_sha }}"   # forces re-pull of the mutable tag on each push

Everything else (prod/stage/local) works without any infra change. Ops note: prod pages will start reading data.podnebnik.org, whose image tag is pinned by hand in the infra repo — promote the data image alongside the website when relevant.

Verification

  • yarn typecheck:gate — OK (zero-error allowlist holds)
  • yarn test — 100/100, fixture base guard passes (resolver + new VITE_DATASETTE_URL="" pin land atomically — "" under the old ?? logic would have broken the suite)
  • yarn snapshot:check — byte-identical, 0 fixture misses (the resolver keeps import.meta.env.* as literal member expressions so the snapshot define block still applies)
  • helm template — preview render shows datasette Deployment (pr-999, revision templated) + /data route; default render identical to before
  • Local e2e: compose website dev server injects VITE_DATASETTE_URL=http://127.0.0.1:8001; local datasette verified serving climate-si

🤖 Generated with Claude Code

Every environment previously queried the stage datasette — the base URL
was baked into the JS bundle, hardcoded in two of the three islands.

A shared resolver (code/datasette-base.ts) now picks the base per
environment: VITE_DATASETTE_URL override > fixture base > same-origin
/data on deployed hosts (podnebnik.org -> its own datasette,
stage.podnebnik.org -> stage-data, via the already-live gateway routes)
> stage default for Node/tests/localhost. One promoted image therefore
reaches the right data on both stage and prod, with no CORS.

Local: docker compose wires the website dev server to the local
datasette (127.0.0.1:8001); bare `yarn start` keeps the stage default,
opt in with VITE_DATASETTE_URL=http://127.0.0.1:8010 for invoke
datasette.

Previews: docker-preview.yaml now detects whether the PR touches the
datasette path set (same list as docker-data.yaml, fail-closed via the
PR files API). If so it builds ghcr.io/podnebnik/data:pr-<N> (db-export
generation + multi-arch runtime in one job), bakes /data into the
website preview bundle, and applies the preview-data label AFTER the
push; otherwise it bakes the stage base and retracts a stale label. The
chart gains a previewDatasette component (Deployment/Service + /data
HTTPRoute rewrite, templated rollout revision for the mutable pr-N tag)
that the infra ApplicationSet enables on the preview-data label.

Fixture safety: the resolver reads import.meta.env.* as full literal
member expressions (the snapshot define block replaces those exact
tokens), fixture mode pins the recorded stage base, and vitest now pins
VITE_DATASETTE_URL="" so a shell-exported override can no longer fail
the suite. Verified: typecheck-gate OK, 100/100 unit tests, snapshot
byte-identical with 0 fixture misses, helm template renders the preview
component and an unchanged default, local compose end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ntadej

ntadej commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Before we merge we should figure out how to auto-deploy data to prod, because we do daily updates. Maybe an automated MR should be triggered like for daily refresh?

Will look at the MR in details later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants