Skip to content

Local development

Two ways to run the platform while working on it: everything in Docker, or the frontend and plugin services on the host with the backend in Docker.

Everything in Docker

bash
docker compose -f infra/docker-compose-dev.yaml up --build

Builds every image from the checkout and exposes the internal ports:

PortService
4242Workbench
8080Backend API
3001service-metamodel
3002service-model
3003service-script
3004service-model-transformation
3005service-config
3006service-config-optimization
3007service-model-csv
3008service-config-mdeo
service-csv, reachable only from inside the compose network
5432–5435PostgreSQL (backend, script, model-transformation, optimizer)

Three optimizer-execution nodes are started and wired as peers, so distributed search can be exercised locally.

Slow to iterate on, but the closest thing to production.

Frontend and plugins on the host

Faster for language and editor work. Start the backend, the databases and the execution services in Docker, and run the rest with npm.

bash
cd app
npm install
npm run build          # packages, editor CSS, workbench

Then, in parallel:

bash
# TypeScript project references, in watch mode
npm run watch

# the workbench dev server on http://localhost:4242
npm run dev

# one per plugin service
npm run -w @mdeo/service-metamodel watch
npm run -w @mdeo/service-metamodel watch:static

The workbench's Vite config already proxies the plugin services, so the same-origin plugin URLs work without further configuration:

PathTarget
/plugin/metamodelhttp://localhost:3000
/plugin/modelhttp://localhost:3001
/plugin/scripthttp://localhost:3002
/plugin/model-transformationhttp://localhost:3003
/plugin/confighttp://localhost:3004
/plugin/config-optimizationhttp://localhost:3005
/plugin/config-mdeohttp://localhost:3006
/plugin/model-csvhttp://localhost:3007
/plugin/csvhttp://localhost:3008
/apihttp://localhost:8080

Set PORT accordingly when starting each service. Adding a new plugin means adding a proxy entry — see the end of Add a plugin.

The proxy also injects the Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers the workbench needs, which is why plugin services should be reached through it rather than directly.

There is a tmux helper in tools/run-dev.sh that starts the whole set of watchers in one session, and tools/stop-dev.sh to tear it down.

Which watcher does what

CommandRebuilds
npm run watch (root)All packages, through TypeScript project references
npm run -w @mdeo/service-x watchRestarts the service process on change
npm run -w @mdeo/service-x watch:staticThe served ES modules in static/
npm run -w @mdeo/editor-x watch:cssThe editor stylesheet

Changes to a served module require a page reload, because the workbench imports it once per session.

Backend and execution services

The Kotlin side is a Gradle build:

bash
cd platform
./gradlew build
./gradlew :backend:run

Modules: backend, common, expression, metamodel, model-transformation, script, optimizer, and the execution services script-execution, model-transformation-execution, optimizer-execution with their shared execution-common.

Linting and formatting

bash
cd app
npm run format          # prettier
npm run lint            # eslint --fix
npm run format:check    # check only, as CI does
npm run lint:check

The documentation site

bash
cd website
npm install
npm run validate        # parse and validate every DSL sample
npm run dev             # http://localhost:5173/mdeo-cloud/
npm run build           # validates, then builds the static site

npm run validate loads the built language packages from app/packages/*/dist and parses every file under website/samples in one shared Langium environment, exactly as the workbench does. It reports parser errors, lexer errors and validation diagnostics, and fails the build on any error — so a sample in the docs cannot drift away from the languages it documents.

Run npm run build:packages in app/ first if the language packages have changed.

Released under the terms of the repository licence.