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
|
"""Canonical filesystem locations for the SSR service, resolved once from the
|
||||||
root. Stage-3-transitional: this package lives at `frontend_ssr/`, a sibling
|
repo root (repo-split Stage 7). content/ is committed here as a starter copy
|
||||||
of the static asset tree `frontend/` (not nested inside it -- nesting would
|
extracted alongside frontend/frontend_ssr -- real cross-repo vendoring from
|
||||||
let StaticFiles serve this package's own source/templates as downloadable
|
thermograph-copy (a build-time clone at a pinned ref) is deferred follow-up
|
||||||
files once app.py mounts frontend/). Still walks up to find `content/` as a
|
work, not yet wired up.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
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")
|
||||||
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")
|
|
||||||
CONTENT_DIR = os.path.join(REPO_DIR, "content")
|
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.
|
"""Shared test setup for the SSR service.
|
||||||
|
|
||||||
Stage-3-transitional: this package still lives in the same repo as the
|
KNOWN GAP (repo-split Stage 7b): this suite does NOT run standalone in this
|
||||||
backend, so a `client` fixture can fake api_client by calling the REAL
|
repo. It still assumes the layout from before the split -- a sibling
|
||||||
backend payload builders (api.content_payloads) against the same synthetic
|
`backend/` checkout with its own `tests/conftest.py` (`make_history`/
|
||||||
history/recent fixtures backend/tests/conftest.py uses, instead of hand-
|
`make_recent`) and `api.content_payloads` module, which this repo no longer
|
||||||
maintaining a parallel set of literal JSON fixtures. This means a shape drift
|
has. Extracted verbatim as reference material for the real fix (a genuine
|
||||||
between content_payloads.py and how content.py consumes it fails here, not
|
cross-repo contract-test job, or a self-contained set of fixtures owned
|
||||||
just in backend/tests/web/test_content.py -- and the two suites assert on
|
here), not yet built -- see this repo's `.forgejo/workflows/build.yml` for
|
||||||
identical numbers, so they can never quietly diverge.
|
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 importlib.util
|
||||||
import os
|
import os
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue