| sidebar_position | 1 |
|---|---|
| title | Development Servers |
This page covers the day-to-day inner loop for working on CodeRunner: getting the repo running locally, the two dev servers, and the gates to run before you call a change done. If you just want to stand up the whole app, start with the Quick Start (Installation). For the big picture of how the pieces fit, see the architecture overview.
CodeRunner is TypeScript on Bun. All non-container code uses Bun for package management, script execution, and the control-plane runtime.
bun install
git submodule update --init --recursiveThe submodule step pulls the pinned vendor/AdvantageScope and
vendor/elastic_dashboard checkouts (see vendor/tools.json for their exact
pins), which bun run build:ascope/bun run build:elastic depend on. If you
don't want to build AdvantageScope from source (it needs emscripten), run
bun run setup:demo (or bun run fetch:dist) to download the prebuilt web
shell and AdvantageScope, and build Choreo inline (bun run build:choreo,
needs only Bun — no submodule, no separate fork download). Elastic Dashboard
is the exception: it needs a local Flutter SDK (bun run build:elastic), so
it's deliberately left out of bun run build entirely — fetch:dist treats
it as optional (a missing/failed fetch leaves /elastic/ serving a 503). See
decision 043
for how each vendored tool's build is wired.
:::note[Dev runs use published host ports, not a Docker network]
The dev loop runs the control plane as a host Bun process, which reaches each
workspace container over a loopback port (FRC_CONTAINER_NETWORK unset). This is
unchanged from before containerization — the shared-network mode is only used
when the control plane itself runs in a container (see
decision 031).
A host process can't resolve container DNS names, so don't set
FRC_CONTAINER_NETWORK for bun run dev:control.
:::
:::note[Windows]
On Windows the AdvantageScope step may appear to hang the first time (it stalls
while bundling/minifying the large hub.js renderer, often for a minute or two).
If it seems stuck, cancel and re-run the build. The second run usually proceeds
quickly. Building under WSL avoids the slowdown entirely.
:::
A one-line map of the top-level directories you'll touch most:
apps/control/: Bun control plane (HTTP, WebSocket, sessions, container orchestration, proxies, and tool assets).apps/web/: React + Vite browser IDE shell.packages/contracts/: shared API schemas, message types, and path rules consumed by both sides.containers/code/: the merged VSCodium + simulator Docker image (see Workspace Image).catalog/: bundled, zero-config lesson catalog baked into the workspace image.e2e/: Playwright end-to-end tests and fixtures.scripts/: TypeScript utility scripts run by Bun (build, backup, cleanup, user admin).docs/: this documentation site.
You'll usually run both at once, in separate terminals.
bun run dev:controlThis runs the control plane with Bun's --watch flag, so it restarts on source
changes. It listens on port 4000 (override with the PORT env var) and serves
the prebuilt web bundle from apps/web/dist/ alongside the API and WebSocket
routes. If you only change backend code, this server plus a built web bundle is
all you need.
Demo mode bypasses authentication and seeds a single demo user, which is handy
for poking at the app without setting up Legion:
bun run dev:control -- --demoThe --demo flag (or the SCRIPTUM_DEMO_MODE env var) is read at startup. Do
not enable it for anything reachable by real students.
bun run dev:webThis starts the Vite dev server on port 5173 with hot module replacement.
Vite proxies API, health, metrics, AdvantageScope, Choreo, Elastic, admin, and
per-user (/u/<id>/…) traffic, including WebSocket upgrades, to the control
plane at http://localhost:4000. The proxy config lives in apps/web/vite.config.ts, so
front-end changes hot-reload at http://localhost:5173 while every backend call
is forwarded to dev:control. Run both servers together for the full HMR loop.
The control plane uses SQLite. Migrations are applied by apps/control/src/migrate.ts
and discovered from the migrations directory resolved in
apps/control/src/config.ts (defaults to apps/control/src/migrations, the
migrations.ts module).
bun run migrate # apply all pending migrations
bun run migrate:status # list each migration and whether it's appliedbun run start runs migrate before serving, so production boots always migrate
first. In dev, run bun run migrate yourself after pulling changes that add a
migration. Migrations target the configured dbPath (under data/ by default).
Formatting, linting, and import organization are handled by Biome.
Run this before finalizing any code change; it applies Biome's safe lint fixes, formatting, and import organization in one pass:
bun run check:fixFor the full local equivalent of CI, run:
bun run verifyverify runs biome ci . (which fails on any unfixed lint/format issue), then
bun run typecheck, then all four test tiers in order: test, test:web,
e2e, and e2e:security. Typechecking spans the contracts, control, web, and
scripts TypeScript projects. See Testing for what each tier
covers, and the CLI reference for the full
script list.