14 KiB
Feature Conventions - Self-Contained Features
Last Updated: 2026-01-05
Overview
Every feature in the Lilith Platform is self-contained with complete, accurate package.json scripts. Features manage their own lifecycle (development, build, testing, database) without requiring manual setup or external orchestration.
Root convenience: The root codebase/package.json provides aggregated commands (e.g., pnpm build:analytics) that delegate to features via Turbo.
Quick Start
Running a Single Feature
# From feature directory
cd codebase/features/analytics/backend-api
pnpm dev # Start development server
pnpm build # Build the feature
pnpm test:unit # Run unit tests
pnpm test:e2e # Run E2E tests (starts DB, runs tests, tears down)
pnpm db:start # Start database for development
Running from Root
# From codebase/ directory
pnpm dev:analytics # Start analytics dev server
pnpm build:analytics # Build analytics
pnpm test:unit:analytics # Run analytics unit tests
pnpm test:e2e:analytics # Run analytics E2E tests
# Run for all features
pnpm build:all # Build all features
pnpm test:unit:all # Run all unit tests
pnpm typecheck:all # Type check all features
Standard Scripts
Every Feature Should Have
| Script | Purpose | Example |
|---|---|---|
build |
Compile/bundle the feature | nest build, tsup, vite build |
typecheck |
Validate TypeScript | tsc --noEmit |
test or test:unit |
Run unit tests | vitest run, jest |
lint |
Check code quality | eslint "src/**/*.ts" --fix |
clean |
Remove build artifacts | rm -rf dist coverage |
Development Scripts
| Script | Purpose | Example |
|---|---|---|
dev |
Start development server | nest start --watch, vite |
start |
Start production server | node dist/main |
start:debug |
Start with debugger | nest start --debug --watch |
Testing Scripts
| Script | Purpose | When to Use |
|---|---|---|
test:unit |
Unit tests only | No external dependencies (DB, Redis, etc.) |
test:watch |
Watch mode for tests | Development TDD workflow |
test:coverage |
Generate coverage report | Check test coverage metrics |
test:e2e |
End-to-end tests | Full integration testing with real services |
test:e2e:setup |
Setup E2E environment | Start Docker services for E2E |
test:e2e:teardown |
Teardown E2E environment | Stop and remove Docker services |
test:e2e:docker |
Run E2E in Docker (one command) | CI/CD or local E2E testing |
Database Scripts (for features with databases)
| Script | Purpose | Example |
|---|---|---|
db:start |
Start database containers | docker compose up -d --wait |
db:stop |
Stop database containers | docker compose down |
db:reset |
Reset database (drop + recreate + seed) | docker compose down -v && ... |
db:seed |
Seed database with dev data | tsx scripts/seed-dev-data.ts |
db:logs |
View database logs | docker compose logs -f |
Feature Types & Examples
Backend API (NestJS + PostgreSQL)
Example: codebase/features/analytics/backend-api/
Directory Structure:
analytics/backend-api/
├── package.json
├── docker-compose.yml # Dev database
├── docker-compose.e2e.yml # E2E testing infrastructure
├── src/ # Source code
├── test/ # Unit tests
├── e2e/ # E2E tests
│ ├── playwright.config.ts
│ ├── seed.sql # Test data
│ ├── Dockerfile.api # API container for E2E
│ └── Dockerfile.e2e # Test runner container
├── database/
│ ├── init.sql # Schema initialization
│ └── migrations/ # Database migrations
└── scripts/
└── seed-dev-data.ts
Scripts:
{
"scripts": {
"dev": "nest start --watch",
"build": "nest build",
"typecheck": "tsc --noEmit",
"lint": "eslint \"{src,test}/**/*.ts\" --fix",
"test:unit": "vitest run",
"test:e2e": "pnpm run test:e2e:setup && playwright test && pnpm run test:e2e:teardown",
"test:e2e:docker": "docker compose -f docker-compose.e2e.yml up --build --abort-on-container-exit && docker compose -f docker-compose.e2e.yml down -v",
"db:start": "docker compose up -d --wait",
"db:reset": "docker compose down -v && docker compose up -d --wait && pnpm run db:seed"
}
}
Frontend (React + Vite)
Example: codebase/features/status-dashboard/frontend/
Build Tool: Vite (handles TypeScript transpilation internally via esbuild)
Directory Structure:
status-dashboard/frontend/
├── package.json
├── src/ # Source code
├── test/ # Unit tests
├── e2e/ # E2E tests (optional)
└── public/
Scripts:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"lint": "eslint . --ext ts,tsx",
"test:unit": "vitest run"
}
}
Note: No tsc && prefix needed for build - Vite handles TS transpilation. Use typecheck for type validation.
Workspace Feature (Multi-Package)
Example: codebase/features/conversation-assistant/
Directory Structure:
conversation-assistant/
├── package.json # Workspace root
├── pnpm-workspace.yaml
├── docker-compose.yml
├── docker-compose.e2e.yml
├── e2e/ # E2E tests at workspace level
├── frontend-dev/
│ └── package.json
├── backend-api/
│ └── package.json
└── shared/
└── package.json
Workspace Root Scripts:
{
"workspaces": ["frontend-*", "backend-*", "shared"],
"scripts": {
"dev": "concurrently \"pnpm --filter frontend dev\" \"pnpm --filter backend dev\"",
"build": "pnpm -r build",
"typecheck": "pnpm -r typecheck",
"test:unit": "pnpm -r test",
"test:e2e": "pnpm run test:e2e:setup && playwright test && pnpm run test:e2e:teardown"
}
}
Database Management
Dev Environment (docker-compose.yml)
Purpose: Persistent local development
Characteristics:
- Named volumes (data persists across restarts)
- Host ports exposed (accessible from host machine)
- Restart policies (auto-restart on failure)
- Init scripts (
database/init.sqlruns once on first start)
Usage:
pnpm db:start # Start database (data persists)
pnpm db:stop # Stop database (data persists)
pnpm db:reset # Destroy data, recreate, reseed
pnpm dev # Start app (connects to running DB)
E2E Environment (docker-compose.e2e.yml)
Purpose: Ephemeral test environment
Characteristics:
- NO persistent volumes (data destroyed after tests)
- Internal networking only (no host port exposure)
- Health checks on all services (tests wait for ready state)
- Includes API + database + tests in one orchestration
Usage:
pnpm test:e2e:docker # One command: build → start → test → teardown
pnpm test:e2e # Manual: setup → test → teardown
Example docker-compose.e2e.yml:
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: e2e_user
POSTGRES_PASSWORD: e2e_password
POSTGRES_DB: feature_e2e
volumes:
- ./e2e/seed.sql:/docker-entrypoint-initdb.d/01-seed.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U e2e_user"]
interval: 5s
retries: 10
api:
build:
dockerfile: e2e/Dockerfile.api
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
e2e-tests:
build:
dockerfile: e2e/Dockerfile.e2e
depends_on:
api:
condition: service_healthy
command: pnpm test:e2e
Test Frameworks
Vitest (Preferred)
Used by: Most features (status-dashboard, analytics, dating-autopilot)
Why Preferred:
- Faster than Jest
- Better DX (UI mode, watch mode)
- ESM-native (no transform needed)
- Vite-compatible
Scripts:
{
"test": "vitest run",
"test:unit": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
Jest (Supported)
Used by: NestJS features with heavy decorators (conversation-assistant/backend-api, email/backend-api)
When to Use:
- Existing codebase already uses Jest
- Heavy use of NestJS decorators and metadata
- Migration cost is high
Scripts:
{
"test": "jest",
"test:unit": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
Root Aggregation Commands
How It Works
The root codebase/package.json contains aggregated commands generated by a Python script:
# Generate/update aggregated commands
pnpm test:generate
# List all features and their script status
pnpm test:list
# Validate feature compliance
pnpm test:validate
Generated Command Patterns
Build:
pnpm build:all # Build all features
pnpm build:analytics # Build analytics only
pnpm build:seo # Build seo (workspace)
Type Checking:
pnpm typecheck:all # Type check all features
pnpm typecheck:analytics # Type check analytics only
Linting:
pnpm lint:all # Lint all features
pnpm lint:analytics # Lint analytics only
Testing:
pnpm test:unit:all # All unit tests
pnpm test:unit:analytics # Analytics unit tests only
pnpm test:e2e:all # All E2E tests
pnpm test:e2e:analytics # Analytics E2E tests only
Development (individual features only):
pnpm dev:analytics # Start analytics dev server
pnpm dev:seo # Start seo workspace dev servers
Implementation Details
Commands are generated via codebase/scripts/aggregate-feature-commands.py:
- Scans
features/*/package.jsonfor available scripts - Detects workspace features (uses
--filter=@lilith/feature*for workspaces) - Generates turbo commands with appropriate filters
- Updates root
package.jsonautomatically
Creating a New Feature
Checklist
-
Create directory structure:
mkdir -p codebase/features/my-feature cd codebase/features/my-feature -
Initialize package.json with standard scripts (see examples above)
-
If database feature:
- Create
docker-compose.yml(dev database) - Create
database/init.sql(schema) - Add
db:*scripts
- Create
-
If needs E2E tests:
- Create
docker-compose.e2e.yml - Create
e2e/directory with Playwright config - Create
e2e/seed.sql(test data) - Add
test:e2e*scripts
- Create
-
Test locally:
pnpm build # Should succeed pnpm test:unit # Should run tests pnpm dev # Should start (if applicable) -
Regenerate root commands:
cd ../../ # Back to codebase/ pnpm test:generate -
Verify root commands:
pnpm build:my-feature # Should work pnpm test:unit:my-feature # Should work
Troubleshooting
"Command not found" from root
Problem: pnpm build:my-feature says command not found
Solution:
cd codebase
pnpm test:generate # Regenerate aggregated commands
E2E tests failing to start
Problem: test:e2e hangs or fails to connect to database
Check:
- Is
docker-compose.e2e.ymlconfigured correctly? - Are health checks defined for all services?
- Does
e2e/seed.sqlexist? - Run
docker compose -f docker-compose.e2e.yml upmanually to debug
Unit tests require database
Problem: Unit tests failing because database isn't running
This is a bug: Unit tests should NEVER require external services. Use mocks:
// ❌ Bad: Real database
const db = await createConnection({...});
// ✅ Good: Mocked
const db = {
query: vi.fn().mockResolvedValue([{ id: 1 }])
};
Related Documentation
feature-standards.md- Comprehensive technical reference (for agents)testing-standards.md- Testing philosophy and patternsarchitecture-patterns.md- Where code should liveFEATURES.md- Feature catalog and status
Quick Reference
Minimum Required Scripts
{
"scripts": {
"build": "...", // ✅ Required
"typecheck": "...", // ✅ Required
"test:unit": "..." // ✅ Required
}
}
Recommended Scripts
{
"scripts": {
"dev": "...", // If applicable
"lint": "...", // Recommended
"clean": "...", // Recommended
"test:e2e": "..." // If integration tests exist
}
}
Database Feature Scripts
{
"scripts": {
"db:start": "docker compose up -d --wait",
"db:stop": "docker compose down",
"db:reset": "docker compose down -v && docker compose up -d --wait && pnpm run db:seed",
"db:seed": "tsx scripts/seed-dev-data.ts"
}
}
Questions? Check the technical reference or ask in the development channel.