Skip to main content

RFC-0001: Frontend Initialization and Development Experience

Summary

Define how third-party developers use Framework M with the Desk frontend without cloning the monorepo. Provide a "batteries included" experience similar to Frappe's bench start.

Problem Statement

Current State:

  • Frontend (/frontend) exists only in the monorepo
  • Developers must clone the repo to get the Desk UI
  • No single command starts both backend and frontend

Desired State:

pip install framework-m
m new:project crm
cd crm
m start # Works immediately with bundled Desk
m start --with-frontend # For frontend customization

Proposed Solution

┌─────────────────────────────────────────────────────────────────┐
│ Developer Experience │
├─────────────────────────────────────────────────────────────────┤
│ │
│ pip install framework-m │
│ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ m start │ │ m start --with-frontend │ │
│ │ │ │ │ │
│ │ Backend + Bundled Desk │ │ Backend + Vite Dev │ │
│ │ (Static files in pkg) │ │ (Uses @framework-m/desk)│ │
│ │ │ │ │ │
│ │ For: API users, │ │ For: Frontend devs, │ │
│ │ Quick start │ │ Customizers │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Components:

ComponentLocationPurposeRegistry
Bundled Deskframework_m/static/Pre-built React app in PyPI packagePyPI
Frontend Templateframework_m/templates/frontend/Scaffold source for customizationPyPI (bundled)
npm Package@framework-m/deskReusable Refine providers/componentsGitLab npm

GitLab npm Registry Benefits:

  • Private by default (uses CI_JOB_TOKEN)
  • No extra infrastructure or auth setup
  • Same monorepo hosts both packages
  • Can switch to public npmjs.com later via CLI

Option B: npm Package Only

  • No bundled static, always require --with-frontend
  • Simpler PyPI package, but worse DX for non-frontend developers

Option C: Docker-Only Distribution

  • Ship Desk as a Docker container
  • Good for production, bad for development

Phase 1: m start with Bundled Desk

Pre-build Desk UI during release:

# .gitlab-ci.yml
build:frontend:
script:
- cd frontend && pnpm install && pnpm build
- cp -r dist libs/framework-m/src/framework_m/static/

Backend serves static:

# libs/framework-m-standard/src/adapters/web/app.py
from importlib.resources import files

STATIC_DIR = files("framework_m") / "static"

app.mount("/", StaticFilesMiddleware(directory=STATIC_DIR))

CLI command:

# libs/framework-m/src/framework_m/cli/serve.py
def start_command(
port: int = 8000,
reload: bool = False,
with_frontend: bool = False,
):
"""Start Framework M application."""
if with_frontend:
_start_with_frontend(port, reload)
else:
_start_backend_only(port, reload)

Phase 2: m start --with-frontend

Auto-scaffold frontend if not exists:

def _start_with_frontend(port: int, reload: bool):
frontend_dir = Path.cwd() / "frontend"

# Auto-scaffold if not exists
if not frontend_dir.exists():
_scaffold_frontend(frontend_dir)

# Start both processes
backend = _start_backend(port, reload)
frontend = _start_frontend_dev(frontend_dir)

# Wait for both
try:
backend.wait()
frontend.wait()
except KeyboardInterrupt:
backend.terminate()
frontend.terminate()

Scaffold from template:

def _scaffold_frontend(target: Path):
"""Copy frontend template from package."""
from importlib.resources import files, as_file

template = files("framework_m") / "templates/frontend"
with as_file(template) as template_dir:
shutil.copytree(template_dir, target)

print(f"✓ Scaffolded frontend to {target}")
print(" Installing dependencies...")

subprocess.run(["pnpm", "install"], cwd=target, check=True)

Phase 3: npm Package @framework-m/desk

Publish reusable components to GitLab npm registry:

// @framework-m/desk/src/index.ts
export { frameworkMDataProvider } from "./providers/data";
export { authProvider } from "./providers/auth";
export { liveProvider } from "./providers/live";
export { DocTypeList, DocTypeForm, DocTypeShow } from "./components";
export { useDocType, useMetadata, usePermissions } from "./hooks";

Template consumes npm package:

// templates/frontend/package.json
{
"dependencies": {
"@framework-m/desk": "^0.1.0",
"@refinedev/core": "^5.0.0",
"react": "^19.0.0"
}
}

GitLab npm Registry Setup:

# .npmrc in scaffolded frontend/
@framework-m:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/

# CI publishes with CI_JOB_TOKEN
# Users can optionally configure personal token for local dev
# Or switch to public npmjs.com later

Command Reference

CommandDescription
m startStart backend + serve bundled Desk
m start --port 8000Custom port
m start --reloadDev mode with auto-reload
m start --with-frontendStart backend + frontend dev server
m init:frontendScaffold frontend only (no start)
m buildBuild frontend for production

Implementation Checklist

Phase 1: Bundled Desk (Required for v1)

  • CI job to build frontend during release
  • Include static/ in pyproject.toml package data
  • Backend serves static files at /
  • m start command in framework-m CLI

Phase 2: Frontend Scaffold (Required for customizers)

  • Create templates/frontend/ in package
  • m start --with-frontend command
  • m init:frontend command
  • Process manager for backend + frontend

Phase 3: npm Package (Required for ecosystem)

  • Extract Refine providers to @framework-m/desk
  • Publish to npm public registry
  • Template consumes npm package
  • Documentation for frontend customization

Alternatives Considered

OptionProsCons
A: Bundled + npm Package (Chosen)Best DX, reusable components, GitLab private registryPyPI package ~10MB, users need GitLab token
B: npm Package OnlySmaller PyPI packagePoor DX for non-frontend devs
C: Docker OnlyClean separationBad for development
D: Clone MonorepoCurrent stateTerrible DX

Migration Path

For existing users:

# Before (current)
git clone https://gitlab.com/castlecraft/m.git
cd m/frontend && pnpm dev

# After (proposed)
pip install framework-m
m new:project myapp && cd myapp
m start # Just works
m start --with-frontend # If customizing

Files to Create/Modify

FileActionDescription
libs/framework-m/src/framework_m/cli/serve.pyNEWm start command
libs/framework-m/src/framework_m/cli/init.pyNEWm init:frontend command
libs/framework-m/src/framework_m/static/NEWPre-built Desk (from CI)
libs/framework-m/src/framework_m/templates/frontend/NEWFrontend scaffold template
libs/framework-m-desk/NEWnpm package for Refine providers
libs/framework-m/pyproject.tomlMODIFYInclude static + templates
.gitlab-ci.ymlMODIFYAdd frontend + npm package build jobs

Questions for Review

  1. Package Size: Is ~10MB increase in PyPI package acceptable for bundled Desk?
  2. npm Scope: Should we use @framework-m/desk or @castlecraft/framework-m-desk?
  3. Template Updates: How do users get template updates after scaffolding?

References