import { FigmaClient } from '../figma/api.js';
import { extractDesignSpecs, formatSpecsSummary, DesignSpecs } from '../figma/specs-extractor.js';
import { validateImplementation, formatValidationReport, ValidationReport } from '../figma/validator.js';
import { parseComponent } from '../parsers/jsx-parser.js';
import { readFileSync } from 'fs';
export interface GetFigmaSpecsInput {
figmaUrl?: string;
fileKey?: string;
nodeId?: string;
figmaToken: string;
}
export interface GetFigmaSpecsResult {
success: boolean;
specs?: DesignSpecs;
formatted?: string;
error?: string;
}
/**
* Parse Figma URL to extract file key and node ID
* Examples:
* - https://www.figma.com/file/ABC123/FileName?node-id=1:2
* - https://www.figma.com/design/ABC123/FileName?node-id=1-2
*/
function parseFigmaUrl(url: string): { fileKey: string; nodeId?: string } | null {
try {
const urlObj = new URL(url);
// Extract file key from path
const pathMatch = urlObj.pathname.match(/\/(file|design)\/([^/]+)/);
if (!pathMatch) return null;
const fileKey = pathMatch[2];
// Extract node ID from query params
let nodeId = urlObj.searchParams.get('node-id');
if (nodeId) {
// Convert URL format (1-2) to API format (1:2)
nodeId = nodeId.replace('-', ':');
}
return { fileKey, nodeId: nodeId || undefined };
} catch {
return null;
}
}
/**
* Get design specs from Figma
*/
export async function getFigmaSpecs(input: GetFigmaSpecsInput): Promise<GetFigmaSpecsResult> {
try {
let fileKey = input.fileKey;
let nodeId = input.nodeId;
// Parse URL if provided
if (input.figmaUrl) {
const parsed = parseFigmaUrl(input.figmaUrl);
if (!parsed) {
return {
success: false,
error: 'Invalid Figma URL format'
};
}
fileKey = parsed.fileKey;
nodeId = parsed.nodeId || nodeId;
}
if (!fileKey) {
return {
success: false,
error: 'Either figmaUrl or fileKey is required'
};
}
const client = new FigmaClient(input.figmaToken);
let specs: DesignSpecs;
if (nodeId) {
// Get specific node
const node = await client.getNode(fileKey, nodeId);
if (!node) {
return {
success: false,
error: `Node ${nodeId} not found in file ${fileKey}`
};
}
specs = extractDesignSpecs(node);
} else {
// Get entire file (document root)
const file = await client.getFile(fileKey);
specs = extractDesignSpecs(file.document);
}
return {
success: true,
specs,
formatted: formatSpecsSummary(specs)
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
export interface ValidateImplementationInput {
figmaUrl?: string;
fileKey?: string;
nodeId?: string;
figmaToken: string;
componentFile: string;
tolerance?: number;
}
export interface ValidateImplementationResult {
success: boolean;
report?: ValidationReport;
formatted?: string;
figmaSpecs?: DesignSpecs;
error?: string;
}
/**
* Validate component implementation against Figma specs
*/
export async function validateDesignImplementation(
input: ValidateImplementationInput
): Promise<ValidateImplementationResult> {
try {
// Get Figma specs
const figmaResult = await getFigmaSpecs({
figmaUrl: input.figmaUrl,
fileKey: input.fileKey,
nodeId: input.nodeId,
figmaToken: input.figmaToken
});
if (!figmaResult.success || !figmaResult.specs) {
return {
success: false,
error: figmaResult.error || 'Failed to get Figma specs'
};
}
// Parse the component file
const componentCode = readFileSync(input.componentFile, 'utf-8');
const parsedComponent = parseComponent(componentCode, input.componentFile);
// Validate
const report = validateImplementation(
figmaResult.specs,
parsedComponent.styles,
input.tolerance || 2
);
return {
success: true,
report,
formatted: formatValidationReport(report),
figmaSpecs: figmaResult.specs
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Format Figma specs result for MCP output
*/
export function formatFigmaSpecsResult(result: GetFigmaSpecsResult): string {
if (!result.success) {
return `Error: ${result.error}`;
}
const lines: string[] = [];
lines.push('# Figma Design Specs');
lines.push('');
lines.push(result.formatted || '');
lines.push('');
lines.push('---');
lines.push('*Use `validate_design_implementation` to compare these specs with your implementation.*');
return lines.join('\n');
}