Compare commits
2 Commits
e96902c732
...
faa19beef3
| Author | SHA1 | Date | |
|---|---|---|---|
| faa19beef3 | |||
| 41fba59bf7 |
@@ -1,147 +1,139 @@
|
||||
/**
|
||||
* DSS Error Handler - Immune System Antibodies
|
||||
* DSS Error Handler
|
||||
*
|
||||
* The DSS Component's immune system uses these antibodies to detect and report threats.
|
||||
* Converts technical errors into human-friendly, actionable treatment plans.
|
||||
* Converts technical errors into user-friendly, actionable messages.
|
||||
* Integrates with the messaging system for structured error reporting.
|
||||
*
|
||||
* Biological Framework: These error messages use component metaphors to make
|
||||
* issues intuitive. See docs/DSS_ORGANISM_GUIDE.md for the full framework.
|
||||
*
|
||||
* @module error-handler
|
||||
*/
|
||||
|
||||
import { notifyError, ErrorCode } from './messaging.js';
|
||||
|
||||
/**
|
||||
* Error message templates with component metaphors
|
||||
*
|
||||
* These messages use biological language from the DSS Component Framework.
|
||||
* Each error is framed as a symptom the immune system detected, with
|
||||
* a diagnosis and treatment plan.
|
||||
* Error message templates with user-friendly descriptions
|
||||
*/
|
||||
const errorMessages = {
|
||||
// Figma API Errors - Sensory System Issues
|
||||
// Figma API Errors
|
||||
figma_403: {
|
||||
title: '🛡️ IMMUNE ALERT: Sensory Input Blocked',
|
||||
message: 'The DSS sensory organs cannot perceive the Figma file. Your access credentials lack permission.',
|
||||
title: 'Access Denied',
|
||||
message: 'Cannot access the Figma file. Your access credentials lack permission.',
|
||||
actions: [
|
||||
'Verify your Figma authentication token in Settings (nervous system communication)',
|
||||
'Confirm you have access to this file in Figma (sensory perception)',
|
||||
'Check if the file still exists (component awareness)',
|
||||
'Verify your Figma authentication token in Settings',
|
||||
'Confirm you have access to this file in Figma',
|
||||
'Check if the file still exists',
|
||||
],
|
||||
code: ErrorCode.FIGMA_API_ERROR,
|
||||
},
|
||||
figma_404: {
|
||||
title: '🛡️ IMMUNE ALERT: Sensory Target Lost',
|
||||
message: 'The Figma file the DSS sensory organs were trying to perceive doesn\'t exist or is inaccessible.',
|
||||
title: 'File Not Found',
|
||||
message: 'The Figma file doesn\'t exist or is inaccessible.',
|
||||
actions: [
|
||||
'Double-check your Figma file key in Settings (sensory focus)',
|
||||
'Double-check your Figma file key in Settings',
|
||||
'Verify the file hasn\'t been deleted in Figma',
|
||||
'Confirm you have access to the file in Figma (sensory perception)',
|
||||
'Confirm you have access to the file in Figma',
|
||||
],
|
||||
code: ErrorCode.FIGMA_API_ERROR,
|
||||
},
|
||||
figma_401: {
|
||||
title: '🔌 NERVOUS SYSTEM ALERT: Authentication Expired',
|
||||
message: 'The DSS nervous system\'s authentication with Figma has failed. Your sensory input token is invalid or expired.',
|
||||
title: 'Authentication Failed',
|
||||
message: 'Figma authentication failed. Your access token is invalid or expired.',
|
||||
actions: [
|
||||
'Refresh your Figma authentication token in Settings (nervous system repair)',
|
||||
'Get a fresh token from figma.com/settings (Account → Personal Access Tokens)',
|
||||
'Refresh your Figma authentication token in Settings',
|
||||
'Get a fresh token from figma.com/settings (Account -> Personal Access Tokens)',
|
||||
'Ensure you copied the full token without truncation',
|
||||
],
|
||||
code: ErrorCode.FIGMA_CONNECTION_FAILED,
|
||||
},
|
||||
figma_429: {
|
||||
title: '⚡ METABOLISM ALERT: Sensory Overload',
|
||||
message: 'The DSS is sensing too quickly. Figma\'s rate limits have been triggered.',
|
||||
title: 'Rate Limit Exceeded',
|
||||
message: 'Too many requests. Figma\'s rate limits have been triggered.',
|
||||
actions: [
|
||||
'Let the component rest for 1-2 minutes before sensing again',
|
||||
'Reduce how frequently the sensory system extracts data',
|
||||
'Wait 1-2 minutes before trying again',
|
||||
'Reduce how frequently you extract data',
|
||||
],
|
||||
code: ErrorCode.FIGMA_API_ERROR,
|
||||
},
|
||||
figma_500: {
|
||||
title: '🔌 EXTERNAL SYSTEM ALERT: Figma Component Stressed',
|
||||
message: 'Figma\'s servers are experiencing stress. This is external to DSS.',
|
||||
title: 'Figma Server Error',
|
||||
message: 'Figma\'s servers are experiencing issues. This is external to DSS.',
|
||||
actions: [
|
||||
'Wait while the external component recovers',
|
||||
'Check Figma health: status.figma.com',
|
||||
'Wait and try again later',
|
||||
'Check Figma status: status.figma.com',
|
||||
],
|
||||
code: ErrorCode.FIGMA_API_ERROR,
|
||||
},
|
||||
figma_demo: {
|
||||
title: '🛡️ IMMUNE ALERT: Invalid Sensory Configuration',
|
||||
message: 'The sensory organs are configured to look at "demo" which doesn\'t exist in Figma.',
|
||||
title: 'Invalid Configuration',
|
||||
message: 'The Figma file key is configured to "demo" which doesn\'t exist in Figma.',
|
||||
actions: [
|
||||
'Update Settings with your real Figma file key (configure sensory input)',
|
||||
'Update Settings with your real Figma file key',
|
||||
'Find your file key in the Figma URL: figma.com/file/[FILE_KEY]/...',
|
||||
'Use the Figma file selector in Settings',
|
||||
],
|
||||
code: ErrorCode.FIGMA_INVALID_KEY,
|
||||
},
|
||||
|
||||
// API Connection Errors - Nervous System / Heart Issues
|
||||
// API Connection Errors
|
||||
api_network: {
|
||||
title: '❤️ CRITICAL: Heart Not Responding',
|
||||
message: 'The DSS nervous system cannot reach the heart (server). The component is not responding.',
|
||||
title: 'Server Not Responding',
|
||||
message: 'Cannot connect to the DSS server.',
|
||||
actions: [
|
||||
'Verify the heart is beating: curl http://localhost:3456/health',
|
||||
'Restart the heart: cd tools/api && python3 -m uvicorn server:app --port 3456',
|
||||
'Check your network connection to the component',
|
||||
'Verify the server is running: curl http://localhost:3456/health',
|
||||
'Restart the server: ./scripts/dss start',
|
||||
'Check your network connection',
|
||||
],
|
||||
code: ErrorCode.SYSTEM_NETWORK,
|
||||
},
|
||||
api_timeout: {
|
||||
title: '⚡ METABOLISM ALERT: Component Overloaded',
|
||||
message: 'The DSS component took too long to respond. The heart may be stressed or metabolism sluggish.',
|
||||
title: 'Request Timeout',
|
||||
message: 'The server took too long to respond.',
|
||||
actions: [
|
||||
'Let the component rest and try again shortly',
|
||||
'Check the heart\'s logs for signs of stress: tail -f /tmp/dss-demo.log',
|
||||
'Reduce metabolic load (try processing smaller batches)',
|
||||
'Try again in a few moments',
|
||||
'Check the server logs: ./scripts/dss logs',
|
||||
'Try processing smaller batches',
|
||||
],
|
||||
code: ErrorCode.API_TIMEOUT,
|
||||
},
|
||||
api_500: {
|
||||
title: '🧠 BRAIN ALERT: Critical Processing Error',
|
||||
message: 'The DSS brain encountered a fatal error while processing your request.',
|
||||
title: 'Server Error',
|
||||
message: 'The server encountered an error while processing your request.',
|
||||
actions: [
|
||||
'Examine the brain\'s thoughts in the logs: tail -f /tmp/dss-demo.log',
|
||||
'Retry the operation to see if it recovers',
|
||||
'Report the issue if the component keeps failing',
|
||||
'Check the server logs: ./scripts/dss logs',
|
||||
'Retry the operation',
|
||||
'Report the issue if it persists',
|
||||
],
|
||||
code: ErrorCode.API_SERVER_ERROR,
|
||||
},
|
||||
|
||||
// Validation Errors - Immune System / Genetics
|
||||
// Validation Errors
|
||||
validation_missing_field: {
|
||||
title: '🛡️ IMMUNE ALERT: DNA Incomplete',
|
||||
message: 'The genetic code (configuration) is missing essential information. The component cannot proceed.',
|
||||
title: 'Missing Required Field',
|
||||
message: 'The configuration is missing required information.',
|
||||
actions: [
|
||||
'Fill in all fields marked as required (complete the genetic code)',
|
||||
'Fill in all fields marked as required',
|
||||
'Ensure each input field contains valid information',
|
||||
],
|
||||
code: ErrorCode.VALIDATION_MISSING_FIELD,
|
||||
},
|
||||
validation_invalid_format: {
|
||||
title: '🛡️ IMMUNE ALERT: Genetic Mutation Detected',
|
||||
message: 'One or more genetic sequences (configuration values) have an invalid format.',
|
||||
title: 'Invalid Format',
|
||||
message: 'One or more configuration values have an invalid format.',
|
||||
actions: [
|
||||
'Verify URLs start with http:// or https:// (correct genetic sequence)',
|
||||
'Check email addresses follow standard format (valid genetic code)',
|
||||
'Ensure file keys contain only letters, numbers, and hyphens (genetic pattern match)',
|
||||
'Verify URLs start with http:// or https://',
|
||||
'Check email addresses follow standard format',
|
||||
'Ensure file keys contain only letters, numbers, and hyphens',
|
||||
],
|
||||
code: ErrorCode.VALIDATION_INVALID_FORMAT,
|
||||
},
|
||||
|
||||
// Generic fallback
|
||||
unknown: {
|
||||
title: '🧬 ORGANISM ALERT: Unexplained Symptom',
|
||||
message: 'The DSS component experienced an unexpected problem. The root cause is unclear.',
|
||||
title: 'Unexpected Error',
|
||||
message: 'An unexpected problem occurred.',
|
||||
actions: [
|
||||
'Try the operation again (component may self-heal)',
|
||||
'Refresh if the issue persists (restart vitals)',
|
||||
'Check browser console for clues about the component\'s condition',
|
||||
'Try the operation again',
|
||||
'Refresh the page if the issue persists',
|
||||
'Check browser console for details',
|
||||
],
|
||||
code: ErrorCode.SYSTEM_UNEXPECTED,
|
||||
},
|
||||
@@ -253,7 +245,7 @@ export function handleError(error, context = {}) {
|
||||
});
|
||||
|
||||
// Log full details to console for debugging
|
||||
console.group(`🔴 ${parsed.title}`);
|
||||
console.group(`[ERROR] ${parsed.title}`);
|
||||
console.log('Message:', parsed.message);
|
||||
if (parsed.actions) {
|
||||
console.log('Actions:', parsed.actions);
|
||||
@@ -281,23 +273,23 @@ export async function tryWithErrorHandling(fn, context = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user-friendly HTTP status message using component metaphors
|
||||
* Get user-friendly HTTP status message
|
||||
* @param {number} status - HTTP status code
|
||||
* @returns {string} User-friendly message with biological context
|
||||
* @returns {string} User-friendly message
|
||||
*/
|
||||
export function getStatusMessage(status) {
|
||||
const messages = {
|
||||
400: '🛡️ Genetic Code Invalid - the DNA sequence doesn\'t compile',
|
||||
401: '🔐 Authentication Failed - the nervous system can\'t verify identity',
|
||||
403: '🚫 Access Forbidden - immune system rejected this component',
|
||||
404: '👻 Target Lost - sensory organs can\'t perceive the resource',
|
||||
429: '⚡ Metabolism Overloaded - component sensing too quickly',
|
||||
500: '🧠 Brain Error - critical neural processing failure',
|
||||
502: '💀 Component Unresponsive - the heart has stopped beating',
|
||||
503: '🏥 Component In Recovery - temporarily unable to metabolize requests',
|
||||
400: 'Bad Request - invalid input data',
|
||||
401: 'Authentication Failed - invalid credentials',
|
||||
403: 'Access Forbidden - permission denied',
|
||||
404: 'Not Found - resource doesn\'t exist',
|
||||
429: 'Rate Limited - too many requests',
|
||||
500: 'Server Error - internal processing failure',
|
||||
502: 'Bad Gateway - server not responding',
|
||||
503: 'Service Unavailable - temporarily unable to handle requests',
|
||||
};
|
||||
|
||||
return messages[status] || `🔴 Unknown Component State - HTTP ${status}`;
|
||||
return messages[status] || `Unknown Error - HTTP ${status}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
/**
|
||||
* DSS Logger - Component Brain Consciousness System
|
||||
* DSS Logger - Structured Logging System
|
||||
*
|
||||
* The DSS brain uses this logger to become conscious of what's happening.
|
||||
* Log levels represent the component's level of awareness and concern.
|
||||
* Provides structured logging with categories and levels for the DSS admin UI.
|
||||
*
|
||||
* Framework: DSS Component Framework
|
||||
* See: docs/DSS_ORGANISM_GUIDE.md#brain
|
||||
*
|
||||
* Log Categories (Organ Systems):
|
||||
* 'heart' - ❤️ Database operations and data persistence
|
||||
* 'brain' - 🧠 Validation, analysis, and decision making
|
||||
* 'nervous' - 🔌 API calls, webhooks, communication
|
||||
* 'digestive' - 🍽️ Data ingestion, parsing, transformation
|
||||
* 'circulatory' - 🩸 Design token flow and distribution
|
||||
* 'metabolic' - ⚡ Style-dictionary transformations
|
||||
* 'endocrine' - 🎛️ Theme system and configuration
|
||||
* 'immune' - 🛡️ Validation, error detection, security
|
||||
* 'sensory' - 👁️ Asset loading, Figma perception
|
||||
* 'skin' - 🎨 UI rendering, Storybook output
|
||||
* 'skeleton' - 🦴 Schema and structure validation
|
||||
*
|
||||
* Provides structured logging with biological awareness levels and optional remote logging.
|
||||
* Log Categories:
|
||||
* 'storage' - Database operations and data persistence
|
||||
* 'validation' - Validation, analysis, and decision making
|
||||
* 'api' - API calls, webhooks, communication
|
||||
* 'parser' - Data ingestion, parsing, transformation
|
||||
* 'tokens' - Design token flow and distribution
|
||||
* 'transform' - Style-dictionary transformations
|
||||
* 'config' - Theme system and configuration
|
||||
* 'security' - Validation, error detection, security
|
||||
* 'assets' - Asset loading, Figma integration
|
||||
* 'ui' - UI rendering, Storybook output
|
||||
* 'schema' - Schema and structure validation
|
||||
*/
|
||||
|
||||
// Component awareness levels - how conscious is the system?
|
||||
const LOG_LEVELS = {
|
||||
DEBUG: 0, // 🧠 Deep thought - brain analyzing internal processes
|
||||
INFO: 1, // 💭 Awareness - component knows what's happening
|
||||
WARN: 2, // ⚠️ Symptom - component detected something unusual
|
||||
ERROR: 3, // 🛡️ Immune alert - component detected a threat
|
||||
NONE: 4 // 🌙 Sleep - component is silent
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
NONE: 4
|
||||
};
|
||||
|
||||
class Logger {
|
||||
@@ -76,23 +69,22 @@ class Logger {
|
||||
this.logs.shift();
|
||||
}
|
||||
|
||||
// Console output with component awareness emojis
|
||||
const levelEmojis = {
|
||||
DEBUG: '🧠', // Brain thinking deeply
|
||||
INFO: '💭', // Component aware
|
||||
WARN: '⚠️', // Symptom detected
|
||||
ERROR: '🛡️' // Immune alert - threat detected
|
||||
const levelIcons = {
|
||||
DEBUG: '[D]',
|
||||
INFO: '[I]',
|
||||
WARN: '[W]',
|
||||
ERROR: '[E]'
|
||||
};
|
||||
|
||||
const colors = {
|
||||
DEBUG: 'color: #666; font-style: italic', // Gray, thoughtful
|
||||
INFO: 'color: #2196F3; font-weight: bold', // Blue, informative
|
||||
WARN: 'color: #FF9800; font-weight: bold', // Orange, warning
|
||||
ERROR: 'color: #F44336; font-weight: bold' // Red, critical
|
||||
DEBUG: 'color: #666; font-style: italic',
|
||||
INFO: 'color: #2196F3; font-weight: bold',
|
||||
WARN: 'color: #FF9800; font-weight: bold',
|
||||
ERROR: 'color: #F44336; font-weight: bold'
|
||||
};
|
||||
|
||||
const emoji = levelEmojis[level] || '🔘';
|
||||
const prefix = `${emoji} [${category}]`;
|
||||
const icon = levelIcons[level] || '[?]';
|
||||
const prefix = `${icon} [${category}]`;
|
||||
const style = colors[level] || '';
|
||||
|
||||
if (data) {
|
||||
@@ -120,56 +112,49 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧠 DEBUG - Brain's deep thoughts
|
||||
* Internal analysis and detailed consciousness
|
||||
* DEBUG - Detailed internal information
|
||||
*/
|
||||
debug(category, message, data) {
|
||||
this._log('DEBUG', category, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 💭 INFO - Component awareness
|
||||
* The system knows what's happening, stays informed
|
||||
* INFO - General information
|
||||
*/
|
||||
info(category, message, data) {
|
||||
this._log('INFO', category, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ WARN - Symptom detection
|
||||
* Component detected something unusual but not critical
|
||||
* WARN - Warning, something unusual detected
|
||||
*/
|
||||
warn(category, message, data) {
|
||||
this._log('WARN', category, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🛡️ ERROR - Immune alert
|
||||
* Component detected a threat - critical consciousness
|
||||
* ERROR - Error, something went wrong
|
||||
*/
|
||||
error(category, message, data) {
|
||||
this._log('ERROR', category, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 📜 Get recent consciousness records
|
||||
* Retrieve the component's recent thoughts and awareness
|
||||
* Get recent log entries
|
||||
*/
|
||||
getRecentLogs(count = 50) {
|
||||
return this.logs.slice(-count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧠 Clear the mind
|
||||
* Erase recent consciousness logs
|
||||
* Clear all logs
|
||||
*/
|
||||
clear() {
|
||||
this.logs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 📤 Export consciousness
|
||||
* Save the component's awareness to a file for analysis
|
||||
* Export logs to file
|
||||
*/
|
||||
export() {
|
||||
const dataStr = JSON.stringify(this.logs, null, 2);
|
||||
@@ -185,13 +170,11 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧠 ORGANISM CONSCIOUSNESS
|
||||
* Create the DSS component's brain - a singleton logger that tracks all awareness
|
||||
* DSS Logger singleton
|
||||
*/
|
||||
const logger = new Logger('DSS', 'INFO');
|
||||
|
||||
// Set log level from localStorage or URL param
|
||||
// Allow tuning the component's consciousness level (awareness sensitivity)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const logLevel = urlParams.get('log') || localStorage.getItem('dss_log_level') || 'INFO';
|
||||
logger.setLevel(logLevel.toUpperCase());
|
||||
|
||||
@@ -21,15 +21,15 @@ import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Get project root - tools/api/server.py -> tools/api -> tools -> project_root
|
||||
# Get project root - apps/api/server.py -> apps/api -> apps -> project_root
|
||||
_server_file = Path(__file__).resolve()
|
||||
_project_root = _server_file.parent.parent.parent # /home/.../dss
|
||||
|
||||
# Try loading from multiple possible .env locations
|
||||
env_paths = [
|
||||
_project_root / "dss-mvp1" / ".env", # dss-mvp1/.env (primary)
|
||||
_project_root / ".env", # root .env
|
||||
_server_file.parent / ".env", # tools/api/.env
|
||||
_project_root / ".env", # root .env (primary)
|
||||
_project_root / "storybook" / ".env", # storybook/.env
|
||||
_server_file.parent / ".env", # apps/api/.env
|
||||
]
|
||||
for env_path in env_paths:
|
||||
if env_path.exists():
|
||||
@@ -50,33 +50,70 @@ from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
import sys
|
||||
# Add tools directory to path (legacy imports)
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
# Add dss-mvp1 directory to path (consolidated dss package)
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "dss-mvp1"))
|
||||
# Add project root to path for dss package
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
# Import browser logger router
|
||||
from browser_logger import router as browser_log_router
|
||||
# Import browser logger router (local import from same directory)
|
||||
from apps.api.browser_logger import router as browser_log_router
|
||||
|
||||
# Legacy imports (will gradually migrate these)
|
||||
from config import config
|
||||
from storage.json_store import (
|
||||
# DSS package imports - unified package
|
||||
from dss import settings
|
||||
from dss.storage.json_store import (
|
||||
Projects, Components, SyncHistory, ActivityLog, Teams, Cache, get_stats,
|
||||
FigmaFiles, CodeMetrics, TestResults, TokenDrift, Tokens, Styles,
|
||||
Integrations, IntegrationHealth
|
||||
)
|
||||
from figma.figma_tools import FigmaToolSuite
|
||||
from dss.figma.figma_tools import FigmaToolSuite
|
||||
from dss.services.project_manager import ProjectManager
|
||||
from dss.services.config_service import ConfigService, DSSConfig
|
||||
from dss.services.sandboxed_fs import SandboxedFS
|
||||
|
||||
# New consolidated dss imports - now available!
|
||||
# from dss import DesignToken, TokenSource, ProjectScanner, etc.
|
||||
# Additional DSS imports available:
|
||||
# from dss import DesignToken, TokenSource, ProjectScanner
|
||||
# from dss.ingest import CSSTokenSource, SCSSTokenSource, TailwindTokenSource
|
||||
# from dss.analyze import ReactAnalyzer, StyleAnalyzer, QuickWinFinder
|
||||
# from dss.storybook import StorybookScanner, StoryGenerator
|
||||
|
||||
# MVP1 Configuration Architecture - Services
|
||||
from services.project_manager import ProjectManager
|
||||
from services.config_service import ConfigService, DSSConfig
|
||||
from services.sandboxed_fs import SandboxedFS
|
||||
|
||||
# === Legacy Config Compatibility ===
|
||||
# Wrapper to maintain compatibility with old config.x.y references
|
||||
class _FigmaConfigCompat:
|
||||
@property
|
||||
def is_configured(self):
|
||||
return settings.figma_configured
|
||||
@property
|
||||
def token(self):
|
||||
return settings.FIGMA_TOKEN
|
||||
@property
|
||||
def cache_ttl(self):
|
||||
return settings.FIGMA_CACHE_TTL
|
||||
|
||||
class _ServerConfigCompat:
|
||||
@property
|
||||
def env(self):
|
||||
return settings.SERVER_ENV
|
||||
@property
|
||||
def port(self):
|
||||
return settings.SERVER_PORT
|
||||
@property
|
||||
def host(self):
|
||||
return settings.SERVER_HOST
|
||||
@property
|
||||
def is_production(self):
|
||||
return settings.is_production
|
||||
|
||||
class _ConfigCompat:
|
||||
figma = _FigmaConfigCompat()
|
||||
server = _ServerConfigCompat()
|
||||
|
||||
def summary(self):
|
||||
return {
|
||||
"figma": {"configured": settings.figma_configured, "cache_ttl": settings.FIGMA_CACHE_TTL},
|
||||
"server": {"port": settings.SERVER_PORT, "env": settings.SERVER_ENV, "log_level": settings.LOG_LEVEL},
|
||||
"database": {"path": str(settings.DATABASE_PATH)},
|
||||
}
|
||||
|
||||
config = _ConfigCompat()
|
||||
|
||||
|
||||
# === Runtime Configuration ===
|
||||
@@ -316,7 +353,7 @@ class TokenDriftCreate(BaseModel):
|
||||
|
||||
# === Authentication ===
|
||||
|
||||
from auth.atlassian_auth import get_auth
|
||||
from dss.auth.atlassian_auth import get_auth
|
||||
|
||||
async def get_current_user(authorization: Optional[str] = Header(None)) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -384,71 +421,62 @@ async def root():
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
"""
|
||||
🏥 ORGANISM VITAL SIGNS CHECK
|
||||
Health check endpoint.
|
||||
|
||||
Performs a complete health diagnostic on the DSS component.
|
||||
Returns 200 OK with vital signs if component is healthy.
|
||||
Performs a complete health diagnostic on the DSS server.
|
||||
Returns 200 OK with service status.
|
||||
|
||||
Vital Signs Checked:
|
||||
- ❤️ Heart (Database) - Is the source of truth responsive?
|
||||
- 🧠 Brain (MCP Handler) - Is the decision-making system online?
|
||||
- 👁️ Sensory (Figma) - Are the external perception organs configured?
|
||||
Services Checked:
|
||||
- Storage - Is the data directory accessible?
|
||||
- MCP Handler - Is the MCP handler initialized?
|
||||
- Figma - Is the Figma integration configured?
|
||||
"""
|
||||
import os
|
||||
import psutil
|
||||
from pathlib import Path
|
||||
|
||||
# ❤️ Check Heart (storage) connectivity
|
||||
db_ok = False
|
||||
# Check storage connectivity
|
||||
storage_ok = False
|
||||
try:
|
||||
from storage.json_store import DATA_DIR
|
||||
db_ok = DATA_DIR.exists()
|
||||
from dss.storage.json_store import DATA_DIR
|
||||
storage_ok = DATA_DIR.exists()
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
print(f"🏥 VITAL SIGN: Heart (storage) error: {type(e).__name__}: {e}", flush=True)
|
||||
print(f" Traceback:\n{error_trace}", flush=True)
|
||||
pass
|
||||
print(f"[Health] Storage check error: {type(e).__name__}: {e}", flush=True)
|
||||
|
||||
# 🧠 Check Brain (MCP handler) functionality
|
||||
# Check MCP handler functionality
|
||||
mcp_ok = False
|
||||
try:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
# Add project root to path (two levels up from tools/api)
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from tools.dss_mcp.handler import get_mcp_handler
|
||||
from dss.mcp.handler import get_mcp_handler
|
||||
handler = get_mcp_handler()
|
||||
mcp_ok = handler is not None
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = ''.join(traceback.format_exception(type(e), e, e.__traceback__))
|
||||
print(f"🧠 BRAIN CHECK: MCP handler error: {type(e).__name__}: {e}", flush=True)
|
||||
print(f" Traceback:\n{error_trace}", flush=True)
|
||||
print(f"[Health] MCP handler check error: {type(e).__name__}: {e}", flush=True)
|
||||
|
||||
# Get uptime (how long component has been conscious)
|
||||
# Get uptime
|
||||
try:
|
||||
process = psutil.Process(os.getpid())
|
||||
uptime_seconds = int((datetime.now() - datetime.fromtimestamp(process.create_time())).total_seconds())
|
||||
except:
|
||||
uptime_seconds = 0
|
||||
|
||||
# Overall vitality assessment
|
||||
status = "healthy" if (db_ok and mcp_ok) else "degraded"
|
||||
# Overall status
|
||||
status = "healthy" if (storage_ok and mcp_ok) else "degraded"
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"vital_signs": {
|
||||
"overall": "🟢 All systems nominal" if status == "healthy" else "🟡 System degradation detected",
|
||||
"consciousness_duration_seconds": uptime_seconds
|
||||
},
|
||||
"uptime_seconds": uptime_seconds,
|
||||
"version": "0.8.0",
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"services": {
|
||||
"storage": "ok" if db_ok else "error",
|
||||
"storage": "ok" if storage_ok else "error",
|
||||
"mcp": "ok" if mcp_ok else "error",
|
||||
"figma": "connected" if config.figma.is_configured else "not configured"
|
||||
}
|
||||
624
apps/cli/package-lock.json
generated
Normal file
624
apps/cli/package-lock.json
generated
Normal file
@@ -0,0 +1,624 @@
|
||||
{
|
||||
"name": "@overbits/dss",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@overbits/dss",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"conf": "^12.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"ora": "^8.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"dss": "dist/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
|
||||
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/atomically": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz",
|
||||
"integrity": "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"stubborn-fs": "^2.0.0",
|
||||
"when-exit": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-spinners": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
|
||||
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/conf": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/conf/-/conf-12.0.0.tgz",
|
||||
"integrity": "sha512-fIWyWUXrJ45cHCIQX+Ck1hrZDIf/9DR0P0Zewn3uNht28hbt5OfGUq8rRWsxi96pZWPyBEd0eY9ama01JTaknA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"atomically": "^2.0.2",
|
||||
"debounce-fn": "^5.1.2",
|
||||
"dot-prop": "^8.0.2",
|
||||
"env-paths": "^3.0.0",
|
||||
"json-schema-typed": "^8.0.1",
|
||||
"semver": "^7.5.4",
|
||||
"uint8array-extras": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/debounce-fn": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-5.1.2.tgz",
|
||||
"integrity": "sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-prop": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz",
|
||||
"integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^3.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
|
||||
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-interactive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
|
||||
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
|
||||
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-typed": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
|
||||
"integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
|
||||
"integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"is-unicode-supported": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
|
||||
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-function": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ora": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
|
||||
"integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"cli-cursor": "^5.0.0",
|
||||
"cli-spinners": "^2.9.2",
|
||||
"is-interactive": "^2.0.0",
|
||||
"is-unicode-supported": "^2.0.0",
|
||||
"log-symbols": "^6.0.0",
|
||||
"stdin-discarder": "^0.2.2",
|
||||
"string-width": "^7.2.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^7.0.0",
|
||||
"signal-exit": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/stdin-discarder": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
|
||||
"integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/stubborn-fs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
|
||||
"integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"stubborn-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/stubborn-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
|
||||
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8array-extras": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-0.3.0.tgz",
|
||||
"integrity": "sha512-erJsJwQ0tKdwuqI0359U8ijkFmfiTcq25JvvzRVc1VP+2son1NJRXhxcAKJmAW3ajM8JSGAfsAXye8g4s+znxA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/when-exit": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
|
||||
"integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
apps/cli/package.json
Normal file
51
apps/cli/package.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@overbits/dss",
|
||||
"version": "0.1.0",
|
||||
"description": "Design System Server - UI Developer Companion",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"dss": "dist/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"start": "node dist/cli.js",
|
||||
"postinstall": "node scripts/postinstall.js",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"design-system",
|
||||
"figma",
|
||||
"tokens",
|
||||
"components",
|
||||
"ui",
|
||||
"developer-tools"
|
||||
],
|
||||
"author": "Overbits",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/overbits/dss"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^12.0.0",
|
||||
"chalk": "^5.3.0",
|
||||
"ora": "^8.0.1",
|
||||
"conf": "^12.0.0",
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"python/api",
|
||||
"scripts",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
@@ -3,5 +3,3 @@ uvicorn[standard]>=0.23.0
|
||||
httpx>=0.24.0
|
||||
python-dotenv>=1.0.0
|
||||
pydantic>=2.0.0
|
||||
mcp>=1.0.0
|
||||
google-generativeai>=0.3.0
|
||||
724
apps/cli/python/api/server.py
Normal file
724
apps/cli/python/api/server.py
Normal file
@@ -0,0 +1,724 @@
|
||||
"""
|
||||
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"
|
||||
)
|
||||
74
apps/cli/scripts/postinstall.js
Normal file
74
apps/cli/scripts/postinstall.js
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* DSS Postinstall Script
|
||||
*
|
||||
* Sets up Python virtual environment and installs dependencies
|
||||
* after npm install.
|
||||
*/
|
||||
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pythonDir = join(__dirname, '..', 'python');
|
||||
const venvDir = join(pythonDir, 'venv');
|
||||
const apiDir = join(pythonDir, 'api');
|
||||
|
||||
console.log('[DSS] Setting up Python environment...');
|
||||
|
||||
// Check if Python 3 is available
|
||||
function getPythonCmd() {
|
||||
const commands = ['python3', 'python'];
|
||||
for (const cmd of commands) {
|
||||
try {
|
||||
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
||||
return cmd;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const python = getPythonCmd();
|
||||
|
||||
if (!python) {
|
||||
console.error('[DSS] Error: Python 3 is required but not found.');
|
||||
console.error('[DSS] Please install Python 3.8+ and try again.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`[DSS] Using ${python}`);
|
||||
|
||||
// Create virtual environment if needed
|
||||
if (!existsSync(venvDir)) {
|
||||
console.log('[DSS] Creating virtual environment...');
|
||||
try {
|
||||
execSync(`${python} -m venv ${venvDir}`, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error('[DSS] Failed to create virtual environment');
|
||||
console.error('[DSS] Try: pip install virtualenv');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Install requirements
|
||||
const pip = join(venvDir, 'bin', 'pip');
|
||||
const requirements = join(apiDir, 'requirements.txt');
|
||||
|
||||
if (existsSync(requirements)) {
|
||||
console.log('[DSS] Installing Python dependencies...');
|
||||
try {
|
||||
execSync(`${pip} install -q -r ${requirements}`, { stdio: 'inherit' });
|
||||
console.log('[DSS] Python environment ready!');
|
||||
} catch (error) {
|
||||
console.error('[DSS] Failed to install Python dependencies');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log('[DSS] No requirements.txt found, skipping dependency install');
|
||||
}
|
||||
|
||||
console.log('[DSS] Setup complete!');
|
||||
40
apps/cli/scripts/publish.sh
Executable file
40
apps/cli/scripts/publish.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# DSS npm publish script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Publishing @overbits/dss to npm..."
|
||||
|
||||
# Ensure we're in the cli directory
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Build
|
||||
echo "📦 Building..."
|
||||
npm run build
|
||||
|
||||
# Check if logged in to npm
|
||||
if ! npm whoami &> /dev/null; then
|
||||
echo "❌ Not logged in to npm. Run: npm login"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify package
|
||||
echo "📋 Package contents:"
|
||||
npm pack --dry-run
|
||||
|
||||
# Confirm
|
||||
read -p "Publish to npm? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Publish
|
||||
npm publish --access public
|
||||
|
||||
echo "✅ Published @overbits/dss!"
|
||||
echo ""
|
||||
echo "Users can now install with:"
|
||||
echo " npm install -g @overbits/dss"
|
||||
echo " npx @overbits/dss"
|
||||
101
apps/cli/src/cli.ts
Normal file
101
apps/cli/src/cli.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* DSS CLI - Design System Server Command Line Interface
|
||||
*
|
||||
* A portable companion for UI developers. Commands:
|
||||
*
|
||||
* - init: Initialize DSS in a project
|
||||
* - status: Check server status
|
||||
* - extract: Extract tokens/components from Figma
|
||||
* - sync: Sync tokens to codebase
|
||||
* - config: Manage configuration
|
||||
* - start: Start the server
|
||||
* - stop: Stop the server
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { initCommand } from './commands/init.js';
|
||||
import { startCommand } from './commands/start.js';
|
||||
import { syncCommand } from './commands/sync.js';
|
||||
import { extractCommand } from './commands/extract.js';
|
||||
import { configCommand } from './commands/config.js';
|
||||
import { statusCommand } from './commands/status.js';
|
||||
import { stopCommand } from './commands/stop.js';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('dss')
|
||||
.description('Design System Server - CLI for UI Developers')
|
||||
.version('0.1.0');
|
||||
|
||||
// Init command - setup DSS in a project
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize DSS in your project')
|
||||
.option('-f, --figma-key <key>', 'Figma file key')
|
||||
.option('-t, --figma-token <token>', 'Figma access token')
|
||||
.action(initCommand);
|
||||
|
||||
// Start command - start the DSS server
|
||||
program
|
||||
.command('start')
|
||||
.description('Start the DSS server')
|
||||
.option('-p, --port <port>', 'Server port', '3456')
|
||||
.option('-d, --dev', 'Development mode with hot-reload')
|
||||
.option('--no-open', 'Do not open browser')
|
||||
.action(startCommand);
|
||||
|
||||
// Sync command - sync tokens from Figma
|
||||
program
|
||||
.command('sync')
|
||||
.description('Sync tokens from Figma to codebase')
|
||||
.option('-f, --format <format>', 'Output format: css, scss, json, ts', 'css')
|
||||
.option('-o, --output <path>', 'Output directory')
|
||||
.option('--file-key <key>', 'Figma file key (overrides config)')
|
||||
.action(syncCommand);
|
||||
|
||||
// Extract command - extract components or tokens
|
||||
program
|
||||
.command('extract <type>')
|
||||
.description('Extract components or tokens from Figma')
|
||||
.option('-f, --format <format>', 'Output format', 'json')
|
||||
.option('-o, --output <path>', 'Output location')
|
||||
.option('--file-key <key>', 'Figma file key')
|
||||
.action(extractCommand);
|
||||
|
||||
// Config command - manage configuration
|
||||
program
|
||||
.command('config')
|
||||
.description('Manage DSS configuration')
|
||||
.option('--set <key=value>', 'Set configuration value')
|
||||
.option('--get <key>', 'Get configuration value')
|
||||
.option('--list', 'List all configuration')
|
||||
.action(configCommand);
|
||||
|
||||
// Stop command - stop the server
|
||||
program
|
||||
.command('stop')
|
||||
.description('Stop the DSS server')
|
||||
.action(stopCommand);
|
||||
|
||||
// Status command - check DSS status
|
||||
program
|
||||
.command('status')
|
||||
.description('Check DSS server status and configuration')
|
||||
.action(statusCommand);
|
||||
|
||||
// Parse arguments
|
||||
program.parse();
|
||||
|
||||
// Show help if no command provided
|
||||
if (!process.argv.slice(2).length) {
|
||||
console.log(chalk.blue(`
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ${chalk.bold('DSS')} - Design System Server ║
|
||||
║ UI Developer Companion ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
`));
|
||||
program.outputHelp();
|
||||
}
|
||||
138
apps/cli/src/commands/config.ts
Normal file
138
apps/cli/src/commands/config.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* DSS Config Command
|
||||
*
|
||||
* Manage DSS configuration.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
getConfig,
|
||||
loadProjectConfig,
|
||||
saveProjectConfig,
|
||||
setGlobalConfig,
|
||||
getGlobalConfig,
|
||||
listGlobalConfig,
|
||||
hasProjectConfig,
|
||||
} from '../lib/config.js';
|
||||
|
||||
interface ConfigOptions {
|
||||
set?: string;
|
||||
get?: string;
|
||||
list?: boolean;
|
||||
}
|
||||
|
||||
export async function configCommand(options: ConfigOptions): Promise<void> {
|
||||
if (options.set) {
|
||||
await setConfig(options.set);
|
||||
} else if (options.get) {
|
||||
await getConfigValue(options.get);
|
||||
} else if (options.list) {
|
||||
await listConfig();
|
||||
} else {
|
||||
await listConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async function setConfig(keyValue: string): Promise<void> {
|
||||
const [key, ...valueParts] = keyValue.split('=');
|
||||
const value = valueParts.join('=');
|
||||
|
||||
if (!key || value === undefined) {
|
||||
console.log(chalk.red(' Invalid format. Use: --set key=value'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Determine if this is a global or project config
|
||||
const globalKeys = ['figmaToken', 'defaultPort', 'defaultFormat'];
|
||||
const projectKeys = ['figmaFileKey', 'port', 'outputDir', 'tokenFormat', 'componentFramework'];
|
||||
|
||||
if (globalKeys.includes(key)) {
|
||||
// Global config
|
||||
const parsedValue = parseValue(value);
|
||||
setGlobalConfig(key, parsedValue as string | number);
|
||||
console.log(chalk.green(` Set global config: ${key}`));
|
||||
console.log(chalk.dim(` Value: ${key === 'figmaToken' ? '***hidden***' : value}`));
|
||||
} else if (projectKeys.includes(key)) {
|
||||
// Project config
|
||||
if (!hasProjectConfig()) {
|
||||
console.log(chalk.yellow(' No project config found. Run: dss init'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = loadProjectConfig();
|
||||
(config as Record<string, unknown>)[key] = parseValue(value);
|
||||
saveProjectConfig(config);
|
||||
|
||||
console.log(chalk.green(` Set project config: ${key}`));
|
||||
console.log(chalk.dim(` Value: ${value}`));
|
||||
} else {
|
||||
console.log(chalk.yellow(` Unknown config key: ${key}`));
|
||||
console.log('');
|
||||
console.log(chalk.dim(' Global keys: figmaToken, defaultPort, defaultFormat'));
|
||||
console.log(chalk.dim(' Project keys: figmaFileKey, port, outputDir, tokenFormat, componentFramework'));
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfigValue(key: string): Promise<void> {
|
||||
const config = getConfig();
|
||||
const value = (config as Record<string, unknown>)[key];
|
||||
|
||||
if (value === undefined) {
|
||||
// Try global config
|
||||
const globalValue = getGlobalConfig(key);
|
||||
if (globalValue !== undefined) {
|
||||
console.log(chalk.dim(` ${key} (global):`), key === 'figmaToken' ? '***hidden***' : String(globalValue));
|
||||
} else {
|
||||
console.log(chalk.yellow(` Config key not found: ${key}`));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.dim(` ${key}:`), key === 'figmaToken' ? '***hidden***' : String(value));
|
||||
}
|
||||
}
|
||||
|
||||
async function listConfig(): Promise<void> {
|
||||
console.log('');
|
||||
console.log(chalk.blue(' DSS Configuration'));
|
||||
console.log(chalk.dim(' ─────────────────'));
|
||||
console.log('');
|
||||
|
||||
// Project config
|
||||
if (hasProjectConfig()) {
|
||||
console.log(chalk.green(' Project Config:'));
|
||||
const projectConfig = loadProjectConfig();
|
||||
Object.entries(projectConfig).forEach(([key, value]) => {
|
||||
console.log(chalk.dim(` ${key}:`), String(value));
|
||||
});
|
||||
} else {
|
||||
console.log(chalk.yellow(' No project config (run: dss init)'));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Global config
|
||||
console.log(chalk.green(' Global Config:'));
|
||||
const globalConf = listGlobalConfig();
|
||||
Object.entries(globalConf).forEach(([key, value]) => {
|
||||
const displayValue = key === 'figmaToken' ? '***hidden***' : String(value);
|
||||
console.log(chalk.dim(` ${key}:`), displayValue);
|
||||
});
|
||||
|
||||
console.log('');
|
||||
|
||||
// Merged config
|
||||
console.log(chalk.green(' Effective Config:'));
|
||||
const merged = getConfig();
|
||||
Object.entries(merged).forEach(([key, value]) => {
|
||||
const displayValue = key === 'figmaToken' ? (value ? '***configured***' : 'not set') : String(value);
|
||||
console.log(chalk.dim(` ${key}:`), displayValue);
|
||||
});
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
function parseValue(value: string): string | number | boolean {
|
||||
if (value === 'true') return true;
|
||||
if (value === 'false') return false;
|
||||
if (!isNaN(Number(value))) return Number(value);
|
||||
return value;
|
||||
}
|
||||
146
apps/cli/src/commands/extract.ts
Normal file
146
apps/cli/src/commands/extract.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* DSS Extract Command
|
||||
*
|
||||
* Extract tokens or components from Figma.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { getConfig, getProjectRoot } from '../lib/config.js';
|
||||
import { getApiClient } from '../lib/api.js';
|
||||
import { isServerRunning } from '../lib/server.js';
|
||||
|
||||
interface ExtractOptions {
|
||||
format: string;
|
||||
output?: string;
|
||||
fileKey?: string;
|
||||
}
|
||||
|
||||
export async function extractCommand(
|
||||
type: string,
|
||||
options: ExtractOptions
|
||||
): Promise<void> {
|
||||
const validTypes = ['tokens', 'components', 'styles', 'all'];
|
||||
if (!validTypes.includes(type)) {
|
||||
console.log(chalk.red(` Invalid type: ${type}`));
|
||||
console.log(chalk.dim(` Valid types: ${validTypes.join(', ')}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const cwd = getProjectRoot();
|
||||
|
||||
// Check if server is running
|
||||
if (!isServerRunning(cwd)) {
|
||||
console.log(chalk.yellow(' DSS server is not running'));
|
||||
console.log(chalk.dim(' Start it with: dss start'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const fileKey = options.fileKey || config.figmaFileKey;
|
||||
if (!fileKey) {
|
||||
console.log(chalk.red(' No Figma file key configured'));
|
||||
console.log(chalk.dim(' Set it with: dss config --set figmaFileKey=YOUR_KEY'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const outputDir = options.output || join(cwd, '.dss', 'output');
|
||||
if (!existsSync(outputDir)) {
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const api = getApiClient({ port: config.port });
|
||||
|
||||
if (type === 'tokens' || type === 'all') {
|
||||
await extractTokens(api, fileKey, options.format, outputDir);
|
||||
}
|
||||
|
||||
if (type === 'components' || type === 'all') {
|
||||
await extractComponents(api, fileKey, outputDir);
|
||||
}
|
||||
|
||||
if (type === 'styles' || type === 'all') {
|
||||
await extractStyles(api, fileKey, outputDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function extractTokens(
|
||||
api: ReturnType<typeof getApiClient>,
|
||||
fileKey: string,
|
||||
format: string,
|
||||
outputDir: string
|
||||
): Promise<void> {
|
||||
const spinner = ora('Extracting tokens...').start();
|
||||
|
||||
try {
|
||||
const result = await api.extractTokens(fileKey, format || 'json');
|
||||
|
||||
const filename = format === 'css' ? 'tokens.css' :
|
||||
format === 'scss' ? '_tokens.scss' :
|
||||
format === 'ts' ? 'tokens.ts' : 'tokens.json';
|
||||
|
||||
const outputPath = join(outputDir, filename);
|
||||
writeFileSync(outputPath, result.formatted_output);
|
||||
|
||||
spinner.succeed(`Extracted ${result.tokens_count} tokens`);
|
||||
console.log(chalk.dim(` Output: ${outputPath}`));
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to extract tokens');
|
||||
console.error(chalk.red(` ${(error as Error).message}`));
|
||||
}
|
||||
}
|
||||
|
||||
async function extractComponents(
|
||||
api: ReturnType<typeof getApiClient>,
|
||||
fileKey: string,
|
||||
outputDir: string
|
||||
): Promise<void> {
|
||||
const spinner = ora('Extracting components...').start();
|
||||
|
||||
try {
|
||||
const result = await api.extractComponents(fileKey);
|
||||
|
||||
const outputPath = join(outputDir, 'components.json');
|
||||
writeFileSync(outputPath, JSON.stringify(result.components, null, 2));
|
||||
|
||||
spinner.succeed(`Extracted ${result.components_count} components`);
|
||||
console.log(chalk.dim(` Output: ${outputPath}`));
|
||||
|
||||
// Show component summary
|
||||
console.log('');
|
||||
result.components.forEach(comp => {
|
||||
console.log(chalk.dim(` - ${comp.name}`));
|
||||
if (comp.variants?.length) {
|
||||
console.log(chalk.dim(` Variants: ${comp.variants.join(', ')}`));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to extract components');
|
||||
console.error(chalk.red(` ${(error as Error).message}`));
|
||||
}
|
||||
}
|
||||
|
||||
async function extractStyles(
|
||||
api: ReturnType<typeof getApiClient>,
|
||||
fileKey: string,
|
||||
outputDir: string
|
||||
): Promise<void> {
|
||||
const spinner = ora('Extracting styles...').start();
|
||||
|
||||
try {
|
||||
// Note: This would need a corresponding API endpoint
|
||||
// For now, we'll extract tokens which include style information
|
||||
const result = await api.extractTokens(fileKey, 'json');
|
||||
|
||||
const outputPath = join(outputDir, 'styles.json');
|
||||
writeFileSync(outputPath, JSON.stringify(result.tokens, null, 2));
|
||||
|
||||
spinner.succeed(`Extracted styles`);
|
||||
console.log(chalk.dim(` Output: ${outputPath}`));
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to extract styles');
|
||||
console.error(chalk.red(` ${(error as Error).message}`));
|
||||
}
|
||||
}
|
||||
107
apps/cli/src/commands/init.ts
Normal file
107
apps/cli/src/commands/init.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* DSS Init Command
|
||||
*
|
||||
* Initialize DSS in the current project.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
getProjectRoot,
|
||||
hasProjectConfig,
|
||||
saveProjectConfig,
|
||||
setGlobalConfig,
|
||||
type DSSConfig,
|
||||
} from '../lib/config.js';
|
||||
|
||||
interface InitOptions {
|
||||
figmaKey?: string;
|
||||
figmaToken?: string;
|
||||
}
|
||||
|
||||
export async function initCommand(options: InitOptions): Promise<void> {
|
||||
const spinner = ora('Initializing DSS...').start();
|
||||
|
||||
try {
|
||||
const projectRoot = getProjectRoot();
|
||||
const dssDir = join(projectRoot, '.dss');
|
||||
|
||||
// Check if already initialized
|
||||
if (hasProjectConfig()) {
|
||||
spinner.warn('DSS is already initialized in this project');
|
||||
console.log(chalk.dim(` Config: ${join(dssDir, 'config.json')}`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create .dss directory
|
||||
if (!existsSync(dssDir)) {
|
||||
mkdirSync(dssDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
const outputDir = join(dssDir, 'output');
|
||||
if (!existsSync(outputDir)) {
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Build config
|
||||
const config: DSSConfig = {
|
||||
port: 3456,
|
||||
outputDir: '.dss/output',
|
||||
tokenFormat: 'css',
|
||||
componentFramework: 'react',
|
||||
};
|
||||
|
||||
if (options.figmaKey) {
|
||||
config.figmaFileKey = options.figmaKey;
|
||||
}
|
||||
|
||||
// Save Figma token globally (not in project config for security)
|
||||
if (options.figmaToken) {
|
||||
setGlobalConfig('figmaToken', options.figmaToken);
|
||||
spinner.info('Figma token saved to global config');
|
||||
}
|
||||
|
||||
// Save project config
|
||||
saveProjectConfig(config);
|
||||
|
||||
spinner.succeed('DSS initialized successfully!');
|
||||
|
||||
console.log('');
|
||||
console.log(chalk.green(' Created:'));
|
||||
console.log(chalk.dim(` .dss/config.json`));
|
||||
console.log(chalk.dim(` .dss/output/`));
|
||||
console.log('');
|
||||
|
||||
// Next steps
|
||||
console.log(chalk.blue(' Next steps:'));
|
||||
if (!options.figmaToken) {
|
||||
console.log(chalk.dim(' 1. Set your Figma token:'));
|
||||
console.log(chalk.white(' dss config --set figmaToken=figd_xxxxx'));
|
||||
}
|
||||
if (!options.figmaKey) {
|
||||
console.log(chalk.dim(` ${options.figmaToken ? '1' : '2'}. Set your Figma file key:`));
|
||||
console.log(chalk.white(' dss config --set figmaFileKey=abc123'));
|
||||
}
|
||||
console.log(chalk.dim(` ${options.figmaToken && options.figmaKey ? '1' : '3'}. Start the server:`));
|
||||
console.log(chalk.white(' dss start'));
|
||||
console.log('');
|
||||
|
||||
// Add to .gitignore if exists
|
||||
const gitignorePath = join(projectRoot, '.gitignore');
|
||||
if (existsSync(gitignorePath)) {
|
||||
const { readFileSync, appendFileSync } = await import('fs');
|
||||
const gitignore = readFileSync(gitignorePath, 'utf-8');
|
||||
if (!gitignore.includes('.dss/')) {
|
||||
appendFileSync(gitignorePath, '\n# DSS\n.dss/\n');
|
||||
console.log(chalk.dim(' Added .dss/ to .gitignore'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to initialize DSS');
|
||||
console.error(chalk.red(` ${(error as Error).message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
109
apps/cli/src/commands/start.ts
Normal file
109
apps/cli/src/commands/start.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* DSS Start Command
|
||||
*
|
||||
* Start the DSS server.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { exec } from 'child_process';
|
||||
import { getConfig, getProjectRoot, hasProjectConfig } from '../lib/config.js';
|
||||
import {
|
||||
startServer,
|
||||
isServerRunning,
|
||||
getServerPid,
|
||||
waitForServer,
|
||||
stopServer,
|
||||
} from '../lib/server.js';
|
||||
|
||||
interface StartOptions {
|
||||
port: string;
|
||||
dev: boolean;
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export async function startCommand(options: StartOptions): Promise<void> {
|
||||
const port = parseInt(options.port, 10);
|
||||
const cwd = getProjectRoot();
|
||||
const config = getConfig();
|
||||
|
||||
// Check if already running
|
||||
if (isServerRunning(cwd)) {
|
||||
const pid = getServerPid(cwd);
|
||||
console.log(chalk.yellow(` DSS is already running (PID: ${pid})`));
|
||||
console.log(chalk.dim(` Dashboard: http://localhost:${port}`));
|
||||
console.log('');
|
||||
console.log(chalk.dim(' To restart: dss stop && dss start'));
|
||||
return;
|
||||
}
|
||||
|
||||
const spinner = ora('Starting DSS server...').start();
|
||||
|
||||
try {
|
||||
// Start server
|
||||
const serverProcess = await startServer({
|
||||
port,
|
||||
dev: options.dev,
|
||||
cwd,
|
||||
});
|
||||
|
||||
if (options.dev) {
|
||||
spinner.succeed('DSS running in development mode');
|
||||
console.log(chalk.dim(' Press Ctrl+C to stop'));
|
||||
console.log('');
|
||||
|
||||
// In dev mode, we're attached to the process
|
||||
serverProcess.on('exit', (code) => {
|
||||
console.log(chalk.dim(`\n Server exited with code ${code}`));
|
||||
process.exit(code || 0);
|
||||
});
|
||||
|
||||
// Handle Ctrl+C
|
||||
process.on('SIGINT', () => {
|
||||
console.log(chalk.dim('\n Stopping server...'));
|
||||
serverProcess.kill('SIGTERM');
|
||||
});
|
||||
} else {
|
||||
// Wait for server to be ready
|
||||
spinner.text = 'Waiting for server to be ready...';
|
||||
const ready = await waitForServer(port, 15000);
|
||||
|
||||
if (!ready) {
|
||||
spinner.fail('Server failed to start');
|
||||
console.error(chalk.red(' Check logs: .dss/dss.log'));
|
||||
await stopServer(cwd);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
spinner.succeed(`DSS started (PID: ${serverProcess.pid})`);
|
||||
console.log('');
|
||||
console.log(chalk.green(' Dashboard:'), chalk.blue(`http://localhost:${port}`));
|
||||
console.log(chalk.green(' API: '), chalk.blue(`http://localhost:${port}/api`));
|
||||
console.log(chalk.green(' Docs: '), chalk.blue(`http://localhost:${port}/docs`));
|
||||
console.log('');
|
||||
console.log(chalk.dim(' Logs: .dss/dss.log'));
|
||||
console.log(chalk.dim(' Stop: dss stop'));
|
||||
console.log('');
|
||||
|
||||
// Show Figma status
|
||||
if (config.figmaFileKey) {
|
||||
console.log(chalk.dim(` Figma file: ${config.figmaFileKey}`));
|
||||
} else {
|
||||
console.log(chalk.yellow(' No Figma file configured'));
|
||||
console.log(chalk.dim(' Run: dss config --set figmaFileKey=YOUR_KEY'));
|
||||
}
|
||||
|
||||
// Open browser if requested
|
||||
if (options.open) {
|
||||
const url = `http://localhost:${port}`;
|
||||
const openCmd = process.platform === 'darwin' ? 'open' :
|
||||
process.platform === 'win32' ? 'start' : 'xdg-open';
|
||||
exec(`${openCmd} ${url}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to start DSS');
|
||||
console.error(chalk.red(` ${(error as Error).message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
95
apps/cli/src/commands/status.ts
Normal file
95
apps/cli/src/commands/status.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* DSS Status Command
|
||||
*
|
||||
* Check DSS server status and configuration.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { getConfig, getProjectRoot, hasProjectConfig } from '../lib/config.js';
|
||||
import { isServerRunning, getServerPid } from '../lib/server.js';
|
||||
import { getApiClient } from '../lib/api.js';
|
||||
|
||||
export async function statusCommand(): Promise<void> {
|
||||
const cwd = getProjectRoot();
|
||||
const config = getConfig();
|
||||
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' DSS Status'));
|
||||
console.log(chalk.dim(' ────────────────────────'));
|
||||
console.log('');
|
||||
|
||||
// Project status
|
||||
if (hasProjectConfig()) {
|
||||
console.log(chalk.green(' Project:'), chalk.dim('Initialized'));
|
||||
console.log(chalk.dim(` Root: ${cwd}`));
|
||||
} else {
|
||||
console.log(chalk.yellow(' Project:'), chalk.dim('Not initialized'));
|
||||
console.log(chalk.dim(' Initialize: dss init'));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Server status
|
||||
const running = isServerRunning(cwd);
|
||||
const pid = getServerPid(cwd);
|
||||
const port = config.port || 3456;
|
||||
|
||||
if (running) {
|
||||
console.log(chalk.green(' Server:'), chalk.dim(`Running (PID: ${pid})`));
|
||||
console.log(chalk.dim(` URL: http://localhost:${port}`));
|
||||
|
||||
// Try to get health info
|
||||
try {
|
||||
const api = getApiClient({ port });
|
||||
const health = await api.health();
|
||||
console.log(chalk.dim(` Mode: ${health.figma_mode}`));
|
||||
} catch {
|
||||
console.log(chalk.yellow(' Unable to get health status'));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow(' Server:'), chalk.dim('Stopped'));
|
||||
console.log(chalk.dim(' Start: dss start'));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Figma integration
|
||||
if (config.figmaToken) {
|
||||
console.log(chalk.green(' Figma:'), chalk.dim('Configured'));
|
||||
|
||||
// Test connection if server is running
|
||||
if (running) {
|
||||
try {
|
||||
const api = getApiClient({ port });
|
||||
const test = await api.testFigmaConnection();
|
||||
if (test.success) {
|
||||
console.log(chalk.green(' Connection:'), chalk.dim(`OK (${test.user})`));
|
||||
} else {
|
||||
console.log(chalk.red(' Connection:'), chalk.dim(test.error || 'Failed'));
|
||||
}
|
||||
} catch {
|
||||
console.log(chalk.yellow(' Connection:'), chalk.dim('Cannot test'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow(' Figma:'), chalk.dim('Not configured'));
|
||||
console.log(chalk.dim(' Configure: dss config --set figmaToken=figd_xxxxx'));
|
||||
}
|
||||
|
||||
if (config.figmaFileKey) {
|
||||
console.log(chalk.green(' Figma File:'), chalk.dim(config.figmaFileKey));
|
||||
} else {
|
||||
console.log(chalk.yellow(' Figma File:'), chalk.dim('Not configured'));
|
||||
console.log(chalk.dim(' Set: dss config --set figmaFileKey=abc123'));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Output config
|
||||
console.log(chalk.dim(' Output:'));
|
||||
console.log(chalk.dim(` Format: ${config.tokenFormat || 'css'}`));
|
||||
console.log(chalk.dim(` Framework: ${config.componentFramework || 'react'}`));
|
||||
console.log(chalk.dim(` Directory: ${config.outputDir || '.dss/output'}`));
|
||||
|
||||
console.log('');
|
||||
}
|
||||
25
apps/cli/src/commands/stop.ts
Normal file
25
apps/cli/src/commands/stop.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* DSS Stop Command
|
||||
*
|
||||
* Stop the DSS server.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { getProjectRoot } from '../lib/config.js';
|
||||
import { isServerRunning, stopServer, getServerPid } from '../lib/server.js';
|
||||
|
||||
export async function stopCommand(): Promise<void> {
|
||||
const cwd = getProjectRoot();
|
||||
|
||||
if (!isServerRunning(cwd)) {
|
||||
console.log(chalk.yellow(' DSS is not running'));
|
||||
return;
|
||||
}
|
||||
|
||||
const pid = getServerPid(cwd);
|
||||
console.log(chalk.dim(` Stopping DSS (PID: ${pid})...`));
|
||||
|
||||
await stopServer(cwd);
|
||||
|
||||
console.log(chalk.green(' DSS stopped'));
|
||||
}
|
||||
102
apps/cli/src/commands/sync.ts
Normal file
102
apps/cli/src/commands/sync.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* DSS Sync Command
|
||||
*
|
||||
* Sync design tokens from Figma to local files.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { getConfig, getProjectRoot } from '../lib/config.js';
|
||||
import { getApiClient } from '../lib/api.js';
|
||||
import { isServerRunning } from '../lib/server.js';
|
||||
|
||||
interface SyncOptions {
|
||||
format: string;
|
||||
output?: string;
|
||||
fileKey?: string;
|
||||
}
|
||||
|
||||
export async function syncCommand(options: SyncOptions): Promise<void> {
|
||||
const config = getConfig();
|
||||
const cwd = getProjectRoot();
|
||||
|
||||
// Check if server is running
|
||||
if (!isServerRunning(cwd)) {
|
||||
console.log(chalk.yellow(' DSS server is not running'));
|
||||
console.log(chalk.dim(' Start it with: dss start'));
|
||||
console.log('');
|
||||
console.log(chalk.dim(' Or sync directly via API if running remotely'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const fileKey = options.fileKey || config.figmaFileKey;
|
||||
if (!fileKey) {
|
||||
console.log(chalk.red(' No Figma file key configured'));
|
||||
console.log(chalk.dim(' Set it with: dss config --set figmaFileKey=YOUR_KEY'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const format = options.format || config.tokenFormat || 'css';
|
||||
const outputPath = options.output || getDefaultOutputPath(format, cwd);
|
||||
|
||||
const spinner = ora(`Syncing tokens from Figma (${format})...`).start();
|
||||
|
||||
try {
|
||||
const api = getApiClient({ port: config.port });
|
||||
|
||||
// Extract tokens
|
||||
spinner.text = 'Extracting tokens from Figma...';
|
||||
const result = await api.extractTokens(fileKey, format);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error('Token extraction failed');
|
||||
}
|
||||
|
||||
// Write output file
|
||||
spinner.text = `Writing ${result.tokens_count} tokens to ${outputPath}...`;
|
||||
|
||||
const dir = dirname(outputPath);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(outputPath, result.formatted_output);
|
||||
|
||||
spinner.succeed(`Synced ${result.tokens_count} tokens`);
|
||||
console.log('');
|
||||
console.log(chalk.green(' Output:'), chalk.dim(outputPath));
|
||||
console.log('');
|
||||
|
||||
// Show token summary
|
||||
const categories = result.tokens.reduce((acc, t) => {
|
||||
acc[t.category] = (acc[t.category] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
console.log(chalk.dim(' Token breakdown:'));
|
||||
Object.entries(categories).forEach(([cat, count]) => {
|
||||
console.log(chalk.dim(` ${cat}: ${count}`));
|
||||
});
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
spinner.fail('Sync failed');
|
||||
console.error(chalk.red(` ${(error as Error).message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultOutputPath(format: string, cwd: string): string {
|
||||
const extensions: Record<string, string> = {
|
||||
css: 'tokens.css',
|
||||
scss: '_tokens.scss',
|
||||
json: 'tokens.json',
|
||||
ts: 'tokens.ts',
|
||||
js: 'tokens.js',
|
||||
};
|
||||
|
||||
const filename = extensions[format] || 'tokens.css';
|
||||
return join(cwd, '.dss', 'output', filename);
|
||||
}
|
||||
15
apps/cli/src/index.ts
Normal file
15
apps/cli/src/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* DSS - Design System Server
|
||||
*
|
||||
* Programmatic API for DSS.
|
||||
*/
|
||||
|
||||
export { DSSApiClient, getApiClient, type ApiOptions } from './lib/api.js';
|
||||
export { getConfig, getProjectRoot, hasProjectConfig, type DSSConfig } from './lib/config.js';
|
||||
export {
|
||||
startServer,
|
||||
stopServer,
|
||||
isServerRunning,
|
||||
getServerPid,
|
||||
waitForServer,
|
||||
} from './lib/server.js';
|
||||
144
apps/cli/src/lib/api.ts
Normal file
144
apps/cli/src/lib/api.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* DSS API Client
|
||||
*
|
||||
* Communicates with the DSS server.
|
||||
*/
|
||||
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
export interface ApiOptions {
|
||||
port?: number;
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
export class DSSApiClient {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(options: ApiOptions = {}) {
|
||||
const config = getConfig();
|
||||
const port = options.port || config.port || 3456;
|
||||
this.baseUrl = options.baseUrl || `http://localhost:${port}/api`;
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ message: response.statusText })) as { message?: string };
|
||||
throw new Error(errorData.message || `Request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
return text ? JSON.parse(text) as T : {} as T;
|
||||
}
|
||||
|
||||
async health(): Promise<{ status: string; figma_mode: string }> {
|
||||
// Health endpoint is at root, not under /api
|
||||
const url = this.baseUrl.replace('/api', '') + '/health';
|
||||
const response = await fetch(url);
|
||||
return response.json() as Promise<{ status: string; figma_mode: string }>;
|
||||
}
|
||||
|
||||
async getConfig(): Promise<Record<string, unknown>> {
|
||||
return this.request('/config');
|
||||
}
|
||||
|
||||
async setFigmaToken(token: string): Promise<void> {
|
||||
await this.request('/config', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ figma_token: token }),
|
||||
});
|
||||
}
|
||||
|
||||
async testFigmaConnection(): Promise<{ success: boolean; user?: string; error?: string }> {
|
||||
return this.request('/config/figma/test', { method: 'POST' });
|
||||
}
|
||||
|
||||
async extractTokens(fileKey: string, format: string = 'css'): Promise<{
|
||||
success: boolean;
|
||||
tokens_count: number;
|
||||
tokens: Array<{ name: string; value: string; type: string; category: string }>;
|
||||
formatted_output: string;
|
||||
output_path: string;
|
||||
}> {
|
||||
return this.request('/figma/extract-variables', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ file_key: fileKey, format }),
|
||||
});
|
||||
}
|
||||
|
||||
async extractComponents(fileKey: string): Promise<{
|
||||
success: boolean;
|
||||
components_count: number;
|
||||
components: Array<{ name: string; key: string; description: string; variants: string[] }>;
|
||||
output_path: string;
|
||||
}> {
|
||||
return this.request('/figma/extract-components', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ file_key: fileKey }),
|
||||
});
|
||||
}
|
||||
|
||||
async syncTokens(fileKey: string, targetPath: string, format: string = 'css'): Promise<{
|
||||
success: boolean;
|
||||
tokens_synced: number;
|
||||
output_file: string;
|
||||
}> {
|
||||
return this.request('/figma/sync-tokens', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ file_key: fileKey, target_path: targetPath, format }),
|
||||
});
|
||||
}
|
||||
|
||||
async generateCode(fileKey: string, componentName: string, framework: string = 'react'): Promise<{
|
||||
success: boolean;
|
||||
component: string;
|
||||
framework: string;
|
||||
code: string;
|
||||
}> {
|
||||
return this.request('/figma/generate-code', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ file_key: fileKey, component_name: componentName, framework }),
|
||||
});
|
||||
}
|
||||
|
||||
async getProjects(): Promise<Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
figma_file_key: string;
|
||||
status: string;
|
||||
}>> {
|
||||
return this.request('/projects');
|
||||
}
|
||||
|
||||
async createProject(data: {
|
||||
name: string;
|
||||
description?: string;
|
||||
figma_file_key?: string;
|
||||
}): Promise<{ id: string; name: string }> {
|
||||
return this.request('/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
let apiClient: DSSApiClient | null = null;
|
||||
|
||||
export function getApiClient(options?: ApiOptions): DSSApiClient {
|
||||
if (!apiClient || options) {
|
||||
apiClient = new DSSApiClient(options);
|
||||
}
|
||||
return apiClient;
|
||||
}
|
||||
95
apps/cli/src/lib/config.ts
Normal file
95
apps/cli/src/lib/config.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* DSS Configuration Manager
|
||||
*
|
||||
* Manages local and project configuration.
|
||||
*/
|
||||
|
||||
import Conf from 'conf';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
// Global user config (stored in home directory)
|
||||
const globalConfig = new Conf({
|
||||
projectName: 'dss',
|
||||
schema: {
|
||||
figmaToken: { type: 'string' },
|
||||
defaultPort: { type: 'number', default: 3456 },
|
||||
defaultFormat: { type: 'string', default: 'css' },
|
||||
},
|
||||
});
|
||||
|
||||
// Project-level config file
|
||||
const PROJECT_CONFIG_FILE = '.dss/config.json';
|
||||
|
||||
export interface DSSConfig {
|
||||
figmaFileKey?: string;
|
||||
figmaToken?: string;
|
||||
port?: number;
|
||||
outputDir?: string;
|
||||
tokenFormat?: 'css' | 'scss' | 'json' | 'ts';
|
||||
componentFramework?: 'react' | 'vue' | 'svelte' | 'webcomponent';
|
||||
}
|
||||
|
||||
export function getProjectRoot(): string {
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
export function getProjectConfigPath(): string {
|
||||
return join(getProjectRoot(), PROJECT_CONFIG_FILE);
|
||||
}
|
||||
|
||||
export function hasProjectConfig(): boolean {
|
||||
return existsSync(getProjectConfigPath());
|
||||
}
|
||||
|
||||
export function loadProjectConfig(): DSSConfig {
|
||||
const configPath = getProjectConfigPath();
|
||||
if (!existsSync(configPath)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(configPath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse project config:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function saveProjectConfig(config: DSSConfig): void {
|
||||
const configPath = getProjectConfigPath();
|
||||
const dir = join(getProjectRoot(), '.dss');
|
||||
|
||||
// Ensure .dss directory exists
|
||||
if (!existsSync(dir)) {
|
||||
const { mkdirSync } = require('fs');
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
export function getConfig(): DSSConfig {
|
||||
const project = loadProjectConfig();
|
||||
return {
|
||||
figmaToken: project.figmaToken || globalConfig.get('figmaToken') as string | undefined,
|
||||
figmaFileKey: project.figmaFileKey,
|
||||
port: project.port || globalConfig.get('defaultPort') as number,
|
||||
outputDir: project.outputDir || '.dss/output',
|
||||
tokenFormat: project.tokenFormat || (globalConfig.get('defaultFormat') as DSSConfig['tokenFormat']),
|
||||
componentFramework: project.componentFramework || 'react',
|
||||
};
|
||||
}
|
||||
|
||||
export function setGlobalConfig(key: string, value: string | number): void {
|
||||
globalConfig.set(key, value);
|
||||
}
|
||||
|
||||
export function getGlobalConfig(key: string): unknown {
|
||||
return globalConfig.get(key);
|
||||
}
|
||||
|
||||
export function listGlobalConfig(): Record<string, unknown> {
|
||||
return globalConfig.store;
|
||||
}
|
||||
197
apps/cli/src/lib/server.ts
Normal file
197
apps/cli/src/lib/server.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* DSS Server Manager
|
||||
*
|
||||
* Manages the Python server subprocess.
|
||||
*/
|
||||
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { existsSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
interface ServerOptions {
|
||||
port: number;
|
||||
dev: boolean;
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
let serverProcess: ChildProcess | null = null;
|
||||
|
||||
export function getPythonPath(): string {
|
||||
// Check for bundled Python venv in npm package (dist/lib -> ../../python/venv)
|
||||
const bundledPython = join(__dirname, '../../python/venv/bin/python3');
|
||||
if (existsSync(bundledPython)) {
|
||||
return bundledPython;
|
||||
}
|
||||
|
||||
// Development path (src/lib -> ../../python/venv)
|
||||
const devPython = join(__dirname, '../../../python/venv/bin/python3');
|
||||
if (existsSync(devPython)) {
|
||||
return devPython;
|
||||
}
|
||||
|
||||
return 'python3';
|
||||
}
|
||||
|
||||
export function getServerPath(): string {
|
||||
// Check for bundled server or use from package
|
||||
const bundledServer = join(__dirname, '../../python/api/server.py');
|
||||
if (existsSync(bundledServer)) {
|
||||
return dirname(bundledServer);
|
||||
}
|
||||
|
||||
// Fall back to relative path from CLI (cli/dist/lib -> ../../../../tools/api)
|
||||
const devServer = join(__dirname, '../../../../tools/api');
|
||||
if (existsSync(join(devServer, 'server.py'))) {
|
||||
return devServer;
|
||||
}
|
||||
|
||||
// Also check development src path (cli/src/lib -> ../../../../tools/api)
|
||||
const srcServer = join(__dirname, '../../../tools/api');
|
||||
if (existsSync(join(srcServer, 'server.py'))) {
|
||||
return srcServer;
|
||||
}
|
||||
|
||||
throw new Error('Could not find DSS server. Run from the design-system-swarm directory or install the full package.');
|
||||
}
|
||||
|
||||
export function getPidFile(cwd: string): string {
|
||||
return join(cwd, '.dss', 'dss.pid');
|
||||
}
|
||||
|
||||
export function getLogFile(cwd: string): string {
|
||||
return join(cwd, '.dss', 'dss.log');
|
||||
}
|
||||
|
||||
export function isServerRunning(cwd: string): boolean {
|
||||
const pidFile = getPidFile(cwd);
|
||||
if (!existsSync(pidFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
||||
// Check if process exists
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
// Process doesn't exist, clean up stale PID file
|
||||
try {
|
||||
unlinkSync(pidFile);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getServerPid(cwd: string): number | null {
|
||||
const pidFile = getPidFile(cwd);
|
||||
if (!existsSync(pidFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function startServer(options: ServerOptions): Promise<ChildProcess> {
|
||||
const { port, dev, cwd } = options;
|
||||
|
||||
if (isServerRunning(cwd)) {
|
||||
throw new Error(`Server is already running (PID: ${getServerPid(cwd)})`);
|
||||
}
|
||||
|
||||
const pythonPath = getPythonPath();
|
||||
const serverDir = getServerPath();
|
||||
|
||||
// Build command args
|
||||
const args = [
|
||||
'-m', 'uvicorn',
|
||||
'server:app',
|
||||
'--host', '0.0.0.0',
|
||||
'--port', String(port),
|
||||
];
|
||||
|
||||
if (dev) {
|
||||
args.push('--reload');
|
||||
}
|
||||
|
||||
// Set environment
|
||||
const env = {
|
||||
...process.env,
|
||||
PYTHONPATH: join(serverDir, '..'),
|
||||
PORT: String(port),
|
||||
};
|
||||
|
||||
// Spawn server process
|
||||
serverProcess = spawn(pythonPath, args, {
|
||||
cwd: serverDir,
|
||||
env,
|
||||
stdio: dev ? 'inherit' : ['ignore', 'pipe', 'pipe'],
|
||||
detached: !dev,
|
||||
});
|
||||
|
||||
if (!dev && serverProcess.pid) {
|
||||
// Write PID file
|
||||
const dssDir = join(cwd, '.dss');
|
||||
if (!existsSync(dssDir)) {
|
||||
const { mkdirSync } = await import('fs');
|
||||
mkdirSync(dssDir, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(getPidFile(cwd), String(serverProcess.pid));
|
||||
|
||||
// Write logs
|
||||
const logStream = await import('fs').then(fs =>
|
||||
fs.createWriteStream(getLogFile(cwd), { flags: 'a' })
|
||||
);
|
||||
|
||||
serverProcess.stdout?.pipe(logStream);
|
||||
serverProcess.stderr?.pipe(logStream);
|
||||
|
||||
// Detach from parent
|
||||
serverProcess.unref();
|
||||
}
|
||||
|
||||
return serverProcess;
|
||||
}
|
||||
|
||||
export async function stopServer(cwd: string): Promise<boolean> {
|
||||
const pid = getServerPid(cwd);
|
||||
if (!pid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
unlinkSync(getPidFile(cwd));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForServer(port: number, timeout = 10000): Promise<boolean> {
|
||||
const start = Date.now();
|
||||
const url = `http://localhost:${port}/health`;
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Server not ready yet
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
20
apps/cli/tsconfig.json
Normal file
20
apps/cli/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 🧬 DSS CLI - Design System Server Organism Controller
|
||||
* DSS CLI - Design System Server Command Line Interface
|
||||
*
|
||||
* A portable companion for UI developers - think of it as the organism's
|
||||
* command-line nervous system. Through these commands, you can:
|
||||
* A portable companion for UI developers. Commands:
|
||||
*
|
||||
* - 🧬 Awaken a new organism (init)
|
||||
* - 💚 Check the organism's vital signs (status)
|
||||
* - 🩸 Direct the sensory organs to perceive Figma (extract)
|
||||
* - 🔄 Circulate extracted nutrients (sync)
|
||||
* - ⚙️ Adjust the organism's behavior (config)
|
||||
* - 🧠 Birth a conscious instance (start)
|
||||
*
|
||||
* Framework: DSS Organism Framework
|
||||
* - init: Initialize DSS in a project
|
||||
* - status: Check server status
|
||||
* - extract: Extract tokens/components from Figma
|
||||
* - sync: Sync tokens to codebase
|
||||
* - config: Manage configuration
|
||||
* - start: Start the server
|
||||
* - stop: Stop the server
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
@@ -29,63 +27,63 @@ const program = new Command();
|
||||
|
||||
program
|
||||
.name('dss')
|
||||
.description('🧬 Design System Server - Organism Controller for UI Developers')
|
||||
.description('Design System Server - CLI for UI Developers')
|
||||
.version('0.1.0');
|
||||
|
||||
// Init command - setup DSS in a project
|
||||
program
|
||||
.command('init')
|
||||
.description('🧬 ORGANISM GENESIS - Create a new design system organism in your project')
|
||||
.option('-f, --figma-key <key>', 'Link to Figma genetic blueprint')
|
||||
.option('-t, --figma-token <token>', 'Figma sensory organ connection token')
|
||||
.description('Initialize DSS in your project')
|
||||
.option('-f, --figma-key <key>', 'Figma file key')
|
||||
.option('-t, --figma-token <token>', 'Figma access token')
|
||||
.action(initCommand);
|
||||
|
||||
// Start command - start the DSS server
|
||||
program
|
||||
.command('start')
|
||||
.description('💚 ORGANISM AWAKENING - Bring the design system organism to life')
|
||||
.option('-p, --port <port>', 'Neural pathway port', '3456')
|
||||
.option('-d, --dev', 'Live consciousness mode with hot-reload')
|
||||
.option('--no-open', 'Do not open sensory interface')
|
||||
.description('Start the DSS server')
|
||||
.option('-p, --port <port>', 'Server port', '3456')
|
||||
.option('-d, --dev', 'Development mode with hot-reload')
|
||||
.option('--no-open', 'Do not open browser')
|
||||
.action(startCommand);
|
||||
|
||||
// Sync command - sync tokens from Figma
|
||||
program
|
||||
.command('sync')
|
||||
.description('🩸 NUTRIENT CIRCULATION - Distribute extracted tokens through the codebase')
|
||||
.option('-f, --format <format>', 'Nutrient format: css, scss, json, ts', 'css')
|
||||
.option('-o, --output <path>', 'Circulation destination')
|
||||
.description('Sync tokens from Figma to codebase')
|
||||
.option('-f, --format <format>', 'Output format: css, scss, json, ts', 'css')
|
||||
.option('-o, --output <path>', 'Output directory')
|
||||
.option('--file-key <key>', 'Figma file key (overrides config)')
|
||||
.action(syncCommand);
|
||||
|
||||
// Extract command - extract components or tokens
|
||||
program
|
||||
.command('extract <type>')
|
||||
.description('👁️ SENSORY PERCEPTION - Direct organism eyes to perceive Figma designs')
|
||||
.option('-f, --format <format>', 'Perception output format', 'json')
|
||||
.option('-o, --output <path>', 'Memory storage location')
|
||||
.description('Extract components or tokens from Figma')
|
||||
.option('-f, --format <format>', 'Output format', 'json')
|
||||
.option('-o, --output <path>', 'Output location')
|
||||
.option('--file-key <key>', 'Figma file key')
|
||||
.action(extractCommand);
|
||||
|
||||
// Config command - manage configuration
|
||||
program
|
||||
.command('config')
|
||||
.description('⚙️ ENDOCRINE ADJUSTMENT - Configure organism behavior and preferences')
|
||||
.option('--set <key=value>', 'Set organism hormone value')
|
||||
.option('--get <key>', 'Read organism hormone value')
|
||||
.option('--list', 'View all hormones')
|
||||
.description('Manage DSS configuration')
|
||||
.option('--set <key=value>', 'Set configuration value')
|
||||
.option('--get <key>', 'Get configuration value')
|
||||
.option('--list', 'List all configuration')
|
||||
.action(configCommand);
|
||||
|
||||
// Stop command - stop the server
|
||||
program
|
||||
.command('stop')
|
||||
.description('😴 ORGANISM REST - Put the design system organism into sleep mode')
|
||||
.description('Stop the DSS server')
|
||||
.action(stopCommand);
|
||||
|
||||
// Status command - check DSS status
|
||||
program
|
||||
.command('status')
|
||||
.description('🏥 VITAL SIGNS CHECK - Monitor organism health and configuration')
|
||||
.description('Check DSS server status and configuration')
|
||||
.action(statusCommand);
|
||||
|
||||
// Parse arguments
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* 🏥 DSS Status Command - Organism Vital Signs
|
||||
* DSS Status Command
|
||||
*
|
||||
* Check DSS design system organism's vital signs, consciousness state,
|
||||
* and sensory organ configuration.
|
||||
* Check DSS server status and configuration.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
@@ -15,48 +14,48 @@ export async function statusCommand(): Promise<void> {
|
||||
const config = getConfig();
|
||||
|
||||
console.log('');
|
||||
console.log(chalk.cyan(' 🏥 ORGANISM VITAL SIGNS'));
|
||||
console.log(chalk.cyan(' DSS Status'));
|
||||
console.log(chalk.dim(' ────────────────────────'));
|
||||
console.log('');
|
||||
|
||||
// Organism status
|
||||
// Project status
|
||||
if (hasProjectConfig()) {
|
||||
console.log(chalk.green(' 🧬 Organism:'), chalk.dim('Born and conscious'));
|
||||
console.log(chalk.dim(` Home: ${cwd}`));
|
||||
console.log(chalk.green(' Project:'), chalk.dim('Initialized'));
|
||||
console.log(chalk.dim(` Root: ${cwd}`));
|
||||
} else {
|
||||
console.log(chalk.yellow(' 🧬 Organism:'), chalk.dim('Not yet born'));
|
||||
console.log(chalk.dim(' Genesis: dss init'));
|
||||
console.log(chalk.yellow(' Project:'), chalk.dim('Not initialized'));
|
||||
console.log(chalk.dim(' Initialize: dss init'));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Consciousness status (server)
|
||||
// Server status
|
||||
const running = isServerRunning(cwd);
|
||||
const pid = getServerPid(cwd);
|
||||
const port = config.port || 3456;
|
||||
|
||||
if (running) {
|
||||
console.log(chalk.green(' 💚 Consciousness:'), chalk.dim(`Awake (PID: ${pid})`));
|
||||
console.log(chalk.dim(` Neural port: http://localhost:${port}`));
|
||||
console.log(chalk.green(' Server:'), chalk.dim(`Running (PID: ${pid})`));
|
||||
console.log(chalk.dim(` URL: http://localhost:${port}`));
|
||||
|
||||
// Try to get health info
|
||||
try {
|
||||
const api = getApiClient({ port });
|
||||
const health = await api.health();
|
||||
console.log(chalk.dim(` Awareness: ${health.figma_mode}`));
|
||||
console.log(chalk.dim(` Mode: ${health.figma_mode}`));
|
||||
} catch {
|
||||
console.log(chalk.yellow(' ⚠️ Unable to read consciousness'));
|
||||
console.log(chalk.yellow(' Unable to get health status'));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow(' 💚 Consciousness:'), chalk.dim('Sleeping'));
|
||||
console.log(chalk.dim(' Awaken: dss start'));
|
||||
console.log(chalk.yellow(' Server:'), chalk.dim('Stopped'));
|
||||
console.log(chalk.dim(' Start: dss start'));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Sensory organs (Figma)
|
||||
// Figma integration
|
||||
if (config.figmaToken) {
|
||||
console.log(chalk.green(' 👁️ Sensory Eyes:'), chalk.dim('Configured'));
|
||||
console.log(chalk.green(' Figma:'), chalk.dim('Configured'));
|
||||
|
||||
// Test connection if server is running
|
||||
if (running) {
|
||||
@@ -64,21 +63,21 @@ export async function statusCommand(): Promise<void> {
|
||||
const api = getApiClient({ port });
|
||||
const test = await api.testFigmaConnection();
|
||||
if (test.success) {
|
||||
console.log(chalk.green(' Perception:'), chalk.dim(`Clear (${test.user})`));
|
||||
console.log(chalk.green(' Connection:'), chalk.dim(`OK (${test.user})`));
|
||||
} else {
|
||||
console.log(chalk.red(' Perception:'), chalk.dim(test.error || 'Blinded'));
|
||||
console.log(chalk.red(' Connection:'), chalk.dim(test.error || 'Failed'));
|
||||
}
|
||||
} catch {
|
||||
console.log(chalk.yellow(' Perception:'), chalk.dim('Cannot test'));
|
||||
console.log(chalk.yellow(' Connection:'), chalk.dim('Cannot test'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow(' 👁️ Sensory Eyes:'), chalk.dim('Not configured'));
|
||||
console.log(chalk.yellow(' Figma:'), chalk.dim('Not configured'));
|
||||
console.log(chalk.dim(' Configure: dss config --set figmaToken=figd_xxxxx'));
|
||||
}
|
||||
|
||||
if (config.figmaFileKey) {
|
||||
console.log(chalk.green(' 📋 Genetic Blueprint:'), chalk.dim(config.figmaFileKey));
|
||||
console.log(chalk.green(' Figma File:'), chalk.dim(config.figmaFileKey));
|
||||
} else {
|
||||
console.log(chalk.yellow(' Figma File:'), chalk.dim('Not configured'));
|
||||
console.log(chalk.dim(' Set: dss config --set figmaFileKey=abc123'));
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,57 +0,0 @@
|
||||
# DSS MVP1 Test Environment Variables
|
||||
# This file contains mock/test values for running tests
|
||||
# DO NOT use these values in production!
|
||||
|
||||
# =============================================================================
|
||||
# Mock API Keys for Testing
|
||||
# =============================================================================
|
||||
# These are MOCK keys from tests/fixtures/api_keys.json
|
||||
# They will NOT work with real APIs
|
||||
|
||||
# Mock Anthropic API key (for testing only)
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-test-mock-key-for-testing-only-do-not-use-in-production-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# Mock Figma token (for testing only)
|
||||
FIGMA_TOKEN=figd_test_mock_token_for_testing_only_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# =============================================================================
|
||||
# Test Configuration
|
||||
# =============================================================================
|
||||
# Use test database
|
||||
DATABASE_PATH=.dss/test.db
|
||||
|
||||
# Disable caching in tests
|
||||
FIGMA_CACHE_TTL=0
|
||||
DSS_CACHE_DIR=.dss/test_cache
|
||||
|
||||
# Test mode
|
||||
NODE_ENV=test
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# =============================================================================
|
||||
# Server Configuration for Tests
|
||||
# =============================================================================
|
||||
PORT=3456
|
||||
DSS_MCP_PORT=3457
|
||||
DSS_MCP_HOST=127.0.0.1
|
||||
|
||||
# =============================================================================
|
||||
# For Real API Testing (Optional)
|
||||
# =============================================================================
|
||||
# If you want to test with REAL APIs, uncomment and add your real keys:
|
||||
# REAL_ANTHROPIC_API_KEY=sk-ant-api03-your-real-key-here
|
||||
# REAL_FIGMA_TOKEN=your-real-figma-token-here
|
||||
# REAL_FIGMA_FILE_KEY=your-real-file-key-here
|
||||
|
||||
# =============================================================================
|
||||
# Usage Instructions
|
||||
# =============================================================================
|
||||
# To use this file:
|
||||
# 1. Copy to .env: cp .env.test .env
|
||||
# 2. Run tests: pytest tests/
|
||||
# 3. Mock APIs will be used automatically
|
||||
#
|
||||
# To test with real APIs:
|
||||
# 1. Add your real keys above (REAL_* variables)
|
||||
# 2. Update test code to use real keys when REAL_* vars are set
|
||||
# 3. Run tests: pytest tests/ --real-api
|
||||
@@ -1,26 +0,0 @@
|
||||
import { create } from '@storybook/theming/create';
|
||||
|
||||
export const dssTheme = create({
|
||||
base: 'light',
|
||||
brandTitle: 'Design System',
|
||||
brandUrl: '',
|
||||
brandImage: '',
|
||||
brandTarget: '_self',
|
||||
colorPrimary: '#3B82F6',
|
||||
colorSecondary: '#10B981',
|
||||
appBg: '#FFFFFF',
|
||||
appContentBg: '#FFFFFF',
|
||||
appBorderColor: '#E5E7EB',
|
||||
textColor: '#1F2937',
|
||||
textInverseColor: '#FFFFFF',
|
||||
textMutedColor: '#6B7280',
|
||||
barTextColor: '#6B7280',
|
||||
barSelectedColor: '#3B82F6',
|
||||
barBg: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
inputBorder: '#D1D5DB',
|
||||
inputTextColor: '#1F2937',
|
||||
inputBorderRadius: 4,
|
||||
fontBase: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||
fontCode: '"Fira Code", "Monaco", monospace',
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
/** @type { import("@storybook/html").StorybookConfig } */
|
||||
const config = {
|
||||
stories: [
|
||||
"../stories/Welcome.stories.js",
|
||||
"../stories/generated/**/*.mdx",
|
||||
"../stories/generated/**/*.stories.@(js|jsx|mjs|ts|tsx)"
|
||||
],
|
||||
addons: [
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-webpack5-compiler-babel",
|
||||
"@chromatic-com/storybook"
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/html-webpack5",
|
||||
options: {}
|
||||
},
|
||||
docs: {}
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,6 +0,0 @@
|
||||
import { addons } from '@storybook/manager-api';
|
||||
import { dssTheme } from './dss-theme';
|
||||
|
||||
addons.setConfig({
|
||||
theme: dssTheme,
|
||||
});
|
||||
@@ -1,188 +0,0 @@
|
||||
/**
|
||||
* Storybook Preview Configuration
|
||||
*
|
||||
* Integrates DSS design tokens into Storybook:
|
||||
* - Applies tokens globally to all stories
|
||||
* - Configures Storybook UI with DSS colors
|
||||
* - Sets up theme switching with token variables
|
||||
* - Enables component stories to use own design system
|
||||
*/
|
||||
|
||||
// Import tokens generated from Figma
|
||||
// These would be auto-generated via build process from token exporters
|
||||
const DSSTokens = {
|
||||
colors: {
|
||||
primary: '#0066FF',
|
||||
secondary: '#FF6B00',
|
||||
success: '#00B600',
|
||||
warning: '#FFB800',
|
||||
danger: '#FF0000',
|
||||
text: '#1A1A1A',
|
||||
textLight: '#666666',
|
||||
surface: '#FFFFFF',
|
||||
background: '#F5F5F5',
|
||||
border: '#E0E0E0',
|
||||
},
|
||||
spacing: {
|
||||
xs: '4px',
|
||||
sm: '8px',
|
||||
md: '16px',
|
||||
lg: '24px',
|
||||
xl: '32px',
|
||||
},
|
||||
typography: {
|
||||
headingFamily: "'Inter', sans-serif",
|
||||
bodyFamily: "'Inter', sans-serif",
|
||||
monospaceFamily: "'Courier New', monospace",
|
||||
},
|
||||
borderRadius: {
|
||||
sm: '4px',
|
||||
md: '8px',
|
||||
lg: '12px',
|
||||
full: '9999px',
|
||||
},
|
||||
shadows: {
|
||||
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
|
||||
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||
lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
};
|
||||
|
||||
// Create Storybook theme using DSS tokens
|
||||
const createStorybookTheme = () => ({
|
||||
base: 'light',
|
||||
colorPrimary: DSSTokens.colors.primary,
|
||||
colorSecondary: DSSTokens.colors.secondary,
|
||||
appBg: DSSTokens.colors.background,
|
||||
appContentBg: DSSTokens.colors.surface,
|
||||
appBorderColor: DSSTokens.colors.border,
|
||||
appBorderRadius: parseInt(DSSTokens.borderRadius.md),
|
||||
textColor: DSSTokens.colors.text,
|
||||
textInverseColor: '#FFFFFF',
|
||||
barTextColor: DSSTokens.colors.text,
|
||||
barBg: DSSTokens.colors.surface,
|
||||
barSelectedColor: DSSTokens.colors.primary,
|
||||
barHoverColor: DSSTokens.colors.primary,
|
||||
barBorderColor: DSSTokens.colors.border,
|
||||
inputBg: '#FFFFFF',
|
||||
inputBorder: DSSTokens.colors.border,
|
||||
inputTextColor: DSSTokens.colors.text,
|
||||
inputBorderRadius: parseInt(DSSTokens.borderRadius.md),
|
||||
brandUrl: 'https://dss.overbits.luz.uy',
|
||||
brandImage: '/dss-logo.svg',
|
||||
brandTitle: 'DSS Design System',
|
||||
});
|
||||
|
||||
// Register all CSS variables globally
|
||||
const registerCSSVariables = () => {
|
||||
const style = document.documentElement.style;
|
||||
|
||||
// Register color tokens
|
||||
Object.entries(DSSTokens.colors).forEach(([name, value]) => {
|
||||
style.setProperty(`--dss-color-${name}`, value);
|
||||
});
|
||||
|
||||
// Register spacing tokens
|
||||
Object.entries(DSSTokens.spacing).forEach(([name, value]) => {
|
||||
style.setProperty(`--dss-spacing-${name}`, value);
|
||||
});
|
||||
|
||||
// Register typography tokens
|
||||
Object.entries(DSSTokens.typography).forEach(([name, value]) => {
|
||||
style.setProperty(`--dss-typography-${name}`, value);
|
||||
});
|
||||
|
||||
// Register border radius tokens
|
||||
Object.entries(DSSTokens.borderRadius).forEach(([name, value]) => {
|
||||
style.setProperty(`--dss-radius-${name}`, value);
|
||||
});
|
||||
|
||||
// Register shadow tokens
|
||||
Object.entries(DSSTokens.shadows).forEach(([name, value]) => {
|
||||
style.setProperty(`--dss-shadow-${name}`, value);
|
||||
});
|
||||
};
|
||||
|
||||
// Export preview configuration
|
||||
export const preview = {
|
||||
parameters: {
|
||||
// Apply DSS theme to Storybook UI
|
||||
docs: {
|
||||
theme: createStorybookTheme(),
|
||||
},
|
||||
// Configure viewport options
|
||||
viewport: {
|
||||
viewports: {
|
||||
mobile: {
|
||||
name: 'Mobile',
|
||||
styles: { width: '375px', height: '812px' },
|
||||
type: 'mobile',
|
||||
},
|
||||
tablet: {
|
||||
name: 'Tablet',
|
||||
styles: { width: '768px', height: '1024px' },
|
||||
type: 'tablet',
|
||||
},
|
||||
desktop: {
|
||||
name: 'Desktop',
|
||||
styles: { width: '1280px', height: '720px' },
|
||||
type: 'desktop',
|
||||
},
|
||||
},
|
||||
},
|
||||
// Setup backgrounds
|
||||
backgrounds: {
|
||||
default: 'light',
|
||||
values: [
|
||||
{ name: 'light', value: DSSTokens.colors.surface },
|
||||
{ name: 'dark', value: '#1A1A1A' },
|
||||
{ name: 'gray', value: DSSTokens.colors.background },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Global decorator to apply DSS tokens to all stories
|
||||
decorators: [
|
||||
(Story, context) => {
|
||||
// Register CSS variables
|
||||
registerCSSVariables();
|
||||
|
||||
// Get the story content
|
||||
const storyContent = Story();
|
||||
|
||||
// Create wrapper div with DSS token styles
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.cssText = `
|
||||
--dss-primary: ${DSSTokens.colors.primary};
|
||||
--dss-secondary: ${DSSTokens.colors.secondary};
|
||||
--dss-text: ${DSSTokens.colors.text};
|
||||
--dss-surface: ${DSSTokens.colors.surface};
|
||||
--dss-background: ${DSSTokens.colors.background};
|
||||
--dss-border: ${DSSTokens.colors.border};
|
||||
--dss-spacing-base: ${DSSTokens.spacing.md};
|
||||
--dss-font-body: ${DSSTokens.typography.bodyFamily};
|
||||
--dss-font-heading: ${DSSTokens.typography.headingFamily};
|
||||
--dss-radius: ${DSSTokens.borderRadius.md};
|
||||
font-family: ${DSSTokens.typography.bodyFamily};
|
||||
color: ${DSSTokens.colors.text};
|
||||
background-color: ${DSSTokens.colors.surface};
|
||||
padding: ${DSSTokens.spacing.lg};
|
||||
`;
|
||||
|
||||
// Append story content to wrapper
|
||||
if (typeof storyContent === 'string') {
|
||||
wrapper.innerHTML = storyContent;
|
||||
} else if (storyContent instanceof Node) {
|
||||
wrapper.appendChild(storyContent);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Export Storybook theme (for use in stories)
|
||||
export const dssTheme = createStorybookTheme();
|
||||
|
||||
// Export DSS tokens for use in stories
|
||||
export const dssTokens = DSSTokens;
|
||||
@@ -1,435 +0,0 @@
|
||||
import type { Preview } from '@storybook/react';
|
||||
|
||||
// Inject design tokens as CSS variables
|
||||
const tokenStyles = `
|
||||
:root {
|
||||
--version: 1.0.0;
|
||||
--source: figma;
|
||||
--figma_file: evCZlaeZrP7X20NIViSJbl;
|
||||
--synced_at: 2025-12-09T12:50:40.840869;
|
||||
--categories-color-neutral-50: {'6:0': {'r': 0.9803921580314636, 'g': 0.9803921580314636, 'b': 0.9803921580314636, 'a': 1}};
|
||||
--categories-color-neutral-100: {'6:0': {'r': 0.9607843160629272, 'g': 0.9607843160629272, 'b': 0.9607843160629272, 'a': 1}};
|
||||
--categories-color-neutral-200: {'6:0': {'r': 0.8980392217636108, 'g': 0.8980392217636108, 'b': 0.8980392217636108, 'a': 1}};
|
||||
--categories-color-neutral-300: {'6:0': {'r': 0.8313725590705872, 'g': 0.8313725590705872, 'b': 0.8313725590705872, 'a': 1}};
|
||||
--categories-color-neutral-400: {'6:0': {'r': 0.6392157077789307, 'g': 0.6392157077789307, 'b': 0.6392157077789307, 'a': 1}};
|
||||
--categories-color-neutral-500: {'6:0': {'r': 0.45098039507865906, 'g': 0.45098039507865906, 'b': 0.45098039507865906, 'a': 1}};
|
||||
--categories-color-neutral-600: {'6:0': {'r': 0.32156863808631897, 'g': 0.32156863808631897, 'b': 0.32156863808631897, 'a': 1}};
|
||||
--categories-color-neutral-700: {'6:0': {'r': 0.250980406999588, 'g': 0.250980406999588, 'b': 0.250980406999588, 'a': 1}};
|
||||
--categories-color-neutral-800: {'6:0': {'r': 0.14901961386203766, 'g': 0.14901961386203766, 'b': 0.14901961386203766, 'a': 1}};
|
||||
--categories-color-neutral-900: {'6:0': {'r': 0.09019608050584793, 'g': 0.09019608050584793, 'b': 0.09019608050584793, 'a': 1}};
|
||||
--categories-color-neutral-950: {'6:0': {'r': 0.03921568766236305, 'g': 0.03921568766236305, 'b': 0.03921568766236305, 'a': 1}};
|
||||
--categories-color-red-50: {'6:0': {'r': 0.9960784316062927, 'g': 0.9490196108818054, 'b': 0.9490196108818054, 'a': 1}};
|
||||
--categories-color-red-100: {'6:0': {'r': 1, 'g': 0.886274516582489, 'b': 0.886274516582489, 'a': 1}};
|
||||
--categories-color-red-200: {'6:0': {'r': 0.9960784316062927, 'g': 0.7921568751335144, 'b': 0.7921568751335144, 'a': 1}};
|
||||
--categories-color-red-300: {'6:0': {'r': 0.9882352948188782, 'g': 0.6470588445663452, 'b': 0.6470588445663452, 'a': 1}};
|
||||
--categories-color-red-400: {'6:0': {'r': 0.9725490212440491, 'g': 0.4431372582912445, 'b': 0.4431372582912445, 'a': 1}};
|
||||
--categories-color-red-500: {'6:0': {'r': 0.9372549057006836, 'g': 0.2666666805744171, 'b': 0.2666666805744171, 'a': 1}};
|
||||
--categories-color-red-600: {'6:0': {'r': 0.8627451062202454, 'g': 0.14901961386203766, 'b': 0.14901961386203766, 'a': 1}};
|
||||
--categories-color-red-700: {'6:0': {'r': 0.7254902124404907, 'g': 0.10980392247438431, 'b': 0.10980392247438431, 'a': 1}};
|
||||
--categories-color-red-800: {'6:0': {'r': 0.6000000238418579, 'g': 0.10588235408067703, 'b': 0.10588235408067703, 'a': 1}};
|
||||
--categories-color-red-900: {'6:0': {'r': 0.49803921580314636, 'g': 0.11372549086809158, 'b': 0.11372549086809158, 'a': 1}};
|
||||
--categories-color-red-950: {'6:0': {'r': 0.2705882489681244, 'g': 0.03921568766236305, 'b': 0.03921568766236305, 'a': 1}};
|
||||
--categories-color-blue-50: {'6:0': {'r': 0.9372549057006836, 'g': 0.9647058844566345, 'b': 1, 'a': 1}};
|
||||
--categories-color-blue-100: {'6:0': {'r': 0.8588235378265381, 'g': 0.9176470637321472, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-blue-200: {'6:0': {'r': 0.7490196228027344, 'g': 0.8588235378265381, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-blue-300: {'6:0': {'r': 0.5764706134796143, 'g': 0.772549033164978, 'b': 0.9921568632125854, 'a': 1}};
|
||||
--categories-color-blue-400: {'6:0': {'r': 0.3764705955982208, 'g': 0.6470588445663452, 'b': 0.9803921580314636, 'a': 1}};
|
||||
--categories-color-blue-500: {'6:0': {'r': 0.23137255012989044, 'g': 0.5098039507865906, 'b': 0.9647058844566345, 'a': 1}};
|
||||
--categories-color-blue-600: {'6:0': {'r': 0.14509804546833038, 'g': 0.38823530077934265, 'b': 0.9215686321258545, 'a': 1}};
|
||||
--categories-color-blue-700: {'6:0': {'r': 0.11372549086809158, 'g': 0.30588236451148987, 'b': 0.8470588326454163, 'a': 1}};
|
||||
--categories-color-blue-800: {'6:0': {'r': 0.11764705926179886, 'g': 0.250980406999588, 'b': 0.686274528503418, 'a': 1}};
|
||||
--categories-color-blue-900: {'6:0': {'r': 0.11764705926179886, 'g': 0.22745098173618317, 'b': 0.5411764979362488, 'a': 1}};
|
||||
--categories-color-blue-950: {'6:0': {'r': 0.09019608050584793, 'g': 0.14509804546833038, 'b': 0.3294117748737335, 'a': 1}};
|
||||
--categories-color-white: {'6:0': {'r': 1, 'g': 1, 'b': 1, 'a': 1}};
|
||||
--categories-color-color: {'6:0': {'r': 1, 'g': 1, 'b': 1, 'a': 1}};
|
||||
--categories-color-black: {'6:0': {'r': 0, 'g': 0, 'b': 0, 'a': 1}};
|
||||
--categories-color-slate-50: {'6:0': {'r': 0.9725490212440491, 'g': 0.9803921580314636, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-slate-100: {'6:0': {'r': 0.9450980424880981, 'g': 0.9607843160629272, 'b': 0.9764705896377563, 'a': 1}};
|
||||
--categories-color-slate-200: {'6:0': {'r': 0.886274516582489, 'g': 0.9098039269447327, 'b': 0.9411764740943909, 'a': 1}};
|
||||
--categories-color-slate-300: {'6:0': {'r': 0.7960784435272217, 'g': 0.8352941274642944, 'b': 0.8823529481887817, 'a': 1}};
|
||||
--categories-color-slate-400: {'6:0': {'r': 0.5803921818733215, 'g': 0.6392157077789307, 'b': 0.7215686440467834, 'a': 1}};
|
||||
--categories-color-slate-500: {'6:0': {'r': 0.3921568691730499, 'g': 0.45490196347236633, 'b': 0.545098066329956, 'a': 1}};
|
||||
--categories-color-slate-600: {'6:0': {'r': 0.27843138575553894, 'g': 0.3333333432674408, 'b': 0.4117647111415863, 'a': 1}};
|
||||
--categories-color-slate-700: {'6:0': {'r': 0.20000000298023224, 'g': 0.2549019753932953, 'b': 0.3333333432674408, 'a': 1}};
|
||||
--categories-color-slate-800: {'6:0': {'r': 0.11764705926179886, 'g': 0.16078431904315948, 'b': 0.23137255012989044, 'a': 1}};
|
||||
--categories-color-slate-900: {'6:0': {'r': 0.05882352963089943, 'g': 0.09019608050584793, 'b': 0.16470588743686676, 'a': 1}};
|
||||
--categories-color-slate-950: {'6:0': {'r': 0.007843137718737125, 'g': 0.0235294122248888, 'b': 0.09019608050584793, 'a': 1}};
|
||||
--categories-color-gray-50: {'6:0': {'r': 0.9764705896377563, 'g': 0.9803921580314636, 'b': 0.9843137264251709, 'a': 1}};
|
||||
--categories-color-gray-100: {'6:0': {'r': 0.9529411792755127, 'g': 0.95686274766922, 'b': 0.9647058844566345, 'a': 1}};
|
||||
--categories-color-gray-200: {'6:0': {'r': 0.8980392217636108, 'g': 0.9058823585510254, 'b': 0.9215686321258545, 'a': 1}};
|
||||
--categories-color-gray-300: {'6:0': {'r': 0.8196078538894653, 'g': 0.8352941274642944, 'b': 0.8588235378265381, 'a': 1}};
|
||||
--categories-color-gray-400: {'6:0': {'r': 0.6117647290229797, 'g': 0.6392157077789307, 'b': 0.686274528503418, 'a': 1}};
|
||||
--categories-color-gray-500: {'6:0': {'r': 0.41960784792900085, 'g': 0.4470588266849518, 'b': 0.501960813999176, 'a': 1}};
|
||||
--categories-color-gray-600: {'6:0': {'r': 0.29411765933036804, 'g': 0.3333333432674408, 'b': 0.38823530077934265, 'a': 1}};
|
||||
--categories-color-gray-700: {'6:0': {'r': 0.21568627655506134, 'g': 0.2549019753932953, 'b': 0.3176470696926117, 'a': 1}};
|
||||
--categories-color-gray-800: {'6:0': {'r': 0.12156862765550613, 'g': 0.16078431904315948, 'b': 0.21568627655506134, 'a': 1}};
|
||||
--categories-color-gray-900: {'6:0': {'r': 0.06666667014360428, 'g': 0.0941176488995552, 'b': 0.15294118225574493, 'a': 1}};
|
||||
--categories-color-gray-950: {'6:0': {'r': 0.0117647061124444, 'g': 0.027450980618596077, 'b': 0.07058823853731155, 'a': 1}};
|
||||
--categories-color-zinc-50: {'6:0': {'r': 0.9803921580314636, 'g': 0.9803921580314636, 'b': 0.9803921580314636, 'a': 1}};
|
||||
--categories-color-zinc-100: {'6:0': {'r': 0.95686274766922, 'g': 0.95686274766922, 'b': 0.9607843160629272, 'a': 1}};
|
||||
--categories-color-zinc-200: {'6:0': {'r': 0.8941176533699036, 'g': 0.8941176533699036, 'b': 0.9058823585510254, 'a': 1}};
|
||||
--categories-color-zinc-300: {'6:0': {'r': 0.8313725590705872, 'g': 0.8313725590705872, 'b': 0.8470588326454163, 'a': 1}};
|
||||
--categories-color-zinc-400: {'6:0': {'r': 0.6313725709915161, 'g': 0.6313725709915161, 'b': 0.6666666865348816, 'a': 1}};
|
||||
--categories-color-zinc-500: {'6:0': {'r': 0.4431372582912445, 'g': 0.4431372582912445, 'b': 0.47843137383461, 'a': 1}};
|
||||
--categories-color-zinc-600: {'6:0': {'r': 0.32156863808631897, 'g': 0.32156863808631897, 'b': 0.35686275362968445, 'a': 1}};
|
||||
--categories-color-zinc-700: {'6:0': {'r': 0.24705882370471954, 'g': 0.24705882370471954, 'b': 0.27450981736183167, 'a': 1}};
|
||||
--categories-color-zinc-800: {'6:0': {'r': 0.15294118225574493, 'g': 0.15294118225574493, 'b': 0.16470588743686676, 'a': 1}};
|
||||
--categories-color-zinc-900: {'6:0': {'r': 0.0941176488995552, 'g': 0.0941176488995552, 'b': 0.10588235408067703, 'a': 1}};
|
||||
--categories-color-zinc-950: {'6:0': {'r': 0.03529411926865578, 'g': 0.03529411926865578, 'b': 0.04313725605607033, 'a': 1}};
|
||||
--categories-color-stone-50: {'6:0': {'r': 0.9803921580314636, 'g': 0.9803921580314636, 'b': 0.9764705896377563, 'a': 1}};
|
||||
--categories-color-stone-100: {'6:0': {'r': 0.9607843160629272, 'g': 0.9607843160629272, 'b': 0.95686274766922, 'a': 1}};
|
||||
--categories-color-stone-200: {'6:0': {'r': 0.9058823585510254, 'g': 0.8980392217636108, 'b': 0.8941176533699036, 'a': 1}};
|
||||
--categories-color-stone-300: {'6:0': {'r': 0.8392156958580017, 'g': 0.8274509906768799, 'b': 0.8196078538894653, 'a': 1}};
|
||||
--categories-color-stone-400: {'6:0': {'r': 0.658823549747467, 'g': 0.6352941393852234, 'b': 0.6196078658103943, 'a': 1}};
|
||||
--categories-color-stone-500: {'6:0': {'r': 0.47058823704719543, 'g': 0.4431372582912445, 'b': 0.42352941632270813, 'a': 1}};
|
||||
--categories-color-stone-600: {'6:0': {'r': 0.34117648005485535, 'g': 0.32549020648002625, 'b': 0.30588236451148987, 'a': 1}};
|
||||
--categories-color-stone-700: {'6:0': {'r': 0.2666666805744171, 'g': 0.250980406999588, 'b': 0.23529411852359772, 'a': 1}};
|
||||
--categories-color-stone-800: {'6:0': {'r': 0.16078431904315948, 'g': 0.14509804546833038, 'b': 0.1411764770746231, 'a': 1}};
|
||||
--categories-color-stone-900: {'6:0': {'r': 0.10980392247438431, 'g': 0.09803921729326248, 'b': 0.09019608050584793, 'a': 1}};
|
||||
--categories-color-stone-950: {'6:0': {'r': 0.0470588244497776, 'g': 0.03921568766236305, 'b': 0.03529411926865578, 'a': 1}};
|
||||
--categories-color-orange-50: {'6:0': {'r': 1, 'g': 0.9686274528503418, 'b': 0.929411768913269, 'a': 1}};
|
||||
--categories-color-orange-100: {'6:0': {'r': 1, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}};
|
||||
--categories-color-orange-200: {'6:0': {'r': 0.9960784316062927, 'g': 0.843137264251709, 'b': 0.6666666865348816, 'a': 1}};
|
||||
--categories-color-orange-300: {'6:0': {'r': 0.9921568632125854, 'g': 0.729411780834198, 'b': 0.45490196347236633, 'a': 1}};
|
||||
--categories-color-orange-400: {'6:0': {'r': 0.9843137264251709, 'g': 0.572549045085907, 'b': 0.23529411852359772, 'a': 1}};
|
||||
--categories-color-orange-500: {'6:0': {'r': 0.9764705896377563, 'g': 0.45098039507865906, 'b': 0.08627451211214066, 'a': 1}};
|
||||
--categories-color-orange-600: {'6:0': {'r': 0.9176470637321472, 'g': 0.3450980484485626, 'b': 0.0470588244497776, 'a': 1}};
|
||||
--categories-color-orange-700: {'6:0': {'r': 0.7607843279838562, 'g': 0.2549019753932953, 'b': 0.0470588244497776, 'a': 1}};
|
||||
--categories-color-orange-800: {'6:0': {'r': 0.6039215922355652, 'g': 0.20392157137393951, 'b': 0.07058823853731155, 'a': 1}};
|
||||
--categories-color-orange-900: {'6:0': {'r': 0.48627451062202454, 'g': 0.1764705926179886, 'b': 0.07058823853731155, 'a': 1}};
|
||||
--categories-color-orange-950: {'6:0': {'r': 0.26274511218070984, 'g': 0.0784313753247261, 'b': 0.027450980618596077, 'a': 1}};
|
||||
--categories-color-amber-50: {'6:0': {'r': 1, 'g': 0.9843137264251709, 'b': 0.9215686321258545, 'a': 1}};
|
||||
--categories-color-amber-100: {'6:0': {'r': 0.9960784316062927, 'g': 0.9529411792755127, 'b': 0.7803921699523926, 'a': 1}};
|
||||
--categories-color-amber-200: {'6:0': {'r': 0.9921568632125854, 'g': 0.9019607901573181, 'b': 0.5411764979362488, 'a': 1}};
|
||||
--categories-color-amber-300: {'6:0': {'r': 0.9882352948188782, 'g': 0.8274509906768799, 'b': 0.3019607961177826, 'a': 1}};
|
||||
--categories-color-amber-400: {'6:0': {'r': 0.9843137264251709, 'g': 0.7490196228027344, 'b': 0.1411764770746231, 'a': 1}};
|
||||
--categories-color-amber-500: {'6:0': {'r': 0.9607843160629272, 'g': 0.6196078658103943, 'b': 0.04313725605607033, 'a': 1}};
|
||||
--categories-color-amber-600: {'6:0': {'r': 0.8509804010391235, 'g': 0.46666666865348816, 'b': 0.0235294122248888, 'a': 1}};
|
||||
--categories-color-amber-700: {'6:0': {'r': 0.7058823704719543, 'g': 0.32549020648002625, 'b': 0.03529411926865578, 'a': 1}};
|
||||
--categories-color-amber-800: {'6:0': {'r': 0.572549045085907, 'g': 0.250980406999588, 'b': 0.054901961237192154, 'a': 1}};
|
||||
--categories-color-amber-900: {'6:0': {'r': 0.47058823704719543, 'g': 0.2078431397676468, 'b': 0.05882352963089943, 'a': 1}};
|
||||
--categories-color-amber-950: {'6:0': {'r': 0.2705882489681244, 'g': 0.10196078568696976, 'b': 0.0117647061124444, 'a': 1}};
|
||||
--categories-color-lime-50: {'6:0': {'r': 0.9686274528503418, 'g': 0.9960784316062927, 'b': 0.9058823585510254, 'a': 1}};
|
||||
--categories-color-lime-100: {'6:0': {'r': 0.9254902005195618, 'g': 0.9882352948188782, 'b': 0.7960784435272217, 'a': 1}};
|
||||
--categories-color-lime-200: {'6:0': {'r': 0.8509804010391235, 'g': 0.9764705896377563, 'b': 0.615686297416687, 'a': 1}};
|
||||
--categories-color-lime-300: {'6:0': {'r': 0.7450980544090271, 'g': 0.9490196108818054, 'b': 0.3921568691730499, 'a': 1}};
|
||||
--categories-color-lime-400: {'6:0': {'r': 0.6392157077789307, 'g': 0.9019607901573181, 'b': 0.2078431397676468, 'a': 1}};
|
||||
--categories-color-lime-500: {'6:0': {'r': 0.5176470875740051, 'g': 0.800000011920929, 'b': 0.08627451211214066, 'a': 1}};
|
||||
--categories-color-lime-600: {'6:0': {'r': 0.3960784375667572, 'g': 0.6392157077789307, 'b': 0.05098039284348488, 'a': 1}};
|
||||
--categories-color-lime-700: {'6:0': {'r': 0.3019607961177826, 'g': 0.48627451062202454, 'b': 0.05882352963089943, 'a': 1}};
|
||||
--categories-color-lime-800: {'6:0': {'r': 0.24705882370471954, 'g': 0.3843137323856354, 'b': 0.07058823853731155, 'a': 1}};
|
||||
--categories-color-lime-900: {'6:0': {'r': 0.21176470816135406, 'g': 0.32549020648002625, 'b': 0.0784313753247261, 'a': 1}};
|
||||
--categories-color-lime-950: {'6:0': {'r': 0.10196078568696976, 'g': 0.18039216101169586, 'b': 0.019607843831181526, 'a': 1}};
|
||||
--categories-color-emerald-50: {'6:0': {'r': 0.9254902005195618, 'g': 0.9921568632125854, 'b': 0.9607843160629272, 'a': 1}};
|
||||
--categories-color-emerald-100: {'6:0': {'r': 0.8196078538894653, 'g': 0.9803921580314636, 'b': 0.8980392217636108, 'a': 1}};
|
||||
--categories-color-emerald-200: {'6:0': {'r': 0.6549019813537598, 'g': 0.9529411792755127, 'b': 0.8156862854957581, 'a': 1}};
|
||||
--categories-color-emerald-300: {'6:0': {'r': 0.4313725531101227, 'g': 0.9058823585510254, 'b': 0.7176470756530762, 'a': 1}};
|
||||
--categories-color-emerald-400: {'6:0': {'r': 0.20392157137393951, 'g': 0.8274509906768799, 'b': 0.6000000238418579, 'a': 1}};
|
||||
--categories-color-emerald-500: {'6:0': {'r': 0.062745101749897, 'g': 0.7254902124404907, 'b': 0.5058823823928833, 'a': 1}};
|
||||
--categories-color-emerald-600: {'6:0': {'r': 0.019607843831181526, 'g': 0.5882353186607361, 'b': 0.4117647111415863, 'a': 1}};
|
||||
--categories-color-emerald-700: {'6:0': {'r': 0.01568627543747425, 'g': 0.47058823704719543, 'b': 0.34117648005485535, 'a': 1}};
|
||||
--categories-color-emerald-800: {'6:0': {'r': 0.0235294122248888, 'g': 0.37254902720451355, 'b': 0.27450981736183167, 'a': 1}};
|
||||
--categories-color-emerald-900: {'6:0': {'r': 0.0235294122248888, 'g': 0.30588236451148987, 'b': 0.23137255012989044, 'a': 1}};
|
||||
--categories-color-emerald-950: {'6:0': {'r': 0.007843137718737125, 'g': 0.1725490242242813, 'b': 0.13333334028720856, 'a': 1}};
|
||||
--categories-color-teal-50: {'6:0': {'r': 0.9411764740943909, 'g': 0.9921568632125854, 'b': 0.9803921580314636, 'a': 1}};
|
||||
--categories-color-teal-100: {'6:0': {'r': 0.800000011920929, 'g': 0.9843137264251709, 'b': 0.9450980424880981, 'a': 1}};
|
||||
--categories-color-teal-200: {'6:0': {'r': 0.6000000238418579, 'g': 0.9647058844566345, 'b': 0.8941176533699036, 'a': 1}};
|
||||
--categories-color-teal-300: {'6:0': {'r': 0.3686274588108063, 'g': 0.9176470637321472, 'b': 0.8313725590705872, 'a': 1}};
|
||||
--categories-color-teal-400: {'6:0': {'r': 0.1764705926179886, 'g': 0.8313725590705872, 'b': 0.7490196228027344, 'a': 1}};
|
||||
--categories-color-teal-500: {'6:0': {'r': 0.0784313753247261, 'g': 0.7215686440467834, 'b': 0.6509804129600525, 'a': 1}};
|
||||
--categories-color-teal-600: {'6:0': {'r': 0.05098039284348488, 'g': 0.5803921818733215, 'b': 0.5333333611488342, 'a': 1}};
|
||||
--categories-color-teal-700: {'6:0': {'r': 0.05882352963089943, 'g': 0.4627451002597809, 'b': 0.4313725531101227, 'a': 1}};
|
||||
--categories-color-teal-800: {'6:0': {'r': 0.06666667014360428, 'g': 0.3686274588108063, 'b': 0.3490196168422699, 'a': 1}};
|
||||
--categories-color-teal-900: {'6:0': {'r': 0.07450980693101883, 'g': 0.30588236451148987, 'b': 0.29019609093666077, 'a': 1}};
|
||||
--categories-color-teal-950: {'6:0': {'r': 0.01568627543747425, 'g': 0.18431372940540314, 'b': 0.18039216101169586, 'a': 1}};
|
||||
--categories-color-cyan-50: {'6:0': {'r': 0.9254902005195618, 'g': 0.9960784316062927, 'b': 1, 'a': 1}};
|
||||
--categories-color-cyan-100: {'6:0': {'r': 0.8117647171020508, 'g': 0.9803921580314636, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-cyan-200: {'6:0': {'r': 0.6470588445663452, 'g': 0.9529411792755127, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-cyan-300: {'6:0': {'r': 0.40392157435417175, 'g': 0.9098039269447327, 'b': 0.9764705896377563, 'a': 1}};
|
||||
--categories-color-cyan-400: {'6:0': {'r': 0.13333334028720856, 'g': 0.8274509906768799, 'b': 0.9333333373069763, 'a': 1}};
|
||||
--categories-color-cyan-500: {'6:0': {'r': 0.0235294122248888, 'g': 0.7137255072593689, 'b': 0.8313725590705872, 'a': 1}};
|
||||
--categories-color-cyan-600: {'6:0': {'r': 0.0313725508749485, 'g': 0.5686274766921997, 'b': 0.6980392336845398, 'a': 1}};
|
||||
--categories-color-cyan-700: {'6:0': {'r': 0.054901961237192154, 'g': 0.45490196347236633, 'b': 0.5647059082984924, 'a': 1}};
|
||||
--categories-color-cyan-800: {'6:0': {'r': 0.08235294371843338, 'g': 0.3686274588108063, 'b': 0.4588235318660736, 'a': 1}};
|
||||
--categories-color-cyan-900: {'6:0': {'r': 0.08627451211214066, 'g': 0.30588236451148987, 'b': 0.38823530077934265, 'a': 1}};
|
||||
--categories-color-cyan-950: {'6:0': {'r': 0.0313725508749485, 'g': 0.20000000298023224, 'b': 0.2666666805744171, 'a': 1}};
|
||||
--categories-color-sky-50: {'6:0': {'r': 0.9411764740943909, 'g': 0.9764705896377563, 'b': 1, 'a': 1}};
|
||||
--categories-color-sky-100: {'6:0': {'r': 0.8784313797950745, 'g': 0.9490196108818054, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-sky-200: {'6:0': {'r': 0.729411780834198, 'g': 0.9019607901573181, 'b': 0.9921568632125854, 'a': 1}};
|
||||
--categories-color-sky-300: {'6:0': {'r': 0.4901960790157318, 'g': 0.8274509906768799, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-sky-400: {'6:0': {'r': 0.21960784494876862, 'g': 0.7411764860153198, 'b': 0.9725490212440491, 'a': 1}};
|
||||
--categories-color-sky-500: {'6:0': {'r': 0.054901961237192154, 'g': 0.6470588445663452, 'b': 0.9137254953384399, 'a': 1}};
|
||||
--categories-color-sky-600: {'6:0': {'r': 0.007843137718737125, 'g': 0.5176470875740051, 'b': 0.7803921699523926, 'a': 1}};
|
||||
--categories-color-sky-700: {'6:0': {'r': 0.0117647061124444, 'g': 0.4117647111415863, 'b': 0.6313725709915161, 'a': 1}};
|
||||
--categories-color-sky-800: {'6:0': {'r': 0.027450980618596077, 'g': 0.3490196168422699, 'b': 0.5215686559677124, 'a': 1}};
|
||||
--categories-color-sky-900: {'6:0': {'r': 0.0470588244497776, 'g': 0.29019609093666077, 'b': 0.4313725531101227, 'a': 1}};
|
||||
--categories-color-sky-950: {'6:0': {'r': 0.0313725508749485, 'g': 0.18431372940540314, 'b': 0.2862745225429535, 'a': 1}};
|
||||
--categories-color-indigo-50: {'6:0': {'r': 0.9333333373069763, 'g': 0.9490196108818054, 'b': 1, 'a': 1}};
|
||||
--categories-color-indigo-100: {'6:0': {'r': 0.8784313797950745, 'g': 0.9058823585510254, 'b': 1, 'a': 1}};
|
||||
--categories-color-indigo-200: {'6:0': {'r': 0.7803921699523926, 'g': 0.8235294222831726, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-indigo-300: {'6:0': {'r': 0.6470588445663452, 'g': 0.7058823704719543, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-indigo-400: {'6:0': {'r': 0.5058823823928833, 'g': 0.5490196347236633, 'b': 0.9725490212440491, 'a': 1}};
|
||||
--categories-color-indigo-500: {'6:0': {'r': 0.38823530077934265, 'g': 0.4000000059604645, 'b': 0.9450980424880981, 'a': 1}};
|
||||
--categories-color-indigo-600: {'6:0': {'r': 0.30980393290519714, 'g': 0.27450981736183167, 'b': 0.8980392217636108, 'a': 1}};
|
||||
--categories-color-indigo-700: {'6:0': {'r': 0.26274511218070984, 'g': 0.21960784494876862, 'b': 0.7921568751335144, 'a': 1}};
|
||||
--categories-color-indigo-800: {'6:0': {'r': 0.21568627655506134, 'g': 0.1882352977991104, 'b': 0.6392157077789307, 'a': 1}};
|
||||
--categories-color-indigo-900: {'6:0': {'r': 0.1921568661928177, 'g': 0.18039216101169586, 'b': 0.5058823823928833, 'a': 1}};
|
||||
--categories-color-indigo-950: {'6:0': {'r': 0.11764705926179886, 'g': 0.10588235408067703, 'b': 0.29411765933036804, 'a': 1}};
|
||||
--categories-color-violet-50: {'6:0': {'r': 0.9607843160629272, 'g': 0.9529411792755127, 'b': 1, 'a': 1}};
|
||||
--categories-color-violet-100: {'6:0': {'r': 0.929411768913269, 'g': 0.9137254953384399, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-violet-200: {'6:0': {'r': 0.8666666746139526, 'g': 0.8392156958580017, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-violet-300: {'6:0': {'r': 0.7686274647712708, 'g': 0.7098039388656616, 'b': 0.9921568632125854, 'a': 1}};
|
||||
--categories-color-violet-400: {'6:0': {'r': 0.6549019813537598, 'g': 0.545098066329956, 'b': 0.9803921580314636, 'a': 1}};
|
||||
--categories-color-violet-500: {'6:0': {'r': 0.545098066329956, 'g': 0.3607843220233917, 'b': 0.9647058844566345, 'a': 1}};
|
||||
--categories-color-violet-600: {'6:0': {'r': 0.48627451062202454, 'g': 0.22745098173618317, 'b': 0.929411768913269, 'a': 1}};
|
||||
--categories-color-violet-700: {'6:0': {'r': 0.4274509847164154, 'g': 0.1568627506494522, 'b': 0.8509804010391235, 'a': 1}};
|
||||
--categories-color-violet-800: {'6:0': {'r': 0.35686275362968445, 'g': 0.12941177189350128, 'b': 0.7137255072593689, 'a': 1}};
|
||||
--categories-color-violet-900: {'6:0': {'r': 0.2980392277240753, 'g': 0.11372549086809158, 'b': 0.5843137502670288, 'a': 1}};
|
||||
--categories-color-violet-950: {'6:0': {'r': 0.18039216101169586, 'g': 0.062745101749897, 'b': 0.3960784375667572, 'a': 1}};
|
||||
--categories-color-purple-50: {'6:0': {'r': 0.9803921580314636, 'g': 0.9607843160629272, 'b': 1, 'a': 1}};
|
||||
--categories-color-purple-100: {'6:0': {'r': 0.9529411792755127, 'g': 0.9098039269447327, 'b': 1, 'a': 1}};
|
||||
--categories-color-purple-200: {'6:0': {'r': 0.9137254953384399, 'g': 0.8352941274642944, 'b': 1, 'a': 1}};
|
||||
--categories-color-purple-300: {'6:0': {'r': 0.8470588326454163, 'g': 0.7058823704719543, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-purple-400: {'6:0': {'r': 0.7529411911964417, 'g': 0.5176470875740051, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-purple-500: {'6:0': {'r': 0.658823549747467, 'g': 0.3333333432674408, 'b': 0.9686274528503418, 'a': 1}};
|
||||
--categories-color-purple-600: {'6:0': {'r': 0.5764706134796143, 'g': 0.20000000298023224, 'b': 0.9176470637321472, 'a': 1}};
|
||||
--categories-color-purple-700: {'6:0': {'r': 0.4941176474094391, 'g': 0.13333334028720856, 'b': 0.8078431487083435, 'a': 1}};
|
||||
--categories-color-purple-800: {'6:0': {'r': 0.41960784792900085, 'g': 0.12941177189350128, 'b': 0.658823549747467, 'a': 1}};
|
||||
--categories-color-purple-900: {'6:0': {'r': 0.3450980484485626, 'g': 0.10980392247438431, 'b': 0.529411792755127, 'a': 1}};
|
||||
--categories-color-purple-950: {'6:0': {'r': 0.23137255012989044, 'g': 0.027450980618596077, 'b': 0.3921568691730499, 'a': 1}};
|
||||
--categories-color-fuchsia-50: {'6:0': {'r': 0.9921568632125854, 'g': 0.95686274766922, 'b': 1, 'a': 1}};
|
||||
--categories-color-fuchsia-100: {'6:0': {'r': 0.9803921580314636, 'g': 0.9098039269447327, 'b': 1, 'a': 1}};
|
||||
--categories-color-fuchsia-200: {'6:0': {'r': 0.9607843160629272, 'g': 0.8156862854957581, 'b': 0.9960784316062927, 'a': 1}};
|
||||
--categories-color-fuchsia-300: {'6:0': {'r': 0.9411764740943909, 'g': 0.6705882549285889, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-fuchsia-400: {'6:0': {'r': 0.9098039269447327, 'g': 0.4745098054409027, 'b': 0.9764705896377563, 'a': 1}};
|
||||
--categories-color-fuchsia-500: {'6:0': {'r': 0.8509804010391235, 'g': 0.27450981736183167, 'b': 0.9372549057006836, 'a': 1}};
|
||||
--categories-color-fuchsia-600: {'6:0': {'r': 0.7529411911964417, 'g': 0.14901961386203766, 'b': 0.8274509906768799, 'a': 1}};
|
||||
--categories-color-fuchsia-700: {'6:0': {'r': 0.6352941393852234, 'g': 0.10980392247438431, 'b': 0.686274528503418, 'a': 1}};
|
||||
--categories-color-fuchsia-800: {'6:0': {'r': 0.5254902243614197, 'g': 0.09803921729326248, 'b': 0.5607843399047852, 'a': 1}};
|
||||
--categories-color-fuchsia-900: {'6:0': {'r': 0.43921568989753723, 'g': 0.10196078568696976, 'b': 0.4588235318660736, 'a': 1}};
|
||||
--categories-color-fuchsia-950: {'6:0': {'r': 0.29019609093666077, 'g': 0.01568627543747425, 'b': 0.30588236451148987, 'a': 1}};
|
||||
--categories-color-pink-50: {'6:0': {'r': 0.9921568632125854, 'g': 0.9490196108818054, 'b': 0.9725490212440491, 'a': 1}};
|
||||
--categories-color-pink-100: {'6:0': {'r': 0.9882352948188782, 'g': 0.9058823585510254, 'b': 0.9529411792755127, 'a': 1}};
|
||||
--categories-color-pink-200: {'6:0': {'r': 0.9843137264251709, 'g': 0.8117647171020508, 'b': 0.9098039269447327, 'a': 1}};
|
||||
--categories-color-pink-300: {'6:0': {'r': 0.9764705896377563, 'g': 0.658823549747467, 'b': 0.8313725590705872, 'a': 1}};
|
||||
--categories-color-pink-400: {'6:0': {'r': 0.95686274766922, 'g': 0.4470588266849518, 'b': 0.7137255072593689, 'a': 1}};
|
||||
--categories-color-pink-500: {'6:0': {'r': 0.9254902005195618, 'g': 0.2823529541492462, 'b': 0.6000000238418579, 'a': 1}};
|
||||
--categories-color-pink-600: {'6:0': {'r': 0.8588235378265381, 'g': 0.15294118225574493, 'b': 0.46666666865348816, 'a': 1}};
|
||||
--categories-color-pink-700: {'6:0': {'r': 0.7450980544090271, 'g': 0.0941176488995552, 'b': 0.364705890417099, 'a': 1}};
|
||||
--categories-color-pink-800: {'6:0': {'r': 0.615686297416687, 'g': 0.09019608050584793, 'b': 0.3019607961177826, 'a': 1}};
|
||||
--categories-color-pink-900: {'6:0': {'r': 0.5137255191802979, 'g': 0.0941176488995552, 'b': 0.26274511218070984, 'a': 1}};
|
||||
--categories-color-pink-950: {'6:0': {'r': 0.3137255012989044, 'g': 0.027450980618596077, 'b': 0.1411764770746231, 'a': 1}};
|
||||
--categories-color-rose-50: {'6:0': {'r': 1, 'g': 0.9450980424880981, 'b': 0.9490196108818054, 'a': 1}};
|
||||
--categories-color-rose-100: {'6:0': {'r': 1, 'g': 0.8941176533699036, 'b': 0.9019607901573181, 'a': 1}};
|
||||
--categories-color-rose-200: {'6:0': {'r': 0.9960784316062927, 'g': 0.8039215803146362, 'b': 0.8274509906768799, 'a': 1}};
|
||||
--categories-color-rose-300: {'6:0': {'r': 0.9921568632125854, 'g': 0.6431372761726379, 'b': 0.686274528503418, 'a': 1}};
|
||||
--categories-color-rose-400: {'6:0': {'r': 0.9843137264251709, 'g': 0.4431372582912445, 'b': 0.5215686559677124, 'a': 1}};
|
||||
--categories-color-rose-500: {'6:0': {'r': 0.95686274766922, 'g': 0.24705882370471954, 'b': 0.3686274588108063, 'a': 1}};
|
||||
--categories-color-rose-600: {'6:0': {'r': 0.8823529481887817, 'g': 0.11372549086809158, 'b': 0.2823529541492462, 'a': 1}};
|
||||
--categories-color-rose-700: {'6:0': {'r': 0.7450980544090271, 'g': 0.07058823853731155, 'b': 0.23529411852359772, 'a': 1}};
|
||||
--categories-color-rose-800: {'6:0': {'r': 0.6235294342041016, 'g': 0.07058823853731155, 'b': 0.2235294133424759, 'a': 1}};
|
||||
--categories-color-rose-900: {'6:0': {'r': 0.5333333611488342, 'g': 0.07450980693101883, 'b': 0.21568627655506134, 'a': 1}};
|
||||
--categories-color-rose-950: {'6:0': {'r': 0.2980392277240753, 'g': 0.019607843831181526, 'b': 0.09803921729326248, 'a': 1}};
|
||||
--categories-color-green-50: {'6:0': {'r': 0.9411764740943909, 'g': 0.9921568632125854, 'b': 0.95686274766922, 'a': 1}};
|
||||
--categories-color-green-100: {'6:0': {'r': 0.8627451062202454, 'g': 0.9882352948188782, 'b': 0.9058823585510254, 'a': 1}};
|
||||
--categories-color-green-200: {'6:0': {'r': 0.7333333492279053, 'g': 0.9686274528503418, 'b': 0.8156862854957581, 'a': 1}};
|
||||
--categories-color-green-300: {'6:0': {'r': 0.5254902243614197, 'g': 0.9372549057006836, 'b': 0.6745098233222961, 'a': 1}};
|
||||
--categories-color-green-400: {'6:0': {'r': 0.29019609093666077, 'g': 0.8705882430076599, 'b': 0.501960813999176, 'a': 1}};
|
||||
--categories-color-green-500: {'6:0': {'r': 0.13333334028720856, 'g': 0.772549033164978, 'b': 0.3686274588108063, 'a': 1}};
|
||||
--categories-color-green-600: {'6:0': {'r': 0.08627451211214066, 'g': 0.6392157077789307, 'b': 0.29019609093666077, 'a': 1}};
|
||||
--categories-color-green-700: {'6:0': {'r': 0.08235294371843338, 'g': 0.501960813999176, 'b': 0.239215686917305, 'a': 1}};
|
||||
--categories-color-green-800: {'6:0': {'r': 0.08627451211214066, 'g': 0.3960784375667572, 'b': 0.20392157137393951, 'a': 1}};
|
||||
--categories-color-green-900: {'6:0': {'r': 0.0784313753247261, 'g': 0.32549020648002625, 'b': 0.1764705926179886, 'a': 1}};
|
||||
--categories-color-green-950: {'6:0': {'r': 0.019607843831181526, 'g': 0.18039216101169586, 'b': 0.08627451211214066, 'a': 1}};
|
||||
--categories-color-yellow-50: {'6:0': {'r': 0.9960784316062927, 'g': 0.9882352948188782, 'b': 0.9098039269447327, 'a': 1}};
|
||||
--categories-color-yellow-100: {'6:0': {'r': 0.9960784316062927, 'g': 0.9764705896377563, 'b': 0.7647058963775635, 'a': 1}};
|
||||
--categories-color-yellow-200: {'6:0': {'r': 0.9960784316062927, 'g': 0.9411764740943909, 'b': 0.5411764979362488, 'a': 1}};
|
||||
--categories-color-yellow-300: {'6:0': {'r': 0.9921568632125854, 'g': 0.8784313797950745, 'b': 0.27843138575553894, 'a': 1}};
|
||||
--categories-color-yellow-400: {'6:0': {'r': 0.9803921580314636, 'g': 0.800000011920929, 'b': 0.08235294371843338, 'a': 1}};
|
||||
--categories-color-yellow-500: {'6:0': {'r': 0.9176470637321472, 'g': 0.7019608020782471, 'b': 0.0313725508749485, 'a': 1}};
|
||||
--categories-color-yellow-600: {'6:0': {'r': 0.7921568751335144, 'g': 0.5411764979362488, 'b': 0.01568627543747425, 'a': 1}};
|
||||
--categories-color-yellow-700: {'6:0': {'r': 0.6313725709915161, 'g': 0.3843137323856354, 'b': 0.027450980618596077, 'a': 1}};
|
||||
--categories-color-yellow-800: {'6:0': {'r': 0.5215686559677124, 'g': 0.3019607961177826, 'b': 0.054901961237192154, 'a': 1}};
|
||||
--categories-color-yellow-900: {'6:0': {'r': 0.4431372582912445, 'g': 0.24705882370471954, 'b': 0.07058823853731155, 'a': 1}};
|
||||
--categories-color-yellow-950: {'6:0': {'r': 0.25882354378700256, 'g': 0.125490203499794, 'b': 0.0235294122248888, 'a': 1}};
|
||||
--categories-color-general-background: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5103'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-general-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30321'}};
|
||||
--categories-color-general-primary: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30320'}};
|
||||
--categories-color-general-primary-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30311'}};
|
||||
--categories-color-general-secondary: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:109'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30312'}};
|
||||
--categories-color-general-secondary-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30320'}};
|
||||
--categories-color-general-accent: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30312'}};
|
||||
--categories-color-general-accent-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30320'}};
|
||||
--categories-color-general-muted: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30312'}};
|
||||
--categories-color-general-muted-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:105'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30316'}};
|
||||
--categories-color-general-destructive: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1997'}, '618:1': {'r': 0.6196078658103943, 'g': 0.250980406999588, 'b': 0.25882354378700256, 'a': 1}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1997'}};
|
||||
--categories-color-general-border: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:103'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30313'}};
|
||||
--categories-color-general-input: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '618:1': {'r': 1, 'g': 1, 'b': 1, 'a': 0.05000000074505806}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-card-card: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-card-card-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30321'}};
|
||||
--categories-color-popover-popover: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5103'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5103'}};
|
||||
--categories-color-popover-popover-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5103'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-unofficial-foreground-alt: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30318'}};
|
||||
--categories-color-unofficial-body-background: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-unofficial-destructive-border: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1996'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1996'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1996'}};
|
||||
--categories-color-unofficial-destructive-subtle: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1991'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:2001'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1991'}};
|
||||
--categories-color-unofficial-contrast-(deprecated): {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5103'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5103'}};
|
||||
--categories-color-unofficial-backdrop: {'618:0': {'r': 0, 'g': 0, 'b': 0, 'a': 0.6000000238418579}, '618:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.6000000238418579}, '847:4': {'r': 0.20000000298023224, 'g': 0.2549019753932953, 'b': 0.3333333432674408, 'a': 0.6000000238418579}};
|
||||
--categories-color-unofficial-mid-(deprecated): {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30316'}};
|
||||
--categories-color-unofficial-mid-alt: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:107'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:105'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30317'}};
|
||||
--categories-color-unofficial-destructive-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1997'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1995'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1997'}};
|
||||
--categories-color-unofficial-ghost-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:103'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30318'}};
|
||||
--categories-color-unofficial-ghost: {'618:0': {'r': 1, 'g': 1, 'b': 1, 'a': 9.999999747378752e-05}, '618:1': {'r': 1, 'g': 1, 'b': 1, 'a': 9.999999747378752e-05}, '847:4': {'r': 1, 'g': 1, 'b': 1, 'a': 9.999999747378752e-05}};
|
||||
--categories-color-unofficial-ghost-hover: {'618:0': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}, '618:1': {'r': 1, 'g': 1, 'b': 1, 'a': 0.10000000149011612}, '847:4': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}};
|
||||
--categories-color-unofficial-primary-hover: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30318'}};
|
||||
--categories-color-unofficial-secondary-hover: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30311'}};
|
||||
--categories-color-unofficial-outline: {'618:0': {'r': 1, 'g': 1, 'b': 1, 'a': 0.10000000149011612}, '618:1': {'r': 1, 'g': 1, 'b': 1, 'a': 0.05000000074505806}, '847:4': {'r': 1, 'g': 1, 'b': 1, 'a': 0.10000000149011612}};
|
||||
--categories-color-unofficial-outline-hover: {'618:0': {'r': 0, 'g': 0, 'b': 0, 'a': 0.0333000011742115}, '618:1': {'r': 1, 'g': 1, 'b': 1, 'a': 0.10000000149011612}, '847:4': {'r': 0.20000000298023224, 'g': 0.2549019753932953, 'b': 0.3333333432674408, 'a': 0.0333000011742115}};
|
||||
--categories-color-unofficial-outline-active: {'618:0': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}, '618:1': {'r': 1, 'g': 1, 'b': 1, 'a': 0.15000000596046448}, '847:4': {'r': 0.20000000298023224, 'g': 0.2549019753932953, 'b': 0.3333333432674408, 'a': 0.05000000074505806}};
|
||||
--categories-color-unofficial-accent-0: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30311'}};
|
||||
--categories-color-unofficial-accent-2: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:103'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:109'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30313'}};
|
||||
--categories-color-unofficial-accent-3: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30314'}};
|
||||
--categories-color-unofficial-border-0: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30311'}};
|
||||
--categories-color-unofficial-border-1: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30312'}};
|
||||
--categories-color-unofficial-border-3: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30314'}};
|
||||
--categories-color-unofficial-border-4: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:105'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:107'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30315'}};
|
||||
--categories-color-unofficial-border-5: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30316'}};
|
||||
--categories-color-focus-ring: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30314'}};
|
||||
--categories-color-sidebar-sidebar: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:111'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30312'}};
|
||||
--categories-color-sidebar-sidebar-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30318'}};
|
||||
--categories-color-sidebar-sidebar-accent: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30313'}};
|
||||
--categories-color-sidebar-sidebar-accent-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:102'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30318'}};
|
||||
--categories-color-sidebar-sidebar-primary: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30320'}};
|
||||
--categories-color-sidebar-sidebar-primary-foreground: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:101'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:110'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30311'}};
|
||||
--categories-color-sidebar-sidebar-border: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:103'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:109'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30313'}};
|
||||
--categories-color-sidebar-sidebar-ring: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:104'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:108'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30314'}};
|
||||
--categories-color-sidebar-unofficial-sidebar-muted: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '618:1': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:6:106'}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30316'}};
|
||||
--categories-color-focus-ring-error: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1994'}, '618:1': {'r': 0.4274509847164154, 'g': 0.18039216101169586, 'b': 0.18431372940540314, 'a': 1}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:1994'}};
|
||||
--categories-color-chart-legacy-chart-1: {'618:0': {'r': 0.9607843160629272, 'g': 0.29019609093666077, 'b': 0, 'a': 1}, '618:1': {'r': 0.0784313753247261, 'g': 0.27843138575553894, 'b': 0.9019607901573181, 'a': 1}, '847:4': {'r': 0.12156862765550613, 'g': 0.46666666865348816, 'b': 0.7058823704719543, 'a': 1}};
|
||||
--categories-color-chart-legacy-chart-2: {'618:0': {'r': 0, 'g': 0.5882353186607361, 'b': 0.5372549295425415, 'a': 1}, '618:1': {'r': 0, 'g': 0.7372549176216125, 'b': 0.4901960790157318, 'a': 1}, '847:4': {'r': 1, 'g': 0.49803921580314636, 'b': 0.054901961237192154, 'a': 1}};
|
||||
--categories-color-chart-legacy-chart-3: {'618:0': {'r': 0.062745101749897, 'g': 0.30588236451148987, 'b': 0.3921568691730499, 'a': 1}, '618:1': {'r': 0.9921568632125854, 'g': 0.6039215922355652, 'b': 0, 'a': 1}, '847:4': {'r': 0.1725490242242813, 'g': 0.6274510025978088, 'b': 0.1725490242242813, 'a': 1}};
|
||||
--categories-color-chart-legacy-chart-4: {'618:0': {'r': 1, 'g': 0.7254902124404907, 'b': 0, 'a': 1}, '618:1': {'r': 0.6784313917160034, 'g': 0.27450981736183167, 'b': 1, 'a': 1}, '847:4': {'r': 0.8392156958580017, 'g': 0.15294118225574493, 'b': 0.1568627506494522, 'a': 1}};
|
||||
--categories-color-chart-legacy-chart-5: {'618:0': {'r': 0.9960784316062927, 'g': 0.6039215922355652, 'b': 0, 'a': 1}, '618:1': {'r': 1, 'g': 0.125490203499794, 'b': 0.33725491166114807, 'a': 1}, '847:4': {'r': 0.5803921818733215, 'g': 0.40392157435417175, 'b': 0.7411764860153198, 'a': 1}};
|
||||
--categories-color-chart-area-orange-fill: {'618:0': {'r': 0.9920479655265808, 'g': 0.8146340250968933, 'b': 0.6118749976158142, 'a': 0.699999988079071}, '618:1': {'r': 0.4588235318660736, 'g': 0.34117648005485535, 'b': 0.21960784494876862, 'a': 0.699999988079071}, '847:4': {'r': 0.9920479655265808, 'g': 0.8146340250968933, 'b': 0.6118749976158142, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-orange-fill-2: {'618:0': {'r': 0.9708533883094788, 'g': 0.690407395362854, 'b': 0.49409517645835876, 'a': 0.699999988079071}, '618:1': {'r': 0.4627451002597809, 'g': 0.21960784494876862, 'b': 0.054901961237192154, 'a': 0.699999988079071}, '847:4': {'r': 0.9708533883094788, 'g': 0.690407395362854, 'b': 0.49409517645835876, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-orange-stroke: {'618:0': {'r': 1, 'g': 0.7215686440467834, 'b': 0.4156862795352936, 'a': 1}, '618:1': {'r': 1, 'g': 0.7215686440467834, 'b': 0.4156862795352936, 'a': 1}, '847:4': {'r': 1, 'g': 0.7215686440467834, 'b': 0.4156862795352936, 'a': 1}};
|
||||
--categories-color-chart-area-orange-stroke-2: {'618:0': {'r': 1, 'g': 0.4117647111415863, 'b': 0, 'a': 1}, '618:1': {'r': 1, 'g': 0.4745098054409027, 'b': 0.08235294371843338, 'a': 1}, '847:4': {'r': 1, 'g': 0.4117647111415863, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-area-blue-fill: {'618:0': {'r': 0.7496619820594788, 'g': 0.8687251806259155, 'b': 1, 'a': 0.699999988079071}, '618:1': {'r': 0.27843138575553894, 'g': 0.364705890417099, 'b': 0.4588235318660736, 'a': 0.699999988079071}, '847:4': {'r': 0.7496619820594788, 'g': 0.8687251806259155, 'b': 1, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-blue-stroke: {'618:0': {'r': 0.5568627715110779, 'g': 0.772549033164978, 'b': 1, 'a': 1}, '618:1': {'r': 0.5568627715110779, 'g': 0.772549033164978, 'b': 1, 'a': 1}, '847:4': {'r': 0.5568627715110779, 'g': 0.772549033164978, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-area-blue-fill-2: {'618:0': {'r': 0.6666666865348816, 'g': 0.800000011920929, 'b': 1, 'a': 0.699999988079071}, '618:1': {'r': 0.12156862765550613, 'g': 0.2549019753932953, 'b': 0.4627451002597809, 'a': 0.699999988079071}, '847:4': {'r': 0.6666666865348816, 'g': 0.800000011920929, 'b': 1, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-blue-stroke-2: {'618:0': {'r': 0.24705882370471954, 'g': 0.5529412031173706, 'b': 1, 'a': 1}, '618:1': {'r': 0.32549020648002625, 'g': 0.6078431606292725, 'b': 1, 'a': 1}, '847:4': {'r': 0.24705882370471954, 'g': 0.5529412031173706, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-area-green-fill: {'618:0': {'r': 0.7252106666564941, 'g': 0.9840070009231567, 'b': 0.822259247303009, 'a': 0.699999988079071}, '618:1': {'r': 0.24705882370471954, 'g': 0.4313725531101227, 'b': 0.3176470696926117, 'a': 0.699999988079071}, '847:4': {'r': 0.7252106666564941, 'g': 0.9840070009231567, 'b': 0.822259247303009, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-green-stroke: {'618:0': {'r': 0.48235294222831726, 'g': 0.9450980424880981, 'b': 0.6687395572662354, 'a': 1}, '618:1': {'r': 0.48235294222831726, 'g': 0.9450980424880981, 'b': 0.658823549747467, 'a': 1}, '847:4': {'r': 0.48235294222831726, 'g': 0.9450980424880981, 'b': 0.6687395572662354, 'a': 1}};
|
||||
--categories-color-chart-area-green-fill-2: {'618:0': {'r': 0.5098943710327148, 'g': 0.8876308798789978, 'b': 0.660988986492157, 'a': 0.699999988079071}, '618:1': {'r': 0.054901961237192154, 'g': 0.3686274588108063, 'b': 0.18039216101169586, 'a': 0.699999988079071}, '847:4': {'r': 0.5098943710327148, 'g': 0.8876308798789978, 'b': 0.660988986492157, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-green-stroke-2: {'618:0': {'r': 0.09803921729326248, 'g': 0.8196078538894653, 'b': 0.38823530077934265, 'a': 1}, '618:1': {'r': 0.09803921729326248, 'g': 0.8196078538894653, 'b': 0.38823530077934265, 'a': 1}, '847:4': {'r': 0.09803921729326248, 'g': 0.8196078538894653, 'b': 0.38823530077934265, 'a': 1}};
|
||||
--categories-color-chart-area-rose-fill: {'618:0': {'r': 1, 'g': 0.8509804010391235, 'b': 0.8705882430076599, 'a': 0.699999988079071}, '618:1': {'r': 0.4588235318660736, 'g': 0.30588236451148987, 'b': 0.32549020648002625, 'a': 0.699999988079071}, '847:4': {'r': 1, 'g': 0.8509804010391235, 'b': 0.8705882430076599, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-rose-stroke: {'618:0': {'r': 1, 'g': 0.6313725709915161, 'b': 0.6784313917160034, 'a': 1}, '618:1': {'r': 1, 'g': 0.6313725709915161, 'b': 0.6784313917160034, 'a': 1}, '847:4': {'r': 1, 'g': 0.6313725709915161, 'b': 0.6784313917160034, 'a': 1}};
|
||||
--categories-color-chart-area-rose-fill-2: {'618:0': {'r': 0.9578414559364319, 'g': 0.5668655633926392, 'b': 0.6601600646972656, 'a': 0.699999988079071}, '618:1': {'r': 0.45490196347236633, 'g': 0.10588235408067703, 'b': 0.1882352977991104, 'a': 0.699999988079071}, '847:4': {'r': 0.9578414559364319, 'g': 0.5668655633926392, 'b': 0.6601600646972656, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-rose-stroke-2: {'618:0': {'r': 1, 'g': 0.30980393290519714, 'b': 0.4745098054409027, 'a': 1}, '618:1': {'r': 1, 'g': 0.27450981736183167, 'b': 0.43921568989753723, 'a': 1}, '847:4': {'r': 1, 'g': 0.30980393290519714, 'b': 0.4745098054409027, 'a': 1}};
|
||||
--categories-color-chart-area-teal-fill: {'618:0': {'r': 0.6622663736343384, 'g': 0.9549305438995361, 'b': 0.9112495183944702, 'a': 0.699999988079071}, '618:1': {'r': 0.2528286874294281, 'g': 0.5769955515861511, 'b': 0.5327908992767334, 'a': 0.699999988079071}, '847:4': {'r': 0.6622663736343384, 'g': 0.9549305438995361, 'b': 0.9112495183944702, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-teal-stroke: {'618:0': {'r': 0.27450981736183167, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}, '618:1': {'r': 0.27450981736183167, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}, '847:4': {'r': 0.27450981736183167, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}};
|
||||
--categories-color-chart-area-teal-fill-2: {'618:0': {'r': 0.4880538582801819, 'g': 0.9056999683380127, 'b': 0.8611510396003723, 'a': 0.699999988079071}, '618:1': {'r': 0.054901961237192154, 'g': 0.3490196168422699, 'b': 0.3176470696926117, 'a': 0.699999988079071}, '847:4': {'r': 0.4880538582801819, 'g': 0.9056999683380127, 'b': 0.8611510396003723, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-teal-stroke-2: {'618:0': {'r': 0.027450980618596077, 'g': 0.7529411911964417, 'b': 0.6745098233222961, 'a': 1}, '618:1': {'r': 0.10980392247438431, 'g': 0.8117647171020508, 'b': 0.7254902124404907, 'a': 1}, '847:4': {'r': 0.027450980618596077, 'g': 0.7529411911964417, 'b': 0.6745098233222961, 'a': 1}};
|
||||
--categories-color-chart-area-purple-fill: {'618:0': {'r': 0.9411764740943909, 'g': 0.8784313797950745, 'b': 1, 'a': 0.699999988079071}, '618:1': {'r': 0.3960784375667572, 'g': 0.3333333432674408, 'b': 0.4627451002597809, 'a': 0.699999988079071}, '847:4': {'r': 0.9411764740943909, 'g': 0.8784313797950745, 'b': 1, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-purple-stroke: {'618:0': {'r': 0.8549019694328308, 'g': 0.6980392336845398, 'b': 1, 'a': 1}, '618:1': {'r': 0.8549019694328308, 'g': 0.6980392336845398, 'b': 1, 'a': 1}, '847:4': {'r': 0.8549019694328308, 'g': 0.6980392336845398, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-area-purple-fill-2: {'618:0': {'r': 0.8705882430076599, 'g': 0.7098039388656616, 'b': 1, 'a': 0.699999988079071}, '618:1': {'r': 0.32549020648002625, 'g': 0.16470588743686676, 'b': 0.46666666865348816, 'a': 0.699999988079071}, '847:4': {'r': 0.8705882430076599, 'g': 0.7098039388656616, 'b': 1, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-purple-stroke-2: {'618:0': {'r': 0.7764706015586853, 'g': 0.4941176474094391, 'b': 1, 'a': 1}, '618:1': {'r': 0.6627451181411743, 'g': 0.4156862795352936, 'b': 0.8666666746139526, 'a': 1}, '847:4': {'r': 0.7764706015586853, 'g': 0.4941176474094391, 'b': 1, 'a': 1}};
|
||||
--categories-color-obra-shadn-docs-obra-shadcn-ui-docs-1: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:16:2015'}, '618:1': {'r': 0.06697625666856766, 'g': 0.08797097951173782, 'b': 0.15845328569412231, 'a': 1}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-chart-area-amber-fill: {'618:0': {'r': 1, 'g': 0.929411768913269, 'b': 0.6745098233222961, 'a': 0.699999988079071}, '618:1': {'r': 0.45490196347236633, 'g': 0.3843137323856354, 'b': 0.12941177189350128, 'a': 0.699999988079071}, '847:4': {'r': 1, 'g': 0.929411768913269, 'b': 0.6745098233222961, 'a': 0.699999988079071}};
|
||||
--categories-color-obra-shadn-docs-obra-shadcn-ui-docs-2: {'618:0': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:534:30510'}, '618:1': {'r': 0.12658406794071198, 'g': 0.11423555761575699, 'b': 0.10479257255792618, 'a': 1}, '847:4': {'type': 'VARIABLE_ALIAS', 'id': 'VariableID:55:5102'}};
|
||||
--categories-color-chart-area-amber-stroke: {'618:0': {'r': 1, 'g': 0.8235294222831726, 'b': 0.1882352977991104, 'a': 1}, '618:1': {'r': 1, 'g': 0.8235294222831726, 'b': 0.1882352977991104, 'a': 1}, '847:4': {'r': 1, 'g': 0.8235294222831726, 'b': 0.1882352977991104, 'a': 1}};
|
||||
--categories-color-chart-area-amber-fill-2: {'618:0': {'r': 0.9960784316062927, 'g': 0.8392156958580017, 'b': 0.6000000238418579, 'a': 0.699999988079071}, '618:1': {'r': 0.45098039507865906, 'g': 0.29411765933036804, 'b': 0.054901961237192154, 'a': 0.699999988079071}, '847:4': {'r': 0.9960784316062927, 'g': 0.8392156958580017, 'b': 0.6000000238418579, 'a': 0.699999988079071}};
|
||||
--categories-color-chart-area-amber-stroke-2: {'618:0': {'r': 0.9960784316062927, 'g': 0.6039215922355652, 'b': 0, 'a': 1}, '618:1': {'r': 1, 'g': 0.6470588445663452, 'b': 0.03921568766236305, 'a': 1}, '847:4': {'r': 0.9960784316062927, 'g': 0.6039215922355652, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-blue-1: {'618:0': {'r': 0.5568627715110779, 'g': 0.772549033164978, 'b': 1, 'a': 1}, '618:1': {'r': 0.5568627715110779, 'g': 0.772549033164978, 'b': 1, 'a': 1}, '847:4': {'r': 0.5568627715110779, 'g': 0.772549033164978, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-static-rose-1: {'618:0': {'r': 1, 'g': 0.6313725709915161, 'b': 0.6784313917160034, 'a': 1}, '618:1': {'r': 1, 'g': 0.6313725709915161, 'b': 0.6784313917160034, 'a': 1}, '847:4': {'r': 1, 'g': 0.6313725709915161, 'b': 0.6784313917160034, 'a': 1}};
|
||||
--categories-color-chart-static-rose-2: {'618:0': {'r': 1, 'g': 0.125490203499794, 'b': 0.33725491166114807, 'a': 1}, '618:1': {'r': 1, 'g': 0.125490203499794, 'b': 0.33725491166114807, 'a': 1}, '847:4': {'r': 1, 'g': 0.125490203499794, 'b': 0.33725491166114807, 'a': 1}};
|
||||
--categories-color-chart-static-rose-3: {'618:0': {'r': 0.9254902005195618, 'g': 0, 'b': 0.24705882370471954, 'a': 1}, '618:1': {'r': 0.9254902005195618, 'g': 0, 'b': 0.24705882370471954, 'a': 1}, '847:4': {'r': 0.9254902005195618, 'g': 0, 'b': 0.24705882370471954, 'a': 1}};
|
||||
--categories-color-chart-static-rose-4: {'618:0': {'r': 0.7803921699523926, 'g': 0, 'b': 0.21176470816135406, 'a': 1}, '618:1': {'r': 0.7803921699523926, 'g': 0, 'b': 0.21176470816135406, 'a': 1}, '847:4': {'r': 0.7803921699523926, 'g': 0, 'b': 0.21176470816135406, 'a': 1}};
|
||||
--categories-color-chart-static-rose-5: {'618:0': {'r': 0.6470588445663452, 'g': 0, 'b': 0.21176470816135406, 'a': 1}, '618:1': {'r': 0.6470588445663452, 'g': 0, 'b': 0.21176470816135406, 'a': 1}, '847:4': {'r': 0.6470588445663452, 'g': 0, 'b': 0.21176470816135406, 'a': 1}};
|
||||
--categories-color-chart-static-purple-1: {'618:0': {'r': 0.8549019694328308, 'g': 0.6980392336845398, 'b': 1, 'a': 1}, '618:1': {'r': 0.8549019694328308, 'g': 0.6980392336845398, 'b': 1, 'a': 1}, '847:4': {'r': 0.8549019694328308, 'g': 0.6980392336845398, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-static-purple-2: {'618:0': {'r': 0.6784313917160034, 'g': 0.27450981736183167, 'b': 1, 'a': 1}, '618:1': {'r': 0.6784313917160034, 'g': 0.27450981736183167, 'b': 1, 'a': 1}, '847:4': {'r': 0.6784313917160034, 'g': 0.27450981736183167, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-static-purple-3: {'618:0': {'r': 0.5960784554481506, 'g': 0.062745101749897, 'b': 0.9803921580314636, 'a': 1}, '618:1': {'r': 0.5960784554481506, 'g': 0.062745101749897, 'b': 0.9803921580314636, 'a': 1}, '847:4': {'r': 0.5960784554481506, 'g': 0.062745101749897, 'b': 0.9803921580314636, 'a': 1}};
|
||||
--categories-color-chart-static-purple-4: {'618:0': {'r': 0.5098039507865906, 'g': 0, 'b': 0.8588235378265381, 'a': 1}, '618:1': {'r': 0.5098039507865906, 'g': 0, 'b': 0.8588235378265381, 'a': 1}, '847:4': {'r': 0.5098039507865906, 'g': 0, 'b': 0.8588235378265381, 'a': 1}};
|
||||
--categories-color-chart-static-purple-5: {'618:0': {'r': 0.4313725531101227, 'g': 0.06666667014360428, 'b': 0.6901960968971252, 'a': 1}, '618:1': {'r': 0.4313725531101227, 'g': 0.06666667014360428, 'b': 0.6901960968971252, 'a': 1}, '847:4': {'r': 0.4313725531101227, 'g': 0.06666667014360428, 'b': 0.6901960968971252, 'a': 1}};
|
||||
--categories-color-chart-static-orange-1: {'618:0': {'r': 1, 'g': 0.7215686440467834, 'b': 0.4156862795352936, 'a': 1}, '618:1': {'r': 1, 'g': 0.7215686440467834, 'b': 0.4156862795352936, 'a': 1}, '847:4': {'r': 1, 'g': 0.7215686440467834, 'b': 0.4156862795352936, 'a': 1}};
|
||||
--categories-color-chart-static-orange-2: {'618:0': {'r': 1, 'g': 0.4117647111415863, 'b': 0, 'a': 1}, '618:1': {'r': 1, 'g': 0.4117647111415863, 'b': 0, 'a': 1}, '847:4': {'r': 1, 'g': 0.4117647111415863, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-orange-3: {'618:0': {'r': 0.9607843160629272, 'g': 0.29019609093666077, 'b': 0, 'a': 1}, '618:1': {'r': 0.9607843160629272, 'g': 0.29019609093666077, 'b': 0, 'a': 1}, '847:4': {'r': 0.9607843160629272, 'g': 0.29019609093666077, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-orange-4: {'618:0': {'r': 0.7921568751335144, 'g': 0.2078431397676468, 'b': 0, 'a': 1}, '618:1': {'r': 0.7921568751335144, 'g': 0.2078431397676468, 'b': 0, 'a': 1}, '847:4': {'r': 0.7921568751335144, 'g': 0.2078431397676468, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-orange-5: {'618:0': {'r': 0.6235294342041016, 'g': 0.1764705926179886, 'b': 0, 'a': 1}, '618:1': {'r': 0.6235294342041016, 'g': 0.1764705926179886, 'b': 0, 'a': 1}, '847:4': {'r': 0.6235294342041016, 'g': 0.1764705926179886, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-teal-1: {'618:0': {'r': 0.27450981736183167, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}, '618:1': {'r': 0.27450981736183167, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}, '847:4': {'r': 0.27450981736183167, 'g': 0.929411768913269, 'b': 0.8352941274642944, 'a': 1}};
|
||||
--categories-color-chart-static-teal-2: {'618:0': {'r': 0, 'g': 0.7333333492279053, 'b': 0.6549019813537598, 'a': 1}, '618:1': {'r': 0, 'g': 0.7333333492279053, 'b': 0.6549019813537598, 'a': 1}, '847:4': {'r': 0, 'g': 0.7333333492279053, 'b': 0.6549019813537598, 'a': 1}};
|
||||
--categories-color-chart-static-teal-3: {'618:0': {'r': 0, 'g': 0.5882353186607361, 'b': 0.5372549295425415, 'a': 1}, '618:1': {'r': 0, 'g': 0.5882353186607361, 'b': 0.5372549295425415, 'a': 1}, '847:4': {'r': 0, 'g': 0.5882353186607361, 'b': 0.5372549295425415, 'a': 1}};
|
||||
--categories-color-chart-static-teal-4: {'618:0': {'r': 0, 'g': 0.47058823704719543, 'b': 0.43529412150382996, 'a': 1}, '618:1': {'r': 0, 'g': 0.47058823704719543, 'b': 0.43529412150382996, 'a': 1}, '847:4': {'r': 0, 'g': 0.47058823704719543, 'b': 0.43529412150382996, 'a': 1}};
|
||||
--categories-color-chart-static-teal-5: {'618:0': {'r': 0, 'g': 0.37254902720451355, 'b': 0.3529411852359772, 'a': 1}, '618:1': {'r': 0, 'g': 0.37254902720451355, 'b': 0.3529411852359772, 'a': 1}, '847:4': {'r': 0, 'g': 0.37254902720451355, 'b': 0.3529411852359772, 'a': 1}};
|
||||
--categories-color-chart-static-blue-2: {'618:0': {'r': 0.16862745583057404, 'g': 0.49803921580314636, 'b': 1, 'a': 1}, '618:1': {'r': 0.16862745583057404, 'g': 0.49803921580314636, 'b': 1, 'a': 1}, '847:4': {'r': 0.16862745583057404, 'g': 0.49803921580314636, 'b': 1, 'a': 1}};
|
||||
--categories-color-chart-static-blue-3: {'618:0': {'r': 0.08235294371843338, 'g': 0.364705890417099, 'b': 0.9882352948188782, 'a': 1}, '618:1': {'r': 0.08235294371843338, 'g': 0.364705890417099, 'b': 0.9882352948188782, 'a': 1}, '847:4': {'r': 0.08235294371843338, 'g': 0.364705890417099, 'b': 0.9882352948188782, 'a': 1}};
|
||||
--categories-color-chart-static-blue-4: {'618:0': {'r': 0.0784313753247261, 'g': 0.27843138575553894, 'b': 0.9019607901573181, 'a': 1}, '618:1': {'r': 0.0784313753247261, 'g': 0.27843138575553894, 'b': 0.9019607901573181, 'a': 1}, '847:4': {'r': 0.0784313753247261, 'g': 0.27843138575553894, 'b': 0.9019607901573181, 'a': 1}};
|
||||
--categories-color-chart-static-blue-5: {'618:0': {'r': 0.09803921729326248, 'g': 0.23529411852359772, 'b': 0.7215686440467834, 'a': 1}, '618:1': {'r': 0.09803921729326248, 'g': 0.23529411852359772, 'b': 0.7215686440467834, 'a': 1}, '847:4': {'r': 0.09803921729326248, 'g': 0.23529411852359772, 'b': 0.7215686440467834, 'a': 1}};
|
||||
--categories-color-chart-static-amber-1: {'618:0': {'r': 1, 'g': 0.8235294222831726, 'b': 0.1882352977991104, 'a': 1}, '618:1': {'r': 1, 'g': 0.8235294222831726, 'b': 0.1882352977991104, 'a': 1}, '847:4': {'r': 1, 'g': 0.8235294222831726, 'b': 0.1882352977991104, 'a': 1}};
|
||||
--categories-color-chart-static-amber-2: {'618:0': {'r': 0.9960784316062927, 'g': 0.6039215922355652, 'b': 0, 'a': 1}, '618:1': {'r': 0.9960784316062927, 'g': 0.6039215922355652, 'b': 0, 'a': 1}, '847:4': {'r': 0.9960784316062927, 'g': 0.6039215922355652, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-amber-3: {'618:0': {'r': 0.8823529481887817, 'g': 0.4431372582912445, 'b': 0, 'a': 1}, '618:1': {'r': 0.8823529481887817, 'g': 0.4431372582912445, 'b': 0, 'a': 1}, '847:4': {'r': 0.8823529481887817, 'g': 0.4431372582912445, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-amber-4: {'618:0': {'r': 0.7333333492279053, 'g': 0.3019607961177826, 'b': 0, 'a': 1}, '618:1': {'r': 0.7333333492279053, 'g': 0.3019607961177826, 'b': 0, 'a': 1}, '847:4': {'r': 0.7333333492279053, 'g': 0.3019607961177826, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-amber-5: {'618:0': {'r': 0.5921568870544434, 'g': 0.23529411852359772, 'b': 0, 'a': 1}, '618:1': {'r': 0.5921568870544434, 'g': 0.23529411852359772, 'b': 0, 'a': 1}, '847:4': {'r': 0.5921568870544434, 'g': 0.23529411852359772, 'b': 0, 'a': 1}};
|
||||
--categories-color-chart-static-green-1: {'618:0': {'r': 0.48235294222831726, 'g': 0.9450980424880981, 'b': 0.658823549747467, 'a': 1}, '618:1': {'r': 0.48235294222831726, 'g': 0.9450980424880981, 'b': 0.658823549747467, 'a': 1}, '847:4': {'r': 0.48235294222831726, 'g': 0.9450980424880981, 'b': 0.658823549747467, 'a': 1}};
|
||||
--categories-color-chart-static-green-2: {'618:0': {'r': 0, 'g': 0.7882353067398071, 'b': 0.3176470696926117, 'a': 1}, '618:1': {'r': 0, 'g': 0.7882353067398071, 'b': 0.3176470696926117, 'a': 1}, '847:4': {'r': 0, 'g': 0.7882353067398071, 'b': 0.3176470696926117, 'a': 1}};
|
||||
--categories-color-chart-static-green-3: {'618:0': {'r': 0, 'g': 0.6509804129600525, 'b': 0.24313725531101227, 'a': 1}, '618:1': {'r': 0, 'g': 0.6509804129600525, 'b': 0.24313725531101227, 'a': 1}, '847:4': {'r': 0, 'g': 0.6509804129600525, 'b': 0.24313725531101227, 'a': 1}};
|
||||
--categories-color-chart-static-green-4: {'618:0': {'r': 0, 'g': 0.5098039507865906, 'b': 0.21176470816135406, 'a': 1}, '618:1': {'r': 0, 'g': 0.5098039507865906, 'b': 0.21176470816135406, 'a': 1}, '847:4': {'r': 0, 'g': 0.5098039507865906, 'b': 0.21176470816135406, 'a': 1}};
|
||||
--categories-color-chart-static-green-5: {'618:0': {'r': 0.003921568859368563, 'g': 0.4000000059604645, 'b': 0.1882352977991104, 'a': 1}, '618:1': {'r': 0.003921568859368563, 'g': 0.4000000059604645, 'b': 0.1882352977991104, 'a': 1}, '847:4': {'r': 0.003921568859368563, 'g': 0.4000000059604645, 'b': 0.1882352977991104, 'a': 1}};
|
||||
--categories-color-2xs-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}};
|
||||
--categories-color-xs-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.05000000074505806}};
|
||||
--categories-color-sm-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}};
|
||||
--categories-color-md-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}};
|
||||
--categories-color-lg-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}};
|
||||
--categories-color-xl-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.10000000149011612}};
|
||||
--categories-color-2xl-color: {'848:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.25}, '851:1': {'r': 0, 'g': 0, 'b': 0, 'a': 0.25}};
|
||||
--categories-typography-heading-1: None;
|
||||
--categories-typography-heading-2: None;
|
||||
--categories-typography-paragraph-small-regular: None;
|
||||
--categories-typography-paragraph-small-bold: None;
|
||||
--categories-typography-heading-3: None;
|
||||
--categories-typography-paragraph-bold: None;
|
||||
--categories-typography-paragraph-regular: None;
|
||||
--categories-typography-paragraph-mini-regular: None;
|
||||
--categories-typography-heading-4: None;
|
||||
--categories-typography-monospaced: None;
|
||||
--categories-typography-paragraph-medium: None;
|
||||
--categories-typography-paragraph-small-medium: None;
|
||||
--categories-typography-paragraph-mini-bold: None;
|
||||
--categories-typography-paragraph-mini-medium: None;
|
||||
--categories-effect-shadow-sm: None;
|
||||
--categories-effect-shadow-lg: None;
|
||||
--categories-effect-shadow-2xs: None;
|
||||
--categories-effect-shadow-xs: None;
|
||||
--categories-effect-shadow-md: None;
|
||||
--categories-effect-shadow-xl: None;
|
||||
--categories-effect-shadow-2xl: None;
|
||||
--categories-effect-focus-ring: None;
|
||||
--categories-effect-focus-ring-error: None;
|
||||
--categories-effect-focus-ring-sidebar: None;
|
||||
}
|
||||
`;
|
||||
|
||||
// Add styles to document
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.textContent = tokenStyles;
|
||||
document.head.appendChild(styleSheet);
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
backgrounds: {
|
||||
default: 'light',
|
||||
values: [
|
||||
{ name: 'light', value: '#FFFFFF' },
|
||||
{ name: 'dark', value: '#1F2937' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
@@ -1,211 +0,0 @@
|
||||
"""
|
||||
Design System Server (DSS) - Consolidated Platform
|
||||
A modern design system orchestration platform with comprehensive tooling
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0-consolidated"
|
||||
|
||||
# Core models
|
||||
from dss.models import (
|
||||
Theme,
|
||||
Component,
|
||||
ComponentVariant,
|
||||
Project,
|
||||
ProjectMetadata,
|
||||
DesignToken as ModelDesignToken,
|
||||
TokenCategory as ModelTokenCategory,
|
||||
)
|
||||
|
||||
# Validators
|
||||
from dss.validators import (
|
||||
ProjectValidator,
|
||||
ValidationResult,
|
||||
ValidationError,
|
||||
ValidationStage,
|
||||
)
|
||||
|
||||
# Tools
|
||||
from dss.tools import (
|
||||
StyleDictionaryTool,
|
||||
StyleDictionaryWrapper,
|
||||
ShadcnTool,
|
||||
ShadcnWrapper,
|
||||
FigmaWrapper,
|
||||
FigmaAPIError,
|
||||
)
|
||||
|
||||
# Ingest (multi-source token extraction)
|
||||
from dss.ingest import (
|
||||
DesignToken,
|
||||
TokenSource,
|
||||
TokenCollection,
|
||||
CSSTokenSource,
|
||||
SCSSTokenSource,
|
||||
TailwindTokenSource,
|
||||
JSONTokenSource,
|
||||
TokenMerger,
|
||||
MergeStrategy,
|
||||
)
|
||||
|
||||
# Analyze (code analysis and scanning)
|
||||
from dss.analyze import (
|
||||
ProjectAnalysis,
|
||||
StylePattern,
|
||||
QuickWin,
|
||||
ProjectScanner,
|
||||
ReactAnalyzer,
|
||||
StyleAnalyzer,
|
||||
DependencyGraph,
|
||||
QuickWinFinder,
|
||||
)
|
||||
|
||||
# Storybook (integration and generation)
|
||||
from dss.storybook import (
|
||||
StorybookScanner,
|
||||
StoryGenerator,
|
||||
ThemeGenerator,
|
||||
)
|
||||
|
||||
# Settings and configuration
|
||||
from dss.settings import (
|
||||
DSSSettings,
|
||||
DSSManager,
|
||||
settings,
|
||||
manager,
|
||||
)
|
||||
|
||||
# Status dashboard
|
||||
from dss.status import (
|
||||
StatusDashboard,
|
||||
HealthMetric,
|
||||
)
|
||||
|
||||
# Translations (dictionary system)
|
||||
from dss.translations import (
|
||||
TranslationSource,
|
||||
MappingType,
|
||||
TokenMapping,
|
||||
ComponentMapping,
|
||||
PatternMapping,
|
||||
CustomProp,
|
||||
TranslationMappings,
|
||||
TranslationDictionary,
|
||||
TranslationRegistry,
|
||||
ResolvedToken,
|
||||
ResolvedTheme,
|
||||
TranslationDictionaryLoader,
|
||||
TokenResolver,
|
||||
ThemeMerger,
|
||||
TranslationValidator,
|
||||
TranslationDictionaryWriter,
|
||||
DSS_CANONICAL_TOKENS,
|
||||
DSS_CANONICAL_COMPONENTS,
|
||||
DSS_TOKEN_ALIASES,
|
||||
DSS_COMPONENT_VARIANTS,
|
||||
is_valid_dss_token,
|
||||
resolve_alias,
|
||||
get_canonical_token_categories,
|
||||
)
|
||||
|
||||
# Project Management
|
||||
from dss.project import (
|
||||
DSSProject,
|
||||
ProjectConfig,
|
||||
FigmaSource,
|
||||
FigmaFile,
|
||||
OutputConfig,
|
||||
ProjectStatus,
|
||||
ProjectManager,
|
||||
ProjectRegistry,
|
||||
FigmaProjectSync,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Version
|
||||
"__version__",
|
||||
# Models
|
||||
"Theme",
|
||||
"Component",
|
||||
"ComponentVariant",
|
||||
"Project",
|
||||
"ProjectMetadata",
|
||||
"ModelDesignToken",
|
||||
"ModelTokenCategory",
|
||||
# Validators
|
||||
"ProjectValidator",
|
||||
"ValidationResult",
|
||||
"ValidationError",
|
||||
"ValidationStage",
|
||||
# Tools
|
||||
"StyleDictionaryTool",
|
||||
"StyleDictionaryWrapper",
|
||||
"ShadcnTool",
|
||||
"ShadcnWrapper",
|
||||
"FigmaWrapper",
|
||||
"FigmaAPIError",
|
||||
# Ingest
|
||||
"DesignToken",
|
||||
"TokenSource",
|
||||
"TokenCollection",
|
||||
"CSSTokenSource",
|
||||
"SCSSTokenSource",
|
||||
"TailwindTokenSource",
|
||||
"JSONTokenSource",
|
||||
"TokenMerger",
|
||||
"MergeStrategy",
|
||||
# Analyze
|
||||
"ProjectAnalysis",
|
||||
"StylePattern",
|
||||
"QuickWin",
|
||||
"ProjectScanner",
|
||||
"ReactAnalyzer",
|
||||
"StyleAnalyzer",
|
||||
"DependencyGraph",
|
||||
"QuickWinFinder",
|
||||
# Storybook
|
||||
"StorybookScanner",
|
||||
"StoryGenerator",
|
||||
"ThemeGenerator",
|
||||
# Settings
|
||||
"DSSSettings",
|
||||
"DSSManager",
|
||||
"settings",
|
||||
"manager",
|
||||
# Status
|
||||
"StatusDashboard",
|
||||
"HealthMetric",
|
||||
# Translations
|
||||
"TranslationSource",
|
||||
"MappingType",
|
||||
"TokenMapping",
|
||||
"ComponentMapping",
|
||||
"PatternMapping",
|
||||
"CustomProp",
|
||||
"TranslationMappings",
|
||||
"TranslationDictionary",
|
||||
"TranslationRegistry",
|
||||
"ResolvedToken",
|
||||
"ResolvedTheme",
|
||||
"TranslationDictionaryLoader",
|
||||
"TokenResolver",
|
||||
"ThemeMerger",
|
||||
"TranslationValidator",
|
||||
"TranslationDictionaryWriter",
|
||||
"DSS_CANONICAL_TOKENS",
|
||||
"DSS_CANONICAL_COMPONENTS",
|
||||
"DSS_TOKEN_ALIASES",
|
||||
"DSS_COMPONENT_VARIANTS",
|
||||
"is_valid_dss_token",
|
||||
"resolve_alias",
|
||||
"get_canonical_token_categories",
|
||||
# Project Management
|
||||
"DSSProject",
|
||||
"ProjectConfig",
|
||||
"FigmaSource",
|
||||
"FigmaFile",
|
||||
"OutputConfig",
|
||||
"ProjectStatus",
|
||||
"ProjectManager",
|
||||
"ProjectRegistry",
|
||||
"FigmaProjectSync",
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
"""FastAPI routes and application"""
|
||||
@@ -1,522 +0,0 @@
|
||||
"""
|
||||
FastAPI routes for DSS Export/Import system
|
||||
|
||||
Provides REST API endpoints for project export, import, merge, and analysis.
|
||||
All operations support both synchronous and asynchronous (background job) modes.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, BackgroundTasks, Query
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
import logging
|
||||
|
||||
from dss.export_import.service import DSSProjectService, ExportSummary, ImportSummary, MergeSummary
|
||||
from dss.models.project import Project
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/api/projects", tags=["export_import"])
|
||||
|
||||
# Initialize service layer
|
||||
service = DSSProjectService(busy_timeout_ms=5000)
|
||||
|
||||
# In-memory job tracking (replace with Redis/database in production)
|
||||
_jobs: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Pydantic Models for API Responses
|
||||
# ============================================================================
|
||||
|
||||
class ExportResponse(BaseModel):
|
||||
"""Response from export endpoint"""
|
||||
success: bool
|
||||
file_size_bytes: Optional[int] = None
|
||||
token_count: Optional[int] = None
|
||||
component_count: Optional[int] = None
|
||||
error: Optional[str] = None
|
||||
duration_seconds: Optional[float] = None
|
||||
|
||||
|
||||
class ImportResponse(BaseModel):
|
||||
"""Response from import endpoint"""
|
||||
success: bool
|
||||
project_name: Optional[str] = None
|
||||
token_count: Optional[int] = None
|
||||
component_count: Optional[int] = None
|
||||
migration_performed: Optional[bool] = None
|
||||
warnings: Optional[List[str]] = None
|
||||
error: Optional[str] = None
|
||||
duration_seconds: Optional[float] = None
|
||||
job_id: Optional[str] = None
|
||||
|
||||
|
||||
class MergeResponse(BaseModel):
|
||||
"""Response from merge endpoint"""
|
||||
success: bool
|
||||
new_items: Optional[int] = None
|
||||
updated_items: Optional[int] = None
|
||||
conflicts: Optional[int] = None
|
||||
resolution_strategy: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
duration_seconds: Optional[float] = None
|
||||
job_id: Optional[str] = None
|
||||
|
||||
|
||||
class AnalysisResponse(BaseModel):
|
||||
"""Response from analysis endpoint"""
|
||||
is_valid: bool
|
||||
project_name: Optional[str] = None
|
||||
schema_version: Optional[str] = None
|
||||
token_count: Optional[int] = None
|
||||
component_count: Optional[int] = None
|
||||
migration_needed: Optional[bool] = None
|
||||
errors: Optional[List[str]] = None
|
||||
warnings: Optional[List[str]] = None
|
||||
|
||||
|
||||
class JobStatus(BaseModel):
|
||||
"""Status of a background job"""
|
||||
job_id: str
|
||||
status: str # pending, running, completed, failed
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
created_at: str
|
||||
completed_at: Optional[str] = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Export Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.post("/{project_id}/export", response_class=FileResponse)
|
||||
async def export_project(
|
||||
project_id: str,
|
||||
background_tasks: Optional[BackgroundTasks] = None,
|
||||
background: bool = Query(False, description="Run as background job")
|
||||
) -> FileResponse:
|
||||
"""
|
||||
Export a project to a .dss archive file
|
||||
|
||||
Args:
|
||||
project_id: ID of project to export
|
||||
background: If true, schedule as background job (for large projects)
|
||||
|
||||
Returns:
|
||||
.dss archive file download
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
# Export synchronously
|
||||
curl -X POST http://localhost:8000/api/projects/my-project/export \
|
||||
-o my-project.dss
|
||||
|
||||
# Export as background job
|
||||
curl -X POST "http://localhost:8000/api/projects/my-project/export?background=true"
|
||||
```
|
||||
"""
|
||||
try:
|
||||
# Load project (adapt to your data source)
|
||||
project = _load_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail=f"Project not found: {project_id}")
|
||||
|
||||
# Export
|
||||
output_path = Path("/tmp") / f"{project_id}_export.dss"
|
||||
result: ExportSummary = service.export_project(project, output_path)
|
||||
|
||||
if not result.success:
|
||||
raise HTTPException(status_code=400, detail=result.error)
|
||||
|
||||
# Return file for download
|
||||
return FileResponse(
|
||||
result.archive_path,
|
||||
media_type="application/zip",
|
||||
filename=f"{project.name}.dss"
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Export failed for {project_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Import Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.post("/import", response_model=ImportResponse)
|
||||
async def import_project(
|
||||
file: UploadFile = File(...),
|
||||
strategy: str = Query("replace", description="Import strategy: replace or merge"),
|
||||
background: bool = Query(False, description="Run as background job")
|
||||
) -> ImportResponse:
|
||||
"""
|
||||
Import a project from a .dss archive file
|
||||
|
||||
Args:
|
||||
file: .dss archive file to import
|
||||
strategy: Import strategy (replace=full restoration, merge=smart update)
|
||||
background: If true, schedule as background job (for large archives)
|
||||
|
||||
Returns:
|
||||
Import result summary
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
# Import synchronously
|
||||
curl -X POST http://localhost:8000/api/projects/import \
|
||||
-F "file=@my-project.dss"
|
||||
|
||||
# Import with merge strategy
|
||||
curl -X POST "http://localhost:8000/api/projects/import?strategy=merge" \
|
||||
-F "file=@updates.dss"
|
||||
|
||||
# Import as background job
|
||||
curl -X POST "http://localhost:8000/api/projects/import?background=true" \
|
||||
-F "file=@large-project.dss"
|
||||
```
|
||||
"""
|
||||
archive_path = None
|
||||
job_id = None
|
||||
|
||||
try:
|
||||
# Save uploaded file
|
||||
archive_path = Path("/tmp") / f"import_{datetime.now().timestamp()}.dss"
|
||||
contents = await file.read()
|
||||
archive_path.write_bytes(contents)
|
||||
|
||||
# Check if should run as background job
|
||||
if service._should_schedule_background(archive_path):
|
||||
# Schedule background job
|
||||
job_id = _create_job_id()
|
||||
_jobs[job_id] = {
|
||||
"status": "pending",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"type": "import",
|
||||
"archive_path": str(archive_path),
|
||||
"strategy": strategy
|
||||
}
|
||||
|
||||
# In production: queue with Celery, RQ, or similar
|
||||
# For now: return job ID for polling
|
||||
return ImportResponse(
|
||||
success=True,
|
||||
job_id=job_id,
|
||||
duration_seconds=0
|
||||
)
|
||||
|
||||
# Run synchronously
|
||||
result: ImportSummary = service.import_project(archive_path, strategy)
|
||||
|
||||
if not result.success:
|
||||
raise HTTPException(status_code=400, detail=result.error)
|
||||
|
||||
return ImportResponse(
|
||||
success=True,
|
||||
project_name=result.project_name,
|
||||
token_count=result.item_counts.get("tokens") if result.item_counts else None,
|
||||
component_count=result.item_counts.get("components") if result.item_counts else None,
|
||||
migration_performed=result.migration_performed,
|
||||
warnings=result.warnings or [],
|
||||
duration_seconds=result.duration_seconds
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Import failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
# Cleanup uploaded file after async processing
|
||||
if archive_path and archive_path.exists() and job_id is None:
|
||||
try:
|
||||
archive_path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Merge Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.post("/{project_id}/merge", response_model=MergeResponse)
|
||||
async def merge_project(
|
||||
project_id: str,
|
||||
file: UploadFile = File(...),
|
||||
strategy: str = Query("keep_local", description="Conflict resolution: overwrite, keep_local, or fork"),
|
||||
background: bool = Query(False, description="Run as background job")
|
||||
) -> MergeResponse:
|
||||
"""
|
||||
Merge updates from a .dss archive into a project
|
||||
|
||||
Args:
|
||||
project_id: ID of project to merge into
|
||||
file: .dss archive with updates
|
||||
strategy: Conflict resolution strategy
|
||||
background: If true, schedule as background job
|
||||
|
||||
Returns:
|
||||
Merge result summary
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
# Merge with keep_local strategy (preserve local changes)
|
||||
curl -X POST "http://localhost:8000/api/projects/my-project/merge?strategy=keep_local" \
|
||||
-F "file=@updates.dss"
|
||||
|
||||
# Merge with overwrite strategy (accept remote changes)
|
||||
curl -X POST "http://localhost:8000/api/projects/my-project/merge?strategy=overwrite" \
|
||||
-F "file=@updates.dss"
|
||||
|
||||
# Merge as background job (for large archives)
|
||||
curl -X POST "http://localhost:8000/api/projects/my-project/merge?background=true" \
|
||||
-F "file=@large-update.dss"
|
||||
```
|
||||
"""
|
||||
archive_path = None
|
||||
job_id = None
|
||||
|
||||
try:
|
||||
# Load project
|
||||
project = _load_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail=f"Project not found: {project_id}")
|
||||
|
||||
# Save uploaded file
|
||||
archive_path = Path("/tmp") / f"merge_{datetime.now().timestamp()}.dss"
|
||||
contents = await file.read()
|
||||
archive_path.write_bytes(contents)
|
||||
|
||||
# Check if should run as background job
|
||||
if service._should_schedule_background(archive_path):
|
||||
job_id = _create_job_id()
|
||||
_jobs[job_id] = {
|
||||
"status": "pending",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"type": "merge",
|
||||
"project_id": project_id,
|
||||
"archive_path": str(archive_path),
|
||||
"strategy": strategy
|
||||
}
|
||||
return MergeResponse(
|
||||
success=True,
|
||||
job_id=job_id,
|
||||
duration_seconds=0
|
||||
)
|
||||
|
||||
# Run synchronously
|
||||
result: MergeSummary = service.merge_project(project, archive_path, strategy)
|
||||
|
||||
if not result.success:
|
||||
raise HTTPException(status_code=400, detail=result.error)
|
||||
|
||||
return MergeResponse(
|
||||
success=True,
|
||||
new_items=result.new_items_count,
|
||||
updated_items=result.updated_items_count,
|
||||
conflicts=result.conflicts_count,
|
||||
resolution_strategy=result.resolution_strategy,
|
||||
duration_seconds=result.duration_seconds
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Merge failed for {project_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if archive_path and archive_path.exists() and job_id is None:
|
||||
try:
|
||||
archive_path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Analysis Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.post("/{project_id}/analyze-merge")
|
||||
async def analyze_merge(
|
||||
project_id: str,
|
||||
file: UploadFile = File(...)
|
||||
) -> AnalysisResponse:
|
||||
"""
|
||||
Analyze merge without applying it (safe preview)
|
||||
|
||||
Args:
|
||||
project_id: ID of project to analyze merge into
|
||||
file: .dss archive to analyze
|
||||
|
||||
Returns:
|
||||
Merge analysis (what changes would happen)
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/projects/my-project/analyze-merge \
|
||||
-F "file=@updates.dss"
|
||||
```
|
||||
"""
|
||||
archive_path = None
|
||||
|
||||
try:
|
||||
# Load project
|
||||
project = _load_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail=f"Project not found: {project_id}")
|
||||
|
||||
# Save uploaded file
|
||||
archive_path = Path("/tmp") / f"analyze_{datetime.now().timestamp()}.dss"
|
||||
contents = await file.read()
|
||||
archive_path.write_bytes(contents)
|
||||
|
||||
# Analyze
|
||||
analysis = service.analyze_merge(project, archive_path)
|
||||
|
||||
return AnalysisResponse(
|
||||
is_valid=analysis.is_valid,
|
||||
new_items=len(analysis.new_items.get("tokens", [])),
|
||||
conflicts=len(analysis.conflicted_items)
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Merge analysis failed for {project_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if archive_path and archive_path.exists():
|
||||
try:
|
||||
archive_path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/analyze-archive")
|
||||
async def analyze_archive(
|
||||
file: UploadFile = File(...)
|
||||
) -> AnalysisResponse:
|
||||
"""
|
||||
Analyze a .dss archive without importing it (safe preview)
|
||||
|
||||
Args:
|
||||
file: .dss archive to analyze
|
||||
|
||||
Returns:
|
||||
Archive analysis details
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/projects/analyze-archive \
|
||||
-F "file=@project.dss"
|
||||
```
|
||||
"""
|
||||
archive_path = None
|
||||
|
||||
try:
|
||||
# Save uploaded file
|
||||
archive_path = Path("/tmp") / f"analyze_archive_{datetime.now().timestamp()}.dss"
|
||||
contents = await file.read()
|
||||
archive_path.write_bytes(contents)
|
||||
|
||||
# Analyze
|
||||
analysis = service.analyze_import(archive_path)
|
||||
|
||||
return AnalysisResponse(
|
||||
is_valid=analysis.is_valid,
|
||||
project_name=analysis.project_name,
|
||||
schema_version=analysis.schema_version,
|
||||
token_count=analysis.content_summary.get("tokens", {}).get("count"),
|
||||
component_count=analysis.content_summary.get("components", {}).get("count"),
|
||||
migration_needed=analysis.migration_needed,
|
||||
errors=[e.message for e in analysis.errors],
|
||||
warnings=analysis.warnings
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Archive analysis failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
finally:
|
||||
if archive_path and archive_path.exists():
|
||||
try:
|
||||
archive_path.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Job Status Endpoint
|
||||
# ============================================================================
|
||||
|
||||
@router.get("/jobs/{job_id}", response_model=JobStatus)
|
||||
async def get_job_status(job_id: str) -> JobStatus:
|
||||
"""
|
||||
Get status of a background job
|
||||
|
||||
Args:
|
||||
job_id: ID of the job (returned from async endpoint)
|
||||
|
||||
Returns:
|
||||
Current job status and result (if completed)
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
curl http://localhost:8000/api/projects/jobs/job-123
|
||||
```
|
||||
"""
|
||||
job = _jobs.get(job_id)
|
||||
if not job:
|
||||
raise HTTPException(status_code=404, detail=f"Job not found: {job_id}")
|
||||
|
||||
return JobStatus(
|
||||
job_id=job_id,
|
||||
status=job.get("status", "unknown"),
|
||||
result=job.get("result"),
|
||||
error=job.get("error"),
|
||||
created_at=job.get("created_at", ""),
|
||||
completed_at=job.get("completed_at")
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
def _load_project(project_id: str) -> Optional[Project]:
|
||||
"""
|
||||
Load a project by ID
|
||||
|
||||
ADAPT THIS to your actual data source (database, API, etc.)
|
||||
"""
|
||||
try:
|
||||
# Example: Load from database
|
||||
# return db.query(Project).filter(Project.id == project_id).first()
|
||||
|
||||
# For now: return a dummy project
|
||||
# In production: implement actual loading
|
||||
logger.warning(f"Using dummy project for {project_id} - implement _load_project()")
|
||||
return Project(
|
||||
name=project_id,
|
||||
description="Auto-loaded project",
|
||||
author="system"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load project {project_id}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _create_job_id() -> str:
|
||||
"""Generate unique job ID"""
|
||||
import uuid
|
||||
return str(uuid.uuid4())[:8]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Export router for inclusion in FastAPI app
|
||||
# ============================================================================
|
||||
|
||||
__all__ = ["router"]
|
||||
@@ -1,14 +0,0 @@
|
||||
"""Wrappers for external design system tools"""
|
||||
|
||||
from .style_dictionary import StyleDictionaryTool, StyleDictionaryWrapper
|
||||
from .shadcn import ShadcnTool, ShadcnWrapper
|
||||
from .figma import FigmaWrapper, FigmaAPIError
|
||||
|
||||
__all__ = [
|
||||
"StyleDictionaryTool",
|
||||
"StyleDictionaryWrapper",
|
||||
"ShadcnTool",
|
||||
"ShadcnWrapper",
|
||||
"FigmaWrapper",
|
||||
"FigmaAPIError"
|
||||
]
|
||||
@@ -1,316 +0,0 @@
|
||||
"""
|
||||
Figma API wrapper for design token extraction
|
||||
Based on Figmagic architecture and W3C DTCG format standards
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Any
|
||||
from dss.models.theme import Theme, DesignToken, TokenCategory
|
||||
|
||||
|
||||
class FigmaWrapper:
|
||||
"""
|
||||
Wrapper for Figma REST API
|
||||
Extracts design tokens from Figma Variables and converts to W3C DTCG format
|
||||
|
||||
Architecture:
|
||||
Figma Variables API → W3C DTCG format → DSS Theme model → StyleDictionary → outputs
|
||||
"""
|
||||
|
||||
FIGMA_API_BASE = "https://api.figma.com/v1"
|
||||
|
||||
def __init__(self, api_token: str, file_key: str, use_cache: bool = True):
|
||||
"""
|
||||
Initialize Figma wrapper
|
||||
|
||||
Args:
|
||||
api_token: Figma personal access token
|
||||
file_key: Figma file key (from URL)
|
||||
use_cache: Whether to cache API responses
|
||||
"""
|
||||
if not api_token or not file_key:
|
||||
raise ValueError("Figma API token and file key are required")
|
||||
|
||||
self.api_token = api_token
|
||||
self.file_key = file_key
|
||||
self.use_cache = use_cache
|
||||
self.cache_path = Path.home() / ".dss" / "figma_cache.json"
|
||||
|
||||
self.headers = {
|
||||
"X-Figma-Token": self.api_token
|
||||
}
|
||||
|
||||
def get_variables(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Fetch variables from Figma file using Variables API
|
||||
|
||||
Returns:
|
||||
Raw Figma Variables API response
|
||||
|
||||
Raises:
|
||||
FigmaAPIError: If API request fails
|
||||
"""
|
||||
# Check cache first
|
||||
if self.use_cache and self.cache_path.exists():
|
||||
with open(self.cache_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
# Fetch from API
|
||||
url = f"{self.FIGMA_API_BASE}/files/{self.file_key}/variables/local"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
|
||||
# Cache response
|
||||
if self.use_cache:
|
||||
self.cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self.cache_path, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
return data
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 403:
|
||||
raise FigmaAPIError("Invalid Figma API token (403 Forbidden)")
|
||||
elif e.response.status_code == 404:
|
||||
raise FigmaAPIError(f"Figma file '{self.file_key}' not found (404)")
|
||||
else:
|
||||
raise FigmaAPIError(f"Figma API error: {e}")
|
||||
except Exception as e:
|
||||
raise FigmaAPIError(f"Failed to fetch Figma variables: {e}")
|
||||
|
||||
def extract_themes(self) -> Dict[str, Theme]:
|
||||
"""
|
||||
Extract themes from Figma Variables
|
||||
|
||||
Figma uses "variable collections" with "modes" for themes.
|
||||
Example: Collection "Colors" might have modes "Light" and "Dark"
|
||||
|
||||
Returns:
|
||||
Dict mapping theme name to DSS Theme object
|
||||
"""
|
||||
figma_data = self.get_variables()
|
||||
|
||||
# Build mode ID → theme name mapping
|
||||
mode_map = self._build_mode_map(figma_data.get("meta", {}).get("variableCollections", {}))
|
||||
|
||||
# Extract variables and convert to DTCG format
|
||||
variables = figma_data.get("meta", {}).get("variables", {})
|
||||
dtcg_tokens = self._convert_to_dtcg(variables)
|
||||
|
||||
# Structure tokens by theme
|
||||
themes = self._structure_by_theme(dtcg_tokens, mode_map)
|
||||
|
||||
return themes
|
||||
|
||||
def _build_mode_map(self, variable_collections: Dict[str, Any]) -> Dict[str, str]:
|
||||
"""
|
||||
Build mapping of mode ID → theme name
|
||||
|
||||
Args:
|
||||
variable_collections: Figma variable collections data
|
||||
|
||||
Returns:
|
||||
Dict mapping mode ID to theme name (e.g., {"331:7": "Light"})
|
||||
"""
|
||||
mode_map = {}
|
||||
|
||||
for collection_id, collection in variable_collections.items():
|
||||
modes = collection.get("modes", [])
|
||||
for mode in modes:
|
||||
mode_id = mode.get("modeId")
|
||||
mode_name = mode.get("name")
|
||||
if mode_id and mode_name:
|
||||
mode_map[mode_id] = mode_name
|
||||
|
||||
return mode_map
|
||||
|
||||
def _convert_to_dtcg(self, variables: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Convert Figma variables to W3C DTCG format
|
||||
|
||||
DTCG format:
|
||||
{
|
||||
"color": {
|
||||
"primary": {
|
||||
"$value": "#0066cc",
|
||||
"$type": "color",
|
||||
"$description": "Primary brand color"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Args:
|
||||
variables: Figma variables data
|
||||
|
||||
Returns:
|
||||
DTCG-formatted token tree
|
||||
"""
|
||||
tokens = {}
|
||||
|
||||
for var_id, variable in variables.items():
|
||||
# Skip remote variables
|
||||
if variable.get("remote", False):
|
||||
continue
|
||||
|
||||
name = variable.get("name", "")
|
||||
resolved_type = variable.get("resolvedType", "")
|
||||
values_by_mode = variable.get("valuesByMode", {})
|
||||
description = variable.get("description", "")
|
||||
|
||||
# Convert Figma type to DTCG type
|
||||
dtcg_type = self._map_figma_type_to_dtcg(resolved_type)
|
||||
|
||||
# Parse name into nested structure (e.g., "colors/primary/500" → colors.primary.500)
|
||||
path_parts = name.split("/")
|
||||
|
||||
# Create token object with values by mode
|
||||
token_obj = {
|
||||
"$type": dtcg_type,
|
||||
"valuesByMode": values_by_mode
|
||||
}
|
||||
|
||||
if description:
|
||||
token_obj["$description"] = description
|
||||
|
||||
# Set in nested structure
|
||||
self._set_nested(tokens, path_parts, token_obj)
|
||||
|
||||
return tokens
|
||||
|
||||
def _structure_by_theme(self, dtcg_tokens: Dict[str, Any], mode_map: Dict[str, str]) -> Dict[str, Theme]:
|
||||
"""
|
||||
Structure tokens by theme using mode mapping
|
||||
|
||||
Args:
|
||||
dtcg_tokens: DTCG tokens with valuesByMode
|
||||
mode_map: Mapping of mode ID to theme name
|
||||
|
||||
Returns:
|
||||
Dict of theme name → DSS Theme
|
||||
"""
|
||||
themes = {}
|
||||
|
||||
# Initialize themes
|
||||
for mode_name in set(mode_map.values()):
|
||||
themes[mode_name] = Theme(
|
||||
name=f"DSS {mode_name}",
|
||||
version="1.0.0",
|
||||
tokens={}
|
||||
)
|
||||
|
||||
# Recursively extract tokens for each theme
|
||||
def extract_tokens(node: Dict[str, Any], path: str = ""):
|
||||
for key, value in node.items():
|
||||
if key.startswith("$"):
|
||||
# Skip metadata keys
|
||||
continue
|
||||
|
||||
current_path = f"{path}/{key}" if path else key
|
||||
|
||||
if isinstance(value, dict) and "valuesByMode" in value:
|
||||
# This is a token leaf node
|
||||
dtcg_type = value.get("$type", "other")
|
||||
description = value.get("$description", "")
|
||||
values_by_mode = value["valuesByMode"]
|
||||
|
||||
# Create token for each mode
|
||||
for mode_id, token_value in values_by_mode.items():
|
||||
theme_name = mode_map.get(mode_id)
|
||||
if theme_name and theme_name in themes:
|
||||
# Convert path to token name (use last part for simplicity)
|
||||
token_name = key
|
||||
|
||||
# Format value based on type
|
||||
formatted_value = self._format_value(token_value, dtcg_type)
|
||||
|
||||
# Map DTCG type to DSS TokenCategory
|
||||
category = self._map_dtcg_type_to_category(dtcg_type)
|
||||
|
||||
# Create DesignToken
|
||||
design_token = DesignToken(
|
||||
name=token_name,
|
||||
value=formatted_value,
|
||||
type=dtcg_type,
|
||||
category=category,
|
||||
description=description or f"{token_name} token"
|
||||
)
|
||||
|
||||
themes[theme_name].tokens[token_name] = design_token
|
||||
|
||||
elif isinstance(value, dict):
|
||||
# Recurse into nested groups
|
||||
extract_tokens(value, current_path)
|
||||
|
||||
extract_tokens(dtcg_tokens)
|
||||
|
||||
return themes
|
||||
|
||||
def _map_figma_type_to_dtcg(self, figma_type: str) -> str:
|
||||
"""Map Figma variable type to W3C DTCG type"""
|
||||
type_map = {
|
||||
"COLOR": "color",
|
||||
"FLOAT": "number", # Could be dimension, duration, etc.
|
||||
"STRING": "string",
|
||||
"BOOLEAN": "boolean"
|
||||
}
|
||||
return type_map.get(figma_type, "other")
|
||||
|
||||
def _map_dtcg_type_to_category(self, dtcg_type: str) -> TokenCategory:
|
||||
"""Map DTCG type to DSS TokenCategory"""
|
||||
category_map = {
|
||||
"color": TokenCategory.COLOR,
|
||||
"dimension": TokenCategory.SPACING,
|
||||
"fontFamily": TokenCategory.TYPOGRAPHY,
|
||||
"fontSize": TokenCategory.TYPOGRAPHY,
|
||||
"fontWeight": TokenCategory.TYPOGRAPHY,
|
||||
"lineHeight": TokenCategory.TYPOGRAPHY,
|
||||
"borderRadius": TokenCategory.RADIUS,
|
||||
"shadow": TokenCategory.SHADOW,
|
||||
"border": TokenCategory.BORDER,
|
||||
}
|
||||
return category_map.get(dtcg_type, TokenCategory.OTHER)
|
||||
|
||||
def _format_value(self, value: Any, dtcg_type: str) -> str:
|
||||
"""
|
||||
Format Figma value to string representation
|
||||
|
||||
Args:
|
||||
value: Raw Figma value
|
||||
dtcg_type: DTCG type
|
||||
|
||||
Returns:
|
||||
Formatted value string
|
||||
"""
|
||||
if dtcg_type == "color" and isinstance(value, dict):
|
||||
# Figma color format: {r: 0-1, g: 0-1, b: 0-1, a: 0-1}
|
||||
r = int(value.get("r", 0) * 255)
|
||||
g = int(value.get("g", 0) * 255)
|
||||
b = int(value.get("b", 0) * 255)
|
||||
a = value.get("a", 1)
|
||||
|
||||
if a == 1:
|
||||
return f"rgb({r}, {g}, {b})"
|
||||
else:
|
||||
return f"rgba({r}, {g}, {b}, {a})"
|
||||
|
||||
return str(value)
|
||||
|
||||
def _set_nested(self, obj: Dict, path: List[str], value: Any):
|
||||
"""Set value in nested dictionary using path"""
|
||||
current = obj
|
||||
for part in path[:-1]:
|
||||
if part not in current:
|
||||
current[part] = {}
|
||||
current = current[part]
|
||||
current[path[-1]] = value
|
||||
|
||||
|
||||
class FigmaAPIError(Exception):
|
||||
"""Exception raised for Figma API errors"""
|
||||
pass
|
||||
@@ -1,112 +0,0 @@
|
||||
"""
|
||||
Shadcn CLI wrapper for component management
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
|
||||
class ShadcnWrapper:
|
||||
"""
|
||||
Wrapper for shadcn/ui CLI
|
||||
Manages shadcn component installation and configuration
|
||||
"""
|
||||
|
||||
def __init__(self, shadcn_path: str = "npx shadcn-ui@latest"):
|
||||
"""
|
||||
Initialize Shadcn wrapper
|
||||
|
||||
Args:
|
||||
shadcn_path: Path to shadcn executable (default: npx shadcn-ui@latest)
|
||||
"""
|
||||
self.shadcn_path = shadcn_path
|
||||
|
||||
def add_component(
|
||||
self,
|
||||
component_name: str,
|
||||
project_path: Path,
|
||||
overwrite: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a shadcn component to project
|
||||
|
||||
Args:
|
||||
component_name: Component to add (e.g., 'button', 'card')
|
||||
project_path: Project root directory
|
||||
overwrite: Whether to overwrite existing components
|
||||
|
||||
Returns:
|
||||
Dict with installation result
|
||||
"""
|
||||
cmd = [
|
||||
"npx", "shadcn-ui@latest", "add", component_name,
|
||||
"--yes" # Auto-confirm
|
||||
]
|
||||
|
||||
if overwrite:
|
||||
cmd.append("--overwrite")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=project_path,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
return {
|
||||
"success": result.returncode == 0,
|
||||
"component": component_name,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr
|
||||
}
|
||||
|
||||
def init_shadcn(self, project_path: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Initialize shadcn in a project
|
||||
|
||||
Args:
|
||||
project_path: Project root directory
|
||||
|
||||
Returns:
|
||||
Dict with initialization result
|
||||
"""
|
||||
cmd = ["npx", "shadcn-ui@latest", "init", "--yes"]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=project_path,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
return {
|
||||
"success": result.returncode == 0,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr
|
||||
}
|
||||
|
||||
def list_available_components(self) -> List[str]:
|
||||
"""
|
||||
List available shadcn components
|
||||
|
||||
Returns:
|
||||
List of component names
|
||||
"""
|
||||
# Hardcoded list of common shadcn components
|
||||
# In a real implementation, this would query the shadcn registry
|
||||
return [
|
||||
"accordion", "alert", "alert-dialog", "aspect-ratio",
|
||||
"avatar", "badge", "button", "calendar", "card",
|
||||
"checkbox", "collapsible", "command", "context-menu",
|
||||
"dialog", "dropdown-menu", "form", "hover-card",
|
||||
"input", "label", "menubar", "navigation-menu",
|
||||
"popover", "progress", "radio-group", "scroll-area",
|
||||
"select", "separator", "sheet", "skeleton", "slider",
|
||||
"switch", "table", "tabs", "textarea", "toast",
|
||||
"toggle", "tooltip"
|
||||
]
|
||||
|
||||
|
||||
# Alias for backward compatibility
|
||||
ShadcnTool = ShadcnWrapper
|
||||
@@ -1,247 +0,0 @@
|
||||
"""
|
||||
Style Dictionary wrapper for design token transformation
|
||||
Converts DSS tokens to various output formats (CSS, SCSS, JSON)
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
from dss.models.theme import Theme, DesignToken, TokenCategory
|
||||
|
||||
|
||||
class StyleDictionaryWrapper:
|
||||
"""
|
||||
Wrapper for Amazon Style Dictionary CLI
|
||||
Transforms design tokens into platform-specific outputs
|
||||
"""
|
||||
|
||||
def __init__(self, sd_path: str = "npx style-dictionary"):
|
||||
"""
|
||||
Initialize Style Dictionary wrapper
|
||||
|
||||
Args:
|
||||
sd_path: Path to style-dictionary executable (default: npx style-dictionary)
|
||||
"""
|
||||
self.sd_path = sd_path
|
||||
|
||||
def transform_theme(
|
||||
self,
|
||||
theme: Theme,
|
||||
output_format: str = "css",
|
||||
output_path: Optional[Path] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Transform a DSS theme using style-dictionary
|
||||
|
||||
Args:
|
||||
theme: DSS Theme to transform
|
||||
output_format: Output format (css, scss, json, js)
|
||||
output_path: Optional output directory
|
||||
|
||||
Returns:
|
||||
Dict with transformation result
|
||||
"""
|
||||
# Create temporary directory for style-dictionary config
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmppath = Path(tmpdir)
|
||||
|
||||
# Convert DSS theme to style-dictionary format
|
||||
sd_tokens = self._convert_theme_to_sd_format(theme)
|
||||
|
||||
# Write tokens to JSON file
|
||||
tokens_file = tmppath / "tokens.json"
|
||||
with open(tokens_file, "w") as f:
|
||||
json.dump(sd_tokens, f, indent=2)
|
||||
|
||||
# Create style-dictionary config
|
||||
config = self._create_sd_config(output_format, tmppath)
|
||||
config_file = tmppath / "config.json"
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
# Run style-dictionary build
|
||||
result = self._run_sd_build(config_file, tmppath)
|
||||
|
||||
# Read output files
|
||||
output_files = self._read_output_files(tmppath, output_format)
|
||||
|
||||
return {
|
||||
"success": result.returncode == 0,
|
||||
"output_format": output_format,
|
||||
"files": output_files,
|
||||
"errors": result.stderr if result.returncode != 0 else None
|
||||
}
|
||||
|
||||
def _convert_theme_to_sd_format(self, theme: Theme) -> Dict[str, Any]:
|
||||
"""
|
||||
Convert DSS theme to style-dictionary token format
|
||||
|
||||
Style Dictionary format:
|
||||
{
|
||||
"color": {
|
||||
"primary": { "value": "#0066cc" }
|
||||
}
|
||||
}
|
||||
"""
|
||||
sd_tokens = {}
|
||||
|
||||
for token_name, token in theme.tokens.items():
|
||||
# Group tokens by category
|
||||
category = token.category.value if token.category else "other"
|
||||
|
||||
if category not in sd_tokens:
|
||||
sd_tokens[category] = {}
|
||||
|
||||
# Convert token to SD format
|
||||
sd_tokens[category][token_name] = {
|
||||
"value": token.value,
|
||||
"type": token.type,
|
||||
}
|
||||
|
||||
if token.description:
|
||||
sd_tokens[category][token_name]["comment"] = token.description
|
||||
|
||||
return sd_tokens
|
||||
|
||||
def _create_sd_config(self, output_format: str, build_path: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Create style-dictionary configuration
|
||||
|
||||
Args:
|
||||
output_format: Desired output format
|
||||
build_path: Build directory path
|
||||
|
||||
Returns:
|
||||
Style Dictionary config dict
|
||||
"""
|
||||
config = {
|
||||
"source": ["tokens.json"],
|
||||
"platforms": {}
|
||||
}
|
||||
|
||||
if output_format == "css":
|
||||
config["platforms"]["css"] = {
|
||||
"transformGroup": "css",
|
||||
"buildPath": str(build_path) + "/",
|
||||
"files": [{
|
||||
"destination": "theme.css",
|
||||
"format": "css/variables"
|
||||
}]
|
||||
}
|
||||
elif output_format == "scss":
|
||||
config["platforms"]["scss"] = {
|
||||
"transformGroup": "scss",
|
||||
"buildPath": str(build_path) + "/",
|
||||
"files": [{
|
||||
"destination": "theme.scss",
|
||||
"format": "scss/variables"
|
||||
}]
|
||||
}
|
||||
elif output_format == "json":
|
||||
config["platforms"]["json"] = {
|
||||
"transformGroup": "js",
|
||||
"buildPath": str(build_path) + "/",
|
||||
"files": [{
|
||||
"destination": "theme.json",
|
||||
"format": "json/nested"
|
||||
}]
|
||||
}
|
||||
elif output_format == "js":
|
||||
config["platforms"]["js"] = {
|
||||
"transformGroup": "js",
|
||||
"buildPath": str(build_path) + "/",
|
||||
"files": [{
|
||||
"destination": "theme.js",
|
||||
"format": "javascript/module"
|
||||
}]
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
def _run_sd_build(self, config_file: Path, cwd: Path) -> subprocess.CompletedProcess:
|
||||
"""
|
||||
Run style-dictionary build command
|
||||
|
||||
Args:
|
||||
config_file: Path to config.json
|
||||
cwd: Working directory
|
||||
|
||||
Returns:
|
||||
Subprocess result
|
||||
"""
|
||||
cmd = [
|
||||
"npx", "style-dictionary", "build",
|
||||
"--config", str(config_file)
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def _read_output_files(self, build_path: Path, output_format: str) -> Dict[str, str]:
|
||||
"""
|
||||
Read generated output files
|
||||
|
||||
Args:
|
||||
build_path: Directory containing built files
|
||||
output_format: Output format used
|
||||
|
||||
Returns:
|
||||
Dict of filename -> content
|
||||
"""
|
||||
files = {}
|
||||
|
||||
# Map format to expected file
|
||||
format_files = {
|
||||
"css": "theme.css",
|
||||
"scss": "theme.scss",
|
||||
"json": "theme.json",
|
||||
"js": "theme.js"
|
||||
}
|
||||
|
||||
filename = format_files.get(output_format)
|
||||
if filename:
|
||||
filepath = build_path / filename
|
||||
if filepath.exists():
|
||||
with open(filepath, "r") as f:
|
||||
files[filename] = f.read()
|
||||
|
||||
return files
|
||||
|
||||
def convert_tokens_to_css_vars(self, theme: Theme) -> str:
|
||||
"""
|
||||
Convert DSS theme tokens to CSS custom properties
|
||||
|
||||
Args:
|
||||
theme: DSS Theme
|
||||
|
||||
Returns:
|
||||
CSS string with :root variables
|
||||
"""
|
||||
css_lines = [":root {"]
|
||||
|
||||
for token_name, token in theme.tokens.items():
|
||||
# Convert token name to CSS variable format
|
||||
css_var_name = f"--{token_name}"
|
||||
|
||||
# Add comment if description exists
|
||||
if token.description:
|
||||
css_lines.append(f" /* {token.description} */")
|
||||
|
||||
css_lines.append(f" {css_var_name}: {token.value};")
|
||||
|
||||
css_lines.append("}")
|
||||
|
||||
return "\n".join(css_lines)
|
||||
|
||||
|
||||
# Alias for backward compatibility
|
||||
StyleDictionaryTool = StyleDictionaryWrapper
|
||||
@@ -1,295 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DSS Database Migration Runner
|
||||
|
||||
This script runs SQL migrations in the correct order, with proper error handling
|
||||
and transaction safety.
|
||||
|
||||
Usage:
|
||||
python run_migrations.py # Run all pending migrations
|
||||
python run_migrations.py --check # Show pending migrations only
|
||||
python run_migrations.py --rollback 0001 # Rollback specific migration (CAREFUL!)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
|
||||
class MigrationRunner:
|
||||
"""Manages database migrations with version tracking and rollback support"""
|
||||
|
||||
def __init__(self, db_path: Path = None):
|
||||
"""
|
||||
Initialize migration runner
|
||||
|
||||
Args:
|
||||
db_path: Path to database file. If None, uses default DSS location.
|
||||
"""
|
||||
if db_path is None:
|
||||
# Default DSS database location
|
||||
db_path = Path.cwd() / ".dss" / "dss.db"
|
||||
|
||||
self.db_path = Path(db_path)
|
||||
self.migrations_dir = Path(__file__).parent.parent / "dss" / "storage" / "migrations"
|
||||
self.migrations_table = "_dss_migrations"
|
||||
|
||||
def _ensure_migrations_table(self, conn: sqlite3.Connection):
|
||||
"""Create migrations tracking table if it doesn't exist"""
|
||||
conn.execute(f"""
|
||||
CREATE TABLE IF NOT EXISTS {self.migrations_table} (
|
||||
id TEXT PRIMARY KEY,
|
||||
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
description TEXT,
|
||||
checksum TEXT,
|
||||
status TEXT DEFAULT 'applied'
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
def _get_migration_checksum(self, migration_file: Path) -> str:
|
||||
"""Calculate checksum of migration file for integrity verification"""
|
||||
import hashlib
|
||||
content = migration_file.read_text()
|
||||
return hashlib.sha256(content.encode()).hexdigest()
|
||||
|
||||
def _get_applied_migrations(self, conn: sqlite3.Connection) -> dict:
|
||||
"""Get dictionary of applied migrations"""
|
||||
cursor = conn.execute(f"SELECT id, checksum FROM {self.migrations_table}")
|
||||
return {row[0]: row[1] for row in cursor.fetchall()}
|
||||
|
||||
def _get_pending_migrations(self) -> list:
|
||||
"""Get list of pending migrations in order"""
|
||||
applied = {}
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
self._ensure_migrations_table(conn)
|
||||
applied = self._get_applied_migrations(conn)
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read migration history: {e}")
|
||||
|
||||
pending = []
|
||||
if self.migrations_dir.exists():
|
||||
for migration_file in sorted(self.migrations_dir.glob("*.sql")):
|
||||
migration_id = migration_file.stem # e.g., "0002_add_uuid_columns"
|
||||
if migration_id not in applied:
|
||||
pending.append({
|
||||
'id': migration_id,
|
||||
'file': migration_file,
|
||||
'checksum': self._get_migration_checksum(migration_file),
|
||||
'status': 'pending'
|
||||
})
|
||||
|
||||
return pending
|
||||
|
||||
def check(self) -> bool:
|
||||
"""Check for pending migrations without applying them"""
|
||||
pending = self._get_pending_migrations()
|
||||
|
||||
if not pending:
|
||||
print("✓ No pending migrations - database is up to date")
|
||||
return True
|
||||
|
||||
print(f"Found {len(pending)} pending migration(s):\n")
|
||||
for migration in pending:
|
||||
print(f" - {migration['id']}")
|
||||
print(f" File: {migration['file'].name}")
|
||||
print(f" Checksum: {migration['checksum'][:16]}...")
|
||||
|
||||
return False
|
||||
|
||||
def run(self, dry_run: bool = False) -> bool:
|
||||
"""
|
||||
Run all pending migrations
|
||||
|
||||
Args:
|
||||
dry_run: If True, show migrations but don't apply them
|
||||
|
||||
Returns:
|
||||
True if successful, False if any migration failed
|
||||
"""
|
||||
pending = self._get_pending_migrations()
|
||||
|
||||
if not pending:
|
||||
print("✓ No pending migrations - database is up to date")
|
||||
return True
|
||||
|
||||
print(f"Found {len(pending)} pending migration(s)")
|
||||
if dry_run:
|
||||
print("\nDRY RUN - No changes will be applied\n")
|
||||
else:
|
||||
print("Running migrations...\n")
|
||||
|
||||
# Backup database before running migrations
|
||||
if not dry_run:
|
||||
backup_path = self.db_path.with_suffix(f".backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}")
|
||||
import shutil
|
||||
try:
|
||||
shutil.copy2(self.db_path, backup_path)
|
||||
print(f"✓ Database backed up to: {backup_path}\n")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create backup: {e}")
|
||||
print(" Aborting migration")
|
||||
return False
|
||||
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
self._ensure_migrations_table(conn)
|
||||
|
||||
try:
|
||||
for migration in pending:
|
||||
migration_id = migration['id']
|
||||
migration_file = migration['file']
|
||||
|
||||
print(f"Running: {migration_id}")
|
||||
|
||||
# Read migration SQL
|
||||
sql_content = migration_file.read_text()
|
||||
|
||||
if not dry_run:
|
||||
try:
|
||||
# Execute migration with transaction
|
||||
conn.executescript(sql_content)
|
||||
|
||||
# Record migration as applied
|
||||
conn.execute(f"""
|
||||
INSERT INTO {self.migrations_table}
|
||||
(id, description, checksum, status)
|
||||
VALUES (?, ?, ?, 'applied')
|
||||
""", (migration_id, migration_file.name, migration['checksum']))
|
||||
|
||||
conn.commit()
|
||||
print(f" ✓ Migration applied successfully\n")
|
||||
except sqlite3.Error as e:
|
||||
conn.rollback()
|
||||
print(f" ✗ Migration failed: {e}")
|
||||
print(f" ✗ Changes rolled back")
|
||||
return False
|
||||
else:
|
||||
# Dry run: just show what would happen
|
||||
print(" (DRY RUN - Would execute)")
|
||||
lines = sql_content.split('\n')[:5] # Show first 5 lines
|
||||
for line in lines:
|
||||
if line.strip() and not line.strip().startswith('--'):
|
||||
print(f" {line[:70]}")
|
||||
if len(sql_content.split('\n')) > 5:
|
||||
print(f" ... ({len(sql_content.split(chr(10)))} lines total)")
|
||||
print()
|
||||
|
||||
if not dry_run:
|
||||
print("\n✓ All migrations applied successfully")
|
||||
return True
|
||||
else:
|
||||
print("✓ Dry run complete - no changes made")
|
||||
return True
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def status(self) -> None:
|
||||
"""Show migration status"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
self._ensure_migrations_table(conn)
|
||||
|
||||
cursor = conn.execute(f"""
|
||||
SELECT id, applied_at, status FROM {self.migrations_table}
|
||||
ORDER BY applied_at
|
||||
""")
|
||||
|
||||
applied = cursor.fetchall()
|
||||
print(f"Applied migrations ({len(applied)}):\n")
|
||||
|
||||
if applied:
|
||||
for row in applied:
|
||||
print(f" ✓ {row[0]}")
|
||||
print(f" Applied at: {row[1]}")
|
||||
else:
|
||||
print(" (none)")
|
||||
|
||||
pending = self._get_pending_migrations()
|
||||
print(f"\nPending migrations ({len(pending)}):\n")
|
||||
|
||||
if pending:
|
||||
for migration in pending:
|
||||
print(f" ⏳ {migration['id']}")
|
||||
else:
|
||||
print(" (none)")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error reading migration status: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="DSS Database Migration Runner",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python run_migrations.py # Run all pending migrations
|
||||
python run_migrations.py --check # Check for pending migrations
|
||||
python run_migrations.py --dry-run # Show what would be applied
|
||||
python run_migrations.py --status # Show migration status
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--check',
|
||||
action='store_true',
|
||||
help='Check for pending migrations without applying them'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Show migrations that would be applied without applying them'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--status',
|
||||
action='store_true',
|
||||
help='Show migration status (applied and pending)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--db',
|
||||
type=Path,
|
||||
help='Path to database file (default: .dss/dss.db)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
runner = MigrationRunner(db_path=args.db)
|
||||
|
||||
try:
|
||||
if args.status:
|
||||
runner.status()
|
||||
return 0
|
||||
elif args.check:
|
||||
success = runner.check()
|
||||
return 0 if success else 1
|
||||
elif args.dry_run:
|
||||
success = runner.run(dry_run=True)
|
||||
return 0 if success else 1
|
||||
else:
|
||||
# Run migrations
|
||||
success = runner.run(dry_run=False)
|
||||
return 0 if success else 1
|
||||
except KeyboardInterrupt:
|
||||
print("\nMigration cancelled by user")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/bin/bash
|
||||
# DSS MVP1 Setup Script
|
||||
# Configures DSS for dss.overbits.luz.uy
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "🚀 DSS MVP1 Setup for dss.overbits.luz.uy"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if running as overbits user
|
||||
if [ "$USER" != "overbits" ]; then
|
||||
echo -e "${YELLOW}⚠️ Warning: This script is designed for user 'overbits'${NC}"
|
||||
echo -e " Current user: $USER"
|
||||
read -p "Continue anyway? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1. Check dependencies
|
||||
echo "📦 Checking dependencies..."
|
||||
python3 -m dss.settings check-deps || {
|
||||
echo -e "${RED}❌ Dependency check failed${NC}"
|
||||
echo " Installing Python dependencies..."
|
||||
pip install -r requirements.txt
|
||||
echo " Installing Node dependencies..."
|
||||
npm install
|
||||
}
|
||||
|
||||
# 2. Create necessary directories
|
||||
echo ""
|
||||
echo "📁 Creating directories..."
|
||||
mkdir -p ~/.dss/{cache,logs,backups}
|
||||
mkdir -p dist/tokens
|
||||
mkdir -p components
|
||||
echo -e "${GREEN}✅ Directories created${NC}"
|
||||
|
||||
# 3. Check for API keys
|
||||
echo ""
|
||||
echo "🔑 Checking API keys..."
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
echo -e "${YELLOW}⚠️ .env file not found - already created${NC}"
|
||||
fi
|
||||
|
||||
# Check if keys are configured
|
||||
if grep -q "^ANTHROPIC_API_KEY=$" .env 2>/dev/null; then
|
||||
echo -e "${YELLOW}⚠️ ANTHROPIC_API_KEY not set in .env${NC}"
|
||||
echo " Get your key from: https://console.anthropic.com/settings/keys"
|
||||
fi
|
||||
|
||||
if grep -q "^FIGMA_TOKEN=$" .env 2>/dev/null; then
|
||||
echo -e "${YELLOW}⚠️ FIGMA_TOKEN not set in .env${NC}"
|
||||
echo " Get your token from: https://www.figma.com/developers/api#access-tokens"
|
||||
fi
|
||||
|
||||
if grep -q "^JWT_SECRET=$" .env 2>/dev/null; then
|
||||
echo -e "${YELLOW}⚠️ JWT_SECRET not set in .env${NC}"
|
||||
echo " Generate with: openssl rand -hex 32"
|
||||
read -p "Generate JWT_SECRET now? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
JWT_SECRET=$(openssl rand -hex 32)
|
||||
sed -i "s/^JWT_SECRET=$/JWT_SECRET=$JWT_SECRET/" .env
|
||||
echo -e "${GREEN}✅ JWT_SECRET generated and saved to .env${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. Run tests
|
||||
echo ""
|
||||
read -p "Run tests to verify setup? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "🧪 Running tests..."
|
||||
python3 -m dss.settings test || {
|
||||
echo -e "${RED}❌ Tests failed${NC}"
|
||||
echo " Check the output above for errors"
|
||||
exit 1
|
||||
}
|
||||
echo -e "${GREEN}✅ All tests passed!${NC}"
|
||||
fi
|
||||
|
||||
# 5. System info
|
||||
echo ""
|
||||
echo "📊 System Information:"
|
||||
python3 -m dss.settings info
|
||||
|
||||
# 6. Final instructions
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo -e "${GREEN}✅ Setup Complete!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Add your API keys to .env:"
|
||||
echo " - ANTHROPIC_API_KEY (https://console.anthropic.com/settings/keys)"
|
||||
echo " - FIGMA_TOKEN (https://www.figma.com/developers/api#access-tokens)"
|
||||
echo ""
|
||||
echo " 2. Configure your Figma file:"
|
||||
echo " - Add FIGMA_FILE_KEY to .env"
|
||||
echo " - Format: figma.com/file/{FILE_KEY}/..."
|
||||
echo ""
|
||||
echo " 3. Start the server:"
|
||||
echo " python3 -m uvicorn dss.api.server:app --host 0.0.0.0 --port 3456"
|
||||
echo ""
|
||||
echo " 4. Visit: https://dss.overbits.luz.uy"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " python3 -m dss.settings test # Run tests"
|
||||
echo " python3 -m dss.settings reset # Reset to fresh state"
|
||||
echo " python3 -m dss.settings info # Show system info"
|
||||
echo ""
|
||||
echo "See SETTINGS.md for full documentation"
|
||||
echo "=========================================="
|
||||
@@ -1,40 +0,0 @@
|
||||
export default {
|
||||
title: "DSS/Welcome",
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export const GettingStarted = {
|
||||
render: () => {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = `
|
||||
<div style="font-family: system-ui; padding: 2rem; max-width: 700px;">
|
||||
<h1 style="color: #1a1a2e; margin-bottom: 1rem;">Design System Server</h1>
|
||||
<p style="color: #666; font-size: 1.1rem; line-height: 1.6;">
|
||||
Welcome to DSS Storybook. This is your interactive component library.
|
||||
</p>
|
||||
|
||||
<div style="background: #f8f9fa; border-radius: 8px; padding: 1.5rem; margin: 1.5rem 0;">
|
||||
<h2 style="color: #1a1a2e; font-size: 1.2rem; margin-bottom: 1rem;">Getting Started</h2>
|
||||
<ol style="color: #444; line-height: 2;">
|
||||
<li>Go to the <strong>Admin UI</strong> to configure your design system</li>
|
||||
<li>Import components from Figma or your component library</li>
|
||||
<li>Click <strong>"Initialize Storybook"</strong> to generate component stories</li>
|
||||
<li>Browse your components in the sidebar</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div style="background: #e8f4fd; border-left: 4px solid #0066cc; padding: 1rem; margin: 1.5rem 0;">
|
||||
<strong style="color: #0066cc;">No components loaded yet</strong>
|
||||
<p style="color: #444; margin: 0.5rem 0 0 0;">
|
||||
Initialize your design system from the Admin UI to populate this Storybook.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="color: #888; font-size: 0.9rem; margin-top: 2rem;">
|
||||
DSS v1.0.0 | <a href="/admin-ui/" style="color: #0066cc;">Open Admin UI</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
return div;
|
||||
},
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_react_project(tmp_path: Path) -> Path:
|
||||
"""
|
||||
Creates a temporary mock React project structure for testing.
|
||||
"""
|
||||
project_dir = tmp_path / "test-project"
|
||||
project_dir.mkdir()
|
||||
|
||||
# Create src directory
|
||||
src_dir = project_dir / "src"
|
||||
src_dir.mkdir()
|
||||
|
||||
# Create components directory
|
||||
components_dir = src_dir / "components"
|
||||
components_dir.mkdir()
|
||||
|
||||
# Component A
|
||||
(components_dir / "ComponentA.jsx").write_text("""
|
||||
import React from 'react';
|
||||
import './ComponentA.css';
|
||||
|
||||
const ComponentA = () => {
|
||||
return <div className="component-a">Component A</div>;
|
||||
};
|
||||
|
||||
export default ComponentA;
|
||||
""")
|
||||
|
||||
(components_dir / "ComponentA.css").write_text("""
|
||||
.component-a {
|
||||
color: blue;
|
||||
}
|
||||
""")
|
||||
|
||||
# Component B
|
||||
(components_dir / "ComponentB.tsx").write_text("""
|
||||
import React from 'react';
|
||||
import ComponentA from './ComponentA';
|
||||
|
||||
const ComponentB = () => {
|
||||
return (
|
||||
<div>
|
||||
<ComponentA />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentB;
|
||||
""")
|
||||
|
||||
# App.js
|
||||
(src_dir / "App.js").write_text("""
|
||||
import React from 'react';
|
||||
import ComponentB from './components/ComponentB';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<ComponentB />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
""")
|
||||
|
||||
# package.json
|
||||
(project_dir / "package.json").write_text("""
|
||||
{
|
||||
"name": "test-project",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
return project_dir
|
||||
151
dss/__init__.py
Normal file
151
dss/__init__.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
DSS - Design System Server
|
||||
|
||||
A Model Context Protocol (MCP) server that provides Claude Code with 40+ design system tools.
|
||||
Supports local development and remote team deployment.
|
||||
|
||||
Usage:
|
||||
from dss import settings, Projects, Components
|
||||
from dss.mcp import MCPServer
|
||||
from dss.storage import Projects, Components, Tokens
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
# Settings & Configuration
|
||||
from dss.settings import settings, DSSSettings, DSSManager, manager
|
||||
|
||||
# Storage Layer
|
||||
from dss.storage.json_store import (
|
||||
Projects,
|
||||
Components,
|
||||
Tokens,
|
||||
Styles,
|
||||
SyncHistory,
|
||||
ActivityLog,
|
||||
Teams,
|
||||
Cache,
|
||||
FigmaFiles,
|
||||
CodeMetrics,
|
||||
TestResults,
|
||||
TokenDrift,
|
||||
Integrations,
|
||||
IntegrationHealth,
|
||||
get_stats,
|
||||
)
|
||||
|
||||
# Analyze
|
||||
from dss.analyze.base import (
|
||||
ProjectAnalysis,
|
||||
QuickWin,
|
||||
ComponentInfo,
|
||||
StylePattern,
|
||||
Framework,
|
||||
StylingApproach,
|
||||
)
|
||||
from dss.analyze.scanner import ProjectScanner
|
||||
|
||||
# Ingest
|
||||
from dss.ingest.base import (
|
||||
DesignToken,
|
||||
TokenCollection,
|
||||
TokenSource,
|
||||
TokenType,
|
||||
TokenCategory,
|
||||
)
|
||||
|
||||
# Export/Import
|
||||
from dss.export_import.service import DSSArchiveExporter, DSSArchiveImporter
|
||||
from dss.export_import.merger import SmartMerger
|
||||
|
||||
# Storybook
|
||||
from dss.storybook.generator import StoryGenerator
|
||||
from dss.storybook.scanner import StorybookScanner
|
||||
|
||||
# Translations
|
||||
from dss.translations import TranslationDictionary, TokenResolver
|
||||
|
||||
# Services
|
||||
from dss.services.project_manager import ProjectManager
|
||||
from dss.services.config_service import ConfigService, DSSConfig
|
||||
from dss.services.sandboxed_fs import SandboxedFS
|
||||
|
||||
# Figma
|
||||
from dss.figma.figma_tools import FigmaToolSuite
|
||||
|
||||
# Project
|
||||
from dss.project.manager import DSSProject
|
||||
|
||||
# Models
|
||||
from dss.models.theme import Theme
|
||||
from dss.models.component import Component
|
||||
from dss.models.project import Project
|
||||
|
||||
# Validators
|
||||
from dss.validators.schema import ProjectValidator, ValidationResult
|
||||
|
||||
__all__ = [
|
||||
# Version
|
||||
"__version__",
|
||||
# Settings
|
||||
"settings",
|
||||
"DSSSettings",
|
||||
"DSSManager",
|
||||
"manager",
|
||||
# Storage
|
||||
"Projects",
|
||||
"Components",
|
||||
"Tokens",
|
||||
"Styles",
|
||||
"SyncHistory",
|
||||
"ActivityLog",
|
||||
"Teams",
|
||||
"Cache",
|
||||
"FigmaFiles",
|
||||
"CodeMetrics",
|
||||
"TestResults",
|
||||
"TokenDrift",
|
||||
"Integrations",
|
||||
"IntegrationHealth",
|
||||
"get_stats",
|
||||
# Analyze
|
||||
"ProjectAnalysis",
|
||||
"QuickWin",
|
||||
"ComponentInfo",
|
||||
"StylePattern",
|
||||
"Framework",
|
||||
"StylingApproach",
|
||||
"ProjectScanner",
|
||||
# Ingest
|
||||
"DesignToken",
|
||||
"TokenCollection",
|
||||
"TokenSource",
|
||||
"TokenType",
|
||||
"TokenCategory",
|
||||
# Export/Import
|
||||
"DSSArchiveExporter",
|
||||
"DSSArchiveImporter",
|
||||
"SmartMerger",
|
||||
# Storybook
|
||||
"StoryGenerator",
|
||||
"StorybookScanner",
|
||||
# Translations
|
||||
"TranslationDictionary",
|
||||
"TokenResolver",
|
||||
# Services
|
||||
"ProjectManager",
|
||||
"ConfigService",
|
||||
"DSSConfig",
|
||||
"SandboxedFS",
|
||||
# Figma
|
||||
"FigmaToolSuite",
|
||||
# Project
|
||||
"DSSProject",
|
||||
# Models
|
||||
"Theme",
|
||||
"Component",
|
||||
"Project",
|
||||
# Validators
|
||||
"ProjectValidator",
|
||||
"ValidationResult",
|
||||
]
|
||||
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
from atlassian import Jira, Confluence
|
||||
|
||||
from storage.json_store import read_json, write_json, SYSTEM_DIR
|
||||
from dss.storage.json_store import read_json, write_json, SYSTEM_DIR
|
||||
|
||||
|
||||
class AtlassianAuth:
|
||||
@@ -10,7 +10,7 @@ from .security import TimestampConflictResolver
|
||||
from ..models.project import Project
|
||||
from ..models.theme import DesignToken
|
||||
from ..models.component import Component
|
||||
from storage.json_store import Projects, Components, Tokens
|
||||
from dss.storage.json_store import Projects, Components, Tokens
|
||||
|
||||
|
||||
MergeStrategy = Literal["overwrite", "keep_local", "fork", "skip"]
|
||||
@@ -20,7 +20,7 @@ from .importer import DSSArchiveImporter, ImportAnalysis
|
||||
from .merger import SmartMerger, ConflictResolutionMode, MergeAnalysis
|
||||
from .security import DatabaseLockingStrategy, MemoryLimitManager
|
||||
from ..models.project import Project
|
||||
from storage.json_store import Projects, ActivityLog
|
||||
from dss.storage.json_store import Projects, ActivityLog
|
||||
|
||||
|
||||
@dataclass
|
||||
0
dss/figma/__init__.py
Normal file
0
dss/figma/__init__.py
Normal file
@@ -19,18 +19,14 @@ Tools:
|
||||
import json
|
||||
import hashlib
|
||||
import asyncio
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, List, Any
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
import httpx
|
||||
|
||||
# Add parent to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from config import config
|
||||
from storage.json_store import Cache, ActivityLog
|
||||
from dss.settings import settings
|
||||
from dss.storage.json_store import Cache, ActivityLog
|
||||
|
||||
@dataclass
|
||||
class DesignToken:
|
||||
@@ -67,9 +63,9 @@ class FigmaClient:
|
||||
"""
|
||||
|
||||
def __init__(self, token: Optional[str] = None):
|
||||
self.token = token or config.figma.token
|
||||
self.token = token or settings.FIGMA_TOKEN
|
||||
self.base_url = "https://api.figma.com/v1"
|
||||
self.cache_ttl = config.figma.cache_ttl
|
||||
self.cache_ttl = settings.FIGMA_CACHE_TTL
|
||||
self._use_real_api = bool(self.token)
|
||||
|
||||
def _cache_key(self, endpoint: str) -> str:
|
||||
@@ -11,7 +11,7 @@ from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from storage.json_store import ActivityLog, append_jsonl, read_jsonl, SYSTEM_DIR # JSON storage
|
||||
from dss.storage.json_store import ActivityLog, append_jsonl, read_jsonl, SYSTEM_DIR # JSON storage
|
||||
|
||||
|
||||
class AuditEventType(Enum):
|
||||
0
dss/mcp/context/__init__.py
Normal file
0
dss/mcp/context/__init__.py
Normal file
@@ -17,7 +17,7 @@ from pathlib import Path
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from storage.json_store import Projects, Components, Tokens
|
||||
from dss.storage.json_store import Projects, Components, Tokens
|
||||
from analyze.scanner import ProjectScanner
|
||||
from ..config import mcp_config
|
||||
|
||||
@@ -22,7 +22,7 @@ from pathlib import Path
|
||||
# Note: sys.path is set up by the importing module (server.py)
|
||||
# Do NOT modify sys.path here as it causes relative import issues
|
||||
|
||||
from storage.json_store import Projects, ActivityLog
|
||||
from dss.storage.json_store import Projects, ActivityLog
|
||||
from .config import mcp_config, integration_config
|
||||
from .context.project_context import get_context_manager, ProjectContext
|
||||
from .tools.project_tools import PROJECT_TOOLS, ProjectTools
|
||||
0
dss/mcp/integrations/__init__.py
Normal file
0
dss/mcp/integrations/__init__.py
Normal file
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
|
||||
from ..config import mcp_config
|
||||
from storage.json_store import Cache, read_json, write_json, SYSTEM_DIR
|
||||
from dss.storage.json_store import Cache, read_json, write_json, SYSTEM_DIR
|
||||
|
||||
|
||||
class CircuitState(Enum):
|
||||
@@ -13,7 +13,7 @@ from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from .config import mcp_config
|
||||
from storage.json_store import ActivityLog, read_json, write_json, DATA_DIR # JSON storage
|
||||
from dss.storage.json_store import ActivityLog, read_json, write_json, DATA_DIR # JSON storage
|
||||
|
||||
|
||||
class OperationStatus(Enum):
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user