framework-m-studio 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- framework_m_studio/__init__.py +16 -0
- framework_m_studio/app.py +283 -0
- framework_m_studio/cli.py +247 -0
- framework_m_studio/codegen/__init__.py +34 -0
- framework_m_studio/codegen/generator.py +291 -0
- framework_m_studio/codegen/parser.py +545 -0
- framework_m_studio/codegen/templates/doctype.py.jinja2 +69 -0
- framework_m_studio/codegen/templates/test_doctype.py.jinja2 +58 -0
- framework_m_studio/codegen/test_generator.py +368 -0
- framework_m_studio/codegen/transformer.py +406 -0
- framework_m_studio/discovery.py +193 -0
- framework_m_studio/docs_generator.py +318 -0
- framework_m_studio/git/__init__.py +1 -0
- framework_m_studio/git/adapter.py +309 -0
- framework_m_studio/git/github_provider.py +321 -0
- framework_m_studio/git/protocol.py +249 -0
- framework_m_studio/py.typed +0 -0
- framework_m_studio/routes.py +552 -0
- framework_m_studio/sdk_generator.py +239 -0
- framework_m_studio/workspace.py +295 -0
- framework_m_studio-0.2.2.dist-info/METADATA +65 -0
- framework_m_studio-0.2.2.dist-info/RECORD +24 -0
- framework_m_studio-0.2.2.dist-info/WHEEL +4 -0
- framework_m_studio-0.2.2.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Framework M Studio - Visual DocType Builder & Developer Tools.
|
|
2
|
+
|
|
3
|
+
This package provides development-time tools for Framework M:
|
|
4
|
+
- Visual DocType builder (Studio UI)
|
|
5
|
+
- LibCST-based code generators
|
|
6
|
+
- Extended CLI commands (codegen, docs)
|
|
7
|
+
|
|
8
|
+
This package is separated from framework-m core to keep the
|
|
9
|
+
runtime lightweight. Install as a dev dependency.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Framework M Studio - Litestar Application.
|
|
2
|
+
|
|
3
|
+
This module provides the Studio web application for visual DocType building.
|
|
4
|
+
It serves both the API endpoints and the React SPA.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
m studio # Starts on port 9000
|
|
8
|
+
m studio --port 9001 # Custom port
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from litestar import Litestar, Response, Router, get
|
|
17
|
+
from litestar.config.cors import CORSConfig
|
|
18
|
+
from litestar.response import File, Redirect
|
|
19
|
+
from litestar.static_files import create_static_files_router
|
|
20
|
+
|
|
21
|
+
from framework_m_studio.routes import DocTypeController
|
|
22
|
+
|
|
23
|
+
# Path to static files (built React app)
|
|
24
|
+
STATIC_DIR = Path(__file__).parent / "static"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# Studio API Routes (Health & Field Types)
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@get(["/studio/api", "/studio/api/"], tags=["Studio"])
|
|
33
|
+
async def api_root() -> dict[str, Any]:
|
|
34
|
+
"""API root endpoint with available endpoints."""
|
|
35
|
+
return {
|
|
36
|
+
"service": "framework-m-studio",
|
|
37
|
+
"version": "0.1.0",
|
|
38
|
+
"endpoints": {
|
|
39
|
+
"health": "/studio/api/health",
|
|
40
|
+
"field_types": "/studio/api/field-types",
|
|
41
|
+
"doctypes": "/studio/api/doctypes",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_spa_response(path: str) -> Response[Any]:
|
|
47
|
+
"""Helper to serve SPA files."""
|
|
48
|
+
if not STATIC_DIR.exists():
|
|
49
|
+
# Development mode: no built assets yet
|
|
50
|
+
return Response(
|
|
51
|
+
content={
|
|
52
|
+
"message": "Studio UI not built yet",
|
|
53
|
+
"hint": "Run: cd apps/studio/studio_ui && pnpm build",
|
|
54
|
+
"api_health": "/studio/api/health",
|
|
55
|
+
"api_doctypes": "/studio/api/doctypes",
|
|
56
|
+
"api_field_types": "/studio/api/field-types",
|
|
57
|
+
},
|
|
58
|
+
media_type="application/json",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Check for actual file
|
|
62
|
+
file_path = STATIC_DIR / path
|
|
63
|
+
if file_path.is_file():
|
|
64
|
+
# Serve the actual file with proper content type detection
|
|
65
|
+
content = file_path.read_bytes()
|
|
66
|
+
# Let Litestar infer content type from file extension
|
|
67
|
+
from mimetypes import guess_type
|
|
68
|
+
|
|
69
|
+
content_type, _ = guess_type(str(file_path))
|
|
70
|
+
return Response(
|
|
71
|
+
content=content,
|
|
72
|
+
media_type=content_type or "application/octet-stream",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Serve index.html for SPA routing (fallback for client-side routes)
|
|
76
|
+
index_path = STATIC_DIR / "index.html"
|
|
77
|
+
if index_path.exists():
|
|
78
|
+
content = index_path.read_bytes()
|
|
79
|
+
return Response(
|
|
80
|
+
content=content,
|
|
81
|
+
media_type="text/html; charset=utf-8",
|
|
82
|
+
headers={
|
|
83
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
84
|
+
"Pragma": "no-cache",
|
|
85
|
+
"Expires": "0",
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return Response(
|
|
90
|
+
content={"error": "Studio UI not found"},
|
|
91
|
+
media_type="application/json",
|
|
92
|
+
status_code=404,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@get("/studio/ui/{path:path}", include_in_schema=False)
|
|
97
|
+
async def serve_spa(path: str) -> Response[Any]:
|
|
98
|
+
"""Serve Studio SPA with client-side routing support under /studio/ui/*."""
|
|
99
|
+
return _get_spa_response(path)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@get(["/studio/ui", "/studio/ui/"], include_in_schema=False)
|
|
103
|
+
async def serve_studio_root() -> Response[Any]:
|
|
104
|
+
"""Serve Studio root (index.html) at /studio/ui."""
|
|
105
|
+
return _get_spa_response("index.html")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@get(["/studio", "/studio/"], include_in_schema=False)
|
|
109
|
+
async def redirect_to_ui() -> Redirect:
|
|
110
|
+
"""Redirect /studio to /studio/ui/ for convenience."""
|
|
111
|
+
return Redirect(path="/studio/ui/")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@get("/studio/api/health", tags=["Studio"])
|
|
115
|
+
async def health_check() -> dict[str, str]:
|
|
116
|
+
"""Health check endpoint for Studio API."""
|
|
117
|
+
return {"status": "ok", "service": "framework-m-studio"}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@get("/studio/api/field-types", tags=["Studio"])
|
|
121
|
+
async def list_field_types() -> dict[str, Any]:
|
|
122
|
+
"""List available field types.
|
|
123
|
+
|
|
124
|
+
Returns built-in types plus any registered by installed apps.
|
|
125
|
+
Uses FieldRegistry for dynamic discovery.
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
from framework_m.adapters.db.field_registry import FieldRegistry
|
|
129
|
+
|
|
130
|
+
types_list = []
|
|
131
|
+
for type_info in FieldRegistry.get_instance().get_all_types():
|
|
132
|
+
types_list.append(
|
|
133
|
+
{
|
|
134
|
+
"name": type_info.name,
|
|
135
|
+
"pydantic_type": type_info.pydantic_type,
|
|
136
|
+
"label": type_info.label,
|
|
137
|
+
"ui_widget": type_info.ui_widget,
|
|
138
|
+
"category": type_info.category,
|
|
139
|
+
"validators": type_info.validators,
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
return {"field_types": types_list}
|
|
143
|
+
except ImportError:
|
|
144
|
+
# Fallback to static list if framework_m not available
|
|
145
|
+
return {
|
|
146
|
+
"field_types": [
|
|
147
|
+
{
|
|
148
|
+
"name": "str",
|
|
149
|
+
"pydantic_type": "str",
|
|
150
|
+
"label": "Text",
|
|
151
|
+
"ui_widget": "text",
|
|
152
|
+
"category": "text",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "int",
|
|
156
|
+
"pydantic_type": "int",
|
|
157
|
+
"label": "Integer",
|
|
158
|
+
"ui_widget": "number",
|
|
159
|
+
"category": "number",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "float",
|
|
163
|
+
"pydantic_type": "float",
|
|
164
|
+
"label": "Decimal",
|
|
165
|
+
"ui_widget": "number",
|
|
166
|
+
"category": "number",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "bool",
|
|
170
|
+
"pydantic_type": "bool",
|
|
171
|
+
"label": "Checkbox",
|
|
172
|
+
"ui_widget": "checkbox",
|
|
173
|
+
"category": "boolean",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "date",
|
|
177
|
+
"pydantic_type": "date",
|
|
178
|
+
"label": "Date",
|
|
179
|
+
"ui_widget": "date",
|
|
180
|
+
"category": "datetime",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "datetime",
|
|
184
|
+
"pydantic_type": "datetime",
|
|
185
|
+
"label": "DateTime",
|
|
186
|
+
"ui_widget": "datetime",
|
|
187
|
+
"category": "datetime",
|
|
188
|
+
},
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# =============================================================================
|
|
194
|
+
# SPA Serving (Catch-all for React Router)
|
|
195
|
+
# =============================================================================
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@get("/favicon.ico", include_in_schema=False)
|
|
199
|
+
async def serve_favicon() -> File | dict[str, str]:
|
|
200
|
+
"""Serve favicon from static directory."""
|
|
201
|
+
favicon_path = STATIC_DIR / "favicon.ico"
|
|
202
|
+
if favicon_path.exists():
|
|
203
|
+
return File(path=favicon_path)
|
|
204
|
+
return {"error": "Favicon not found"}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@get("/", include_in_schema=False)
|
|
208
|
+
async def root_redirect() -> Redirect:
|
|
209
|
+
"""Redirect root to /studio/."""
|
|
210
|
+
return Redirect(path="/studio/")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# =============================================================================
|
|
214
|
+
# Router and App Assembly
|
|
215
|
+
# =============================================================================
|
|
216
|
+
|
|
217
|
+
studio_api_router = Router(
|
|
218
|
+
path="/",
|
|
219
|
+
route_handlers=[
|
|
220
|
+
api_root,
|
|
221
|
+
health_check,
|
|
222
|
+
list_field_types,
|
|
223
|
+
],
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def create_app() -> Litestar:
|
|
228
|
+
"""Create the Studio Litestar application."""
|
|
229
|
+
route_handlers: list[Any] = [
|
|
230
|
+
studio_api_router,
|
|
231
|
+
DocTypeController, # File System API for DocTypes
|
|
232
|
+
]
|
|
233
|
+
# Always include favicon and redirects
|
|
234
|
+
route_handlers.extend(
|
|
235
|
+
[
|
|
236
|
+
serve_favicon,
|
|
237
|
+
root_redirect,
|
|
238
|
+
]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Create static files router for Studio SPA
|
|
242
|
+
# HashRouter is used on the frontend, so we only need to serve index.html at root
|
|
243
|
+
# and static assets.
|
|
244
|
+
|
|
245
|
+
# Static assets (js/css/images)
|
|
246
|
+
if STATIC_DIR.exists():
|
|
247
|
+
route_handlers.append(
|
|
248
|
+
create_static_files_router(
|
|
249
|
+
path="/studio/ui/assets",
|
|
250
|
+
directories=[STATIC_DIR / "assets"],
|
|
251
|
+
name="studio_assets",
|
|
252
|
+
html_mode=False,
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# We mount the serve_studio_root handler which has path="/studio" and "/studio/"
|
|
257
|
+
route_handlers.append(serve_studio_root)
|
|
258
|
+
route_handlers.append(serve_spa)
|
|
259
|
+
route_handlers.append(redirect_to_ui)
|
|
260
|
+
# CORS config for development (frontend on different port)
|
|
261
|
+
cors_config = CORSConfig(
|
|
262
|
+
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
|
|
263
|
+
allow_methods=["*"],
|
|
264
|
+
allow_headers=["*"],
|
|
265
|
+
allow_credentials=True,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return Litestar(
|
|
269
|
+
route_handlers=route_handlers,
|
|
270
|
+
cors_config=cors_config,
|
|
271
|
+
debug=True,
|
|
272
|
+
openapi_config=None, # Studio doesn't need its own OpenAPI
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# Application instance for uvicorn
|
|
277
|
+
app = create_app()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
__all__ = [
|
|
281
|
+
"app",
|
|
282
|
+
"create_app",
|
|
283
|
+
]
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Framework M Studio CLI Commands.
|
|
2
|
+
|
|
3
|
+
This module provides CLI commands that are registered via entry points
|
|
4
|
+
when framework-m-studio is installed. These extend the base `m` CLI
|
|
5
|
+
with developer tools.
|
|
6
|
+
|
|
7
|
+
Entry Point Registration (pyproject.toml):
|
|
8
|
+
[project.entry-points."framework_m.cli_commands"]
|
|
9
|
+
codegen = "framework_m_studio.cli:codegen_app"
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
|
|
16
|
+
import cyclopts
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# Codegen Sub-App
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
codegen_app = cyclopts.App(
|
|
23
|
+
name="codegen",
|
|
24
|
+
help="Code generation tools for Framework M",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@codegen_app.command(name="client")
|
|
29
|
+
def codegen_client(
|
|
30
|
+
lang: Annotated[
|
|
31
|
+
str,
|
|
32
|
+
cyclopts.Parameter(
|
|
33
|
+
name="--lang",
|
|
34
|
+
help="Target language: ts (TypeScript) or py (Python)",
|
|
35
|
+
),
|
|
36
|
+
] = "ts",
|
|
37
|
+
out: Annotated[
|
|
38
|
+
str,
|
|
39
|
+
cyclopts.Parameter(
|
|
40
|
+
name="--out",
|
|
41
|
+
help="Output directory for generated code",
|
|
42
|
+
),
|
|
43
|
+
] = "./generated",
|
|
44
|
+
openapi_url: Annotated[
|
|
45
|
+
str,
|
|
46
|
+
cyclopts.Parameter(
|
|
47
|
+
name="--openapi-url",
|
|
48
|
+
help="URL to fetch OpenAPI schema from",
|
|
49
|
+
),
|
|
50
|
+
] = "http://localhost:8000/schema/openapi.json",
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Generate API client from OpenAPI schema.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
m codegen client --lang ts --out ./frontend/src/api
|
|
56
|
+
m codegen client --lang py --out ./scripts/api_client
|
|
57
|
+
"""
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
from framework_m_studio.sdk_generator import (
|
|
61
|
+
fetch_openapi_schema,
|
|
62
|
+
generate_typescript_client,
|
|
63
|
+
generate_typescript_types,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
print(f"Generating {lang.upper()} client...")
|
|
67
|
+
print(f" OpenAPI URL: {openapi_url}")
|
|
68
|
+
print(f" Output: {out}")
|
|
69
|
+
|
|
70
|
+
# Create output directory
|
|
71
|
+
output_path = Path(out)
|
|
72
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
|
|
74
|
+
# Fetch schema and generate
|
|
75
|
+
schema = fetch_openapi_schema(openapi_url)
|
|
76
|
+
if lang.lower() == "ts":
|
|
77
|
+
types_code = generate_typescript_types(schema)
|
|
78
|
+
client_code = generate_typescript_client(schema)
|
|
79
|
+
(output_path / "types.ts").write_text(types_code)
|
|
80
|
+
(output_path / "client.ts").write_text(client_code)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@codegen_app.command(name="doctype")
|
|
84
|
+
def codegen_doctype(
|
|
85
|
+
name: Annotated[
|
|
86
|
+
str,
|
|
87
|
+
cyclopts.Parameter(help="DocType class name (PascalCase)"),
|
|
88
|
+
],
|
|
89
|
+
app: Annotated[
|
|
90
|
+
str | None,
|
|
91
|
+
cyclopts.Parameter(
|
|
92
|
+
name="--app",
|
|
93
|
+
help="Target app directory",
|
|
94
|
+
),
|
|
95
|
+
] = None,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Generate DocType Python code from schema.
|
|
98
|
+
|
|
99
|
+
This is the programmatic version of the Studio UI's
|
|
100
|
+
DocType builder. Useful for CI/CD pipelines.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
m codegen doctype Invoice --app apps/billing
|
|
104
|
+
"""
|
|
105
|
+
print(f"Generating DocType: {name}")
|
|
106
|
+
if app:
|
|
107
|
+
print(f" Target app: {app}")
|
|
108
|
+
print()
|
|
109
|
+
print("⚠️ Not yet implemented. Coming in Phase 07.")
|
|
110
|
+
print(" Will use LibCST for code generation")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# =============================================================================
|
|
114
|
+
# Docs Sub-App (Optional - registered via separate entry point if needed)
|
|
115
|
+
# =============================================================================
|
|
116
|
+
|
|
117
|
+
docs_app = cyclopts.App(
|
|
118
|
+
name="docs",
|
|
119
|
+
help="Documentation generation tools",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@docs_app.command(name="generate")
|
|
124
|
+
def docs_generate(
|
|
125
|
+
output: Annotated[
|
|
126
|
+
str,
|
|
127
|
+
cyclopts.Parameter(
|
|
128
|
+
name="--output",
|
|
129
|
+
help="Output directory for documentation",
|
|
130
|
+
),
|
|
131
|
+
] = "./docs/api",
|
|
132
|
+
openapi_url: Annotated[
|
|
133
|
+
str | None,
|
|
134
|
+
cyclopts.Parameter(
|
|
135
|
+
name="--openapi-url",
|
|
136
|
+
help="URL to fetch OpenAPI schema from (optional)",
|
|
137
|
+
),
|
|
138
|
+
] = None,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Generate API documentation from DocTypes.
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
m docs generate --output ./docs/api
|
|
144
|
+
"""
|
|
145
|
+
from pathlib import Path
|
|
146
|
+
|
|
147
|
+
from framework_m_studio.docs_generator import run_docs_generate
|
|
148
|
+
|
|
149
|
+
# Use current working directory as project root
|
|
150
|
+
project_root = Path.cwd()
|
|
151
|
+
|
|
152
|
+
# Look in src/doctypes if it exists, otherwise use project root
|
|
153
|
+
doctypes_dir = project_root / "src" / "doctypes"
|
|
154
|
+
scan_root = project_root / "src" if doctypes_dir.exists() else project_root
|
|
155
|
+
|
|
156
|
+
run_docs_generate(
|
|
157
|
+
output=output,
|
|
158
|
+
project_root=str(scan_root),
|
|
159
|
+
openapi_url=openapi_url,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# =============================================================================
|
|
164
|
+
# Studio Sub-App (Main command to start Studio server)
|
|
165
|
+
# =============================================================================
|
|
166
|
+
|
|
167
|
+
studio_app = cyclopts.App(
|
|
168
|
+
name="studio",
|
|
169
|
+
help="Start Framework M Studio visual editor",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@studio_app.default
|
|
174
|
+
def studio_serve(
|
|
175
|
+
port: Annotated[
|
|
176
|
+
int,
|
|
177
|
+
cyclopts.Parameter(
|
|
178
|
+
name="--port",
|
|
179
|
+
help="Port to run Studio on",
|
|
180
|
+
),
|
|
181
|
+
] = 9000,
|
|
182
|
+
host: Annotated[
|
|
183
|
+
str,
|
|
184
|
+
cyclopts.Parameter(
|
|
185
|
+
name="--host",
|
|
186
|
+
help="Host to bind to",
|
|
187
|
+
),
|
|
188
|
+
] = "127.0.0.1",
|
|
189
|
+
reload: Annotated[
|
|
190
|
+
bool,
|
|
191
|
+
cyclopts.Parameter(
|
|
192
|
+
name="--reload",
|
|
193
|
+
help="Enable auto-reload for development",
|
|
194
|
+
),
|
|
195
|
+
] = False,
|
|
196
|
+
cloud: Annotated[
|
|
197
|
+
bool,
|
|
198
|
+
cyclopts.Parameter(
|
|
199
|
+
name="--cloud",
|
|
200
|
+
help="Enable cloud mode (Git-backed workspaces)",
|
|
201
|
+
),
|
|
202
|
+
] = False,
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Start Framework M Studio.
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
m studio # Start on port 9000
|
|
208
|
+
m studio --port 8000 # Custom port
|
|
209
|
+
m studio --reload # Development mode
|
|
210
|
+
m studio --cloud # Enable cloud mode
|
|
211
|
+
"""
|
|
212
|
+
import os
|
|
213
|
+
|
|
214
|
+
import uvicorn
|
|
215
|
+
|
|
216
|
+
# Print startup banner
|
|
217
|
+
print()
|
|
218
|
+
print("🎨 Starting Framework M Studio")
|
|
219
|
+
print(f" ➜ Local: http://{host}:{port}/studio/")
|
|
220
|
+
print(f" ➜ API: http://{host}:{port}/studio/api/")
|
|
221
|
+
print(f" 🔌 API Health: http://{host}:{port}/studio/api/health")
|
|
222
|
+
print()
|
|
223
|
+
|
|
224
|
+
if cloud:
|
|
225
|
+
print("☁️ Cloud mode enabled - Git-backed workspaces")
|
|
226
|
+
os.environ["STUDIO_CLOUD_MODE"] = "1"
|
|
227
|
+
print()
|
|
228
|
+
|
|
229
|
+
# Start uvicorn
|
|
230
|
+
uvicorn.run(
|
|
231
|
+
"framework_m_studio.app:app",
|
|
232
|
+
host=host,
|
|
233
|
+
port=port,
|
|
234
|
+
reload=reload,
|
|
235
|
+
log_level="info",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
__all__ = [
|
|
240
|
+
"codegen_app",
|
|
241
|
+
"codegen_client",
|
|
242
|
+
"codegen_doctype",
|
|
243
|
+
"docs_app",
|
|
244
|
+
"docs_generate",
|
|
245
|
+
"studio_app",
|
|
246
|
+
"studio_serve",
|
|
247
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Code generation package for Framework M Studio.
|
|
2
|
+
|
|
3
|
+
This package provides:
|
|
4
|
+
- LibCST-based parsing for existing DocType files
|
|
5
|
+
- Jinja2-based code generation for new files
|
|
6
|
+
- Transformers for modifying existing files while preserving formatting
|
|
7
|
+
- Test generators for DocTypes
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from framework_m_studio.codegen.generator import (
|
|
11
|
+
generate_doctype_source,
|
|
12
|
+
update_doctype_source,
|
|
13
|
+
)
|
|
14
|
+
from framework_m_studio.codegen.parser import (
|
|
15
|
+
ConfigSchema,
|
|
16
|
+
DocTypeSchema,
|
|
17
|
+
FieldSchema,
|
|
18
|
+
parse_doctype,
|
|
19
|
+
)
|
|
20
|
+
from framework_m_studio.codegen.test_generator import (
|
|
21
|
+
generate_test,
|
|
22
|
+
generate_test_file,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ConfigSchema",
|
|
27
|
+
"DocTypeSchema",
|
|
28
|
+
"FieldSchema",
|
|
29
|
+
"generate_doctype_source",
|
|
30
|
+
"generate_test",
|
|
31
|
+
"generate_test_file",
|
|
32
|
+
"parse_doctype",
|
|
33
|
+
"update_doctype_source",
|
|
34
|
+
]
|