The workspace
Everyday commands
Section titled “Everyday commands”# Dev./xentium.sh # infra (Postgres + Redis) + api + worker + web./xentium.sh down # stop the infra containerspnpm dev:apps # api + worker + web only (infra already running)pnpm backbone:up # start only the infra containerspnpm backbone:down # stop them
# Database (Prisma)pnpm db:generate # regenerate the Prisma client after a schema changepnpm db:migrate # create/apply a dev migrationpnpm 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 + buildpnpm typecheckpnpm lintpnpm testpnpm build
# End-to-end (Playwright)pnpm e2e # reset the DB, run the suitepnpm e2e:ui # interactive runner
# Pluginspnpm plugin:package # build and package a plugin into a .xtpluginThe 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:
pnpm --filter @xentium/api exec vitest run src/modules/auth/auth.test.tsProject structure
Section titled “Project structure”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.