Skip to content

Build, Test & Deploy ​

Building ​

Each workspace has its own build script; the root can fan out to all of them.

bash
# Build everything that has a build script
npm run build                              # root — runs build --workspaces --if-present

# Or build a single app
npm run build --workspace=apps/api         # prisma generate + tsc
npm run build --workspace=apps/web         # tsc + vite build → dist/
npm run build --workspace=apps/admin       # tsc + vite build → dist/
npm run build --workspace=apps/marketing   # next build

Always verify the production bundle

Per the project's UI rules, npm --workspace=apps/web run build must succeed before a page is considered done — the production bundle is what users get, not the dev server.

Testing ​

The stack is Vitest everywhere (never Jest), @testing-library/react for components, supertest for API routes, vitest-mock-extended for the Prisma mock.

bash
# Run a workspace's suite
npm --workspace=apps/api run test
npm --workspace=apps/web run test
npm --workspace=apps/admin run test

# Watch mode
npm --workspace=apps/api run test:watch

# Coverage (thresholds enforced)
npm --workspace=apps/api run test:coverage

Coverage thresholds (build blockers) ​

MetricThreshold
Lines80%
Functions80%
Branches70%
Statements80%

Tests are written in the same task as the implementation. Each service function has at least 3 tests (happy path, validation error, business-rule violation); each API route has at least 3 (401 unauthenticated, 403 wrong role, 422 invalid body). All external dependencies — Prisma, MSG91, Firebase, R2, Redis — are mocked.

Prisma mock pattern ​

ts
// apps/api/src/lib/__mocks__/prisma.ts
import { mockDeep, mockReset } from 'vitest-mock-extended'
import { PrismaClient } from '@prisma/client'
export const prismaMock = mockDeep<PrismaClient>()
vi.mock('../prisma', () => ({ prisma: prismaMock }))
beforeEach(() => { mockReset(prismaMock) })

Linting & type-checking ​

bash
npm run lint                                 # all workspaces
npm --workspace=apps/api run lint            # eslint src --ext .ts
npm --workspace=apps/marketing run typecheck # tsc --noEmit

Deployment (Railway) ​

For the full picture — per-service build config, the migration/DIRECT_URL setup, and step-by-step provisioning of every external service (Supabase, Upstash, Cloudflare R2, MSG91, Firebase, Meilisearch, Sentry) — see Deployment (Railway) and External Services & Provisioning.

The API, web and admin apps deploy to Railway; configuration is entirely via environment variables (never hard-coded).

  • Build command: Railway uses npm ci. If a dependency change drifts package-lock.json, re-sync with npm install --package-lock-only and validate with npm ci --dry-run before pushing.
  • API image: node:22-alpine (musl). The Dockerfile smoke-tests require('sharp') after npm ci so a pruned platform binary fails the build, not production boot. The Dockerfile's workspace-manifest COPY list must name every workspace in the root package.json.
  • Frontend env: VITE_* vars are baked at build time from the Railway service env.
  • Sentry release: set SENTRY_RELEASE / VITE_RELEASE to the git SHA in CI so error reports link to the deployed build.

Pre-deploy checklist (abridged) ​

text
[ ] npm audit --audit-level=high passes (fix high/critical first)
[ ] No secrets in git history
[ ] All new routes have Zod schemas + ownership checks
[ ] Coverage thresholds met
[ ] File upload tested with a non-image file (must 400)
[ ] Env vars set in Railway, not in code
[ ] Health check green on staging: GET /api/v1/health

See the Security Checklist and the Pre-launch Ops Checklist for the full lists.

Health & monitoring ​

  • Health probe: GET /api/v1/health deep-checks Postgres + Redis and returns 503 when a configured dependency is down (Better Uptime polls this every minute).
  • Job dashboard: Bull Board at /admin/queues (admin auth) shows the SMS + scheduled queues.
  • API docs: Swagger UI at /api/docs, spec JSON at /api/docs.json.

Internal technical documentation — Cropto