@salesforce/b2c-dx-mcp 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -370
- package/content/pwav3/components.md +400 -0
- package/content/pwav3/config.md +124 -0
- package/content/pwav3/data-fetching.md +213 -0
- package/content/pwav3/extensibility.md +167 -0
- package/content/pwav3/i18n.md +214 -0
- package/content/pwav3/quick-reference.md +169 -0
- package/content/pwav3/routing.md +107 -0
- package/content/pwav3/state-management.md +193 -0
- package/content/pwav3/styling.md +248 -0
- package/content/pwav3/testing.md +124 -0
- package/content/site-theming/theming-accessibility.md +126 -0
- package/content/site-theming/theming-questions.md +208 -0
- package/content/site-theming/theming-validation.md +174 -0
- package/dist/registry.js +1 -1
- package/dist/services.d.ts +10 -10
- package/dist/services.js +19 -12
- package/dist/tools/cartridges/index.js +1 -6
- package/dist/tools/index.d.ts +1 -4
- package/dist/tools/index.js +1 -4
- package/dist/tools/mrt/index.js +1 -6
- package/dist/tools/pwav3/index.d.ts +12 -3
- package/dist/tools/pwav3/index.js +5 -63
- package/dist/tools/pwav3/pwa-kit-development-guidelines.d.ts +9 -0
- package/dist/tools/pwav3/pwa-kit-development-guidelines.js +151 -0
- package/dist/tools/scapi/index.d.ts +1 -1
- package/dist/tools/scapi/index.js +6 -1
- package/dist/tools/scapi/scapi-custom-api-scaffold.d.ts +60 -0
- package/dist/tools/scapi/scapi-custom-api-scaffold.js +175 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +24 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +53 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +42 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.js +325 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +40 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.js +312 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +9 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.js +92 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +114 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.js +98 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +71 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +260 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +61 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.js +234 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +65 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +268 -0
- package/dist/tools/storefrontnext/index.d.ts +17 -0
- package/dist/tools/storefrontnext/index.js +10 -60
- package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +15 -0
- package/dist/tools/storefrontnext/page-designer-decorator/index.js +3 -3
- package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js} +3 -3
- package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +92 -0
- package/dist/tools/storefrontnext/site-theming/color-contrast.js +186 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.js +131 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +11 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.js +78 -0
- package/dist/tools/storefrontnext/site-theming/index.d.ts +14 -0
- package/dist/tools/storefrontnext/site-theming/index.js +122 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.js +316 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +62 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.js +410 -0
- package/dist/tools/storefrontnext/site-theming/types.d.ts +35 -0
- package/dist/tools/storefrontnext/site-theming/types.js +7 -0
- package/oclif.manifest.json +1 -1
- package/package.json +8 -5
- /package/content/{auth.md → sfnext/auth.md} +0 -0
- /package/content/{components.md → sfnext/components.md} +0 -0
- /package/content/{config.md → sfnext/config.md} +0 -0
- /package/content/{data-fetching.md → sfnext/data-fetching.md} +0 -0
- /package/content/{extensions.md → sfnext/extensions.md} +0 -0
- /package/content/{i18n.md → sfnext/i18n.md} +0 -0
- /package/content/{page-designer.md → sfnext/page-designer.md} +0 -0
- /package/content/{performance.md → sfnext/performance.md} +0 -0
- /package/content/{pitfalls.md → sfnext/pitfalls.md} +0 -0
- /package/content/{quick-reference.md → sfnext/quick-reference.md} +0 -0
- /package/content/{state-management.md → sfnext/state-management.md} +0 -0
- /package/content/{styling.md → sfnext/styling.md} +0 -0
- /package/content/{testing.md → sfnext/testing.md} +0 -0
- /package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts} +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Analyzes differences between matched component and Figma design.
|
|
8
|
+
*
|
|
9
|
+
* @param matchedComponent - The existing component to compare against
|
|
10
|
+
* @param figmaCode - The Figma-generated React code
|
|
11
|
+
* @param _figmaMetadata - Reserved for future use (e.g., component hierarchy analysis).
|
|
12
|
+
* Currently unused but kept in the signature to avoid a breaking change once metadata
|
|
13
|
+
* analysis is implemented.
|
|
14
|
+
*/
|
|
15
|
+
export function analyzeComponentDifferences(matchedComponent, figmaCode, _figmaMetadata) {
|
|
16
|
+
const differences = {
|
|
17
|
+
styling: [],
|
|
18
|
+
structural: [],
|
|
19
|
+
behavioral: [],
|
|
20
|
+
props: [],
|
|
21
|
+
};
|
|
22
|
+
// Analyze styling differences
|
|
23
|
+
differences.styling = analyzeStylingDifferences(matchedComponent.code, figmaCode);
|
|
24
|
+
// Analyze structural differences
|
|
25
|
+
differences.structural = analyzeStructuralDifferences(matchedComponent.code, figmaCode);
|
|
26
|
+
// Analyze behavioral differences (hooks, state, effects)
|
|
27
|
+
differences.behavioral = analyzeBehavioralDifferences(matchedComponent.code, figmaCode);
|
|
28
|
+
// Analyze prop differences
|
|
29
|
+
differences.props = analyzePropDifferences(matchedComponent.code, figmaCode);
|
|
30
|
+
return differences;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Analyzes styling differences (Tailwind classes, CSS, theme tokens)
|
|
34
|
+
*/
|
|
35
|
+
function analyzeStylingDifferences(existingCode, figmaCode) {
|
|
36
|
+
const differences = [];
|
|
37
|
+
// Extract className usage
|
|
38
|
+
const existingClasses = extractTailwindClasses(existingCode);
|
|
39
|
+
const figmaClasses = extractTailwindClasses(figmaCode);
|
|
40
|
+
// Check for new classes in Figma design
|
|
41
|
+
const newClasses = figmaClasses.filter((c) => !existingClasses.includes(c));
|
|
42
|
+
if (newClasses.length > 0) {
|
|
43
|
+
differences.push({
|
|
44
|
+
description: `New Tailwind classes: ${newClasses.slice(0, 5).join(', ')}${newClasses.length > 5 ? '...' : ''}`,
|
|
45
|
+
severity: newClasses.length > 10 ? 'major' : newClasses.length > 3 ? 'moderate' : 'minor',
|
|
46
|
+
isBackwardCompatible: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Check for inline styles (anti-pattern)
|
|
50
|
+
if (figmaCode.includes('style={{') || figmaCode.includes('style="')) {
|
|
51
|
+
differences.push({
|
|
52
|
+
description: 'Figma code contains inline styles (needs conversion to Tailwind)',
|
|
53
|
+
severity: 'moderate',
|
|
54
|
+
isBackwardCompatible: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return differences;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Analyzes structural differences (JSX hierarchy, elements)
|
|
61
|
+
*/
|
|
62
|
+
function analyzeStructuralDifferences(existingCode, figmaCode) {
|
|
63
|
+
const differences = [];
|
|
64
|
+
// Extract JSX elements
|
|
65
|
+
const existingElements = extractJSXElements(existingCode);
|
|
66
|
+
const figmaElements = extractJSXElements(figmaCode);
|
|
67
|
+
// Check for different root elements
|
|
68
|
+
if (existingElements[0] !== figmaElements[0]) {
|
|
69
|
+
differences.push({
|
|
70
|
+
description: `Different root element: ${existingElements[0]} vs ${figmaElements[0]}`,
|
|
71
|
+
severity: 'moderate',
|
|
72
|
+
isBackwardCompatible: false,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// Check for additional nested elements
|
|
76
|
+
const newElements = figmaElements.filter((e) => !existingElements.includes(e));
|
|
77
|
+
if (newElements.length > 0) {
|
|
78
|
+
differences.push({
|
|
79
|
+
description: `New elements in Figma design: ${newElements.join(', ')}`,
|
|
80
|
+
severity: newElements.length > 3 ? 'major' : 'minor',
|
|
81
|
+
isBackwardCompatible: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return differences;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Analyzes behavioral differences (hooks, state, effects, event handlers)
|
|
88
|
+
*/
|
|
89
|
+
function analyzeBehavioralDifferences(existingCode, figmaCode) {
|
|
90
|
+
const differences = [];
|
|
91
|
+
// Check for 'use client' directive
|
|
92
|
+
const existingIsClient = existingCode.includes("'use client'") || existingCode.includes('"use client"');
|
|
93
|
+
const figmaIsClient = figmaCode.includes("'use client'") || figmaCode.includes('"use client"');
|
|
94
|
+
if (existingIsClient !== figmaIsClient) {
|
|
95
|
+
differences.push({
|
|
96
|
+
description: figmaIsClient
|
|
97
|
+
? 'Figma design requires client-side rendering'
|
|
98
|
+
: 'Existing component is client-side, Figma design could be RSC',
|
|
99
|
+
severity: 'major',
|
|
100
|
+
isBackwardCompatible: false,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Check for new hooks
|
|
104
|
+
const existingHooks = extractHooks(existingCode);
|
|
105
|
+
const figmaHooks = extractHooks(figmaCode);
|
|
106
|
+
const newHooks = figmaHooks.filter((h) => !existingHooks.includes(h));
|
|
107
|
+
if (newHooks.length > 0) {
|
|
108
|
+
differences.push({
|
|
109
|
+
description: `New React hooks needed: ${newHooks.join(', ')}`,
|
|
110
|
+
severity: 'moderate',
|
|
111
|
+
isBackwardCompatible: newHooks.every((h) => h.startsWith('use')),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Check for event handlers
|
|
115
|
+
const existingHasHandlers = /on[A-Z]\w+=/g.test(existingCode);
|
|
116
|
+
const figmaHasHandlers = /on[A-Z]\w+=/g.test(figmaCode);
|
|
117
|
+
if (!existingHasHandlers && figmaHasHandlers) {
|
|
118
|
+
differences.push({
|
|
119
|
+
description: 'Figma design includes event handlers (onClick, onChange, etc.)',
|
|
120
|
+
severity: 'moderate',
|
|
121
|
+
isBackwardCompatible: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return differences;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Analyzes prop differences
|
|
128
|
+
* parses TypeScript interfaces to compare prop definitions
|
|
129
|
+
*/
|
|
130
|
+
function analyzePropDifferences(existingCode, figmaCode) {
|
|
131
|
+
const differences = [];
|
|
132
|
+
const existingPropCount = (existingCode.match(/interface\s+\w+Props/g) || []).length;
|
|
133
|
+
const figmaPropCount = (figmaCode.match(/interface\s+\w+Props/g) || []).length;
|
|
134
|
+
if (figmaPropCount > existingPropCount) {
|
|
135
|
+
differences.push({
|
|
136
|
+
description: 'Figma design may require additional props',
|
|
137
|
+
severity: 'minor',
|
|
138
|
+
isBackwardCompatible: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return differences;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Extracts Tailwind classes from code
|
|
145
|
+
*/
|
|
146
|
+
function extractTailwindClasses(code) {
|
|
147
|
+
const classRegex = /className=["']([^"']+)["']/g;
|
|
148
|
+
const classes = new Set();
|
|
149
|
+
let match;
|
|
150
|
+
while ((match = classRegex.exec(code)) !== null) {
|
|
151
|
+
const classList = match[1].split(/\s+/);
|
|
152
|
+
for (const c of classList)
|
|
153
|
+
classes.add(c);
|
|
154
|
+
}
|
|
155
|
+
return [...classes];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extracts JSX elements from code (simplified)
|
|
159
|
+
*/
|
|
160
|
+
function extractJSXElements(code) {
|
|
161
|
+
// Simple regex to find JSX opening tags
|
|
162
|
+
const elementRegex = /<([A-Z][a-zA-Z0-9]*|[a-z]+)[\s>]/g;
|
|
163
|
+
const elements = new Set();
|
|
164
|
+
let match;
|
|
165
|
+
while ((match = elementRegex.exec(code)) !== null) {
|
|
166
|
+
elements.add(match[1]);
|
|
167
|
+
}
|
|
168
|
+
return [...elements];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Extracts React hooks from code
|
|
172
|
+
*/
|
|
173
|
+
function extractHooks(code) {
|
|
174
|
+
const hookRegex = /\b(use[A-Z]\w+)\(/g;
|
|
175
|
+
const hooks = new Set();
|
|
176
|
+
let match;
|
|
177
|
+
while ((match = hookRegex.exec(code)) !== null) {
|
|
178
|
+
hooks.add(match[1]);
|
|
179
|
+
}
|
|
180
|
+
return [...hooks];
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Determines the appropriate action based on differences
|
|
184
|
+
* Uses type of difference + impact assessment
|
|
185
|
+
*/
|
|
186
|
+
export function determineAction(matchedComponent, differences) {
|
|
187
|
+
const allDifferences = [
|
|
188
|
+
...differences.styling,
|
|
189
|
+
...differences.structural,
|
|
190
|
+
...differences.behavioral,
|
|
191
|
+
...differences.props,
|
|
192
|
+
];
|
|
193
|
+
const severityScores = { minor: 1, moderate: 3, major: 5 };
|
|
194
|
+
let differenceScore = 0;
|
|
195
|
+
for (const diff of allDifferences) {
|
|
196
|
+
differenceScore += severityScores[diff.severity];
|
|
197
|
+
}
|
|
198
|
+
// Count breaking changes
|
|
199
|
+
const breakingChanges = allDifferences.filter((d) => !d.isBackwardCompatible).length;
|
|
200
|
+
// Decision thresholds
|
|
201
|
+
const REUSE_THRESHOLD = 2; // Only minor styling differences
|
|
202
|
+
const EXTEND_THRESHOLD = 10; // Moderate differences that can be added
|
|
203
|
+
// REUSE: Minimal differences, mostly styling
|
|
204
|
+
if (differenceScore <= REUSE_THRESHOLD && breakingChanges === 0) {
|
|
205
|
+
return {
|
|
206
|
+
action: 'REUSE',
|
|
207
|
+
confidence: Math.round(matchedComponent.similarity),
|
|
208
|
+
matchedComponent: {
|
|
209
|
+
path: matchedComponent.path,
|
|
210
|
+
name: matchedComponent.name,
|
|
211
|
+
similarity: matchedComponent.similarity,
|
|
212
|
+
},
|
|
213
|
+
differences: allDifferences.map((d) => d.description),
|
|
214
|
+
recommendation: `The existing component "${matchedComponent.name}" can be reused with different props or minor styling adjustments.`,
|
|
215
|
+
suggestedApproach: `Use the existing component at ${matchedComponent.path} and customize it through props.`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// CREATE: Major structural or behavioral differences, or many breaking changes
|
|
219
|
+
if (differenceScore > EXTEND_THRESHOLD || breakingChanges > 2) {
|
|
220
|
+
return {
|
|
221
|
+
action: 'CREATE',
|
|
222
|
+
confidence: 85,
|
|
223
|
+
matchedComponent: {
|
|
224
|
+
path: matchedComponent.path,
|
|
225
|
+
name: matchedComponent.name,
|
|
226
|
+
similarity: matchedComponent.similarity,
|
|
227
|
+
},
|
|
228
|
+
differences: allDifferences.map((d) => d.description),
|
|
229
|
+
recommendation: `Differences are significant enough to warrant creating a new component.`,
|
|
230
|
+
suggestedApproach: `Create a new component. You may reference patterns from ${matchedComponent.path} but build a new component.`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// EXTEND: Moderate differences that can be added
|
|
234
|
+
// Determine extend strategy: props / variant / composition
|
|
235
|
+
const extendStrategy = determineExtendStrategy(differences, allDifferences);
|
|
236
|
+
return {
|
|
237
|
+
action: 'EXTEND',
|
|
238
|
+
confidence: Math.round((matchedComponent.similarity + 100 - differenceScore * 2) / 2),
|
|
239
|
+
matchedComponent: {
|
|
240
|
+
path: matchedComponent.path,
|
|
241
|
+
name: matchedComponent.name,
|
|
242
|
+
similarity: matchedComponent.similarity,
|
|
243
|
+
},
|
|
244
|
+
differences: allDifferences.map((d) => d.description),
|
|
245
|
+
recommendation: `The existing component "${matchedComponent.name}" can be extended to support the Figma design.`,
|
|
246
|
+
suggestedApproach: generateExtendApproach(extendStrategy, matchedComponent, differences),
|
|
247
|
+
extendStrategy,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Determines the best extend strategy based on differences
|
|
252
|
+
* Context-dependent: checks type of difference then validates with impact
|
|
253
|
+
*/
|
|
254
|
+
function determineExtendStrategy(differences, allDifferences) {
|
|
255
|
+
// Props extension: Only new optional behaviors (1-3 new props, backward compatible)
|
|
256
|
+
if (differences.props.length <= 3 &&
|
|
257
|
+
differences.structural.length === 0 &&
|
|
258
|
+
differences.behavioral.length <= 1 &&
|
|
259
|
+
allDifferences.every((d) => d.isBackwardCompatible)) {
|
|
260
|
+
return 'props';
|
|
261
|
+
}
|
|
262
|
+
// Composition: Structural changes or new child components
|
|
263
|
+
if (differences.structural.length > 0 || differences.behavioral.some((d) => !d.isBackwardCompatible)) {
|
|
264
|
+
return 'composition';
|
|
265
|
+
}
|
|
266
|
+
// Variant pattern: Visual variations (styling focused, 4+ new classes)
|
|
267
|
+
if (differences.styling.some((d) => d.severity !== 'minor')) {
|
|
268
|
+
return 'variant';
|
|
269
|
+
}
|
|
270
|
+
// Default to props for small changes
|
|
271
|
+
return 'props';
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Generates extend approach description based on strategy
|
|
275
|
+
*/
|
|
276
|
+
function generateExtendApproach(strategy, matchedComponent, differences) {
|
|
277
|
+
const componentPath = matchedComponent.path;
|
|
278
|
+
const componentName = matchedComponent.name;
|
|
279
|
+
switch (strategy) {
|
|
280
|
+
case 'composition': {
|
|
281
|
+
return `**Composition Pattern**
|
|
282
|
+
Create a new component that wraps/composes the existing one:
|
|
283
|
+
1. Create new component: ${componentName}Enhanced.tsx
|
|
284
|
+
2. Import and compose: <${componentName}>{/* new elements */}</${componentName}>
|
|
285
|
+
3. Structural changes: ${differences.structural.map((d) => d.description).join(', ')}
|
|
286
|
+
4. This preserves the existing component while adding new behavior
|
|
287
|
+
|
|
288
|
+
This approach works because there are structural changes that would break existing usage if added directly.`;
|
|
289
|
+
}
|
|
290
|
+
case 'props': {
|
|
291
|
+
return `**Props Extension Pattern**
|
|
292
|
+
Extend the existing component by adding new optional props:
|
|
293
|
+
1. Modify ${componentPath}
|
|
294
|
+
2. Add new props to the interface (${differences.props.map((d) => d.description).join(', ')})
|
|
295
|
+
3. Implement the new prop behavior while maintaining backward compatibility
|
|
296
|
+
4. Ensure existing usage is not affected
|
|
297
|
+
|
|
298
|
+
This approach works because the changes are small and backward compatible.`;
|
|
299
|
+
}
|
|
300
|
+
case 'variant': {
|
|
301
|
+
return `**Variant Pattern**
|
|
302
|
+
Add new visual variants to the existing component:
|
|
303
|
+
1. Modify ${componentPath}
|
|
304
|
+
2. Add variant definitions using your variant system (e.g., CVA)
|
|
305
|
+
3. New styling: ${differences.styling.map((d) => d.description).join(', ')}
|
|
306
|
+
4. Extend the component's visual options without breaking existing usage
|
|
307
|
+
|
|
308
|
+
This approach works because the changes are primarily styling-focused.`;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=decision.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentAnalysisResult, GenerateComponentInput } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Formats the component generation recommendation for AI/developer.
|
|
4
|
+
*
|
|
5
|
+
* @param analysis - Component analysis result with REUSE/EXTEND/CREATE decision
|
|
6
|
+
* @param input - Original generate-component input (component name, etc.)
|
|
7
|
+
* @returns Formatted markdown string with recommendation, key differences, suggested approach, and next steps
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatRecommendation(analysis: ComponentAnalysisResult, input: GenerateComponentInput): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Formats the component generation recommendation for AI/developer.
|
|
8
|
+
*
|
|
9
|
+
* @param analysis - Component analysis result with REUSE/EXTEND/CREATE decision
|
|
10
|
+
* @param input - Original generate-component input (component name, etc.)
|
|
11
|
+
* @returns Formatted markdown string with recommendation, key differences, suggested approach, and next steps
|
|
12
|
+
*/
|
|
13
|
+
export function formatRecommendation(analysis, input) {
|
|
14
|
+
let output = '# Component Generation Recommendation\n\n';
|
|
15
|
+
// Decision and confidence
|
|
16
|
+
output += `**Decision:** ${analysis.action}\n`;
|
|
17
|
+
output += `**Confidence:** ${analysis.confidence}%\n\n`;
|
|
18
|
+
// Matched component (if any)
|
|
19
|
+
if (analysis.matchedComponent) {
|
|
20
|
+
output += `**Matched Component:**\n`;
|
|
21
|
+
output += `- \`${analysis.matchedComponent.name}\` at \`${analysis.matchedComponent.path}\`\n`;
|
|
22
|
+
output += `- Similarity: ${analysis.matchedComponent.similarity}%\n\n`;
|
|
23
|
+
}
|
|
24
|
+
// Recommendation
|
|
25
|
+
output += `**Recommendation:** ${analysis.recommendation}\n\n`;
|
|
26
|
+
// Key differences
|
|
27
|
+
if (analysis.differences && analysis.differences.length > 0) {
|
|
28
|
+
output += `**Key Differences:**\n`;
|
|
29
|
+
for (const [i, diff] of analysis.differences.entries()) {
|
|
30
|
+
output += `${i + 1}. ${diff}\n`;
|
|
31
|
+
}
|
|
32
|
+
output += '\n';
|
|
33
|
+
}
|
|
34
|
+
// Suggested approach
|
|
35
|
+
if (analysis.suggestedApproach) {
|
|
36
|
+
output += `## Suggested Approach\n\n${analysis.suggestedApproach}\n\n`;
|
|
37
|
+
}
|
|
38
|
+
// Next steps
|
|
39
|
+
output += formatNextSteps(analysis, input);
|
|
40
|
+
return output;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Formats next steps based on action type
|
|
44
|
+
*/
|
|
45
|
+
function formatNextSteps(analysis, input) {
|
|
46
|
+
let section = '## Next Steps\n\n';
|
|
47
|
+
switch (analysis.action) {
|
|
48
|
+
case 'CREATE': {
|
|
49
|
+
section += `Create new component: \`${input.componentName}\`\n\n`;
|
|
50
|
+
section += `**Implementation:**\n`;
|
|
51
|
+
section += `1. Create component file structure (index.tsx, types.ts if needed)\n`;
|
|
52
|
+
section += `2. Convert Figma code to StorefrontNext patterns:\n`;
|
|
53
|
+
section += ` - Use React Server Components by default (add 'use client' if needed)\n`;
|
|
54
|
+
section += ` - Replace inline styles with Tailwind classes\n`;
|
|
55
|
+
section += ` - Map colors/spacing to theme tokens\n`;
|
|
56
|
+
section += ` - Add proper TypeScript types and accessibility attributes\n`;
|
|
57
|
+
section += `3. Export component from index\n`;
|
|
58
|
+
if (analysis.matchedComponent) {
|
|
59
|
+
section += `4. Reference patterns from \`${analysis.matchedComponent.path}\` for consistency\n`;
|
|
60
|
+
}
|
|
61
|
+
section += '\n';
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'EXTEND': {
|
|
65
|
+
const strategy = analysis.extendStrategy || 'props';
|
|
66
|
+
section += `**Strategy:** ${strategy === 'props' ? 'Add new props' : strategy === 'variant' ? 'Add variant' : 'Composition'}\n\n`;
|
|
67
|
+
section += `1. Modify \`${analysis.matchedComponent?.path}\`\n`;
|
|
68
|
+
if (strategy === 'props') {
|
|
69
|
+
section += `2. Add new optional props to the interface\n`;
|
|
70
|
+
section += `3. Implement new prop behavior while maintaining backward compatibility\n`;
|
|
71
|
+
}
|
|
72
|
+
else if (strategy === 'variant') {
|
|
73
|
+
section += `2. Add new variant to existing variant definitions\n`;
|
|
74
|
+
section += `3. Apply variant styling using theme tokens\n`;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
section += `2. Create wrapper component that composes the base component\n`;
|
|
78
|
+
section += `3. Add additional elements/functionality in the wrapper\n`;
|
|
79
|
+
}
|
|
80
|
+
section += `4. Validate: ensure existing usages still work\n\n`;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'REUSE': {
|
|
84
|
+
section += `Import and use \`${analysis.matchedComponent?.name}\` from \`${analysis.matchedComponent?.path}\`.\n`;
|
|
85
|
+
section += `Customize through props and Tailwind classes to match the Figma design.\n\n`;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
section += '**Confirm before proceeding with implementation.**\n';
|
|
90
|
+
return section;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=formatter.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate component tool for Figma-to-component workflow.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes Figma design and discovered components to recommend REUSE, EXTEND, or CREATE strategy.
|
|
5
|
+
*
|
|
6
|
+
* @module tools/storefrontnext/figma/generate-component
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { McpTool } from '../../../../utils/index.js';
|
|
10
|
+
import type { Services } from '../../../../services.js';
|
|
11
|
+
/**
|
|
12
|
+
* A component discovered in the codebase that may match the Figma design.
|
|
13
|
+
*
|
|
14
|
+
* @property {string} path - Absolute file path to the component
|
|
15
|
+
* @property {string} name - Component name
|
|
16
|
+
* @property {number} similarity - Similarity score (0-100)
|
|
17
|
+
* @property {'name'|'structure'|'visual'} matchType - Type of match: 'name', 'structure', or 'visual'
|
|
18
|
+
* @property {string} code - Full source code of the component
|
|
19
|
+
*/
|
|
20
|
+
export interface SimilarComponent {
|
|
21
|
+
path: string;
|
|
22
|
+
name: string;
|
|
23
|
+
similarity: number;
|
|
24
|
+
matchType: 'name' | 'structure' | 'visual';
|
|
25
|
+
code: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const generateComponentSchema: z.ZodObject<{
|
|
28
|
+
figmaMetadata: z.ZodString;
|
|
29
|
+
figmaCode: z.ZodString;
|
|
30
|
+
componentName: z.ZodString;
|
|
31
|
+
discoveredComponents: z.ZodArray<z.ZodObject<{
|
|
32
|
+
path: z.ZodString;
|
|
33
|
+
name: z.ZodString;
|
|
34
|
+
similarity: z.ZodNumber;
|
|
35
|
+
matchType: z.ZodEnum<["name", "structure", "visual"]>;
|
|
36
|
+
code: z.ZodString;
|
|
37
|
+
}, "strip", z.ZodTypeAny, {
|
|
38
|
+
name: string;
|
|
39
|
+
code: string;
|
|
40
|
+
path: string;
|
|
41
|
+
similarity: number;
|
|
42
|
+
matchType: "name" | "structure" | "visual";
|
|
43
|
+
}, {
|
|
44
|
+
name: string;
|
|
45
|
+
code: string;
|
|
46
|
+
path: string;
|
|
47
|
+
similarity: number;
|
|
48
|
+
matchType: "name" | "structure" | "visual";
|
|
49
|
+
}>, "many">;
|
|
50
|
+
workspacePath: z.ZodOptional<z.ZodString>;
|
|
51
|
+
}, "strict", z.ZodTypeAny, {
|
|
52
|
+
figmaMetadata: string;
|
|
53
|
+
figmaCode: string;
|
|
54
|
+
componentName: string;
|
|
55
|
+
discoveredComponents: {
|
|
56
|
+
name: string;
|
|
57
|
+
code: string;
|
|
58
|
+
path: string;
|
|
59
|
+
similarity: number;
|
|
60
|
+
matchType: "name" | "structure" | "visual";
|
|
61
|
+
}[];
|
|
62
|
+
workspacePath?: string | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
figmaMetadata: string;
|
|
65
|
+
figmaCode: string;
|
|
66
|
+
componentName: string;
|
|
67
|
+
discoveredComponents: {
|
|
68
|
+
name: string;
|
|
69
|
+
code: string;
|
|
70
|
+
path: string;
|
|
71
|
+
similarity: number;
|
|
72
|
+
matchType: "name" | "structure" | "visual";
|
|
73
|
+
}[];
|
|
74
|
+
workspacePath?: string | undefined;
|
|
75
|
+
}>;
|
|
76
|
+
export type GenerateComponentInput = z.infer<typeof generateComponentSchema>;
|
|
77
|
+
/**
|
|
78
|
+
* Result of component analysis recommending REUSE, EXTEND, or CREATE.
|
|
79
|
+
*
|
|
80
|
+
* @property {'CREATE'|'EXTEND'|'REUSE'} action - Recommended action: 'CREATE', 'EXTEND', or 'REUSE'
|
|
81
|
+
* @property {number} confidence - Confidence score (0-100)
|
|
82
|
+
* @property {{path: string, name: string, similarity: number}} [matchedComponent] - Best-matching component (if action is REUSE or EXTEND)
|
|
83
|
+
* @property {string[]} [differences] - Key differences between Figma design and matched component
|
|
84
|
+
* @property {string} recommendation - Human-readable recommendation text
|
|
85
|
+
* @property {string} [suggestedApproach] - Implementation guidance
|
|
86
|
+
* @property {'composition'|'props'|'variant'} [extendStrategy] - Strategy for EXTEND: 'props', 'variant', or 'composition'
|
|
87
|
+
*/
|
|
88
|
+
export interface ComponentAnalysisResult {
|
|
89
|
+
action: 'CREATE' | 'EXTEND' | 'REUSE';
|
|
90
|
+
confidence: number;
|
|
91
|
+
matchedComponent?: {
|
|
92
|
+
path: string;
|
|
93
|
+
name: string;
|
|
94
|
+
similarity: number;
|
|
95
|
+
};
|
|
96
|
+
differences?: string[];
|
|
97
|
+
recommendation: string;
|
|
98
|
+
suggestedApproach?: string;
|
|
99
|
+
extendStrategy?: 'composition' | 'props' | 'variant';
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Generates a component recommendation from Figma design and discovered components.
|
|
103
|
+
*
|
|
104
|
+
* @param input - Figma design data (metadata, code), component name, and discovered components
|
|
105
|
+
* @returns Formatted recommendation with REUSE/EXTEND/CREATE decision and implementation guidance, or error message on failure
|
|
106
|
+
*/
|
|
107
|
+
export declare function generateComponentRecommendation(input: GenerateComponentInput): string;
|
|
108
|
+
/**
|
|
109
|
+
* Creates the storefront_next_generate_component MCP tool.
|
|
110
|
+
*
|
|
111
|
+
* @param loadServices - Function that loads configuration and returns Services instance
|
|
112
|
+
* @returns MCP tool for component analysis and recommendation
|
|
113
|
+
*/
|
|
114
|
+
export declare function createGenerateComponentTool(loadServices: () => Services): McpTool;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate component tool for Figma-to-component workflow.
|
|
8
|
+
*
|
|
9
|
+
* Analyzes Figma design and discovered components to recommend REUSE, EXTEND, or CREATE strategy.
|
|
10
|
+
*
|
|
11
|
+
* @module tools/storefrontnext/figma/generate-component
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { createToolAdapter, textResult } from '../../../adapter.js';
|
|
15
|
+
import { analyzeComponentDifferences, determineAction } from './decision.js';
|
|
16
|
+
import { formatRecommendation } from './formatter.js';
|
|
17
|
+
const discoveredComponentSchema = z.object({
|
|
18
|
+
path: z.string().describe('Absolute file path to the component'),
|
|
19
|
+
name: z.string().describe('Component name'),
|
|
20
|
+
similarity: z.number().min(0).max(100).describe('Similarity score (0-100)'),
|
|
21
|
+
matchType: z.enum(['name', 'structure', 'visual']).describe('Type of match found'),
|
|
22
|
+
code: z.string().describe('Full source code of the component'),
|
|
23
|
+
});
|
|
24
|
+
export const generateComponentSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
figmaMetadata: z.string().describe('JSON string containing Figma design metadata (from mcp__figma__get_metadata)'),
|
|
27
|
+
figmaCode: z.string().describe('Generated React code from Figma (from mcp__figma__get_design_context)'),
|
|
28
|
+
componentName: z.string().describe('Suggested name for the component extracted from Figma design'),
|
|
29
|
+
discoveredComponents: z
|
|
30
|
+
.array(discoveredComponentSchema)
|
|
31
|
+
.describe('Array of similar components discovered using Glob/Grep/Read. Pass empty array if no similar components found.'),
|
|
32
|
+
workspacePath: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Optional workspace root path. Defaults to the MCP server project directory.'),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
function analyzeComponent(input) {
|
|
39
|
+
const similarComponents = input.discoveredComponents;
|
|
40
|
+
if (similarComponents.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
action: 'CREATE',
|
|
43
|
+
confidence: 95,
|
|
44
|
+
recommendation: `No similar components found in the codebase. Will create new component: ${input.componentName}`,
|
|
45
|
+
suggestedApproach: 'Create a new component following StorefrontNext patterns.',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const topMatch = similarComponents[0];
|
|
49
|
+
const differences = analyzeComponentDifferences(topMatch, input.figmaCode, input.figmaMetadata);
|
|
50
|
+
const decision = determineAction(topMatch, differences);
|
|
51
|
+
return decision;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generates a component recommendation from Figma design and discovered components.
|
|
55
|
+
*
|
|
56
|
+
* @param input - Figma design data (metadata, code), component name, and discovered components
|
|
57
|
+
* @returns Formatted recommendation with REUSE/EXTEND/CREATE decision and implementation guidance, or error message on failure
|
|
58
|
+
*/
|
|
59
|
+
export function generateComponentRecommendation(input) {
|
|
60
|
+
try {
|
|
61
|
+
const analysis = analyzeComponent(input);
|
|
62
|
+
const recommendation = formatRecommendation(analysis, input);
|
|
63
|
+
return recommendation;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
return `# Error: Component Generation Failed\n\n${errorMessage}\n\nPlease check the input parameters and try again.`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates the storefront_next_generate_component MCP tool.
|
|
72
|
+
*
|
|
73
|
+
* @param loadServices - Function that loads configuration and returns Services instance
|
|
74
|
+
* @returns MCP tool for component analysis and recommendation
|
|
75
|
+
*/
|
|
76
|
+
export function createGenerateComponentTool(loadServices) {
|
|
77
|
+
return createToolAdapter({
|
|
78
|
+
name: 'storefront_next_generate_component',
|
|
79
|
+
description: 'Analyzes Figma design and discovered components to recommend component generation strategy. ' +
|
|
80
|
+
'Workflow: 1) Discover similar components using Glob/Grep/Read tools, ' +
|
|
81
|
+
'2) Call this tool with the discoveredComponents parameter, ' +
|
|
82
|
+
'3) Tool analyzes differences and recommends REUSE/EXTEND/CREATE action, ' +
|
|
83
|
+
'4) Tool provides formatted recommendation with code examples and workflow steps. ' +
|
|
84
|
+
'Call this tool AFTER retrieving Figma design data and discovering similar components.',
|
|
85
|
+
toolsets: ['STOREFRONTNEXT'],
|
|
86
|
+
isGA: false,
|
|
87
|
+
requiresInstance: false,
|
|
88
|
+
inputSchema: generateComponentSchema.shape,
|
|
89
|
+
async execute(args, context) {
|
|
90
|
+
return generateComponentRecommendation({
|
|
91
|
+
...args,
|
|
92
|
+
workspacePath: args.workspacePath ?? context.services.resolveWithProjectDirectory(),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
formatOutput: (output) => textResult(output),
|
|
96
|
+
}, loadServices);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=index.js.map
|