Basalt docs
GitHub

Development Environment

Local development runs in DDEV (≥ 1.24): containers for Node 22 (corepack/pnpm enabled) and Postgres 16 — no host-side toolchain required.

ddev start          # boot web (Node 22) + db (Postgres 16)
ddev exec pnpm install   # install the workspace INSIDE the container (Linux binaries)
ddev dev            # start the full dev stack (API + web); Ctrl-C to stop

ddev dev runs pnpm dev (Turborepo) in the web container: the API server (tsx watch, applies migrations on boot) and the Vite dev server with HMR, both served through the DDEV router over TLS.

Service URL Notes
App (Vite) https://basalt.ddev.site:5173 open this — the browser origin; proxies /api to the server
API (direct) https://basalt.ddev.site:3443 for curl/health; /api/v1/health
Postgres via ddev psql host db, db/user/pass db

The dev environment is wired in .ddev/config.yaml (web_environment: DATABASE_URL, BASE_URL, PORT, NODE_ENV) and .ddev/.env.web (AUTH_SECRET, gitignored — regenerate with openssl rand -hex 32). BASE_URL is the Vite origin so Better-Auth cookies and the CSRF origin check line up; vite.config.ts binds all interfaces and points HMR at the DDEV host when IS_DDEV_PROJECT is set (a plain host pnpm dev is unaffected).

WebSockets (collab plane): .ddev/traefik/config/basalt-alpn-h1.yaml restricts the DDEV router to HTTP/1.1 (ALPN). Over HTTP/2 the collab WebSocket's upgrade completes but Traefik does not bridge its data frames to the HTTP/1.1 Node backend, so the editor would sit on "Connecting…". The file is not #ddev-generated; delete it to restore HTTP/2 (and lose live collab in this dev setup).

Conventions:

  • Run all toolchain commands (pnpm install, pnpm dev, pnpm test) inside the container (ddev ssh or ddev exec …) so every machine shares the same Node and Postgres. node_modules holds Linux binaries once installed in the container — do not mix with a host pnpm install (they overwrite each other; pick one and stay there).
  • DDEV is the development environment only. The tested self-host artifact is the Docker compose under infra/docker/ (ADR-0008); CI runs against compose, not DDEV.
  • .ddev/config.yaml is committed. Local overrides belong in .ddev/config.local.yaml (ignored by DDEV's own .gitignore).

Toolchain: pnpm + Turborepo

pnpm is provided by corepack — no global install:

corepack enable pnpm   # once per machine/container
pnpm install           # install the whole workspace

The exact pnpm version is pinned in root package.json#packageManager; corepack picks it up automatically. Node version: .nvmrc (24, engines allow

= 22).

Root scripts (run from the repo root):

Command What it does
pnpm dev turbo run dev — app dev servers
pnpm typecheck turbo run typechecktsc --noEmit per package/app
pnpm lint ESLint (one flat config at the root)
pnpm test turbo run test — Vitest per package/app
pnpm build turbo run buildapps only; packages are consumed as TS source
pnpm format Prettier over the repo

Workspace layout: apps/*, packages/*, and e2e (see pnpm-workspace.yaml). Internal packages export src/index.ts directly — no package build step, no watch mode needed; editing a package is immediately visible to apps.

E2E tests

e2e/ (@basalt/e2e) holds the Playwright end-to-end suite. It tests the self-host artifact: the run builds the Docker image, boots the compose stack from infra/docker/compose.selfhost.yml under an isolated compose project (basalt-e2e — own containers, network, and volume; throwaway credentials, nothing written to disk), waits for /api/v1/health, runs the tests against http://localhost:8080, and tears everything down again with down -v. Nothing survives between runs.

pnpm --filter @basalt/e2e exec playwright install chromium  # once per machine
pnpm --filter @basalt/e2e e2e                # build + boot stack, test, tear down
E2E_PORT=8090 pnpm --filter @basalt/e2e e2e  # if host port 8080 is taken

Requirements: Docker with compose v2.24+ on the host — run this from the host, not inside DDEV (it drives the host's Docker daemon and publishes a host port). Because it boots real containers, e2e is deliberately not part of pnpm test; CI runs it as its own job (.github/workflows/ci.yml, job e2e).