6.4 KiB
Local full-stack development
Use scripts/dev-local.sh; do not start a mixture of local and production data
services manually. The default stack uses conventional PostgreSQL and the Sauci
Node API. It does not start local Supabase. Supabase remains the hosted
identity provider, so local application processes need the public Auth settings
described by their environment examples.
| Service | Owner | Port |
|---|---|---|
| PostgreSQL | Docker Compose | 54320 |
| Sauci API | Node / Hono | 3003 |
| Operations worker | Node / Hono domain modules | none |
| Web | Next.js | 3000 |
| Admin | Vite | 3001 |
| MCP | Hono | 3002 |
| Expo Metro | Expo | 8081 |
If another local project owns one of the application ports, add a different,
unique value to the ignored root .env.local before running up:
SAUCI_WEB_PORT=3100
SAUCI_ADMIN_PORT=3101
SAUCI_MCP_PORT=3102
SAUCI_METRO_PORT=8181
SAUCI_POSTGRES_PORT and SAUCI_API_PORT remain available for their existing
local overrides. Every configured port must be a unique integer from 1 through
65535. Before launching managed services, the launcher checks both IPv4 and IPv6
listeners and fails closed with the owning PID, command, and working directory;
it never stops another project's process.
PostgreSQL is bound to 127.0.0.1, is healthchecked before application startup,
and stores data in a named Docker volume. The local connection string is:
postgresql://sauci:sauci_local_only@127.0.0.1:54320/sauci
Those credentials are for local development only. Deployed environments must
inject unique secrets; they must never consume infra/backend/.env.example.
Start the stack
One command starts the local database, applies migrations and fixtures, and launches the application services:
scripts/dev-local.sh up
Before the first use, install Docker Desktop or Rancher Desktop, tmux, and the
repository dependencies with npm ci. The launcher reads .nvmrc itself. When
the active Node version differs, it loads an installed Homebrew nvm and selects
the exact version automatically. If that version has not been installed yet, run
the command the launcher prints once, then rerun up.
Create the ignored root .env.local with the public Auth settings for the
designated non-production Supabase project (itbzhrvlgvdmzbnhzhyx). Set
SUPABASE_AUTH_URL (or EXPO_PUBLIC_SUPABASE_URL) and
EXPO_PUBLIC_SUPABASE_ANON_KEY; the launcher uses the URL for the API and both
values for Expo and the Admin login. It accepts only this designated hosted project or loopback Auth,
and explicitly rejects the production project and every other hosted project.
Environment variables exported in the shell take precedence over .env.local.
The launcher treats .env.local as data, never shell code: use unquoted
KEY=VALUE lines for only the documented launcher keys. Never place production
credentials or Auth issuer/JWKS, Auth service-role, RevenueCat, or other provider
secrets in .env.local; the launcher removes those inherited values from its
local API and worker processes. Each up replaces its managed tmux windows so
stale application processes cannot retain earlier configuration, while leaving
unrelated windows intact. Credentials are passed only to the local process that
needs them: MCP receives its local API token/key but never DATABASE_URL.
Before replacing a managed window, the launcher verifies and stops only its
current descendant processes. If those descendants do not exit promptly, it
leaves that window in place and fails rather than broadening process cleanup.
This includes a previously captured descendant that has left the pane lineage:
the launcher will not signal it again or replace the window until that captured
process identity is gone.
Authenticated mobile E2E requires a disposable, onboarding-complete user whose
subject has a matching local profile. Pass its credentials as E2E_EMAIL and
E2E_PASSWORD only to the Maestro process; do not store them in an Expo
environment file. To exercise MCP tools, also set a local
ADMIN_API_SERVICE_TOKEN, its fixture ADMIN_API_SERVICE_USER_ID, and a
separate SAUCI_MCP_API_KEY. scripts/dev-local.sh passes the service token to
MCP as SAUCI_ADMIN_API_TOKEN; MCP never receives a database or Supabase service
credential.
up is idempotent. down stops only the launcher's managed tmux application
windows and leaves unrelated windows and the database available. down --all
also stops PostgreSQL but retains its volume, and runs only after managed
application cleanup succeeds.
Use status, logs <service>, restart <service>, and attach for diagnosis.
logs postgres reads Compose logs; other names read their tmux panes.
The worker pane owns scheduled jobs, notifications, moderation classification,
and filesystem cleanup; local development disables external AI classification.
reset is destructive but fails closed unless the database is the dedicated
loopback instance on port 54320. It removes only the sauci-local Compose
volume, starts a fresh healthy database, then runs the API's db:migrate and
db:seed scripts. seed retains the volume and reapplies those two scripts.
The API seed is a no-op unless its documented local fixture identity is set.
The Expo window defaults to Metro with
EXPO_PUBLIC_API_URL=http://127.0.0.1:3003. Use scripts/dev-local.sh ios or
android to launch the native application against the same API. The native
launcher supplies a UTF-8 locale inside its sanitized environment so CocoaPods
does not depend on the calling shell's locale. Physical devices require a
reachable host URL rather than loopback.
e2e/maestro/login-screen-smoke.yaml verifies the native build and Auth shell
without credentials. e2e/maestro/login-smoke.yaml is the authenticated
standalone-API flow and must fail closed unless the non-production fixture above
is explicitly supplied. Neither local nor E2E runs may use the production Auth
project, production subscriber, or production application database.
Run browser E2E through the same launcher:
npm run verify:e2e
It starts the stack when needed, waits for API, web, admin, and MCP endpoints,
and derives Playwright URLs from the configured SAUCI_*_PORT values. Set
SAUCI_E2E_REUSE=true only to reuse a healthy stack managed with the same
configuration. Browser product-data fixtures are direct loopback PostgreSQL
fixtures that prove the standalone API result; they never use local Supabase.