F15 — Two unrelated frontend weaknesses. A render error unmounted the whole React tree: a white window, no navigation, no indication of what happened, and the only way out was knowing to reload. ErrorBoundary now shows the message with a retry and a reload, and clears itself when resetKey (the route) changes, so navigating to a working page just works instead of staying stuck. There are two: one inside AppShell around the routed pages, one at the root for the shell itself and the login screen, which sit outside it. The bundle was one 841 kB file (234 kB gzipped) every visitor downloaded in full, with Vite warning about it on every build. Routes are lazy now and the entry chunk is 377 kB (120 kB gzipped) — a 55% cut, warning gone. Measuring first changed what to split. Monaco turned out not to be in the bundle at all: @monaco-editor/react loads it from cdn.jsdelivr.net, so only the small wrapper ships. xterm.js *is* bundled, all 294 kB of it, and it was reachable from ContainerCard — which renders on every stack detail page — so every visitor paid for a terminal most never open. It is lazy now and lands in its own chunk. (Worth knowing separately: the compose editor therefore needs jsdelivr.net reachable. For a self-hosted tool on an air-gapped network that is a real limitation, but vendoring Monaco means +3 MB and is its own change.) Adding a boundary whose behaviour I could only reason about was not good enough, and the missing frontend test runner was already flagged as the gap from 0.49.0. So this also sets up vitest + jsdom + testing-library and covers the boundary: that it renders the error rather than a blank page, offers a way out, clears on navigation, and stays put on an unrelated re-render. CI runs `npm test` next to pytest. One snag worth recording: installing the dev dependencies triggered npm's optional-dependency pruning and dropped @rollup/rollup-linux-x64-gnu, which broke the build. Reinstalling it directly put a linux-x64-glibc binary in package.json, which would have broken `npm ci` on every other platform — so that was backed out and the lockfile now carries the bindings as rollup's optional deps, where they belong. Verified with a clean `npm ci` in a scratch copy: install, typecheck, build and test all pass from the committed lockfile. 758 backend tests, 7 frontend tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dk43rmEeRfYi5wsLDbmfyG
111 lines
3.7 KiB
YAML
111 lines
3.7 KiB
YAML
# Continuous integration on git.menzel.center (Gitea Actions).
|
|
#
|
|
# Two jobs: `check` runs both test suites (pytest, vitest), the linter and the
|
|
# frontend typecheck; `build-and-push` only starts once `check` is green, so a
|
|
# red suite never reaches the registry (and never reaches the self-update
|
|
# checker, which would happily offer a broken release).
|
|
#
|
|
# Builds and pushes both images to this instance's container registry on every
|
|
# push to main: backend and frontend. Each image gets both a ":latest" tag and
|
|
# a ":{APP_VERSION}" tag, the latter read
|
|
# from backend/version.py (the single source of truth for the release
|
|
# version) - self_update_service compares registry version *tags* against the
|
|
# running APP_VERSION to decide whether an update is available, so without a
|
|
# version tag it would never see one, no matter how far behind :latest is.
|
|
#
|
|
# Runs on ubuntu-latest, not the docker label: that label's image is a bare
|
|
# docker:24-dind with no Node/bash, which breaks actions/checkout (a JS
|
|
# action). ubuntu-latest has both a shell and the Docker CLI, talking to the
|
|
# host daemon through the socket the runner passes in.
|
|
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
|
|
env:
|
|
REGISTRY: git.menzel.center/menzeljonas
|
|
|
|
jobs:
|
|
check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
token: ${{ secrets.CI_TOKEN }}
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12" # matches backend/Dockerfile
|
|
|
|
- name: Install backend + test dependencies
|
|
working-directory: backend
|
|
run: pip install -r requirements-dev.txt
|
|
|
|
- name: Lint (ruff)
|
|
working-directory: backend
|
|
run: ruff check .
|
|
|
|
# The suite runs without a Docker daemon on purpose: it drives the app
|
|
# through TestClient without the lifespan, so no background loops and no
|
|
# socket. See backend/tests/conftest.py.
|
|
- name: Test (pytest)
|
|
working-directory: backend
|
|
run: pytest
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Install frontend dependencies
|
|
working-directory: frontend
|
|
run: npm ci
|
|
|
|
- name: Typecheck (tsc)
|
|
working-directory: frontend
|
|
run: npx tsc --noEmit -p tsconfig.json
|
|
|
|
- name: Test (vitest)
|
|
working-directory: frontend
|
|
run: npm test
|
|
|
|
build-and-push:
|
|
needs: check
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
token: ${{ secrets.CI_TOKEN }}
|
|
|
|
- name: Read app version
|
|
id: version
|
|
run: |
|
|
VERSION=$(grep -oP '(?<=APP_VERSION = ")[^"]+' backend/version.py)
|
|
echo "Building version $VERSION"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Log in to the registry
|
|
run: |
|
|
echo "${{ secrets.CI_TOKEN }}" | docker login git.menzel.center -u menzeljonas --password-stdin
|
|
|
|
- name: Build and push backend
|
|
run: |
|
|
docker build \
|
|
-t "$REGISTRY/stackpilot-backend:latest" \
|
|
-t "$REGISTRY/stackpilot-backend:${{ steps.version.outputs.version }}" \
|
|
./backend
|
|
docker push "$REGISTRY/stackpilot-backend:latest"
|
|
docker push "$REGISTRY/stackpilot-backend:${{ steps.version.outputs.version }}"
|
|
|
|
- name: Build and push frontend
|
|
run: |
|
|
docker build \
|
|
-t "$REGISTRY/stackpilot-frontend:latest" \
|
|
-t "$REGISTRY/stackpilot-frontend:${{ steps.version.outputs.version }}" \
|
|
./frontend
|
|
docker push "$REGISTRY/stackpilot-frontend:latest"
|
|
docker push "$REGISTRY/stackpilot-frontend:${{ steps.version.outputs.version }}"
|