Files
famapp/docs/CONTRIBUTING.md
T

159 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Contributing
## Prerequisites
- **Node.js** 24 LTS (`node --version` should print `v24.x.x`)
- **pnpm** 10 (`pnpm --version` should print `10.x.x`)
- **Docker** with the Compose plugin (used for the local Postgres + MinIO containers)
## Setup
```bash
git clone https://github.com/ginnoir/famapp.git
cd famapp
pnpm install
cp .env.example .env
# Edit .env — minimum required for local dev:
# NEXT_PUBLIC_APP_URL=http://127.0.0.1:3000
# DATABASE_URL=postgres://famapp:famapp@localhost:5432/famapp
# ENABLE_DEV_LOGIN=true
# AUTH_SECRET=<any-32-char-string-for-local>
```
Start everything (database + migrations + seed + dev server):
```bash
pnpm dev:local
```
Then open `http://localhost:3000/login` and click **Dev login**. See `docs/dev-login.md` for the full local setup including HTTPS/PWA testing.
## Available scripts
<!-- AUTO-GENERATED from package.json scripts -->
| Command | Description |
| --------------------- | ----------------------------------------------------------------------------------------- |
| `pnpm dev` | Next.js dev server (localhost only) |
| `pnpm dev:network` | Dev server bound to `0.0.0.0` (LAN access for phone testing) |
| `pnpm dev:local` | Full local stack — starts DB container, runs migrations, seeds, launches dev server |
| `pnpm dev:reset` | Tear down local DB and start fresh (destructive — deletes all local data) |
| `pnpm build` | Production Next.js build |
| `pnpm start` | Start the production build locally |
| `pnpm lint` | ESLint check |
| `pnpm lint:fix` | ESLint with auto-fix |
| `pnpm format` | Prettier — format all files |
| `pnpm format:check` | Prettier — check only (used in CI) |
| `pnpm typecheck` | TypeScript type check (`tsc --noEmit`) |
| `pnpm test:e2e` | Playwright end-to-end tests |
| `pnpm db:generate` | Generate a new Drizzle migration from schema changes |
| `pnpm db:migrate` | Apply pending Drizzle migrations |
| `pnpm db:seed` | Seed the database with dev fixtures |
| `pnpm db:studio` | Open Drizzle Studio (local DB browser) |
| `pnpm gen:icons` | Regenerate PWA icon set from source |
| `pnpm vapid:generate` | Generate VAPID key pair for Web Push |
| `pnpm release` | Interactive release (prompts for semver bump, tags, publishes changelog + GitHub Release) |
| `pnpm release:patch` | Non-interactive patch release |
| `pnpm release:minor` | Non-interactive minor release |
| `pnpm release:major` | Non-interactive major release |
| `pnpm release:dry` | Dry-run release — preview without writing |
<!-- END AUTO-GENERATED -->
## Running tests
### Type check + lint (CI equivalent)
```bash
pnpm typecheck
pnpm lint
pnpm format:check
```
### End-to-end tests
The app must be running first (`pnpm dev:local`). Generate a Playwright auth state file, then run tests:
```powershell
# Generate auth state (run once after starting the app)
New-Item -ItemType Directory -Force tests\.auth | Out-Null
@'
const { chromium } = require('@playwright/test');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('http://127.0.0.1:3000/login');
await page.getByRole('button', { name: 'Dev login' }).click();
await page.waitForURL('http://127.0.0.1:3000/');
await page.context().storageState({ path: 'tests/.auth/dev-user.json' });
await browser.close();
})();
'@ | node -
# Run tests
$env:PLAYWRIGHT_STORAGE_STATE='tests/.auth/dev-user.json'
pnpm test:e2e
```
### Writing tests
- Unit tests: Vitest under `tests/unit/` — only where it pays off (utilities, pure logic).
- E2E tests: Playwright under `tests/e2e/` — one happy-path test per module. Do not write brittle selector-heavy tests for trivial CRUD.
## Code style
- **TypeScript strict** — no `any` without a written reason.
- **No comments** unless the _why_ is non-obvious. Names carry intent.
- **Immutable** — always return new objects; never mutate in place.
- **Module isolation** — a module imports from `_core` and `lib/` only; never from a sibling module.
- **File size** — 200400 lines typical, 800 hard cap.
Formatting and lint run automatically on staged files via `lint-staged` at commit time. You can also run them manually with `pnpm lint:fix` and `pnpm format`.
## Commit format
Commits must follow [Conventional Commits](https://www.conventionalcommits.org/). `commitlint` enforces this at the `commit-msg` hook.
```
<type>: <short description>
[optional body]
```
Allowed types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `revert`
Examples:
```
feat: add plant species search to garden module
fix: calendar event end time off by one day
chore: bump next to 15.6
```
## Adding a module
Every feature lives under `src/modules/<name>/`. A new module needs:
- `schema.ts` — Drizzle tables
- `server/` — server actions and queries
- `components/` — React components
- `manifest.ts` — registers the module with the core registry (nav, entity types, dashboard widget, quick-add actions)
See `CLAUDE.md` for the full architectural brief. The module loader in `src/modules/_core/` discovers manifests automatically — no changes to core code required for a new module.
## Schema changes
1. Edit the relevant `schema.ts`.
2. Run `pnpm db:generate` to create a new migration file under `drizzle/`.
3. Commit the migration alongside the schema change.
4. Never edit a shipped migration — always add a new one.
## PR checklist
- [ ] `pnpm typecheck` passes
- [ ] `pnpm lint` passes
- [ ] `pnpm build` succeeds
- [ ] New Drizzle migration committed if schema changed
- [ ] E2E test added or updated if a user-visible flow changed
- [ ] `docs/tasks/09-pre-deploy-checklist.md` reviewed if touching auth or env