@salesforce/b2c-dx-mcp 0.3.2 → 0.4.0
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 +47 -27
- package/content/page-designer.md +4 -4
- package/dist/commands/mcp.d.ts +12 -0
- package/dist/commands/mcp.js +16 -4
- package/dist/registry.d.ts +4 -4
- package/dist/registry.js +10 -10
- package/dist/services.d.ts +75 -1
- package/dist/services.js +124 -1
- package/dist/tools/adapter.d.ts +29 -21
- package/dist/tools/adapter.js +34 -24
- package/dist/tools/cartridges/index.d.ts +5 -3
- package/dist/tools/cartridges/index.js +55 -25
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/mrt/index.d.ts +2 -2
- package/dist/tools/mrt/index.js +29 -10
- package/dist/tools/page-designer-decorator/analyzer.d.ts +169 -0
- package/dist/tools/page-designer-decorator/analyzer.js +535 -0
- package/dist/tools/page-designer-decorator/index.d.ts +252 -0
- package/dist/tools/page-designer-decorator/index.js +597 -0
- package/dist/tools/page-designer-decorator/rules/1-mode-selection.d.ts +8 -0
- package/dist/tools/page-designer-decorator/rules/1-mode-selection.js +65 -0
- package/dist/tools/page-designer-decorator/rules/2a-auto-mode.d.ts +13 -0
- package/dist/tools/page-designer-decorator/rules/2a-auto-mode.js +87 -0
- package/dist/tools/page-designer-decorator/rules/2b-0-interactive-overview.d.ts +4 -0
- package/dist/tools/page-designer-decorator/rules/2b-0-interactive-overview.js +55 -0
- package/dist/tools/page-designer-decorator/rules/2b-1-interactive-analyze.d.ts +22 -0
- package/dist/tools/page-designer-decorator/rules/2b-1-interactive-analyze.js +109 -0
- package/dist/tools/page-designer-decorator/rules/2b-2-interactive-select-props.d.ts +21 -0
- package/dist/tools/page-designer-decorator/rules/2b-2-interactive-select-props.js +60 -0
- package/dist/tools/page-designer-decorator/rules/2b-3-interactive-configure-attrs.d.ts +27 -0
- package/dist/tools/page-designer-decorator/rules/2b-3-interactive-configure-attrs.js +68 -0
- package/dist/tools/page-designer-decorator/rules/2b-4-interactive-configure-regions.d.ts +4 -0
- package/dist/tools/page-designer-decorator/rules/2b-4-interactive-configure-regions.js +65 -0
- package/dist/tools/page-designer-decorator/rules/2b-5-interactive-confirm-generation.d.ts +11 -0
- package/dist/tools/page-designer-decorator/rules/2b-5-interactive-confirm-generation.js +92 -0
- package/dist/tools/page-designer-decorator/rules.d.ts +51 -0
- package/dist/tools/page-designer-decorator/rules.js +70 -0
- package/dist/tools/page-designer-decorator/templates/decorator-generator.d.ts +116 -0
- package/dist/tools/page-designer-decorator/templates/decorator-generator.js +350 -0
- package/dist/tools/pwav3/index.d.ts +2 -2
- package/dist/tools/pwav3/index.js +13 -13
- package/dist/tools/scapi/index.d.ts +10 -2
- package/dist/tools/scapi/index.js +5 -56
- package/dist/tools/scapi/scapi-custom-apis-status.d.ts +9 -0
- package/dist/tools/scapi/scapi-custom-apis-status.js +152 -0
- package/dist/tools/scapi/scapi-schemas-list.d.ts +12 -0
- package/dist/tools/scapi/scapi-schemas-list.js +248 -0
- package/dist/tools/storefrontnext/developer-guidelines.d.ts +2 -2
- package/dist/tools/storefrontnext/developer-guidelines.js +3 -3
- package/dist/tools/storefrontnext/index.d.ts +2 -2
- package/dist/tools/storefrontnext/index.js +13 -13
- package/oclif.manifest.json +13 -2
- package/package.json +10 -5
|
@@ -0,0 +1,597 @@
|
|
|
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
|
+
import { z } from 'zod';
|
|
7
|
+
import { componentAnalyzer, generateTypeSuggestions, resolveComponent } from './analyzer.js';
|
|
8
|
+
import { generateDecoratorCode } from './templates/decorator-generator.js';
|
|
9
|
+
import { pageDesignerDecoratorRules } from './rules.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// SCHEMA DEFINITION
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export const pageDesignerDecoratorSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
component: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe('Component name (e.g., "ProductCard", "Hero") or file path (e.g., "src/components/ProductCard.tsx"). ' +
|
|
18
|
+
'When a name is provided, the tool automatically searches common component directories. ' +
|
|
19
|
+
'For backward compatibility, file paths are also supported.'),
|
|
20
|
+
searchPaths: z
|
|
21
|
+
.array(z.string())
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Additional directories to search for components (e.g., ["packages/retail/src", "app/features"]). ' +
|
|
24
|
+
'Only used when component is specified by name (not path).'),
|
|
25
|
+
autoMode: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Auto-generate all configurations with sensible defaults (skip interactive workflow). When enabled, automatically selects suitable props, infers types, and generates decorators without user confirmation.'),
|
|
29
|
+
componentId: z.string().optional().describe('Override component ID (default: auto-generated from component name)'),
|
|
30
|
+
conversationContext: z
|
|
31
|
+
.object({
|
|
32
|
+
step: z
|
|
33
|
+
.enum(['analyze', 'select_props', 'configure_attrs', 'configure_regions', 'confirm_generation'])
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Current step in the conversation workflow'),
|
|
36
|
+
componentInfo: z
|
|
37
|
+
.record(z.string(), z.any())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Cached component analysis from previous step'),
|
|
40
|
+
selectedProps: z
|
|
41
|
+
.array(z.string())
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Props from component interface selected to expose in Page Designer'),
|
|
44
|
+
newAttributes: z
|
|
45
|
+
.array(z.object({
|
|
46
|
+
name: z.string(),
|
|
47
|
+
description: z.string().optional(),
|
|
48
|
+
required: z.boolean().optional(),
|
|
49
|
+
}))
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('New attributes to add (not in existing props)'),
|
|
52
|
+
attributeConfig: z
|
|
53
|
+
.record(z.string(), z.object({
|
|
54
|
+
type: z.string().optional(),
|
|
55
|
+
name: z.string().optional(),
|
|
56
|
+
defaultValue: z.any().optional(),
|
|
57
|
+
values: z.array(z.string()).optional(),
|
|
58
|
+
}))
|
|
59
|
+
.optional()
|
|
60
|
+
.describe('Configuration for each attribute (explicit types, names, etc.)'),
|
|
61
|
+
componentMetadata: z
|
|
62
|
+
.object({
|
|
63
|
+
id: z.string(),
|
|
64
|
+
name: z.string(),
|
|
65
|
+
description: z.string(),
|
|
66
|
+
group: z.string().optional(),
|
|
67
|
+
})
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Component decorator configuration'),
|
|
70
|
+
regionConfig: z
|
|
71
|
+
.object({
|
|
72
|
+
enabled: z.boolean().describe('Whether to include @RegionDefinition decorator'),
|
|
73
|
+
regions: z
|
|
74
|
+
.array(z.object({
|
|
75
|
+
id: z.string().describe('Region identifier (e.g., "main", "sidebar")'),
|
|
76
|
+
name: z.string().describe('Display name for the region'),
|
|
77
|
+
description: z.string().optional().describe('Description of the region purpose'),
|
|
78
|
+
maxComponents: z.number().optional().describe('Maximum number of components allowed in region'),
|
|
79
|
+
componentTypeInclusions: z
|
|
80
|
+
.array(z.string())
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Allowed component types (whitelist)'),
|
|
83
|
+
componentTypeExclusions: z
|
|
84
|
+
.array(z.string())
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Disallowed component types (blacklist)'),
|
|
87
|
+
}))
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Array of region definitions'),
|
|
90
|
+
})
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Region configuration for nested content areas'),
|
|
93
|
+
})
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Conversation state for multi-turn interaction'),
|
|
96
|
+
})
|
|
97
|
+
.strict();
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// HELPER FUNCTIONS
|
|
100
|
+
// ============================================================================
|
|
101
|
+
/**
|
|
102
|
+
* Convert component name to kebab-case for use as component ID
|
|
103
|
+
*
|
|
104
|
+
* Page Designer component IDs should be lowercase with hyphens.
|
|
105
|
+
*
|
|
106
|
+
* @param name - PascalCase or camelCase name
|
|
107
|
+
* @returns kebab-case identifier
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* toKebabCase('ProductCard') // => 'product-card'
|
|
111
|
+
* toKebabCase('TwoColumnLayout') // => 'two-column-layout'
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
function toKebabCase(name) {
|
|
116
|
+
return name
|
|
117
|
+
.replaceAll(/([a-z])([A-Z])/g, '$1-$2')
|
|
118
|
+
.replaceAll(/[\s_]+/g, '-')
|
|
119
|
+
.toLowerCase();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert camelCase prop name to human-readable display name
|
|
123
|
+
*
|
|
124
|
+
* Used for attribute names shown to merchants in Page Designer UI.
|
|
125
|
+
*
|
|
126
|
+
* @param fieldName - camelCase field name
|
|
127
|
+
* @returns Human-readable name with proper capitalization
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* toHumanReadableName('imageUrl') // => 'Image Url'
|
|
131
|
+
* toHumanReadableName('ctaButtonText') // => 'Cta Button Text'
|
|
132
|
+
*
|
|
133
|
+
* @internal
|
|
134
|
+
*/
|
|
135
|
+
function toHumanReadableName(fieldName) {
|
|
136
|
+
return fieldName
|
|
137
|
+
.replaceAll(/([A-Z])/g, ' $1')
|
|
138
|
+
.replace(/^./, (str) => str.toUpperCase())
|
|
139
|
+
.trim();
|
|
140
|
+
}
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// WORKFLOW STEP HANDLERS
|
|
143
|
+
// ============================================================================
|
|
144
|
+
/**
|
|
145
|
+
* Handle Interactive Mode - Step 1: Analyze
|
|
146
|
+
*
|
|
147
|
+
* Parses the component file and provides analysis to the LLM:
|
|
148
|
+
* - Component name and structure
|
|
149
|
+
* - All props with types
|
|
150
|
+
* - Categorization (editable, complex, UI-only)
|
|
151
|
+
* - Suggested component ID and name
|
|
152
|
+
*
|
|
153
|
+
* **LLM should then:**
|
|
154
|
+
* - Present findings to user
|
|
155
|
+
* - Ask which props to expose in Page Designer
|
|
156
|
+
* - Collect component metadata (ID, name, description, group)
|
|
157
|
+
* - Call next step with selectedProps and componentMetadata
|
|
158
|
+
*
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
function handleAnalyzeStep(args, workspaceRoot) {
|
|
162
|
+
const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
|
|
163
|
+
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
|
164
|
+
const editableProps = componentInfo.props.filter((p) => !p.isComplex && !p.isUIOnly);
|
|
165
|
+
const complexProps = componentInfo.props.filter((p) => p.isComplex);
|
|
166
|
+
const uiProps = componentInfo.props.filter((p) => p.isUIOnly && !p.isComplex);
|
|
167
|
+
const suggestedComponentId = args.componentId || toKebabCase(componentInfo.componentName);
|
|
168
|
+
const suggestedComponentName = toHumanReadableName(componentInfo.componentName);
|
|
169
|
+
const instructions = pageDesignerDecoratorRules.getAnalyzeInstructions({
|
|
170
|
+
componentName: componentInfo.componentName,
|
|
171
|
+
file: args.component,
|
|
172
|
+
hasDecorators: componentInfo.hasDecorators,
|
|
173
|
+
interfaceName: componentInfo.interfaceName || 'None found',
|
|
174
|
+
totalProps: componentInfo.props.length,
|
|
175
|
+
exportType: componentInfo.exportType,
|
|
176
|
+
hasEditableProps: editableProps.length > 0,
|
|
177
|
+
editableProps,
|
|
178
|
+
hasComplexProps: complexProps.length > 0,
|
|
179
|
+
complexProps,
|
|
180
|
+
hasUIProps: uiProps.length > 0,
|
|
181
|
+
uiProps,
|
|
182
|
+
suggestedComponentId,
|
|
183
|
+
suggestedComponentName,
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: 'text',
|
|
189
|
+
text: instructions,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function handleSelectPropsStep(args, _workspaceRoot) {
|
|
195
|
+
const selectedProps = args.conversationContext?.selectedProps || [];
|
|
196
|
+
const newAttributes = args.conversationContext?.newAttributes || [];
|
|
197
|
+
const componentMetadata = args.conversationContext?.componentMetadata;
|
|
198
|
+
if (!componentMetadata) {
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: 'text',
|
|
203
|
+
text: '⚠️ Missing component metadata. Please provide component ID, name, description, and group from the analyze step.',
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
isError: true,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const confirmation = pageDesignerDecoratorRules.getSelectPropsConfirmation({
|
|
210
|
+
componentMetadata: {
|
|
211
|
+
id: componentMetadata.id,
|
|
212
|
+
name: componentMetadata.name,
|
|
213
|
+
description: componentMetadata.description,
|
|
214
|
+
group: componentMetadata.group || 'odyssey_base',
|
|
215
|
+
},
|
|
216
|
+
selectedProps,
|
|
217
|
+
newAttributes,
|
|
218
|
+
selectedPropsCount: selectedProps.length,
|
|
219
|
+
newAttributesCount: newAttributes.length,
|
|
220
|
+
totalAttributeCount: selectedProps.length + newAttributes.length,
|
|
221
|
+
hasSelectedProps: selectedProps.length > 0,
|
|
222
|
+
hasNewAttributes: newAttributes.length > 0,
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: confirmation,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function handleConfigureAttrsStep(args, workspaceRoot) {
|
|
234
|
+
const selectedProps = args.conversationContext?.selectedProps || [];
|
|
235
|
+
const newAttributes = args.conversationContext?.newAttributes || [];
|
|
236
|
+
const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
|
|
237
|
+
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
|
238
|
+
const attributeAnalysis = [];
|
|
239
|
+
for (const propName of selectedProps) {
|
|
240
|
+
const prop = componentInfo.props.find((p) => p.name === propName);
|
|
241
|
+
if (!prop)
|
|
242
|
+
continue;
|
|
243
|
+
const suggestions = generateTypeSuggestions(propName, prop.type);
|
|
244
|
+
attributeAnalysis.push({
|
|
245
|
+
name: propName,
|
|
246
|
+
source: 'existing',
|
|
247
|
+
tsType: prop.type,
|
|
248
|
+
autoInferred: suggestions.length === 0,
|
|
249
|
+
suggestions,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
for (const attr of newAttributes) {
|
|
253
|
+
const suggestions = generateTypeSuggestions(attr.name, 'string');
|
|
254
|
+
attributeAnalysis.push({
|
|
255
|
+
name: attr.name,
|
|
256
|
+
source: 'new',
|
|
257
|
+
tsType: 'string',
|
|
258
|
+
autoInferred: suggestions.length === 0,
|
|
259
|
+
suggestions,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const autoInferredAttrs = attributeAnalysis.filter((a) => a.autoInferred);
|
|
263
|
+
const needsConfigAttrs = attributeAnalysis.filter((a) => !a.autoInferred);
|
|
264
|
+
const instructions = pageDesignerDecoratorRules.getConfigureAttrsInstructions({
|
|
265
|
+
totalAttributes: attributeAnalysis.length,
|
|
266
|
+
autoInferredCount: autoInferredAttrs.length,
|
|
267
|
+
needsConfigCount: needsConfigAttrs.length,
|
|
268
|
+
hasAutoInferred: autoInferredAttrs.length > 0,
|
|
269
|
+
autoInferredAttrs: autoInferredAttrs.map((a) => ({ name: a.name, tsType: a.tsType })),
|
|
270
|
+
hasNeedsConfig: needsConfigAttrs.length > 0,
|
|
271
|
+
needsConfigAttrs: needsConfigAttrs.map((attr) => ({
|
|
272
|
+
name: attr.name,
|
|
273
|
+
tsType: attr.tsType,
|
|
274
|
+
source: attr.source === 'existing' ? 'Existing prop' : 'New attribute',
|
|
275
|
+
hasSuggestions: attr.suggestions.length > 0,
|
|
276
|
+
suggestions: attr.suggestions,
|
|
277
|
+
suggestedTypes: attr.suggestions.map((s) => s.type).join(', ') || 'string',
|
|
278
|
+
humanReadableName: toHumanReadableName(attr.name),
|
|
279
|
+
hasEnumSuggestion: attr.suggestions.some((s) => s.type === 'enum'),
|
|
280
|
+
})),
|
|
281
|
+
});
|
|
282
|
+
return {
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: 'text',
|
|
286
|
+
text: instructions,
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function handleConfigureRegionsStep(args, workspaceRoot) {
|
|
292
|
+
const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
|
|
293
|
+
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
|
294
|
+
const instructions = pageDesignerDecoratorRules.getConfigureRegionsInstructions({
|
|
295
|
+
componentName: componentInfo.componentName,
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: 'text',
|
|
301
|
+
text: instructions,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function handleConfirmGenerationStep(args, workspaceRoot) {
|
|
307
|
+
const { componentMetadata, selectedProps = [], newAttributes = [], attributeConfig = {}, } = args.conversationContext || {};
|
|
308
|
+
if (!componentMetadata) {
|
|
309
|
+
return {
|
|
310
|
+
content: [
|
|
311
|
+
{
|
|
312
|
+
type: 'text',
|
|
313
|
+
text: 'Error: Missing component metadata. Please start from the beginning.',
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
isError: true,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
|
|
320
|
+
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
|
321
|
+
const attributes = [];
|
|
322
|
+
for (const propName of selectedProps) {
|
|
323
|
+
const prop = componentInfo.props.find((p) => p.name === propName);
|
|
324
|
+
if (!prop)
|
|
325
|
+
continue;
|
|
326
|
+
const config = attributeConfig[propName];
|
|
327
|
+
const hasConfig = config && Object.keys(config).length > 0;
|
|
328
|
+
attributes.push({
|
|
329
|
+
name: propName,
|
|
330
|
+
tsType: prop.type,
|
|
331
|
+
optional: prop.optional,
|
|
332
|
+
hasConfig,
|
|
333
|
+
config,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
for (const attr of newAttributes) {
|
|
337
|
+
const config = attributeConfig[attr.name];
|
|
338
|
+
const hasConfig = config && Object.keys(config).length > 0;
|
|
339
|
+
attributes.push({
|
|
340
|
+
name: attr.name,
|
|
341
|
+
tsType: 'string',
|
|
342
|
+
optional: !attr.required,
|
|
343
|
+
hasConfig,
|
|
344
|
+
config,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const regionConfig = args.conversationContext?.regionConfig;
|
|
348
|
+
const hasRegions = regionConfig?.enabled && regionConfig.regions && regionConfig.regions.length > 0;
|
|
349
|
+
const context = {
|
|
350
|
+
needsImports: true,
|
|
351
|
+
componentId: componentMetadata.id,
|
|
352
|
+
componentName: componentMetadata.name,
|
|
353
|
+
componentDescription: componentMetadata.description,
|
|
354
|
+
componentGroup: componentMetadata.group || 'odyssey_base',
|
|
355
|
+
metadataClassName: `${componentInfo.componentName}Metadata`,
|
|
356
|
+
hasAttributes: attributes.length > 0,
|
|
357
|
+
hasRegions: hasRegions || false,
|
|
358
|
+
hasLoader: false,
|
|
359
|
+
regions: hasRegions ? regionConfig.regions || [] : [],
|
|
360
|
+
attributes,
|
|
361
|
+
};
|
|
362
|
+
const decoratorCode = generateDecoratorCode(context);
|
|
363
|
+
const userResponse = pageDesignerDecoratorRules.getConfirmGenerationInstructions({
|
|
364
|
+
decoratorCode,
|
|
365
|
+
componentName: componentInfo.componentName,
|
|
366
|
+
componentId: componentMetadata.id,
|
|
367
|
+
componentGroup: componentMetadata.group || 'odyssey_base',
|
|
368
|
+
file: args.component,
|
|
369
|
+
attributeCount: attributes.length,
|
|
370
|
+
hasRegions: hasRegions || false,
|
|
371
|
+
regionCount: hasRegions && regionConfig.regions ? regionConfig.regions.length : 0,
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
content: [
|
|
375
|
+
{
|
|
376
|
+
type: 'text',
|
|
377
|
+
text: userResponse,
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Handle Auto Mode - Single-step decorator generation
|
|
384
|
+
*
|
|
385
|
+
* **Fully automated workflow:**
|
|
386
|
+
* 1. Analyzes component
|
|
387
|
+
* 2. Auto-selects suitable props (excludes complex and UI-only)
|
|
388
|
+
* 3. Auto-infers Page Designer types from naming patterns
|
|
389
|
+
* 4. Generates decorator code immediately
|
|
390
|
+
* 5. NO user interaction required
|
|
391
|
+
*
|
|
392
|
+
* **Selection criteria:**
|
|
393
|
+
* - ✅ Simple types (string, number, boolean)
|
|
394
|
+
* - ❌ Complex types (objects, functions, React nodes)
|
|
395
|
+
* - ❌ UI-only props (className, style, onClick, etc.)
|
|
396
|
+
*
|
|
397
|
+
* **Auto-configuration:**
|
|
398
|
+
* - High-confidence patterns get explicit types (url, image, enum)
|
|
399
|
+
* - Others use auto-inferred types
|
|
400
|
+
* - Human-readable names auto-generated
|
|
401
|
+
* - No regions configured (interactive mode for advanced features)
|
|
402
|
+
*
|
|
403
|
+
* **Use cases:**
|
|
404
|
+
* - Quick setup for standard components
|
|
405
|
+
* - Batch processing multiple components
|
|
406
|
+
* - Getting started quickly
|
|
407
|
+
*
|
|
408
|
+
* @internal
|
|
409
|
+
*/
|
|
410
|
+
function handleAutoMode(args, workspaceRoot) {
|
|
411
|
+
const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
|
|
412
|
+
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
|
413
|
+
if (componentInfo.hasDecorators) {
|
|
414
|
+
return {
|
|
415
|
+
content: [
|
|
416
|
+
{
|
|
417
|
+
type: 'text',
|
|
418
|
+
text: `# ⚠️ Component Already Decorated\n\nThe component \`${componentInfo.componentName}\` already has Page Designer decorators.\n\nWould you like to modify the existing decorators instead?`,
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const selectedProps = componentInfo.props.filter((p) => !p.isComplex && !p.isUIOnly).map((p) => p.name);
|
|
424
|
+
const attributeConfig = {};
|
|
425
|
+
const attributes = [];
|
|
426
|
+
for (const propName of selectedProps) {
|
|
427
|
+
const prop = componentInfo.props.find((p) => p.name === propName);
|
|
428
|
+
if (!prop)
|
|
429
|
+
continue;
|
|
430
|
+
const suggestions = generateTypeSuggestions(propName, prop.type);
|
|
431
|
+
const config = {
|
|
432
|
+
name: toHumanReadableName(propName),
|
|
433
|
+
};
|
|
434
|
+
const highPrioritySuggestion = suggestions.find((s) => s.priority === 'high');
|
|
435
|
+
if (highPrioritySuggestion) {
|
|
436
|
+
config.type = highPrioritySuggestion.type;
|
|
437
|
+
if (highPrioritySuggestion.type === 'enum') {
|
|
438
|
+
if (propName.toLowerCase().includes('size')) {
|
|
439
|
+
config.values = ['sm', 'default', 'lg'];
|
|
440
|
+
config.defaultValue = 'default';
|
|
441
|
+
}
|
|
442
|
+
else if (propName.toLowerCase().includes('variant')) {
|
|
443
|
+
config.values = ['default', 'primary', 'secondary'];
|
|
444
|
+
config.defaultValue = 'default';
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (highPrioritySuggestion.type === 'boolean') {
|
|
448
|
+
config.defaultValue = false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (Object.keys(config).length > 1) {
|
|
452
|
+
attributeConfig[propName] = config;
|
|
453
|
+
}
|
|
454
|
+
attributes.push({
|
|
455
|
+
name: propName,
|
|
456
|
+
tsType: prop.type,
|
|
457
|
+
optional: prop.optional,
|
|
458
|
+
hasConfig: Object.keys(config).length > 1,
|
|
459
|
+
config: Object.keys(config).length > 1 ? config : undefined,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const componentId = args.componentId || toKebabCase(componentInfo.componentName);
|
|
463
|
+
const componentName = toHumanReadableName(componentInfo.componentName);
|
|
464
|
+
const componentDescription = `${componentName} component for Page Designer`;
|
|
465
|
+
const context = {
|
|
466
|
+
needsImports: true,
|
|
467
|
+
componentId,
|
|
468
|
+
componentName,
|
|
469
|
+
componentDescription,
|
|
470
|
+
componentGroup: 'odyssey_base',
|
|
471
|
+
metadataClassName: `${componentInfo.componentName}Metadata`,
|
|
472
|
+
hasAttributes: attributes.length > 0,
|
|
473
|
+
hasRegions: false,
|
|
474
|
+
hasLoader: false,
|
|
475
|
+
regions: [],
|
|
476
|
+
attributes,
|
|
477
|
+
};
|
|
478
|
+
const decoratorCode = generateDecoratorCode(context);
|
|
479
|
+
const response = pageDesignerDecoratorRules.getAutoModeInstructions({
|
|
480
|
+
componentName: componentInfo.componentName,
|
|
481
|
+
file: args.component,
|
|
482
|
+
componentId,
|
|
483
|
+
selectedPropCount: selectedProps.length,
|
|
484
|
+
autoConfigCount: Object.keys(attributeConfig).length,
|
|
485
|
+
autoInferredCount: selectedProps.length - Object.keys(attributeConfig).length,
|
|
486
|
+
hasNoSuitableProps: selectedProps.length === 0,
|
|
487
|
+
selectedProps: selectedProps.length > 0 ? selectedProps.map((p) => `\`${p}\``).join(', ') : 'None',
|
|
488
|
+
decoratorCode,
|
|
489
|
+
componentGroup: 'odyssey_base',
|
|
490
|
+
});
|
|
491
|
+
return {
|
|
492
|
+
content: [
|
|
493
|
+
{
|
|
494
|
+
type: 'text',
|
|
495
|
+
text: response,
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
// ============================================================================
|
|
501
|
+
// TOOL EXPORT
|
|
502
|
+
// ============================================================================
|
|
503
|
+
/**
|
|
504
|
+
* Creates the Page Designer decorator tool for Storefront Next.
|
|
505
|
+
*
|
|
506
|
+
* @param loadServices - Function that loads configuration and returns Services instance
|
|
507
|
+
* @returns The configured MCP tool
|
|
508
|
+
*/
|
|
509
|
+
export function createPageDesignerDecoratorTool(loadServices) {
|
|
510
|
+
return {
|
|
511
|
+
name: 'storefront_next_page_designer_decorator',
|
|
512
|
+
description: 'Adds Page Designer decorators (@Component, @AttributeDefinition, @RegionDefinition) to React components. ' +
|
|
513
|
+
'Two modes: autoMode=true for quick setup with defaults, or interactive mode via conversationContext.step. ' +
|
|
514
|
+
'Component discovery uses workingDirectory from flags/env. ' +
|
|
515
|
+
'Auto mode: selects suitable props, infers types, generates code immediately. ' +
|
|
516
|
+
'Interactive mode: multi-step workflow (analyze → select_props → configure_attrs → configure_regions → confirm_generation).',
|
|
517
|
+
inputSchema: pageDesignerDecoratorSchema.shape,
|
|
518
|
+
toolsets: ['STOREFRONTNEXT'],
|
|
519
|
+
isGA: false,
|
|
520
|
+
async handler(args) {
|
|
521
|
+
try {
|
|
522
|
+
// Validate and parse input
|
|
523
|
+
const validatedArgs = pageDesignerDecoratorSchema.parse(args);
|
|
524
|
+
// Use workingDirectory from services to ensure we search in the correct project directory
|
|
525
|
+
// This prevents searches in the home folder when MCP clients spawn servers from ~
|
|
526
|
+
const services = loadServices();
|
|
527
|
+
const workspaceRoot = services.getWorkingDirectory();
|
|
528
|
+
if (validatedArgs.autoMode === undefined && !validatedArgs.conversationContext) {
|
|
529
|
+
const fullPath = resolveComponent(validatedArgs.component, workspaceRoot, validatedArgs.searchPaths);
|
|
530
|
+
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
|
531
|
+
const instructions = pageDesignerDecoratorRules.getModeSelectionInstructions({
|
|
532
|
+
componentName: componentInfo.componentName,
|
|
533
|
+
file: validatedArgs.component,
|
|
534
|
+
});
|
|
535
|
+
return {
|
|
536
|
+
content: [
|
|
537
|
+
{
|
|
538
|
+
type: 'text',
|
|
539
|
+
text: instructions,
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
if (validatedArgs.autoMode) {
|
|
545
|
+
return handleAutoMode(validatedArgs, workspaceRoot);
|
|
546
|
+
}
|
|
547
|
+
const step = validatedArgs.conversationContext?.step || 'analyze';
|
|
548
|
+
switch (step) {
|
|
549
|
+
case 'analyze': {
|
|
550
|
+
return handleAnalyzeStep(validatedArgs, workspaceRoot);
|
|
551
|
+
}
|
|
552
|
+
case 'configure_attrs': {
|
|
553
|
+
return handleConfigureAttrsStep(validatedArgs, workspaceRoot);
|
|
554
|
+
}
|
|
555
|
+
case 'configure_regions': {
|
|
556
|
+
return handleConfigureRegionsStep(validatedArgs, workspaceRoot);
|
|
557
|
+
}
|
|
558
|
+
case 'confirm_generation': {
|
|
559
|
+
return handleConfirmGenerationStep(validatedArgs, workspaceRoot);
|
|
560
|
+
}
|
|
561
|
+
case 'select_props': {
|
|
562
|
+
return handleSelectPropsStep(validatedArgs, workspaceRoot);
|
|
563
|
+
}
|
|
564
|
+
default: {
|
|
565
|
+
const unknownStep = step;
|
|
566
|
+
throw new Error(`Unknown step: ${unknownStep}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch (error) {
|
|
571
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
572
|
+
// Check if it's a Zod validation error
|
|
573
|
+
if (error instanceof Error && error.name === 'ZodError') {
|
|
574
|
+
return {
|
|
575
|
+
content: [
|
|
576
|
+
{
|
|
577
|
+
type: 'text',
|
|
578
|
+
text: `# Error: Invalid Input\n\n${errorMessage}\n\nPlease check your input parameters and try again.`,
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
isError: true,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return {
|
|
585
|
+
content: [
|
|
586
|
+
{
|
|
587
|
+
type: 'text',
|
|
588
|
+
text: `# Error Adding Page Designer Support\n\n${errorMessage}`,
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
isError: true,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
export function renderModeSelection(context) {
|
|
7
|
+
return `# 🎯 Choose Page Designer Setup Mode
|
|
8
|
+
|
|
9
|
+
I need to know which mode you'd like to use for adding Page Designer support to **\`${context.componentName}\`**.
|
|
10
|
+
|
|
11
|
+
## Available Modes
|
|
12
|
+
|
|
13
|
+
### 🤖 Auto Mode (Quick & Automatic)
|
|
14
|
+
- **Best for**: Quick setup, standard components, batch processing
|
|
15
|
+
- **What happens**:
|
|
16
|
+
- Automatically analyzes the component
|
|
17
|
+
- Auto-selects suitable props (excludes complex types)
|
|
18
|
+
- Auto-infers types based on naming patterns
|
|
19
|
+
- Generates decorators immediately with sensible defaults
|
|
20
|
+
- **No confirmation needed** - code generated instantly
|
|
21
|
+
- **Time**: ~1 step
|
|
22
|
+
- **Control**: Low (uses smart defaults)
|
|
23
|
+
|
|
24
|
+
### 👤 Interactive Mode (Step-by-Step)
|
|
25
|
+
- **Best for**: Complex components, custom requirements, learning the process
|
|
26
|
+
- **What happens**:
|
|
27
|
+
- Multi-step workflow with your input at each stage
|
|
28
|
+
- Review and approve prop selections
|
|
29
|
+
- Configure attribute types, names, and defaults
|
|
30
|
+
- Configure regions for nested content (optional)
|
|
31
|
+
- **Requires confirmation** before generating code
|
|
32
|
+
- **Time**: ~4-5 steps
|
|
33
|
+
- **Control**: High (you decide everything)
|
|
34
|
+
|
|
35
|
+
## ⚡ How to Proceed
|
|
36
|
+
|
|
37
|
+
**⚠️ IMPORTANT: WAIT for the user to choose a mode. DO NOT proceed automatically.**
|
|
38
|
+
|
|
39
|
+
Please ask the user: **"Which mode would you like to use: Auto Mode or Interactive Mode?"**
|
|
40
|
+
|
|
41
|
+
Once the user responds:
|
|
42
|
+
|
|
43
|
+
**For Auto Mode**, call the tool again with:
|
|
44
|
+
\`\`\`json
|
|
45
|
+
{
|
|
46
|
+
"file": "${context.file}",
|
|
47
|
+
"autoMode": true
|
|
48
|
+
}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
**For Interactive Mode**, call the tool again with:
|
|
52
|
+
\`\`\`json
|
|
53
|
+
{
|
|
54
|
+
"file": "${context.file}",
|
|
55
|
+
"conversationContext": {
|
|
56
|
+
"step": "analyze"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
💡 **Tip**: If unsure, try **Auto Mode** first. You can always modify the generated decorators later or rerun in Interactive Mode for more control.`;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=1-mode-selection.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AutoModeContext {
|
|
2
|
+
componentName: string;
|
|
3
|
+
file: string;
|
|
4
|
+
componentId: string;
|
|
5
|
+
selectedPropCount: number;
|
|
6
|
+
autoConfigCount: number;
|
|
7
|
+
autoInferredCount: number;
|
|
8
|
+
hasNoSuitableProps: boolean;
|
|
9
|
+
selectedProps: string;
|
|
10
|
+
decoratorCode: string;
|
|
11
|
+
componentGroup: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function renderAutoMode(context: AutoModeContext): string;
|