Standalone frontend: no-walk paths.py, own Dockerfile, own CI
paths.py drops the sibling-directory walk (frontend is now the repo root: static/ and content/ are its own subdirectories, no more sibling walk needed). New Dockerfile (much smaller deps -- fastapi, uvicorn, httpx, jinja2, PyYAML -- no entrypoint script needed at all, frontend has no migrations/pre-boot logic). content/ is a committed starter copy for now, not yet real cross-repo vendoring from thermograph-copy (that repo doesn't exist yet) -- noted in paths.py's own docstring. Real, known gap flagged rather than silently skipped: this process cannot boot standalone (content.register() fetches the IndexNow key from backend at import time with no retry, by design), so build.yml only verifies the image builds, not a live boot+healthz check -- that needs a genuine cross-repo contract-test job (booting a real backend too), separate follow-up work. Same reason tests/ doesn't run here yet (its conftest.py still imports backend's own test fixtures via a sibling path that no longer exists) -- noted directly in the file.
This commit is contained in:
parent
d6056afabe
commit
f97df715d1
6 changed files with 100 additions and 36 deletions
34
.forgejo/workflows/build.yml
Normal file
34
.forgejo/workflows/build.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
name: Build check
|
||||
|
||||
# Proves the split Dockerfile actually builds (repo-split Stage 7b) -- NOT a
|
||||
# live boot+healthz check. content.register() fetches the IndexNow key from
|
||||
# backend at import time with no retry (documented Stage 4 behavior,
|
||||
# unchanged here); without a real, reachable backend this process crashes
|
||||
# immediately on import, by design -- so a standalone boot check would either
|
||||
# be fake (skip the real import path) or require checking out and booting
|
||||
# thermograph-backend too. That's the real fix (a genuine cross-repo
|
||||
# contract-test job, same pattern the original repo-split plan already
|
||||
# scoped for the /cell + ETag contract), not yet built -- flagged as a real
|
||||
# gap, not silently skipped. Full pytest-suite migration is separate,
|
||||
# deferred follow-up work too, same category as thermograph-copy's deferred
|
||||
# vendoring.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq docker.io
|
||||
|
||||
- name: Build
|
||||
run: docker build -t thermograph-frontend:ci .
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.DS_Store
|
||||
.env
|
||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Thermograph frontend: server-rendered content pages, the interactive tool's
|
||||
# SPA shells, and every static asset. Split from the monorepo (repo-split
|
||||
# Stage 7). No migrations, no DB, no pre-boot logic -- a plain shell-form CMD
|
||||
# is enough (unlike backend, no separate entrypoint script needed).
|
||||
FROM python:3.12-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt /tmp/requirements.txt
|
||||
RUN pip install --no-cache-dir -r /tmp/requirements.txt
|
||||
|
||||
COPY . /app/
|
||||
|
||||
RUN useradd --system --create-home --uid 10001 thermograph \
|
||||
&& chown -R thermograph:thermograph /app
|
||||
|
||||
USER thermograph
|
||||
WORKDIR /app
|
||||
|
||||
ENV PORT=8080 \
|
||||
THERMOGRAPH_BASE=/ \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
||||
CMD curl -fsS http://127.0.0.1:${PORT}/healthz || exit 1
|
||||
|
||||
CMD uvicorn app:app --host 0.0.0.0 --port ${PORT}
|
||||
36
paths.py
36
paths.py
|
|
@ -1,32 +1,12 @@
|
|||
"""Canonical filesystem locations for the SSR service, resolved from the repo
|
||||
root. Stage-3-transitional: this package lives at `frontend_ssr/`, a sibling
|
||||
of the static asset tree `frontend/` (not nested inside it -- nesting would
|
||||
let StaticFiles serve this package's own source/templates as downloadable
|
||||
files once app.py mounts frontend/). Still walks up to find `content/` as a
|
||||
sibling too, same as backend/paths.py does today, since both still live in
|
||||
one repo. Once frontend is its own repo (Stage 7), content/ is vendored at
|
||||
build time instead and this goes back to a no-walk resolution, same as
|
||||
backend's.
|
||||
"""Canonical filesystem locations for the SSR service, resolved once from the
|
||||
repo root (repo-split Stage 7). content/ is committed here as a starter copy
|
||||
extracted alongside frontend/frontend_ssr -- real cross-repo vendoring from
|
||||
thermograph-copy (a build-time clone at a pinned ref) is deferred follow-up
|
||||
work, not yet wired up.
|
||||
"""
|
||||
import os
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def _repo_root() -> str:
|
||||
d = _HERE
|
||||
while True:
|
||||
if os.path.isdir(os.path.join(d, "frontend")) and os.path.isdir(os.path.join(d, "content")):
|
||||
return d
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
raise RuntimeError(
|
||||
"cannot locate the repo root (no ancestor has sibling frontend/ and content/)"
|
||||
)
|
||||
d = parent
|
||||
|
||||
|
||||
REPO_DIR = _repo_root()
|
||||
STATIC_DIR = os.path.join(REPO_DIR, "frontend")
|
||||
TEMPLATES_DIR = os.path.join(_HERE, "templates")
|
||||
REPO_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
STATIC_DIR = os.path.join(REPO_DIR, "static")
|
||||
TEMPLATES_DIR = os.path.join(REPO_DIR, "templates")
|
||||
CONTENT_DIR = os.path.join(REPO_DIR, "content")
|
||||
|
|
|
|||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
fastapi==0.115.6
|
||||
uvicorn[standard]==0.34.0
|
||||
httpx==0.28.1
|
||||
jinja2==3.1.6
|
||||
PyYAML==6.0.3
|
||||
|
|
@ -1,13 +1,22 @@
|
|||
"""Shared test setup for the SSR service.
|
||||
|
||||
Stage-3-transitional: this package still lives in the same repo as the
|
||||
backend, so a `client` fixture can fake api_client by calling the REAL
|
||||
backend payload builders (api.content_payloads) against the same synthetic
|
||||
history/recent fixtures backend/tests/conftest.py uses, instead of hand-
|
||||
maintaining a parallel set of literal JSON fixtures. This means a shape drift
|
||||
between content_payloads.py and how content.py consumes it fails here, not
|
||||
just in backend/tests/web/test_content.py -- and the two suites assert on
|
||||
identical numbers, so they can never quietly diverge.
|
||||
KNOWN GAP (repo-split Stage 7b): this suite does NOT run standalone in this
|
||||
repo. It still assumes the layout from before the split -- a sibling
|
||||
`backend/` checkout with its own `tests/conftest.py` (`make_history`/
|
||||
`make_recent`) and `api.content_payloads` module, which this repo no longer
|
||||
has. Extracted verbatim as reference material for the real fix (a genuine
|
||||
cross-repo contract-test job, or a self-contained set of fixtures owned
|
||||
here), not yet built -- see this repo's `.forgejo/workflows/build.yml` for
|
||||
the same gap on the boot-check side. Do not assume `pytest tests` passes
|
||||
here until that follow-up lands.
|
||||
|
||||
Original docstring, still accurate for what the suite is trying to prove:
|
||||
this package used to live in the same repo as the backend, so a `client`
|
||||
fixture could fake api_client by calling the REAL backend payload builders
|
||||
(api.content_payloads) against the same synthetic history/recent fixtures
|
||||
backend/tests/conftest.py uses, instead of hand-maintaining a parallel set of
|
||||
literal JSON fixtures -- so a shape drift between content_payloads.py and how
|
||||
content.py consumes it failed here too, not just in the backend's own suite.
|
||||
"""
|
||||
import importlib.util
|
||||
import os
|
||||
|
|
|
|||
Loading…
Reference in a new issue