Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
230 lines
6.4 KiB
JavaScript
230 lines
6.4 KiB
JavaScript
/**
|
|
* Design System Server (DSS) - Figma Service
|
|
*
|
|
* Client-side interface to Figma API tools.
|
|
* No mocks - requires backend connection.
|
|
*/
|
|
import notificationService from './notification-service.js';
|
|
|
|
class FigmaService {
|
|
constructor() {
|
|
this.apiBase = '/api/figma';
|
|
this.connected = null; // null = unknown, true/false = checked
|
|
this.listeners = new Set();
|
|
}
|
|
|
|
// === Connection ===
|
|
|
|
async checkConnection() {
|
|
try {
|
|
const response = await fetch('/health', { method: 'GET' });
|
|
this.connected = response.ok;
|
|
} catch {
|
|
this.connected = false;
|
|
}
|
|
return this.connected;
|
|
}
|
|
|
|
_requireConnection() {
|
|
if (this.connected === false) {
|
|
throw new Error('API unavailable. Start DSS server first.');
|
|
}
|
|
}
|
|
|
|
// === Event System ===
|
|
|
|
on(event, callback) {
|
|
this.listeners.add({ event, callback });
|
|
return () => this.listeners.delete({ event, callback });
|
|
}
|
|
|
|
emit(event, data) {
|
|
this.listeners.forEach(l => {
|
|
if (l.event === event) l.callback(data);
|
|
});
|
|
}
|
|
|
|
// === API Methods ===
|
|
|
|
async extractVariables(fileKey, format = 'css') {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'extractVariables' });
|
|
|
|
try {
|
|
const response = await fetch(`${this.apiBase}/extract-variables`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey, format })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Failed to extract variables');
|
|
}
|
|
|
|
const result = await response.json();
|
|
this.emit('complete', { operation: 'extractVariables', result });
|
|
|
|
notificationService.create({
|
|
title: 'Variables Extracted',
|
|
message: `Successfully extracted ${result.variable_count || 'variables'} from Figma.`,
|
|
type: 'success',
|
|
source: 'Figma',
|
|
actions: [
|
|
{ label: 'View Tokens', event: 'navigate', payload: { page: 'tokens' } }
|
|
]
|
|
});
|
|
|
|
return result;
|
|
} catch (error) {
|
|
notificationService.create({
|
|
title: 'Extraction Failed',
|
|
message: error.message,
|
|
type: 'error',
|
|
source: 'Figma'
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async extractComponents(fileKey) {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'extractComponents' });
|
|
|
|
const response = await fetch(`${this.apiBase}/extract-components`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Failed to extract components');
|
|
}
|
|
|
|
const result = await response.json();
|
|
this.emit('complete', { operation: 'extractComponents', result });
|
|
return result;
|
|
}
|
|
|
|
async extractStyles(fileKey) {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'extractStyles' });
|
|
|
|
const response = await fetch(`${this.apiBase}/extract-styles`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Failed to extract styles');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async syncTokens(fileKey, targetPath, format = 'css') {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'syncTokens' });
|
|
|
|
try {
|
|
const response = await fetch(`${this.apiBase}/sync-tokens`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey, target_path: targetPath, format })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Sync failed');
|
|
}
|
|
|
|
const result = await response.json();
|
|
this.emit('complete', { operation: 'syncTokens', result });
|
|
|
|
notificationService.create({
|
|
title: 'Tokens Synced',
|
|
message: `Tokens successfully written to ${result.path || targetPath}.`,
|
|
type: 'success',
|
|
source: 'Figma',
|
|
actions: [
|
|
{ label: 'View Tokens', event: 'navigate', payload: { page: 'tokens' } }
|
|
]
|
|
});
|
|
|
|
return result;
|
|
} catch (error) {
|
|
notificationService.create({
|
|
title: 'Token Sync Failed',
|
|
message: error.message,
|
|
type: 'error',
|
|
source: 'Figma',
|
|
actions: [
|
|
{ label: 'Retry', event: 'figma:sync', payload: { fileKey } }
|
|
]
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async visualDiff(fileKey, baselineVersion = 'latest') {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'visualDiff' });
|
|
|
|
const response = await fetch(`${this.apiBase}/visual-diff`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey, baseline_version: baselineVersion })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Visual diff failed');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async validateComponents(fileKey) {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'validateComponents' });
|
|
|
|
const response = await fetch(`${this.apiBase}/validate-components`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Validation failed');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async generateCode(fileKey, componentName, framework = 'webcomponent') {
|
|
this._requireConnection();
|
|
this.emit('loading', { operation: 'generateCode' });
|
|
|
|
const response = await fetch(`${this.apiBase}/generate-code`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file_key: fileKey, component_name: componentName, framework })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Code generation failed');
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
}
|
|
|
|
const figmaService = new FigmaService();
|
|
export { FigmaService };
|
|
export default figmaService;
|