-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathindex.ts
More file actions
310 lines (266 loc) · 14.8 KB
/
index.ts
File metadata and controls
310 lines (266 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/*
* Copyright (c) 2025, Salesforce, Inc.
* SPDX-License-Identifier: Apache-2
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {z} from 'zod';
import {readFileSync, existsSync} from 'node:fs';
import type {McpTool} from '../../../../utils/index.js';
import type {Services} from '../../../../services.js';
import {createToolAdapter, textResult} from '../../../adapter.js';
import {parseFigmaUrl, type FigmaParams} from './figma-url-parser.js';
// prettier-ignore
const DEFAULT_WORKFLOW_CONTENT = `---
description: Figma to StorefrontNext component conversion workflow
taskType: component
---
# Figma to StorefrontNext Component Workflow
IMPORTANT: The figma_to_component tool is a WORKFLOW ORCHESTRATOR that provides instructions only. It does NOT fetch design data or generate components.
After calling figma_to_component, you MUST:
1. Call Figma MCP tools to fetch design data
2. Discover similar components using Glob/Grep/Read
3. Call generate-component tool for REUSE/EXTEND/CREATE recommendation
4. Call map-tokens tool for token mapping
5. Implement the recommended approach
DO NOT STOP after receiving workflow instructions. Execute all steps to complete the conversion.
## WORKFLOW_GUIDELINES
### Overview
This workflow guides through converting a Figma design into a StorefrontNext-compliant component.
### Key Principles
1. Review workflow plan and list out todos to user before fetching designs
2. ALWAYS fetch design context and visual reference from Figma MCP tools first. Do not attempt to generate code without retrieving at minimum the design context and screenshot. Metadata is optional
3. Only call the Figma MCP tools listed in this workflow. If a tool is not available or not enabled, inform the user
4. ALWAYS discover similar components before creating new ones. Use Glob/Grep/Read to search the codebase
5. ALWAYS call generate-component tool with discovered components to get REUSE/EXTEND/CREATE recommendation
6. ALWAYS call map-tokens tool to map Figma tokens to existing theme variables rather than hardcoding values
7. Follow StorefrontNext patterns. All components must adhere to StorefrontNext architecture
8. Present a detailed plan to the user and wait for approval before implementing
9. Validate thoroughly. Always run validation checks before presenting the final component to the developer
### Figma MCP Tools
When calling these tools, always include: clientLanguages="typescript", clientFrameworks="react"
- mcp__figma__get_design_context (REQUIRED): Generates UI code and returns asset URLs
- mcp__figma__get_screenshot (REQUIRED): Provides visual reference of the design
- mcp__figma__get_metadata (OPTIONAL): Retrieves node hierarchy, layer types, names, positions, and sizes. Use only if you need detailed structural information
### StorefrontNext MCP Tools (REQUIRED)
- generate-component: Analyzes Figma design and discovered components, recommends REUSE/EXTEND/CREATE strategy. MUST be called with discoveredComponents parameter
- map-tokens: Maps Figma design tokens to existing theme tokens. MUST be called to avoid hardcoded values
- validate_component: Validates component against StorefrontNext patterns (optional, not yet implemented)
### AI-Driven Component Discovery (Before calling generate-component)
Before calling generate-component, you must discover similar components using your tools:
**Discovery Strategy:**
1. **Name-Based Search (Primary):**
- Use Glob to find component files: \`**/components/**/*.tsx\`, \`**/src/**/*.tsx\`
- Exclude: \`**/node_modules/**\`, \`**/dist/**\`, \`**/*.test.tsx\`, \`**/*.stories.tsx\`
- Use Grep to search for component names similar to the Figma component name
- Look in: export statements, function names, interface names
2. **Structure-Based Search (Secondary):**
- If name search yields poor results, search by code structure
- Look for similar hooks (useState, useEffect, etc.)
- Look for similar element patterns (buttons, forms, layouts)
- Search for 'use client' directive if Figma code is client-side
3. **Read and Score Components:**
- Read each promising match
- Score similarity (0-100) based on:
* Name similarity: How close is the name?
* Purpose similarity: Does it serve the same function?
* Structure similarity: Similar JSX structure, hooks, props?
* Styling similarity: Similar Tailwind classes or theme usage?
- Assign match type: 'name', 'structure', or 'visual'
4. **Select Top Matches:**
- Select top 1-3 matches with similarity >= 50%
- Sort by similarity score (highest first)
- If no matches found, pass empty array to generate-component
**Discovery Tips:**
- Be semantic: "PrimaryButton" and "CallToAction" might serve the same purpose
- Consider component purpose and context, not just file names
- Check common directories first: components/ui/, components/shared/
- Read component code to understand structure, props, and behavior
- Trust your judgment using React patterns knowledge
### Component Requirements
- Use React Server Components (RSC) pattern by default
- Use Tailwind CSS classes with theme tokens, no inline styles or hardcoded values
- Follow TypeScript strict mode conventions
- Include proper accessibility attributes
- Follow existing file naming conventions
- Use absolute imports from '@/components', '@/lib', etc.
## General Development Guidelines
### Core Principles
- Thoroughly analyze requests and the existing project for successful implementation
- Promptly clarify ambiguous requirements
### Development Workflow
- **Analyze Requirements** - Clearly define the objectives and functionalities required
- **Review Existing Code** - Examine the current codebase to identify similar solutions and potentially reusable components
- **Understand Existing Hooks and Utilities** - Familiarize with hooks and utility functions available within the project
- **Plan Implementation** - Design component structure before coding
- **Implement Incrementally** - Develop and test the service in small, manageable steps
- **Test Thoroughly** - Ensure comprehensive testing
### After Generation
- Present the component code to the developer for review
- Provide file path suggestions based on component type
- Highlight any design tokens that don't have existing mappings
- List any validation warnings or suggestions
## WORKFLOW_STEPS
**Create and present to the user a task plan that reflects these steps while keeping the Workflow Guidelines in mind. Wait for approval before proceeding.**
1. REQUIRED: Retrieve design context and generated code using mcp__figma__get_design_context with the provided fileKey and nodeId
2. REQUIRED: Retrieve visual reference using mcp__figma__get_screenshot with the provided fileKey and nodeId
3. OPTIONAL: Retrieve design metadata using mcp__figma__get_metadata with the provided fileKey and nodeId (only if you need detailed structural information about the design hierarchy)
4. REQUIRED: Discover similar components in the codebase:
- Use Glob to find component files in common directories
- Use Grep to search for components with similar names or structure
- Use Read to examine promising matches
- Score similarity (0-100) and select top 1-3 matches
- Prepare discoveredComponents array for next step
5. REQUIRED: Analyze component generation strategy using generate-component tool with discovered components. This provides REUSE/EXTEND/CREATE recommendation. Wait for user approval of strategy before code changes
6. REQUIRED: Map Figma design tokens to existing StorefrontNext theme tokens using the map-tokens tool. Extract color, spacing, and other design tokens from Figma data and pass them to this tool for matching
7. OPTIONAL (not implemented): Validate the generated component against StorefrontNext patterns using the validate_component tool
8. REQUIRED: Implement the recommended approach and present the final component code to the developer for review`;
export const figmaToComponentSchema = z
.object({
figmaUrl: z
.string()
.url()
.describe('The Figma design URL to convert to a StorefrontNext component. Must include node-id parameter.'),
workflowFilePath: z
.string()
.optional()
.describe('Optional absolute path to custom workflow .md file. If not provided, uses default built-in workflow.'),
})
.strict();
export type FigmaToComponentInput = z.infer<typeof figmaToComponentSchema>;
export interface WorkflowConfig {
/** YAML frontmatter key-value pairs. Parsed for future use (e.g., taskType-specific behavior). */
metadata: Record<string, string>;
content: string;
}
function extractWorkflowContent(content: string): {metadata: Record<string, string>; body: string} {
const metadata: Record<string, string> = {};
let body = content;
const metadataMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
if (metadataMatch) {
const metadataText = metadataMatch[1];
body = metadataMatch[2];
for (const line of metadataText.split('\n')) {
const match = line.match(/^(.+?):\s*(.+)$/);
if (match) {
metadata[match[1].trim()] = match[2].trim();
}
}
}
return {metadata, body: body.trim()};
}
function parseWorkflowFile(filePath?: string): WorkflowConfig {
let fileContent: string;
if (filePath) {
if (!existsSync(filePath)) {
throw new Error(`Workflow file not found: ${filePath}`);
}
fileContent = readFileSync(filePath, 'utf8');
} else {
fileContent = DEFAULT_WORKFLOW_CONTENT;
}
const {metadata, body} = extractWorkflowContent(fileContent);
return {metadata, content: body};
}
function formatFigmaParams(params: FigmaParams, originalUrl: string): string {
let section = '## Figma Design Parameters\n\n';
section += '```json\n';
section += JSON.stringify(
{
fileKey: params.fileKey,
nodeId: params.nodeId,
originalUrl,
},
null,
2,
);
section += '\n```\n\n';
section +=
'IMPORTANT: Use these exact parameters when calling Figma MCP tools. The `clientLanguages` parameter should be set to "typescript" and `clientFrameworks` should be set to "react".\n\n';
return section;
}
function formatWorkflowContent(content: string): string {
return `${content}\n\n`;
}
function formatNextStepsReminder(): string {
let reminder = '---\n\n';
reminder += '## CRITICAL: Next Steps Required\n\n';
reminder += 'This tool has provided workflow instructions only. You MUST now execute ALL steps below.\n\n';
reminder +=
'**EXPECTED FINAL OUTPUT:** A recommendation with confidence score from generate_component tool AND a token mapping summary from map_tokens_to_theme tool.\n\n';
reminder += '### Step 1: Fetch Figma Design Data (Parallel Calls)\n';
reminder += 'Call these Figma MCP tools with the parameters above:\n';
reminder += '- `mcp__figma__get_design_context` (REQUIRED) - Get generated React code\n';
reminder += '- `mcp__figma__get_screenshot` (REQUIRED) - Get visual reference\n';
reminder += '- `mcp__figma__get_metadata` (OPTIONAL) - Get node structure and hierarchy if needed\n\n';
reminder += '### Step 2: Discover Similar Components\n';
reminder += 'Use your tools to find existing components:\n';
reminder += '- Use `Glob` to find component files: `**/components/**/*.tsx`\n';
reminder += '- Use `Grep` to search for similar names or patterns\n';
reminder += '- Use `Read` to examine promising matches\n';
reminder += '- Score each match (0-100) based on similarity\n\n';
reminder += '### Step 3: Analyze Component Strategy (CRITICAL - DO NOT SKIP)\n';
reminder +=
'You MUST call `generate_component` tool with:\n- figmaMetadata (from step 1, or empty string if not fetched)\n- figmaCode (from step 1)\n- componentName (extracted from Figma)\n- discoveredComponents (from step 2)\n\n';
reminder += 'This tool returns the recommendation with confidence score that MUST be shown to the user.\n\n';
reminder += '### Step 4: Map Design Tokens (CRITICAL - DO NOT SKIP)\n';
reminder += 'You MUST call `map_tokens_to_theme` tool with tokens extracted from Figma design.\n\n';
reminder += 'This tool returns the token mapping summary that MUST be shown to the user.\n\n';
reminder += '### Step 5: Implement\n';
reminder +=
'After showing the recommendation and token mapping to the user, wait for approval then implement the code changes.\n\n';
reminder +=
'**DO NOT STOP until you have called generate_component AND map_tokens_to_theme and shown their outputs to the user.**\n\n';
return reminder;
}
function formatErrorResponse(details: string): string {
let response = `# Error: Invalid Figma URL\n\n${details}\n\n`;
response += 'Please provide a valid Figma URL in the format:\n';
response += 'https://figma.com/design/:fileKey/:fileName?node-id=1-2\n\n';
response += 'Example:\nhttps://figma.com/design/abc123/MyDesign?node-id=1-2\n';
return response;
}
export function generateWorkflowResponse(figmaUrl: string, workflowFilePath?: string): string {
let figmaParams: FigmaParams;
try {
figmaParams = parseFigmaUrl(figmaUrl);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return formatErrorResponse(errorMessage);
}
let workflowConfig: WorkflowConfig;
try {
workflowConfig = parseWorkflowFile(workflowFilePath);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return `# Error: Workflow File Not Found\n\n${errorMessage}\n\nPlease provide a valid workflow file path or omit the parameter to use the default workflow.\n`;
}
let response = '# Figma to StorefrontNext Workflow Guide\n\n';
response += formatFigmaParams(figmaParams, figmaUrl);
response += formatWorkflowContent(workflowConfig.content);
response += formatNextStepsReminder();
return response;
}
export function createFigmaToComponentTool(loadServices: () => Services): McpTool {
return createToolAdapter<FigmaToComponentInput, string>(
{
name: 'storefront_next_figma_to_component_workflow',
description:
'WORKFLOW ORCHESTRATOR: Call this tool FIRST when converting Figma designs. ' +
'Parses Figma URL to extract fileKey and nodeId, returns step-by-step workflow instructions ' +
'and parameters for subsequent tool calls. ' +
'CRITICAL: This is only the FIRST step. After calling this tool, you MUST continue executing ' +
'the complete workflow: 1) Call Figma MCP tools, 2) Discover similar components, ' +
'3) Call generate_component tool, 4) Call map_tokens_to_theme tool, ' +
'5) Show both outputs to the user then implement the recommended approach.',
toolsets: ['STOREFRONTNEXT'],
isGA: false,
requiresInstance: false,
inputSchema: figmaToComponentSchema.shape,
async execute(args) {
return generateWorkflowResponse(args.figmaUrl, args.workflowFilePath);
},
formatOutput: (output) => textResult(output),
},
loadServices,
);
}