6.9 KiB
Contributing
Prerequisites
- Node.js 24 LTS (
node --versionshould printv24.x.x) - pnpm 10 (
pnpm --versionshould print10.x.x) - Docker with the Compose plugin (used for the local Postgres + MinIO containers)
Setup
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):
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
| 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 |
Running tests
Type check + lint (CI equivalent)
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:
# 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
anywithout 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
_coreandlib/only; never from a sibling module. - File size — 200–400 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. 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 tablesserver/— server actions and queriescomponents/— React componentsmanifest.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
- Edit the relevant
schema.ts. - Run
pnpm db:generateto create a new migration file underdrizzle/. - Commit the migration alongside the schema change.
- Never edit a shipped migration — always add a new one.
PR checklist
pnpm typecheckpassespnpm lintpassespnpm buildsucceeds- New Drizzle migration committed if schema changed
- E2E test added or updated if a user-visible flow changed
docs/tasks/09-pre-deploy-checklist.mdreviewed if touching auth or env