docs: generate project documentation
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
<!-- generated-by: gsd-doc-writer -->
|
||||
# Development Guide
|
||||
|
||||
Local development setup and workflows for FamilySync — a pnpm monorepo with two workspaces: `apps/api` (Hono + Node.js) and `apps/pwa` (React + Vite).
|
||||
|
||||
## Repo Layout
|
||||
|
||||
```
|
||||
familysync/
|
||||
├── apps/
|
||||
│ ├── api/ # Hono API server — Node.js 22, TypeScript, Drizzle/MariaDB
|
||||
│ └── pwa/ # React 19 PWA — Vite, TanStack Query, Schedule-X
|
||||
├── docker-compose.yml
|
||||
├── docker-compose.dev.yml
|
||||
├── package.json # Root workspace scripts
|
||||
└── pnpm-workspace.yaml
|
||||
```
|
||||
|
||||
Key paths inside `apps/api/src/`:
|
||||
|
||||
```
|
||||
src/
|
||||
├── db/
|
||||
│ ├── schema.ts # Drizzle table definitions (source of truth for migrations)
|
||||
│ ├── client.ts # mysql2 pool + drizzle instance
|
||||
│ └── migrations/ # Generated SQL migration files
|
||||
├── routes/ # Hono route files (events, lists, push, sse, me, health)
|
||||
├── auth/ # OIDC middleware
|
||||
├── broker/ # CalDAV broker (tsdav + ical.js)
|
||||
└── lib/ # Shared utilities
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js 22 LTS** — the Dockerfile base is `node:22-alpine`; match this locally
|
||||
- **pnpm 11.5.1** — managed via corepack (`corepack enable pnpm`)
|
||||
- **Docker + Docker Compose** — for MariaDB and Redis in dev
|
||||
- **TypeScript 5.x** — installed per-workspace as a dev dependency
|
||||
|
||||
## Local Setup
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
This installs all workspace packages (`apps/api` and `apps/pwa`) in a single pass.
|
||||
|
||||
### 2. Start the dev database and Redis
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up mariadb redis -d
|
||||
```
|
||||
|
||||
The dev override (`docker-compose.dev.yml`) exposes MariaDB on `localhost:3306` and Redis on `localhost:6379`.
|
||||
|
||||
### 3. Configure environment variables
|
||||
|
||||
Copy the root `.env.example` to `.env` and fill in the required values. The root `.env` is sourced by docker-compose for container env vars. When running the API server directly on the host (outside Docker), you must override `DB_HOST`:
|
||||
|
||||
```bash
|
||||
# Source the root .env, then override DB_HOST for host-side execution
|
||||
DB_HOST=127.0.0.1 node dist/index.js
|
||||
```
|
||||
|
||||
Or set `DB_HOST=127.0.0.1` in your local `.env` for the dev workflow. The docker-compose production config sets `DB_HOST: mariadb` (the service name); that value does not resolve on the host.
|
||||
|
||||
For auth bypass during local UI development, set `DEV_AUTH_BYPASS=true` — this skips OIDC and authenticates as the dev user (id 1).
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### API dev loop
|
||||
|
||||
The `dev` script runs the compiled output via `node --watch`. **A build must exist before starting the dev server** — the watcher restarts `dist/index.js` on file changes, but it does not recompile TypeScript. You must rebuild when source changes.
|
||||
|
||||
```bash
|
||||
# From apps/api — or use the root shortcut
|
||||
pnpm --filter @familysync/api build # compile src/ → dist/
|
||||
pnpm --filter @familysync/api dev # node --watch dist/index.js
|
||||
|
||||
# Root shortcuts
|
||||
pnpm dev:api # runs dev in apps/api (requires dist/ to already exist)
|
||||
```
|
||||
|
||||
Recommended inner loop: run `pnpm --filter @familysync/api build` after each change, the `--watch` process restarts automatically.
|
||||
|
||||
### PWA dev server
|
||||
|
||||
```bash
|
||||
pnpm --filter @familysync/pwa dev
|
||||
# or from root:
|
||||
pnpm dev:pwa
|
||||
```
|
||||
|
||||
Vite serves the PWA with HMR on the configured dev port. The PWA's API calls target the backend; set `VITE_API_URL` (or the Vite proxy config) to point at the running API.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Root workspace scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `pnpm dev:api` | Start API dev watcher (`node --watch dist/index.js`) |
|
||||
| `pnpm dev:pwa` | Start Vite dev server for the PWA |
|
||||
| `pnpm build` | Build both `apps/api` (tsc) and `apps/pwa` (tsc + vite build) |
|
||||
| `pnpm test` | Run API test suite (`vitest run` in `apps/api`) |
|
||||
| `pnpm lint` | Run lint in all workspaces (`pnpm -r lint`) |
|
||||
| `pnpm typecheck` | Run `tsc --noEmit` in all workspaces |
|
||||
|
||||
### `apps/api` scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `pnpm --filter @familysync/api build` | Compile TypeScript (`tsc`) → `dist/` |
|
||||
| `pnpm --filter @familysync/api dev` | Start `node --watch dist/index.js` |
|
||||
| `pnpm --filter @familysync/api start` | Start `node dist/index.js` (no watch) |
|
||||
| `pnpm --filter @familysync/api test` | Run vitest once (`vitest run`) |
|
||||
| `pnpm --filter @familysync/api test:watch` | Run vitest in watch mode |
|
||||
| `pnpm --filter @familysync/api typecheck` | `tsc --noEmit` |
|
||||
| `pnpm --filter @familysync/api db:generate` | Generate SQL migrations from schema changes |
|
||||
| `pnpm --filter @familysync/api db:migrate` | Apply pending migrations to the database |
|
||||
|
||||
### `apps/pwa` scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `pnpm --filter @familysync/pwa dev` | Start Vite dev server with HMR |
|
||||
| `pnpm --filter @familysync/pwa build` | `tsc && vite build` → `dist/` |
|
||||
| `pnpm --filter @familysync/pwa preview` | Serve the production build locally |
|
||||
| `pnpm --filter @familysync/pwa typecheck` | `tsc --noEmit` |
|
||||
| `pnpm --filter @familysync/pwa test` | Run vitest once |
|
||||
|
||||
## Drizzle Migration Workflow
|
||||
|
||||
Schema changes follow a strict two-step process. **`drizzle-kit push` is not available** — it has been removed from the scripts because it emits a false destructive diff (table truncation) on populated MariaDB databases.
|
||||
|
||||
### Step 1 — Generate the migration
|
||||
|
||||
After editing `apps/api/src/db/schema.ts`:
|
||||
|
||||
```bash
|
||||
pnpm --filter @familysync/api db:generate
|
||||
```
|
||||
|
||||
This runs `drizzle-kit generate` and writes a new `.sql` file to `apps/api/src/db/migrations/`. Review the generated SQL before proceeding — confirm it matches the intended schema change with no unexpected `DROP` or truncation statements.
|
||||
|
||||
### Step 2 — Apply the migration
|
||||
|
||||
```bash
|
||||
pnpm --filter @familysync/api db:migrate
|
||||
```
|
||||
|
||||
This runs `drizzle-kit migrate` and applies any pending migration files to the target database. Drizzle reads `DB_HOST`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`, and optionally `DB_PORT` (default `3306`) from the environment, as defined in `apps/api/drizzle.config.ts`.
|
||||
|
||||
When running migrations from the host against the Docker database:
|
||||
|
||||
```bash
|
||||
DB_HOST=127.0.0.1 pnpm --filter @familysync/api db:migrate
|
||||
```
|
||||
|
||||
Migration files live in `apps/api/src/db/migrations/` and are committed to version control.
|
||||
|
||||
## TypeScript Strict Checks
|
||||
|
||||
Both workspaces use `"strict": true` in their `tsconfig.json`. The build step (`tsc`) catches type errors in `apps/api` (since it emits output). For `apps/pwa`, Vite uses esbuild to transpile and does not perform type checking — **vitest will pass even when there are type errors in the PWA**. Always run the typecheck script explicitly:
|
||||
|
||||
```bash
|
||||
# Check both workspaces
|
||||
pnpm typecheck
|
||||
|
||||
# Or individually
|
||||
pnpm --filter @familysync/api typecheck
|
||||
pnpm --filter @familysync/pwa typecheck
|
||||
```
|
||||
|
||||
Run `pnpm typecheck` before opening a PR to catch errors that vitest and Vite builds will silently miss.
|
||||
|
||||
## Code Style
|
||||
|
||||
ESLint and Prettier are listed as the intended linting and formatting tools. Check for config files in each workspace and confirm the `lint` script is wired before running `pnpm lint`. <!-- VERIFY: ESLint and Prettier configs are present in apps/api and apps/pwa -->
|
||||
|
||||
## Docker Compose Dev Stack
|
||||
|
||||
```bash
|
||||
# Bring up the full dev stack (API in Docker + MariaDB + Redis, with ports exposed)
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
|
||||
|
||||
# Bring up only backing services (run API on host for faster iteration)
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up mariadb redis -d
|
||||
```
|
||||
|
||||
The dev override:
|
||||
- Exposes MariaDB on `localhost:3306`
|
||||
- Exposes Redis on `localhost:6379`
|
||||
- Mounts `apps/api/src` into the container for live source access
|
||||
- Sets `NODE_ENV=development`
|
||||
|
||||
The production `docker-compose.yml` builds the API and PWA into a single image (`production` target in `apps/api/Dockerfile`). The PWA `dist/` is copied into the API image's `./public` directory and served on port `3000`.
|
||||
|
||||
## Common Issues
|
||||
|
||||
**`dev` script fails with "Cannot find module"**
|
||||
The `dev` script runs `node --watch dist/index.js`. If `dist/` does not exist or is stale, run `pnpm --filter @familysync/api build` first.
|
||||
|
||||
**DB connection refused when running API on host**
|
||||
`DB_HOST` defaults to `mariadb` (the Docker service name). When running the API outside Docker, override it: `DB_HOST=127.0.0.1`. The dev compose override exposes port 3306 on the host.
|
||||
|
||||
**drizzle-kit migrate says "cannot connect"**
|
||||
Same `DB_HOST` issue. Prepend `DB_HOST=127.0.0.1` to the migrate command when running from the host.
|
||||
|
||||
**API integration tests fail with FK errors**
|
||||
The vitest config sets `fileParallelism: false` to prevent concurrent test files from conflicting via the shared MariaDB. Ensure you are not overriding this. Tests require a running MariaDB — set `DB_HOST=127.0.0.1` and ensure the dev database is up.
|
||||
|
||||
**TypeScript errors missed during development**
|
||||
Vite/esbuild strips types; type errors will not surface in `vitest run` or `vite build` output. Run `pnpm typecheck` explicitly to catch them.
|
||||
Reference in New Issue
Block a user