Template v2: fullstack frontend/ + backend/ (Vite/React 19/TS + rendered FastAPI backend) - #6
Merged
Merged
Conversation
- min-release-age=3: refuse dependency versions younger than 3 days so short-lived malicious releases (recent npm worms were yanked within hours) are never resolved. npm ci from the lockfile is unaffected. Requires npm >= 11.10.0; older npm ignores the key harmlessly. - ignore-scripts=true: neutralize the preinstall-hook execution vector. This CRA project has no dependency that needs an install script. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5mtFwwNP8tzKBC74X7rQk
Full rewrite of the template on top of the supply-chain hardening: - Replace deprecated Create React App (react-scripts 4) with Vite 8 - React 17 -> 19, Material-UI v4 -> MUI v9, react-router v5 -> v7, react-dropzone v11 -> v20; npm audit now reports 0 vulnerabilities - Migrate source to TypeScript (strict), sx-based styling, createRoot - Add TanStack Query data layer with a typed fetch wrapper and example health-check query + file-upload mutation against a Python backend - Dev proxy for /api -> localhost:8000, .env.example with VITE_API_URL - ESLint 10 flat config + Prettier, Vitest + Testing Library with an example test, GitHub Actions CI (lint, format, test, build) - Rewrite README for the template, single npm lockfile (drop yarn.lock), .nvmrc (Node 22), remove CRA/PWA leftovers and generic demo content Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Node 24 has been the active LTS since Oct 2025; Node 22 is in maintenance. CI picks it up via .nvmrc; engines allows >=22. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
- Build stage on node:24-alpine with layer-cached npm ci (BuildKit cache mount) and VITE_API_URL build arg - Runtime stage on nginxinc/nginx-unprivileged (non-root, port 8080) serving the static build: SPA fallback for React Router, immutable caching for hashed assets, no-cache for index.html, gzip, healthcheck, commented /api proxy block for the backend - .dockerignore and README section Verified locally: image builds, container serves / and /about (SPA fallback), cache headers correct, runs as uid 101, healthcheck healthy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Prepares the symmetric frontend/ + backend/ fullstack layout. Pure move, no content changes; CI workflow paths are updated in a follow-up commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Rendered with copier (answers in backend/.copier-answers.yml: package app, FastAPI API + Docker, no CLI) plus two deterministic post-render steps that the future sync job must reproduce: - drop the rendered .github/ (workflows are repo-owned at root) - set [tool.setuptools_scm] root = ".." so the version derives from this repo's git metadata (pyproject is not at the git root here) uv.lock generated and .pre-commit-config.yaml revs aligned with sync-with-uv, mirroring the python-template sync job. The answers file is kept so projects created from this template can run `copier update` on their own backends. backend/ is a mirror of the template: do not edit it in this repo — changes belong in python-copier-template. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
- Status chip reads GET / (shows backend version); mutation example is POST /predict, matching backend/app/api.py - Vite dev proxy targets localhost:7000 and strips the /api prefix; nginx does the same (nginx.compose.conf variant with proxy enabled, used by the root compose.yaml) - Typed endpoint wrappers in src/api/backend.ts - Remove react-dropzone example (no matching backend endpoint in the template API; easy to restore from git history if needed) - Frontend README scoped to the frontend; fullstack docs move to root Verified end-to-end: dev proxy against live uvicorn returns version and prediction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
- Fullstack README (structure, ownership model, quickstart) and CLAUDE.md documenting that backend/ is a rendered mirror - Makefile with delegating targets for both sides - compose.yaml: backend + frontend, nginx /api proxy via compose- specific config mount - Path-filtered workflows: Frontend (lint/format/test/build), Backend (ruff/ty/pytest), Contract (boots the real backend and checks the endpoints the frontend uses — the gate that makes automated backend syncs safe to auto-merge) - Dependabot: actions + npm (backend deps come via template sync) - docs/backend-sync.md: deterministic render recipe and the ready-to- copy sync workflow for python-copier-template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
Both must be fixed in python-copier-template before enabling the backend sync workflow (a sync would revert these local patches). Both also affect python-template itself: - Dockerfile: pinned uv:0.10.12-python3.14-bookworm-slim base does not exist (python 3.14 uv images ship on trixie); switch builder and runtime to trixie - pyproject: add pyyaml — uvicorn requires it to load log_conf.yaml, so the Docker CMD and the `python app/api.py` dev entrypoint crash without it (plus deptry DEP002 ignore, uv.lock update) Verified after patching: docker compose stack serves the frontend on :8080, nginx proxies /api to the backend, browser round-trip (status chip + predict) works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
The Backend/Contract workflows run uvicorn directly, so they cannot catch images that build but fail to start (exactly the two upstream bugs this PR patches). The new Docker workflow builds both images via docker compose and checks the contract through nginx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
The readiness poll hit GET / (served statically by nginx immediately) and then asserted /api/ while uvicorn was still booting, yielding a 502. Poll /api/ through nginx instead, which waits for both containers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011texLkDBELWbXsBf6San3M
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Turns the repo into a fullstack template: a modernized React frontend (
frontend/) paired with a FastAPI backend (backend/) rendered from python-copier-template, plus the glue (compose, CI, contract test) that ties them together. Grew from the original scope (npm supply-chain hardening → frontend modernization → fullstack restructure) — commits are staged for review in that order.Layout & ownership model
backend/is a rendered mirror — never edited here. A sync job in python-copier-template (spec + ready-to-copy workflow in docs/backend-sync.md) re-renders it and opens an auto-merging PR on template changes, python-template style..copier-answers.ymlis kept so projects created from this template can runcopier updateon their own diverged backends..github/(workflows are repo-owned at root), set[tool.setuptools_scm] root = ".."(version derives from this repo's git metadata).Frontend (modernized from CRA)
react-scripts4) → Vite 8; React 17 → 19; strict TypeScript; MUI v4 → v9; react-router v5 → v7; TanStack Query v5;npm audit: 0 vulnerabilitiesGET /api/→ status/version chip,POST /api/predict→ mutation demo (typed wrappers insrc/api/backend.ts)/api→localhost:7000(prefix stripped, no CORS); nginx does the same in prod.nvmrc),.npmrcsupply-chain hardening (min-release-age cooldown + ignore-scripts — the original PR, kept), single npm lockfileBackend
Rendered with
package_name=app,include_api=true,include_docker=true,include_cli=false. uv, ruff, ty, pytest, prek config, uv.lock generated +sync-with-uv, own Dockerfile (uvicorn on port 80).Glue
compose.yaml:docker compose up --build→ nginx serves the frontend on :8080 and proxies/apito the backend containerMakefile(delegating targets), fullstack README, CLAUDE.md with the ownership rules, Dependabot (actions + npm; backend deps come via template sync)Patched locally with
TEMPORarymarkers; both must be fixed upstream before enabling the sync workflow (a sync would revert the patches — details in docs/backend-sync.md):uv:0.10.12-python3.14-bookworm-slimbase doesn't exist (Python 3.14 uv images ship on trixie). Patched to trixie.--log-config log_conf.yaml; both the Docker CMD and thepython app/api.pydev entrypoint crash without it. Patched by addingpyyaml.Verification (all local)
Follow-ups after merge
🤖 Generated with Claude Code
https://claude.ai/code/session_011texLkDBELWbXsBf6San3M