- Create new dss/ Python package at project root - Move MCP core from tools/dss_mcp/ to dss/mcp/ - Move storage layer from tools/storage/ to dss/storage/ - Move domain logic from dss-mvp1/dss/ to dss/ - Move services from tools/api/services/ to dss/services/ - Move API server to apps/api/ - Move CLI to apps/cli/ - Move Storybook assets to storybook/ - Create unified dss/__init__.py with comprehensive exports - Merge configuration into dss/settings.py (Pydantic-based) - Create pyproject.toml for proper package management - Update startup scripts for new paths - Remove old tools/ and dss-mvp1/ directories Architecture changes: - DSS is now MCP-first with 40+ tools for Claude Code - Clean imports: from dss import Projects, Components, FigmaToolSuite - No more sys.path.insert() hacking - apps/ contains thin application wrappers (API, CLI) - Single unified Python package for all DSS logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
725 lines
22 KiB
Python
725 lines
22 KiB
Python
"""
|
|
Design System Server (DSS) - FastAPI Server
|
|
|
|
Portable API server providing:
|
|
- Project management (CRUD)
|
|
- Figma integration endpoints
|
|
- Discovery & health endpoints
|
|
- Activity tracking
|
|
- Runtime configuration management
|
|
- Service discovery (Storybook, etc.)
|
|
|
|
Modes:
|
|
- Server: Deployed remotely, serves design systems to teams
|
|
- Local: Dev companion, UI advisor, local services
|
|
|
|
Uses SQLite for persistence, integrates with Figma tools.
|
|
"""
|
|
|
|
import asyncio
|
|
import subprocess
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime
|
|
|
|
from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from pydantic import BaseModel
|
|
|
|
import sys
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from config import config
|
|
from storage.json_store import (
|
|
Projects, Components, SyncHistory, ActivityLog, Teams, Cache, get_stats
|
|
)
|
|
from figma.figma_tools import FigmaToolSuite
|
|
|
|
|
|
# === Runtime Configuration ===
|
|
|
|
class RuntimeConfig:
|
|
"""
|
|
Runtime configuration that can be modified from the dashboard.
|
|
Persists to .dss/runtime-config.json for portability.
|
|
"""
|
|
def __init__(self):
|
|
self.config_path = Path(__file__).parent.parent.parent / ".dss" / "runtime-config.json"
|
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
self._data = self._load()
|
|
|
|
def _load(self) -> dict:
|
|
if self.config_path.exists():
|
|
try:
|
|
return json.loads(self.config_path.read_text())
|
|
except:
|
|
pass
|
|
return {
|
|
"mode": "local", # "local" or "server"
|
|
"figma": {"token": "", "configured": False},
|
|
"services": {
|
|
"storybook": {"enabled": False, "port": 6006, "url": ""},
|
|
"chromatic": {"enabled": False, "project_token": ""},
|
|
"github": {"enabled": False, "repo": ""},
|
|
},
|
|
"features": {
|
|
"visual_qa": True,
|
|
"token_sync": True,
|
|
"code_gen": True,
|
|
"ai_advisor": False,
|
|
}
|
|
}
|
|
|
|
def _save(self):
|
|
self.config_path.write_text(json.dumps(self._data, indent=2))
|
|
|
|
def get(self, key: str = None):
|
|
if key is None:
|
|
# Return safe copy without secrets
|
|
safe = self._data.copy()
|
|
if safe.get("figma", {}).get("token"):
|
|
safe["figma"]["token"] = "***configured***"
|
|
return safe
|
|
return self._data.get(key)
|
|
|
|
def set(self, key: str, value: Any):
|
|
self._data[key] = value
|
|
self._save()
|
|
return self._data[key]
|
|
|
|
def update(self, updates: dict):
|
|
for key, value in updates.items():
|
|
if isinstance(value, dict) and isinstance(self._data.get(key), dict):
|
|
self._data[key].update(value)
|
|
else:
|
|
self._data[key] = value
|
|
self._save()
|
|
return self.get()
|
|
|
|
def set_figma_token(self, token: str):
|
|
self._data["figma"]["token"] = token
|
|
self._data["figma"]["configured"] = bool(token)
|
|
self._save()
|
|
# Also update the global config
|
|
os.environ["FIGMA_TOKEN"] = token
|
|
return {"configured": bool(token)}
|
|
|
|
|
|
runtime_config = RuntimeConfig()
|
|
|
|
|
|
# === Service Discovery ===
|
|
|
|
class ServiceDiscovery:
|
|
"""Discovers and manages companion services."""
|
|
|
|
KNOWN_SERVICES = {
|
|
"storybook": {"ports": [6006, 6007], "health": "/"},
|
|
"chromatic": {"ports": [], "health": None},
|
|
"vite": {"ports": [5173, 5174, 3000], "health": "/"},
|
|
"webpack": {"ports": [8080, 8081], "health": "/"},
|
|
"nextjs": {"ports": [3000, 3001], "health": "/"},
|
|
}
|
|
|
|
@classmethod
|
|
async def discover(cls) -> dict:
|
|
"""Discover running services by checking known ports."""
|
|
import socket
|
|
|
|
discovered = {}
|
|
for service, info in cls.KNOWN_SERVICES.items():
|
|
for port in info["ports"]:
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(0.5)
|
|
result = sock.connect_ex(('127.0.0.1', port))
|
|
sock.close()
|
|
if result == 0:
|
|
discovered[service] = {
|
|
"running": True,
|
|
"port": port,
|
|
"url": f"http://localhost:{port}"
|
|
}
|
|
break
|
|
except:
|
|
pass
|
|
if service not in discovered:
|
|
discovered[service] = {"running": False, "port": None, "url": None}
|
|
|
|
return discovered
|
|
|
|
@classmethod
|
|
async def check_storybook(cls) -> dict:
|
|
"""Check Storybook status specifically."""
|
|
import httpx
|
|
|
|
configured = runtime_config.get("services").get("storybook", {})
|
|
port = configured.get("port", 6006)
|
|
url = configured.get("url") or f"http://localhost:{port}"
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=2.0) as client:
|
|
resp = await client.get(url)
|
|
return {
|
|
"running": resp.status_code == 200,
|
|
"url": url,
|
|
"port": port
|
|
}
|
|
except:
|
|
return {"running": False, "url": url, "port": port}
|
|
|
|
|
|
# === App Setup ===
|
|
|
|
app = FastAPI(
|
|
title="Design System Server (DSS)",
|
|
description="API for design system management and Figma integration",
|
|
version="1.0.0"
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Mount Admin UI static files
|
|
UI_DIR = Path(__file__).parent.parent.parent / "admin-ui"
|
|
if UI_DIR.exists():
|
|
app.mount("/admin-ui", StaticFiles(directory=str(UI_DIR), html=True), name="admin-ui")
|
|
|
|
# Initialize Figma tools
|
|
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
|
|
|
|
|
|
# === Request/Response Models ===
|
|
|
|
class ProjectCreate(BaseModel):
|
|
name: str
|
|
description: str = ""
|
|
figma_file_key: str = ""
|
|
|
|
class ProjectUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
figma_file_key: Optional[str] = None
|
|
status: Optional[str] = None
|
|
|
|
class FigmaExtractRequest(BaseModel):
|
|
file_key: str
|
|
format: str = "css"
|
|
|
|
class FigmaSyncRequest(BaseModel):
|
|
file_key: str
|
|
target_path: str
|
|
format: str = "css"
|
|
|
|
class TeamCreate(BaseModel):
|
|
name: str
|
|
description: str = ""
|
|
|
|
|
|
# === Root & Health ===
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
"""Redirect to Admin UI dashboard."""
|
|
from fastapi.responses import RedirectResponse
|
|
return RedirectResponse(url="/admin-ui/index.html")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
"""Health check endpoint."""
|
|
return {
|
|
"status": "ok",
|
|
"name": "dss-api",
|
|
"version": "1.0.0",
|
|
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
"figma_mode": figma_suite.mode,
|
|
"config": config.summary()
|
|
}
|
|
|
|
@app.get("/api/stats")
|
|
async def get_statistics():
|
|
"""Get database and system statistics."""
|
|
db_stats = get_stats()
|
|
return {
|
|
"database": db_stats,
|
|
"figma": {
|
|
"mode": figma_suite.mode,
|
|
"configured": config.figma.is_configured
|
|
}
|
|
}
|
|
|
|
|
|
# === Projects ===
|
|
|
|
@app.get("/api/projects")
|
|
async def list_projects(status: Optional[str] = None):
|
|
"""List all projects."""
|
|
projects = Projects.list(status=status)
|
|
return projects
|
|
|
|
@app.get("/api/projects/{project_id}")
|
|
async def get_project(project_id: str):
|
|
"""Get a specific project."""
|
|
project = Projects.get(project_id)
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
return project
|
|
|
|
@app.post("/api/projects")
|
|
async def create_project(project: ProjectCreate):
|
|
"""Create a new project."""
|
|
project_id = f"proj-{int(datetime.utcnow().timestamp() * 1000)}"
|
|
created = Projects.create(
|
|
id=project_id,
|
|
name=project.name,
|
|
description=project.description,
|
|
figma_file_key=project.figma_file_key
|
|
)
|
|
ActivityLog.log(
|
|
action="project_created",
|
|
entity_type="project",
|
|
entity_id=project_id,
|
|
project_id=project_id,
|
|
details={"name": project.name}
|
|
)
|
|
return created
|
|
|
|
@app.put("/api/projects/{project_id}")
|
|
async def update_project(project_id: str, update: ProjectUpdate):
|
|
"""Update a project."""
|
|
existing = Projects.get(project_id)
|
|
if not existing:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
update_data = {k: v for k, v in update.dict().items() if v is not None}
|
|
if not update_data:
|
|
return existing
|
|
|
|
updated = Projects.update(project_id, **update_data)
|
|
ActivityLog.log(
|
|
action="project_updated",
|
|
entity_type="project",
|
|
entity_id=project_id,
|
|
project_id=project_id,
|
|
details=update_data
|
|
)
|
|
return updated
|
|
|
|
@app.delete("/api/projects/{project_id}")
|
|
async def delete_project(project_id: str):
|
|
"""Delete a project."""
|
|
if not Projects.delete(project_id):
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
ActivityLog.log(
|
|
action="project_deleted",
|
|
entity_type="project",
|
|
entity_id=project_id
|
|
)
|
|
return {"success": True}
|
|
|
|
|
|
# === Components ===
|
|
|
|
@app.get("/api/projects/{project_id}/components")
|
|
async def list_components(project_id: str):
|
|
"""List components for a project."""
|
|
if not Projects.get(project_id):
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
return Components.list(project_id)
|
|
|
|
|
|
# === Figma Integration ===
|
|
|
|
@app.post("/api/figma/extract-variables")
|
|
async def extract_variables(request: FigmaExtractRequest, background_tasks: BackgroundTasks):
|
|
"""Extract design tokens from Figma file."""
|
|
try:
|
|
result = await figma_suite.extract_variables(request.file_key, request.format)
|
|
ActivityLog.log(
|
|
action="figma_extract_variables",
|
|
entity_type="figma",
|
|
details={"file_key": request.file_key, "format": request.format, "count": result.get("tokens_count")}
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.post("/api/figma/extract-components")
|
|
async def extract_components(request: FigmaExtractRequest):
|
|
"""Extract components from Figma file."""
|
|
try:
|
|
result = await figma_suite.extract_components(request.file_key)
|
|
ActivityLog.log(
|
|
action="figma_extract_components",
|
|
entity_type="figma",
|
|
details={"file_key": request.file_key, "count": result.get("components_count")}
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.post("/api/figma/extract-styles")
|
|
async def extract_styles(request: FigmaExtractRequest):
|
|
"""Extract styles from Figma file."""
|
|
try:
|
|
result = await figma_suite.extract_styles(request.file_key)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.post("/api/figma/sync-tokens")
|
|
async def sync_tokens(request: FigmaSyncRequest):
|
|
"""Sync tokens from Figma to target path."""
|
|
try:
|
|
result = await figma_suite.sync_tokens(request.file_key, request.target_path, request.format)
|
|
ActivityLog.log(
|
|
action="figma_sync_tokens",
|
|
entity_type="figma",
|
|
details={"file_key": request.file_key, "target": request.target_path, "synced": result.get("tokens_synced")}
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.post("/api/figma/validate")
|
|
async def validate_components(request: FigmaExtractRequest):
|
|
"""Validate components against design system rules."""
|
|
try:
|
|
result = await figma_suite.validate_components(request.file_key)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.post("/api/figma/generate-code")
|
|
async def generate_code(file_key: str, component_name: str, framework: str = "webcomponent"):
|
|
"""Generate component code from Figma."""
|
|
try:
|
|
result = await figma_suite.generate_code(file_key, component_name, framework)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
# === Discovery ===
|
|
|
|
@app.get("/api/discovery")
|
|
async def run_discovery(path: str = "."):
|
|
"""Run project discovery."""
|
|
script_path = Path(__file__).parent.parent / "discovery" / "discover.sh"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[str(script_path), path],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30
|
|
)
|
|
if result.returncode == 0:
|
|
return json.loads(result.stdout)
|
|
else:
|
|
return {"error": result.stderr}
|
|
except subprocess.TimeoutExpired:
|
|
raise HTTPException(status_code=504, detail="Discovery timed out")
|
|
except json.JSONDecodeError:
|
|
return {"raw_output": result.stdout}
|
|
|
|
@app.get("/api/discovery/ports")
|
|
async def discover_ports():
|
|
"""Discover listening ports and services."""
|
|
script_path = Path(__file__).parent.parent / "discovery" / "discover-ports.sh"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[str(script_path)],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10
|
|
)
|
|
return json.loads(result.stdout)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.get("/api/discovery/env")
|
|
async def discover_env(path: str = "."):
|
|
"""Analyze environment configuration."""
|
|
script_path = Path(__file__).parent.parent / "discovery" / "discover-env.sh"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[str(script_path), path],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10
|
|
)
|
|
return json.loads(result.stdout)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
# === Activity & Sync History ===
|
|
|
|
@app.get("/api/activity")
|
|
async def get_activity(limit: int = Query(default=50, le=100)):
|
|
"""Get recent activity log."""
|
|
return ActivityLog.recent(limit=limit)
|
|
|
|
@app.get("/api/sync-history")
|
|
async def get_sync_history(project_id: Optional[str] = None, limit: int = Query(default=20, le=100)):
|
|
"""Get sync history."""
|
|
return SyncHistory.recent(project_id=project_id, limit=limit)
|
|
|
|
|
|
# === Teams ===
|
|
|
|
@app.get("/api/teams")
|
|
async def list_teams():
|
|
"""List all teams."""
|
|
return Teams.list()
|
|
|
|
@app.post("/api/teams")
|
|
async def create_team(team: TeamCreate):
|
|
"""Create a new team."""
|
|
team_id = f"team-{int(datetime.utcnow().timestamp() * 1000)}"
|
|
created = Teams.create(team_id, team.name, team.description)
|
|
return created
|
|
|
|
@app.get("/api/teams/{team_id}")
|
|
async def get_team(team_id: str):
|
|
"""Get a specific team."""
|
|
team = Teams.get(team_id)
|
|
if not team:
|
|
raise HTTPException(status_code=404, detail="Team not found")
|
|
return team
|
|
|
|
|
|
# === Cache Management ===
|
|
|
|
@app.post("/api/cache/clear")
|
|
async def clear_cache():
|
|
"""Clear expired cache entries."""
|
|
count = Cache.clear_expired()
|
|
return {"cleared": count}
|
|
|
|
@app.delete("/api/cache")
|
|
async def purge_cache():
|
|
"""Purge all cache entries."""
|
|
Cache.clear_all()
|
|
return {"success": True}
|
|
|
|
|
|
# === Configuration Management ===
|
|
|
|
class ConfigUpdate(BaseModel):
|
|
mode: Optional[str] = None
|
|
figma_token: Optional[str] = None
|
|
services: Optional[Dict[str, Any]] = None
|
|
features: Optional[Dict[str, bool]] = None
|
|
|
|
|
|
@app.get("/api/config")
|
|
async def get_config():
|
|
"""Get current runtime configuration (secrets masked)."""
|
|
return {
|
|
"config": runtime_config.get(),
|
|
"env": config.summary(),
|
|
"mode": runtime_config.get("mode")
|
|
}
|
|
|
|
|
|
@app.put("/api/config")
|
|
async def update_config(update: ConfigUpdate):
|
|
"""Update runtime configuration."""
|
|
updates = {}
|
|
|
|
if update.mode:
|
|
updates["mode"] = update.mode
|
|
|
|
if update.figma_token is not None:
|
|
runtime_config.set_figma_token(update.figma_token)
|
|
# Reinitialize Figma tools with new token
|
|
global figma_suite
|
|
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
|
|
ActivityLog.log(
|
|
action="figma_token_updated",
|
|
entity_type="config",
|
|
details={"configured": bool(update.figma_token)}
|
|
)
|
|
|
|
if update.services:
|
|
updates["services"] = update.services
|
|
|
|
if update.features:
|
|
updates["features"] = update.features
|
|
|
|
if updates:
|
|
runtime_config.update(updates)
|
|
ActivityLog.log(
|
|
action="config_updated",
|
|
entity_type="config",
|
|
details={"keys": list(updates.keys())}
|
|
)
|
|
|
|
return runtime_config.get()
|
|
|
|
|
|
@app.get("/api/config/figma")
|
|
async def get_figma_config():
|
|
"""Get Figma configuration status."""
|
|
figma_cfg = runtime_config.get("figma")
|
|
return {
|
|
"configured": figma_cfg.get("configured", False),
|
|
"mode": figma_suite.mode,
|
|
"features": {
|
|
"extract_variables": True,
|
|
"extract_components": True,
|
|
"extract_styles": True,
|
|
"sync_tokens": True,
|
|
"validate": True,
|
|
"generate_code": True,
|
|
}
|
|
}
|
|
|
|
|
|
@app.post("/api/config/figma/test")
|
|
async def test_figma_connection():
|
|
"""Test Figma API connection."""
|
|
try:
|
|
# Try to make a simple API call
|
|
if not runtime_config.get("figma").get("configured"):
|
|
return {"success": False, "error": "Figma token not configured"}
|
|
|
|
# Test with a minimal API call
|
|
import httpx
|
|
token = runtime_config._data["figma"]["token"]
|
|
async with httpx.AsyncClient() as client:
|
|
resp = await client.get(
|
|
"https://api.figma.com/v1/me",
|
|
headers={"X-Figma-Token": token}
|
|
)
|
|
if resp.status_code == 200:
|
|
user = resp.json()
|
|
return {
|
|
"success": True,
|
|
"user": user.get("email", "connected"),
|
|
"handle": user.get("handle")
|
|
}
|
|
else:
|
|
return {"success": False, "error": f"API returned {resp.status_code}"}
|
|
except Exception as e:
|
|
return {"success": False, "error": str(e)}
|
|
|
|
|
|
# === Service Discovery ===
|
|
|
|
@app.get("/api/services")
|
|
async def list_services():
|
|
"""List configured and discovered services."""
|
|
configured = runtime_config.get("services")
|
|
discovered = await ServiceDiscovery.discover()
|
|
|
|
return {
|
|
"configured": configured,
|
|
"discovered": discovered,
|
|
"storybook": await ServiceDiscovery.check_storybook()
|
|
}
|
|
|
|
|
|
@app.put("/api/services/{service_name}")
|
|
async def configure_service(service_name: str, config_data: Dict[str, Any]):
|
|
"""Configure a service."""
|
|
services = runtime_config.get("services") or {}
|
|
services[service_name] = {**services.get(service_name, {}), **config_data}
|
|
runtime_config.set("services", services)
|
|
|
|
ActivityLog.log(
|
|
action="service_configured",
|
|
entity_type="service",
|
|
entity_id=service_name,
|
|
details={"keys": list(config_data.keys())}
|
|
)
|
|
|
|
return services[service_name]
|
|
|
|
|
|
@app.get("/api/services/storybook")
|
|
async def get_storybook_status():
|
|
"""Get Storybook service status."""
|
|
return await ServiceDiscovery.check_storybook()
|
|
|
|
|
|
# === DSS Mode ===
|
|
|
|
@app.get("/api/mode")
|
|
async def get_mode():
|
|
"""Get current DSS mode."""
|
|
mode = runtime_config.get("mode")
|
|
return {
|
|
"mode": mode,
|
|
"description": "Local dev companion" if mode == "local" else "Remote design system server",
|
|
"features": runtime_config.get("features")
|
|
}
|
|
|
|
|
|
@app.put("/api/mode")
|
|
async def set_mode(mode: str):
|
|
"""Set DSS mode (local or server)."""
|
|
if mode not in ["local", "server"]:
|
|
raise HTTPException(status_code=400, detail="Mode must be 'local' or 'server'")
|
|
|
|
runtime_config.set("mode", mode)
|
|
ActivityLog.log(
|
|
action="mode_changed",
|
|
entity_type="config",
|
|
details={"mode": mode}
|
|
)
|
|
|
|
return {"mode": mode, "success": True}
|
|
|
|
|
|
# === Run Server ===
|
|
|
|
# === Static Files (Admin UI) ===
|
|
# Mount at the end so API routes take precedence
|
|
# This enables portable mode: ./dss start serves everything on one port
|
|
|
|
UI_DIR = Path(__file__).parent.parent.parent / "admin-ui"
|
|
if UI_DIR.exists():
|
|
app.mount("/", StaticFiles(directory=str(UI_DIR), html=True), name="ui")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
port = int(os.getenv("PORT", "3456"))
|
|
host = os.getenv("HOST", "0.0.0.0")
|
|
|
|
url = f"http://{host}:{port}"
|
|
print(f"""
|
|
╔═══════════════════════════════════════════════════════════════╗
|
|
║ Design System Server (DSS) - Portable Server ║
|
|
╠═══════════════════════════════════════════════════════════════╣
|
|
║ Dashboard: {url + '/':^47}║
|
|
║ API: {url + '/api':^47}║
|
|
║ Docs: {url + '/docs':^47}║
|
|
║ Environment: {config.server.env:^47}║
|
|
║ Figma Mode: {figma_suite.mode:^47}║
|
|
╚═══════════════════════════════════════════════════════════════╝
|
|
""")
|
|
|
|
uvicorn.run(
|
|
"server:app",
|
|
host=host,
|
|
port=port,
|
|
reload=config.server.env == "development"
|
|
)
|