Skip to content

The workspace

Terminal window
# Dev
./xentium.sh # infra (Postgres + Redis) + api + worker + web
./xentium.sh down # stop the infra containers
pnpm dev:apps # api + worker + web only (infra already running)
pnpm backbone:up # start only the infra containers
pnpm backbone:down # stop them
# Database (Prisma)
pnpm db:generate # regenerate the Prisma client after a schema change
pnpm db:migrate # create/apply a dev migration
pnpm db:reset # drop everything and re-apply (DESTRUCTIVE)
pnpm db:studio # open Prisma Studio
# Checks (run them all before you open a PR)
pnpm backbone:check # typecheck + lint + test + build
pnpm typecheck
pnpm lint
pnpm test
pnpm build
# End-to-end (Playwright)
pnpm e2e # reset the DB, run the suite
pnpm e2e:ui # interactive runner
# Plugins
pnpm plugin:package # build and package a plugin into a .xtplugin

The API tests use their own database. installer.test.ts tests the wizard starting from an empty database, so the suite truncates every table. That’s why it runs against a separate <yourdb>_test database, which apps/api/vitest.config.ts creates and migrates for you. On top of that, assertDisposableDatabase() refuses to truncate any database whose name doesn’t end in _test, so a wrong DATABASE_URL fails instead of wiping your data.

Set TEST_DATABASE_URL to point the suite somewhere specific (CI). ALLOW_DESTRUCTIVE_TESTS=1 switches the guard off. Only use it on a database you’re happy to lose.

Run a single API test file:

Terminal window
pnpm --filter @xentium/api exec vitest run src/modules/auth/auth.test.ts

xentium/
├─ apps/
│ ├─ api/ Express API + BullMQ worker
│ │ └─ src/
│ │ ├─ app/ app wiring, route registration
│ │ ├─ config/ Zod env, rate limits, upload limits
│ │ ├─ core/ platform engines: plugin / license / installer, plus the
│ │ │ extension registries (search, home feed, XEC codes, ACP pages)
│ │ ├─ infrastructure/ db, redis, queue, storage, email, logging
│ │ ├─ modules/ feature modules (auth, users, articles, moderation, …)
│ │ ├─ shared/ errors (XEC), middleware, http, utils
│ │ ├─ jobs/ BullMQ job handlers
│ │ ├─ server.ts HTTP entry point
│ │ └─ worker.ts worker entry point
│ ├─ web/ Vite + React frontend
│ │ └─ src/ app, pages, features, components, layouts, hooks,
│ │ lib, plugins (slot registries), styles
│ └─ docs/ this wiki (doc.xentium.org), built from docs/ and this README
├─ packages/ @xentium/* (↑ = published to npm)
│ ├─ contracts/ ↑ shared types, DTOs and enums (permissions, pagination, XEC, …)
│ ├─ errors/ ↑ XentiumError, the XEC registry, Express error middleware
│ ├─ server-kit/ ↑ settings, pagination, secrets, rate limiting, uploads, storage
│ ├─ auth/ ↑ tokens, password hashing, revocation, request guards
│ ├─ plugin-engine/ ↑ manifest validation, migration SQL guard, registry, router
│ ├─ sdk/ runtime helpers (slug, access) + the licence public key
│ ├─ plugin-sdk/ the plugin authoring contract (not on npm yet, see below)
│ ├─ ui/ shared React components (GroupAccessPicker)
│ └─ config/ shared tsconfig base
├─ plugins/
│ ├─ official/ first-party plugins (licensed, not in this repo)
│ └─ community-examples/ reference plugins for local development
├─ prisma/ schema + migrations (client → apps/api/src/generated/prisma)
├─ docker/ Compose for Postgres 17 + Redis 7
├─ scripts/ tooling (plugin packaging, versioning, …)
├─ e2e/ Playwright suites
└─ docs/ developer docs (published at doc.xentium.org)

Every feature module in apps/api/src/modules/<name>/ has the same shape: *.routes.ts → *.controller.ts → *.service.ts, plus *.validation.ts, *.types.ts and *.test.ts. See Working on core for when a *.repository.ts is worth it.