Development
Day-to-day commands, the demo store, and the live API check.
Commands
| Command | Does |
|---|---|
pnpm run dev | All apps via Turbo |
pnpm run dev:web · dev:admin · dev:api | One app on its own port |
pnpm run type-check | tsc --noEmit in every app — or type-check:<app> for one |
pnpm run lint | ESLint in every app (no-explicit-any is an error) — or lint:<app> |
pnpm run build | Production builds for every app — or build:<app> (Turbo builds its dependencies first) |
pnpm run dev:worker · start:worker | The background worker — email, Stripe webhook, refund and scheduled jobs from the Postgres queue (pg-boss). Needs JOBS_MODE=queue; without it the API runs those jobs inline and the worker is not needed |
pnpm run seed | Load the demo catalogue into beauty-booth — same as pnpm --filter api run seed:bloom-booth (-- --fresh rebuilds the catalogue) |
pnpm --filter api run assets:upload | Put the demo photos on ImageKit under bloom-booth/ — idempotent, run before the first seed |
pnpm run test | Unit and component tests — Jest in the API, Vitest with jsdom and Testing Library in the storefront and the dashboard |
pnpm run test:e2e | Playwright smoke test: its own API, storefront and dashboard against the bloom_booth_e2e database, one cash order through every job (test:e2e:ui for the UI, test:e2e:report for the last report) |
pnpm run check:api | Live check suite against a running API — creates and removes its own records |
pnpm --filter api run create:admin <email> <password> [name] | Headless administrator |
The demo store
apps/api/data/bloom-booth/ holds the demo beauty catalogue as data: content.json — 67 products, 8 aisles and 31 sections, 41 brands, 6 journal posts, 3 hero slides, testimonials, sale promos and coupons — and image-manifest.json, the ImageKit URL of every photo. assets:upload downloads each product photo once and uploads it under bloom-booth/; seed:bloom-booth writes the documents with those URLs. Both are idempotent — documents are upserted on their slug and finished uploads are remembered, so a second run changes nothing.
pnpm --filter api run assets:upload # photos → ImageKit, under bloom-booth/
pnpm --filter api run seed:bloom-booth # load or refresh the demo catalogue
pnpm --filter api run seed:bloom-booth -- --fresh # empty the catalogue and CMS collections firstscripts/lib/dbGuard.ts: they refuse any database but beauty-booth (or BLOOM_DB_NAME) before a single query runs, and the asset scripts refuse any media root but bloom-booth/. If the cluster or the ImageKit account is shared with another store, never loosen either check — --fresh and assets:purge delete.It never creates customers or orders and never touches users, orders or your Configuration.
The live API check
check:api exercises a *running* server end to end — middleware order, auth chains, response envelopes, role rules — which unit tests against controllers cannot. It creates its own admin and customer, and its teardown removes every record it made, including leftovers from a crashed run.
pnpm run check:api # against http://localhost:8000
API_URL=https://api-staging.example.com pnpm run check:apiNext.js gotchas already handled
<html>carriesdata-scroll-behavior="smooth"— Next 16 no longer overrides smooth scrolling during navigation without it, and you land mid-page.- Never call
setStateinside an effect to react to a route change; adjust during render with a previous-value guard. ESLint enforces it. - Per-user reads use
cache: "no-store"— Next's Data Cache keys on the URL, not the session. - A client component must not import a *value* from a module that reaches
next/headers;tscwill not catch it, onlypnpm run builddoes. - A page reads what it shows, once. Shared storefront readers are wrapped in React
cache()and take alimit; the dashboard counts with count queries (/api/contacts/count,/api/stats) and reads grids throughview=table— never fetch a list to measure it or sort 500 rows in JS.