Skip to main content

Framework Development Setup

This guide is for contributors working on the Framework M source (the monorepo). It assumes you are comfortable with uv, pnpm, and a Python/TypeScript monorepo.

If you only want to build an application with the published packages, use Getting Started instead.


1. Prerequisites​

ToolVersion
Python3.12+
uvlatest
Node.js24+
pnpm12+
gitany recent version
Dockeroptional, for the Postgres/NATS/Redis stack

2. Clone and install​

Run everything from the repository root.

git clone https://gitlab.com/framework-m/framework-m.git
cd framework-m

# Python workspace + every optional extra + the dev group
uv sync --all-packages --all-extras --all-groups

# JavaScript workspace
pnpm install

# Build the internal JS packages (required before the dev server starts)
pnpm run ci:js:build-internal

Why each step is required:

  • uv sync --all-packages --all-extras --all-groups installs all Python workspace members, the dev group, and the optional extras ([db], [server], the ASGI engines, etc.). Plain uv sync is not enough: importing framework_m_standard.bootstrap reaches SQLAlchemy, create_app() requires cryptography, and m dev needs an ASGI engine. All three live in extras.
  • pnpm install links the pnpm workspace packages.
  • pnpm run ci:js:build-internal builds @framework-m/ui, @framework-m/plugin-sdk, @framework-m/vite-plugin, and @framework-m/desk into their dist/ directories. frontend/vite.config.ts imports @framework-m/vite-plugin, whose package exports point at dist/. dist/ is gitignored, so a fresh clone fails to start Vite until this build runs.

Re-run the internal build whenever you change one of those packages' build output or exports.


3. Configure the local environment​

Option A — SQLite​

Create .env at the repo root:

DATABASE_URL=sqlite+aiosqlite:///./dev.db

# Generate with: openssl rand -hex 32
FRAMEWORK_M_JWT_SECRET=<paste-the-generated-value>

m dev sets M_DEV_MODE=true, so the JWT secret is not strictly required during development, but set it so local behaviour matches production. m prod does require it.

On startup, the dev server automatically syncs the schema from the registered DocTypes. To do that explicitly instead:

uv run m migrate sync

The monorepo already ships alembic.ini and alembic/, so m migrate init is not needed here.

Option B — Docker stack (Postgres/NATS/Redis)​

cp .env.example .env

Confirm the database URL uses the async driver:

DATABASE_URL=postgresql+asyncpg://framework_m:${POSTGRES_PASSWORD}@localhost:5432/framework_m_dev

Then start the services:

docker compose up -d

NATS and Redis are optional; leave NATS_URL/REDIS_URL unset if you do not need them. The dev server still creates the schema from DocTypes on startup.

For Postgres, prefer the dev server's startup schema sync. m migrate sync converts +asyncpg to +psycopg2 for its synchronous reflection step, and psycopg2 is not part of the extras.


4. Run the development stack​

uv run m dev

With the monorepo's frontend/ present, this starts:

ProcessURL
Backend APIhttp://127.0.0.1:8888
Vite UIhttp://127.0.0.1:5173

Open http://127.0.0.1:5173/ for the UI. Vite proxies /api to the backend on :8888. In dev mode the backend does not serve the bundled Desk.

The backend uses an async server, which is Granian by default (part of --all-extras). Additionally Uvicorn is also supported.

If you want to force use an engine then run:

FRAMEWORK_M_SERVER_ENGINE=uvicorn uv run m dev

Notes:

  • Without DATABASE_URL, the backend runs stateless and logs [SyncSchema] Database disabled, skipping schema sync.
  • m dev uses honcho: if any one process exits, the whole stack is stopped. Use m dev --no-frontend to isolate a backend problem.
  • m dev --studio also starts Studio on port 9999.
  • The frontend predev hook merges package locales into frontend/public/locales; review/revert unrelated locale changes before committing.

5. Create a login (optional)​

To use the Desk UI, create a local user (in a second terminal while the dev server runs, or before starting it):

uv run m user create \
--email admin@local.com \
--password 'ChangeMe123!' \
--name "Local Admin" \
--role Admin

Sign in at http://127.0.0.1:5173/ with those credentials. --role is repeatable.


6. Run the checks​

Lint and format​

uv run ruff check .
uv run ruff format . # apply formatting
uv run ruff format --check . # CI-style check only

Type checking​

Use the per-package wrapper (the same commands CI runs):

uv run m typecheck --cwd libs/framework-m-core --strict
uv run m typecheck --cwd libs/framework-m-standard --strict
uv run m typecheck --cwd libs/framework-m --strict
uv run m typecheck --cwd apps/studio --strict
uv run m typecheck --cwd libs/framework-mx-mongo --strict

Tests and architecture​

uv run pytest -n auto
# Equivalent to the CI invocation:
# uv run m test -c pyproject.toml -n auto

uv run lint-imports

For full CI parity, also run:

uv run m lint libs/ apps/
uv run m format --check libs/ apps/
uv run m security libs/ apps/ -ll

7. Frontend and Studio​

The shell's Vite config aliases @framework-m/desk, @framework-m/ui, and @framework-m/plugin-sdk to their source files, so changes there hot-reload during m dev. @framework-m/vite-plugin is loaded from its built dist/.

If the plugin itself is updated, rebuild it after changing:

pnpm --filter @framework-m/vite-plugin build
# or rebuild every internal package:
pnpm run ci:js:build-internal

To work on the Studio UI with hot reload:

# terminal 1 — Studio API/UI
uv run m studio --port 9999

# terminal 2 — Vite dev server
cd apps/studio/studio_ui
pnpm dev

Open http://127.0.0.1:9999/studio/ui/.

Avoid running pnpm build in apps/studio/studio_ui locally: it rewrites tracked static assets with hashed filenames. CI builds release assets; use pnpm dev for local work.


8. Troubleshooting​

SymptomFix
No module named 'sqlalchemy'Run uv sync --all-packages --all-extras --all-groups (plain uv sync omits [db])
Payload encryption requires the 'cryptography' packageRun the full sync above; cryptography comes from [server]
No module named uvicorn / No module named granianRun the full sync above; install [server-uvicorn]/[server-granian] for a targeted install
Failed to resolve entry for package "@framework-m/vite-plugin"Run pnpm run ci:js:build-internal
[SyncSchema] Database disabled, skipping schema syncSet DATABASE_URL in .env
m dev exits after one process failsHoncho stops the whole stack by design; scroll up to the failing process, or run m dev --no-frontend
mypy: error: Missing target module, package, files, or commandUse uv run m typecheck --cwd <package> --strict
Postgres async-driver errorUse postgresql+asyncpg://…, not postgresql://…
Locale files modified while running m devThe frontend predev hook merges package locales; review/revert before committing

9. Next steps​