Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
170
tools/api/services/config_service.py
Normal file
170
tools/api/services/config_service.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
ConfigService - Project Configuration Management
|
||||
|
||||
Handles loading, saving, and validating project-specific .dss/config.json files.
|
||||
Uses Pydantic for schema validation with sensible defaults.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict, Any
|
||||
from pydantic import BaseModel, Field
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# === Configuration Schema ===
|
||||
|
||||
class FigmaConfig(BaseModel):
|
||||
"""Figma integration settings."""
|
||||
file_id: Optional[str] = None
|
||||
team_id: Optional[str] = None
|
||||
|
||||
|
||||
class TokensConfig(BaseModel):
|
||||
"""Design token export settings."""
|
||||
output_path: str = "./tokens"
|
||||
format: str = "css" # css | scss | json | js
|
||||
|
||||
|
||||
class AIConfig(BaseModel):
|
||||
"""AI assistant behavior settings."""
|
||||
allowed_operations: List[str] = Field(default_factory=lambda: ["read", "write"])
|
||||
context_files: List[str] = Field(default_factory=lambda: ["./README.md"])
|
||||
max_file_size_kb: int = 500
|
||||
|
||||
|
||||
class DSSConfig(BaseModel):
|
||||
"""
|
||||
Complete DSS project configuration schema.
|
||||
|
||||
Stored in: [project_root]/.dss/config.json
|
||||
"""
|
||||
schema_version: str = "1.0"
|
||||
figma: FigmaConfig = Field(default_factory=FigmaConfig)
|
||||
tokens: TokensConfig = Field(default_factory=TokensConfig)
|
||||
ai: AIConfig = Field(default_factory=AIConfig)
|
||||
|
||||
class Config:
|
||||
# Allow extra fields for forward compatibility
|
||||
extra = "allow"
|
||||
|
||||
|
||||
# === Config Service ===
|
||||
|
||||
class ConfigService:
|
||||
"""
|
||||
Service for managing project configuration files.
|
||||
|
||||
Loads .dss/config.json from project roots, validates against schema,
|
||||
and provides defaults when config is missing.
|
||||
"""
|
||||
|
||||
CONFIG_FILENAME = "config.json"
|
||||
DSS_FOLDER = ".dss"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize config service."""
|
||||
logger.info("ConfigService initialized")
|
||||
|
||||
def get_config_path(self, project_root: str) -> Path:
|
||||
"""Get path to config file for a project."""
|
||||
return Path(project_root) / self.DSS_FOLDER / self.CONFIG_FILENAME
|
||||
|
||||
def get_config(self, project_root: str) -> DSSConfig:
|
||||
"""
|
||||
Load configuration for a project.
|
||||
|
||||
Args:
|
||||
project_root: Absolute path to project root directory
|
||||
|
||||
Returns:
|
||||
DSSConfig object (defaults if config file missing)
|
||||
"""
|
||||
config_path = self.get_config_path(project_root)
|
||||
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
data = json.load(f)
|
||||
config = DSSConfig(**data)
|
||||
logger.debug(f"Loaded config from {config_path}")
|
||||
return config
|
||||
except (json.JSONDecodeError, Exception) as e:
|
||||
logger.warning(f"Failed to parse config at {config_path}: {e}")
|
||||
# Fall through to return defaults
|
||||
|
||||
logger.debug(f"Using default config for {project_root}")
|
||||
return DSSConfig()
|
||||
|
||||
def save_config(self, project_root: str, config: DSSConfig) -> None:
|
||||
"""
|
||||
Save configuration for a project.
|
||||
|
||||
Args:
|
||||
project_root: Absolute path to project root directory
|
||||
config: DSSConfig object to save
|
||||
"""
|
||||
config_path = self.get_config_path(project_root)
|
||||
|
||||
# Ensure .dss directory exists
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(config.dict(), f, indent=2)
|
||||
|
||||
logger.info(f"Saved config to {config_path}")
|
||||
|
||||
def update_config(self, project_root: str, updates: Dict[str, Any]) -> DSSConfig:
|
||||
"""
|
||||
Update specific fields in project config.
|
||||
|
||||
Args:
|
||||
project_root: Absolute path to project root directory
|
||||
updates: Dictionary of fields to update
|
||||
|
||||
Returns:
|
||||
Updated DSSConfig object
|
||||
"""
|
||||
config = self.get_config(project_root)
|
||||
|
||||
# Deep merge updates
|
||||
config_dict = config.dict()
|
||||
for key, value in updates.items():
|
||||
if isinstance(value, dict) and isinstance(config_dict.get(key), dict):
|
||||
config_dict[key].update(value)
|
||||
else:
|
||||
config_dict[key] = value
|
||||
|
||||
new_config = DSSConfig(**config_dict)
|
||||
self.save_config(project_root, new_config)
|
||||
return new_config
|
||||
|
||||
def init_config(self, project_root: str) -> DSSConfig:
|
||||
"""
|
||||
Initialize config file for a new project.
|
||||
|
||||
Creates .dss/ folder and config.json with defaults if not exists.
|
||||
|
||||
Args:
|
||||
project_root: Absolute path to project root directory
|
||||
|
||||
Returns:
|
||||
DSSConfig object (new or existing)
|
||||
"""
|
||||
config_path = self.get_config_path(project_root)
|
||||
|
||||
if config_path.exists():
|
||||
logger.debug(f"Config already exists at {config_path}")
|
||||
return self.get_config(project_root)
|
||||
|
||||
config = DSSConfig()
|
||||
self.save_config(project_root, config)
|
||||
logger.info(f"Initialized new config at {config_path}")
|
||||
return config
|
||||
|
||||
def config_exists(self, project_root: str) -> bool:
|
||||
"""Check if config file exists for a project."""
|
||||
return self.get_config_path(project_root).exists()
|
||||
Reference in New Issue
Block a user