.PHONY: help install dev test lint format typecheck migrate migrate-up shell superuser up down logs ps build clean frontend-install frontend-lint frontend-typecheck frontend-build frontend-test frontend-snapshot-schema frontend-gen-api seed-smoke e2e SHELL := /bin/bash .SHELLFLAGS := -eu -o pipefail -c BACKEND := cd backend && FRONTEND := cd frontend && COMPOSE := docker compose -f infra/docker-compose.yml DOCKER_RUN := $(COMPOSE) run --rm backend help: @echo "Common targets:" @echo " install uv sync (creates backend/.venv)" @echo " dev Start docker-compose stack (postgres, redis, web, workers, frontend)" @echo " test Run pytest locally" @echo " lint Run ruff check + mypy" @echo " format Run ruff format + ruff --fix" @echo " typecheck Run mypy" @echo " migrate Create new Django migrations (in container)" @echo " migrate-up Apply Django migrations (in container)" @echo " shell Django shell (in container)" @echo " superuser Create Django superuser (in container)" @echo " up / down docker compose up -d / down" @echo " logs docker compose logs -f" @echo " ps docker compose ps" @echo " build Rebuild backend image" @echo " clean Remove caches and .venv" @echo " frontend-install npm ci in frontend/" @echo " frontend-lint Run frontend eslint" @echo " frontend-typecheck Run tsc --noEmit (covers *.test.tsx; next build does not)" @echo " frontend-build Run next build (type-check + bundle)" @echo " frontend-test Run frontend unit tests (Vitest)" @echo " frontend-snapshot-schema Snapshot OpenAPI schema to frontend/openapi.json" @echo " frontend-gen-api Regenerate the TypeScript API client from openapi.json" @echo " seed-smoke Start compose Postgres + migrate + seed the Playwright smoke fixtures" @echo " e2e Seed + run the Playwright smoke suite (dev stack must be DOWN on the e2e ports)" install: $(BACKEND) uv sync --group dev dev: up @echo "→ http://localhost:3000/ (Next.js frontend)" @echo "→ http://localhost:8000/api/docs/ (Swagger UI)" @echo "→ http://localhost:8000/api/health/" @echo "→ http://localhost:8000/admin/" test: $(BACKEND) uv run pytest lint: $(BACKEND) uv run ruff check . $(BACKEND) uv run mypy . format: $(BACKEND) uv run ruff format . $(BACKEND) uv run ruff check --fix . typecheck: $(BACKEND) uv run mypy . migrate: $(DOCKER_RUN) python manage.py makemigrations migrate-up: $(DOCKER_RUN) python manage.py migrate shell: $(DOCKER_RUN) python manage.py shell superuser: $(DOCKER_RUN) python manage.py createsuperuser up: $(COMPOSE) up -d down: $(COMPOSE) down logs: $(COMPOSE) logs -f ps: $(COMPOSE) ps build: $(COMPOSE) build clean: $(BACKEND) rm -rf .venv .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov # Frontend ergonomics (CI runs the same commands in .github/workflows/frontend.yml). frontend-install: $(FRONTEND) npm ci frontend-lint: $(FRONTEND) npm run lint frontend-typecheck: $(FRONTEND) npm run typecheck frontend-build: $(FRONTEND) npm run build frontend-test: $(FRONTEND) npm run test # Snapshot the OpenAPI schema for the @hey-api/openapi-ts client generator. # Uses test_postgres settings so integer field bounds match PROD (postgres): # drf-spectacular derives a field's maximum/minimum/format from the DB backend's # integer_field_range, and sqlite reports int64 for every integer while postgres # reports true per-type ranges (smallint/int/bigint). The sqlite snapshot would # ship wrong bounds in the committed client. Still runs OFFLINE, no Docker, no # DB, no Redis: spectacular generates from code and integer_field_range is a pure # lookup, so the postgres ENGINE need not be reachable. --validate fails fast on # schema warnings. frontend-snapshot-schema: $(BACKEND) uv run python manage.py spectacular --settings config.settings.test_postgres --format openapi-json --validate --file ../frontend/openapi.json @echo "✓ Schema → frontend/openapi.json" # Regenerate the typed TS client from the snapshot. Commits the output under # frontend/src/lib/api/ so PR diffs show the API surface change (committing both # the snapshot and the generated client keeps drift visible in review). frontend-gen-api: $(FRONTEND) npm run gen:api # Playwright end-to-end smoke suite. Both targets run the # backend under config.settings.smoke (relaxed login throttle, LocMem cache, # no Redis) against a real Postgres (the compose `infra-postgres-1`, or set # DATABASE_URL). Export the settings + DB to the whole recipe so the migrate/seed # prestep AND Playwright's Django webServer (started by `npm run test:e2e`) share # the same database. Ports are env-overridable (E2E_FRONTEND_PORT / E2E_BACKEND_PORT) # in case an unrelated local project holds 3000/8000. # # Local preconditions: (1) the Chromium binary is installed once with # `cd frontend && npx playwright install chromium`; (2) the `make dev` stack must # be DOWN on the default ports (e2e starts its OWN smoke-configured servers and # fails loudly on a port clash, so it no longer silently reuses a dev server), or # set E2E_FRONTEND_PORT / E2E_BACKEND_PORT to free ports. Do NOT `make up` to # provide Postgres: that starts the FULL dev stack onto 3000/8000, the exact # clash above; the recipe below starts ONLY the postgres service. e2e seed-smoke: export DJANGO_SETTINGS_MODULE := config.settings.smoke e2e seed-smoke: export DATABASE_URL := $(if $(DATABASE_URL),$(DATABASE_URL),postgres://postgres:postgres@localhost:5432/millennium) # Start ONLY the compose postgres (idempotent no-op if already up), then migrate # (also idempotent) so a standalone `make seed-smoke` works from a cold start. # The compose-up is skipped when the caller provides DATABASE_URL, an external # DB that may itself hold port 5432. `e2e` reuses this as a prerequisite, so # migrate/seed runs before Playwright starts the servers. seed-smoke: ifndef DATABASE_URL $(COMPOSE) up -d postgres endif $(BACKEND) uv run python manage.py migrate $(BACKEND) uv run python manage.py seed_smoke --reset e2e: seed-smoke $(FRONTEND) npm run test:e2e