- 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>
353 lines
12 KiB
Python
353 lines
12 KiB
Python
"""
|
|
DSS Core Sync
|
|
|
|
Syncs the canonical DSS Figma (shadcn/ui) to the DSS core tokens.
|
|
This is the base layer that all skins and projects inherit from.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
from dss.project.core import (
|
|
DSS_FIGMA_REFERENCE,
|
|
DSS_CORE_DIR,
|
|
DSS_CACHE_DIR,
|
|
DSS_CORE_THEMES,
|
|
ensure_dss_directories,
|
|
)
|
|
from dss.project.figma import FigmaProjectSync, FigmaStyleData
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DSSCoreSync:
|
|
"""
|
|
Syncs the DSS core design system from Figma.
|
|
|
|
The shadcn/ui Figma file is the canonical source for:
|
|
- Color tokens (light/dark themes)
|
|
- Typography scale
|
|
- Spacing scale
|
|
- Component definitions
|
|
- Effects (shadows, etc.)
|
|
"""
|
|
|
|
def __init__(self, figma_token: Optional[str] = None):
|
|
"""
|
|
Initialize DSS core sync.
|
|
|
|
Args:
|
|
figma_token: Figma token. Uses FIGMA_TOKEN env var if not provided.
|
|
"""
|
|
self.figma_token = figma_token or os.environ.get("FIGMA_TOKEN")
|
|
self.reference = DSS_FIGMA_REFERENCE
|
|
ensure_dss_directories()
|
|
|
|
@property
|
|
def core_manifest_path(self) -> Path:
|
|
"""Path to DSS core manifest file."""
|
|
return DSS_CORE_DIR / "manifest.json"
|
|
|
|
@property
|
|
def core_tokens_path(self) -> Path:
|
|
"""Path to DSS core tokens file."""
|
|
return DSS_CORE_DIR / "tokens.json"
|
|
|
|
@property
|
|
def core_themes_path(self) -> Path:
|
|
"""Path to DSS core themes file."""
|
|
return DSS_CORE_DIR / "themes.json"
|
|
|
|
@property
|
|
def core_components_path(self) -> Path:
|
|
"""Path to DSS core components file."""
|
|
return DSS_CORE_DIR / "components.json"
|
|
|
|
def get_sync_status(self) -> Dict[str, Any]:
|
|
"""Get current sync status."""
|
|
manifest = self._load_manifest()
|
|
|
|
return {
|
|
"synced": manifest is not None,
|
|
"last_sync": manifest.get("last_sync") if manifest else None,
|
|
"figma_reference": {
|
|
"team_id": self.reference.team_id,
|
|
"project_id": self.reference.project_id,
|
|
"uikit_file_key": self.reference.uikit_file_key,
|
|
"uikit_file_name": self.reference.uikit_file_name,
|
|
},
|
|
"core_dir": str(DSS_CORE_DIR),
|
|
"files": {
|
|
"manifest": self.core_manifest_path.exists(),
|
|
"tokens": self.core_tokens_path.exists(),
|
|
"themes": self.core_themes_path.exists(),
|
|
"components": self.core_components_path.exists(),
|
|
}
|
|
}
|
|
|
|
def sync(self, force: bool = False) -> Dict[str, Any]:
|
|
"""
|
|
Sync DSS core from Figma.
|
|
|
|
Args:
|
|
force: Force sync even if recently synced
|
|
|
|
Returns:
|
|
Sync result with extracted data summary
|
|
"""
|
|
if not self.figma_token:
|
|
return {
|
|
"success": False,
|
|
"error": "FIGMA_TOKEN not configured. Set env var or pass token."
|
|
}
|
|
|
|
# Check if sync needed
|
|
manifest = self._load_manifest()
|
|
if manifest and not force:
|
|
last_sync = manifest.get("last_sync")
|
|
if last_sync:
|
|
# Could add time-based check here
|
|
pass
|
|
|
|
try:
|
|
# Initialize Figma sync
|
|
figma = FigmaProjectSync(token=self.figma_token)
|
|
|
|
# Extract styles from UIKit file
|
|
logger.info(f"Syncing from Figma: {self.reference.uikit_file_name}")
|
|
styles = figma.get_file_styles(self.reference.uikit_file_key)
|
|
|
|
# Process and save tokens
|
|
tokens = self._process_tokens(styles)
|
|
self._save_tokens(tokens)
|
|
|
|
# Save themes (combine Figma + defaults)
|
|
themes = self._process_themes(styles)
|
|
self._save_themes(themes)
|
|
|
|
# Save components
|
|
components = self._process_components(styles)
|
|
self._save_components(components)
|
|
|
|
# Update manifest
|
|
self._save_manifest(styles)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Synced DSS core from {self.reference.uikit_file_name}",
|
|
"summary": {
|
|
"colors": len(styles.colors),
|
|
"typography": len(styles.typography),
|
|
"effects": len(styles.effects),
|
|
"variables": len(styles.variables),
|
|
},
|
|
"files_written": [
|
|
str(self.core_manifest_path),
|
|
str(self.core_tokens_path),
|
|
str(self.core_themes_path),
|
|
str(self.core_components_path),
|
|
]
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.exception("DSS core sync failed")
|
|
return {"success": False, "error": str(e)}
|
|
|
|
def _process_tokens(self, styles: FigmaStyleData) -> Dict[str, Any]:
|
|
"""Process Figma styles into DSS token format."""
|
|
tokens = {
|
|
"version": "1.0.0",
|
|
"source": "figma",
|
|
"figma_file": self.reference.uikit_file_key,
|
|
"synced_at": datetime.now().isoformat(),
|
|
"categories": {}
|
|
}
|
|
|
|
# Colors
|
|
tokens["categories"]["color"] = {}
|
|
for path, data in styles.colors.items():
|
|
tokens["categories"]["color"][path] = {
|
|
"value": None, # Value comes from variables or manual mapping
|
|
"figma_id": data.get("figma_id"),
|
|
"description": data.get("description", ""),
|
|
}
|
|
|
|
# Add variables as color tokens (they have actual values)
|
|
for path, data in styles.variables.items():
|
|
if data.get("type") == "COLOR":
|
|
tokens["categories"]["color"][path] = {
|
|
"value": data.get("values", {}),
|
|
"figma_id": data.get("figma_id"),
|
|
"type": "variable",
|
|
}
|
|
|
|
# Typography
|
|
tokens["categories"]["typography"] = {}
|
|
for path, data in styles.typography.items():
|
|
tokens["categories"]["typography"][path] = {
|
|
"value": None,
|
|
"figma_id": data.get("figma_id"),
|
|
"name": data.get("name"),
|
|
}
|
|
|
|
# Effects (shadows, blurs)
|
|
tokens["categories"]["effect"] = {}
|
|
for path, data in styles.effects.items():
|
|
tokens["categories"]["effect"][path] = {
|
|
"value": None,
|
|
"figma_id": data.get("figma_id"),
|
|
"name": data.get("name"),
|
|
}
|
|
|
|
return tokens
|
|
|
|
def _process_themes(self, styles: FigmaStyleData) -> Dict[str, Any]:
|
|
"""Process themes, merging Figma data with DSS defaults."""
|
|
themes = {
|
|
"version": "1.0.0",
|
|
"source": "dss-core",
|
|
"synced_at": datetime.now().isoformat(),
|
|
"themes": {}
|
|
}
|
|
|
|
# Start with DSS core defaults
|
|
for theme_name, theme_data in DSS_CORE_THEMES.items():
|
|
themes["themes"][theme_name] = {
|
|
"description": theme_data["description"],
|
|
"colors": theme_data["colors"].copy(),
|
|
"source": "dss-defaults",
|
|
}
|
|
|
|
# Overlay any Figma variables that map to themes
|
|
# (Figma variables can have modes like light/dark)
|
|
for path, data in styles.variables.items():
|
|
values_by_mode = data.get("values", {})
|
|
for mode_id, value in values_by_mode.items():
|
|
# Try to map mode to theme
|
|
# This is simplified - real implementation would use Figma mode names
|
|
pass
|
|
|
|
return themes
|
|
|
|
def _process_components(self, styles: FigmaStyleData) -> Dict[str, Any]:
|
|
"""Extract component information from Figma."""
|
|
from dss.project.core import DSS_CORE_COMPONENTS
|
|
|
|
components = {
|
|
"version": "1.0.0",
|
|
"source": "dss-core",
|
|
"synced_at": datetime.now().isoformat(),
|
|
"components": {}
|
|
}
|
|
|
|
# Start with DSS core component definitions
|
|
for name, comp_data in DSS_CORE_COMPONENTS.items():
|
|
components["components"][name] = {
|
|
"variants": comp_data.get("variants", []),
|
|
"source": "dss-core",
|
|
}
|
|
|
|
return components
|
|
|
|
def _load_manifest(self) -> Optional[Dict[str, Any]]:
|
|
"""Load existing manifest if present."""
|
|
if self.core_manifest_path.exists():
|
|
try:
|
|
with open(self.core_manifest_path, "r") as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return None
|
|
return None
|
|
|
|
def _save_manifest(self, styles: FigmaStyleData):
|
|
"""Save sync manifest."""
|
|
manifest = {
|
|
"version": "1.0.0",
|
|
"last_sync": datetime.now().isoformat(),
|
|
"figma_reference": {
|
|
"team_id": self.reference.team_id,
|
|
"team_name": self.reference.team_name,
|
|
"project_id": self.reference.project_id,
|
|
"project_name": self.reference.project_name,
|
|
"uikit_file_key": self.reference.uikit_file_key,
|
|
"uikit_file_name": self.reference.uikit_file_name,
|
|
},
|
|
"stats": {
|
|
"colors": len(styles.colors),
|
|
"typography": len(styles.typography),
|
|
"effects": len(styles.effects),
|
|
"variables": len(styles.variables),
|
|
}
|
|
}
|
|
|
|
with open(self.core_manifest_path, "w") as f:
|
|
json.dump(manifest, f, indent=2)
|
|
|
|
def _save_tokens(self, tokens: Dict[str, Any]):
|
|
"""Save tokens to file."""
|
|
with open(self.core_tokens_path, "w") as f:
|
|
json.dump(tokens, f, indent=2)
|
|
|
|
def _save_themes(self, themes: Dict[str, Any]):
|
|
"""Save themes to file."""
|
|
with open(self.core_themes_path, "w") as f:
|
|
json.dump(themes, f, indent=2)
|
|
|
|
def _save_components(self, components: Dict[str, Any]):
|
|
"""Save components to file."""
|
|
with open(self.core_components_path, "w") as f:
|
|
json.dump(components, f, indent=2)
|
|
|
|
def get_tokens(self) -> Optional[Dict[str, Any]]:
|
|
"""Load synced tokens."""
|
|
if self.core_tokens_path.exists():
|
|
with open(self.core_tokens_path, "r") as f:
|
|
return json.load(f)
|
|
return None
|
|
|
|
def get_themes(self) -> Optional[Dict[str, Any]]:
|
|
"""Load synced themes."""
|
|
if self.core_themes_path.exists():
|
|
with open(self.core_themes_path, "r") as f:
|
|
return json.load(f)
|
|
return None
|
|
|
|
def get_components(self) -> Optional[Dict[str, Any]]:
|
|
"""Load synced components."""
|
|
if self.core_components_path.exists():
|
|
with open(self.core_components_path, "r") as f:
|
|
return json.load(f)
|
|
return None
|
|
|
|
|
|
# =============================================================================
|
|
# CONVENIENCE FUNCTIONS
|
|
# =============================================================================
|
|
|
|
def sync_dss_core(figma_token: Optional[str] = None, force: bool = False) -> Dict[str, Any]:
|
|
"""Sync DSS core from Figma."""
|
|
sync = DSSCoreSync(figma_token=figma_token)
|
|
return sync.sync(force=force)
|
|
|
|
|
|
def get_dss_core_status() -> Dict[str, Any]:
|
|
"""Get DSS core sync status."""
|
|
sync = DSSCoreSync()
|
|
return sync.get_sync_status()
|
|
|
|
|
|
def get_dss_core_tokens() -> Optional[Dict[str, Any]]:
|
|
"""Get DSS core tokens (must be synced first)."""
|
|
sync = DSSCoreSync()
|
|
return sync.get_tokens()
|
|
|
|
|
|
def get_dss_core_themes() -> Optional[Dict[str, Any]]:
|
|
"""Get DSS core themes."""
|
|
sync = DSSCoreSync()
|
|
return sync.get_themes()
|