Skip to content

Commit 5d727ab

Browse files
authored
Moved the page designer decorator MCP tool into the storefronnext toolset (#196)
* Moved the page designer decorator MCP tool into the storefronnext toolset * Address complexity warning in the build * Improve page-designer-decorator test coverage (73% → 95% branches) and fix max-nested-callbacks lint
1 parent 98fd711 commit 5d727ab

16 files changed

Lines changed: 727 additions & 93 deletions

packages/b2c-dx-mcp/src/tools/storefrontnext/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type {McpTool} from '../../utils/index.js';
2727
import type {Services} from '../../services.js';
2828
import {createToolAdapter, jsonResult} from '../adapter.js';
2929
import {createDeveloperGuidelinesTool} from './developer-guidelines.js';
30-
import {createPageDesignerDecoratorTool} from '../page-designer-decorator/index.js';
30+
import {createPageDesignerDecoratorTool} from './page-designer-decorator/index.js';
3131

3232
/**
3333
* Common input type for placeholder tools.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/README.md renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,18 +237,19 @@ Run the comprehensive Mocha test suite:
237237

238238
```bash
239239
cd packages/b2c-dx-mcp
240-
pnpm run test:agent -- test/tools/page-designer-decorator/index.test.ts
240+
pnpm run test:agent -- test/tools/storefrontnext/page-designer-decorator/index.test.ts
241241
```
242242

243243
The test suite covers:
244+
244245
- Component discovery (name-based, kebab-case, nested, path-based, custom paths, name collisions)
245246
- Auto mode (basic, type inference, complex props exclusion, UI-only props exclusion, edge cases)
246247
- Interactive mode (all steps: analyze, select_props, configure_attrs, configure_regions, confirm_generation)
247248
- Error handling (invalid input, invalid step name, missing parameters)
248249
- Edge cases (no props, only complex props, optional props, union types, already decorated components)
249250
- Working directory resolution (from --working-directory flag or SFCC_WORKING_DIRECTORY env var via Services)
250251

251-
See [`test/tools/page-designer-decorator/README.md`](../../../test/tools/page-designer-decorator/README.md) for detailed testing instructions.
252+
See [`test/tools/storefrontnext/page-designer-decorator/README.md`](../../../../test/tools/storefrontnext/page-designer-decorator/README.md) for detailed testing instructions.
252253

253254
## 🎓 Learning Resources
254255

packages/b2c-dx-mcp/src/tools/page-designer-decorator/analyzer.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/analyzer.ts

File renamed without changes.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/index.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/index.ts

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {z, type ZodRawShape} from 'zod';
88
import {componentAnalyzer, generateTypeSuggestions, resolveComponent, type TypeSuggestion} from './analyzer.js';
99
import {generateDecoratorCode, type AttributeContext, type MetadataContext} from './templates/decorator-generator.js';
1010
import {pageDesignerDecoratorRules} from './rules.js';
11-
import type {McpTool} from '../../utils/index.js';
12-
import type {Services} from '../../services.js';
11+
import type {McpTool} from '../../../utils/index.js';
12+
import type {Services} from '../../../services.js';
1313

1414
// ============================================================================
1515
// SCHEMA DEFINITION
@@ -366,13 +366,64 @@ function handleConfigureRegionsStep(args: PageDesignerDecoratorInput, workspaceR
366366
};
367367
}
368368

369+
function hasNonEmptyConfig(config: Record<string, unknown> | undefined): config is Record<string, unknown> {
370+
return config !== null && config !== undefined && Object.keys(config).length > 0;
371+
}
372+
373+
function buildAttributesFromProps(
374+
selectedProps: string[],
375+
props: {name: string; type: string; optional: boolean}[],
376+
attributeConfig: Record<string, Record<string, unknown>>,
377+
): AttributeContext[] {
378+
return selectedProps.flatMap((propName) => {
379+
const prop = props.find((p) => p.name === propName);
380+
if (!prop) return [];
381+
const config = attributeConfig[propName];
382+
return [
383+
{
384+
name: propName,
385+
tsType: prop.type,
386+
optional: prop.optional,
387+
hasConfig: hasNonEmptyConfig(config),
388+
config,
389+
},
390+
];
391+
});
392+
}
393+
394+
function buildAttributesFromNewAttrs(
395+
newAttributes: {name: string; required?: boolean}[],
396+
attributeConfig: Record<string, Record<string, unknown>>,
397+
): AttributeContext[] {
398+
return newAttributes.map((attr) => {
399+
const config = attributeConfig[attr.name];
400+
return {
401+
name: attr.name,
402+
tsType: 'string',
403+
optional: !attr.required,
404+
hasConfig: hasNonEmptyConfig(config),
405+
config,
406+
};
407+
});
408+
}
409+
410+
function resolveRegions(conversationContext: PageDesignerDecoratorInput['conversationContext']) {
411+
const regionConfig = conversationContext?.regionConfig;
412+
const enabled = Boolean(regionConfig?.enabled && regionConfig.regions?.length);
413+
return {
414+
hasRegions: enabled,
415+
regions: enabled ? regionConfig!.regions! : [],
416+
regionCount: enabled ? regionConfig!.regions!.length : 0,
417+
};
418+
}
419+
369420
function handleConfirmGenerationStep(args: PageDesignerDecoratorInput, workspaceRoot: string) {
370421
const {
371422
componentMetadata,
372423
selectedProps = [],
373424
newAttributes = [],
374425
attributeConfig = {},
375-
} = args.conversationContext || {};
426+
} = args.conversationContext ?? {};
376427

377428
if (!componentMetadata) {
378429
return {
@@ -389,51 +440,25 @@ function handleConfirmGenerationStep(args: PageDesignerDecoratorInput, workspace
389440
const fullPath = resolveComponent(args.component, workspaceRoot, args.searchPaths);
390441
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
391442

392-
const attributes: AttributeContext[] = [];
393-
394-
for (const propName of selectedProps) {
395-
const prop = componentInfo.props.find((p) => p.name === propName);
396-
if (!prop) continue;
397-
398-
const config = attributeConfig[propName];
399-
const hasConfig = config && Object.keys(config).length > 0;
400-
401-
attributes.push({
402-
name: propName,
403-
tsType: prop.type,
404-
optional: prop.optional,
405-
hasConfig,
406-
config,
407-
});
408-
}
409-
410-
for (const attr of newAttributes) {
411-
const config = attributeConfig[attr.name];
412-
const hasConfig = config && Object.keys(config).length > 0;
413-
414-
attributes.push({
415-
name: attr.name,
416-
tsType: 'string',
417-
optional: !attr.required,
418-
hasConfig,
419-
config,
420-
});
421-
}
443+
const attributes = [
444+
...buildAttributesFromProps(selectedProps, componentInfo.props, attributeConfig),
445+
...buildAttributesFromNewAttrs(newAttributes, attributeConfig),
446+
];
422447

423-
const regionConfig = args.conversationContext?.regionConfig;
424-
const hasRegions = regionConfig?.enabled && regionConfig.regions && regionConfig.regions.length > 0;
448+
const {hasRegions, regions, regionCount} = resolveRegions(args.conversationContext);
449+
const componentGroup = componentMetadata.group ?? 'odyssey_base';
425450

426451
const context: MetadataContext = {
427452
needsImports: true,
428453
componentId: componentMetadata.id,
429454
componentName: componentMetadata.name,
430455
componentDescription: componentMetadata.description,
431-
componentGroup: componentMetadata.group || 'odyssey_base',
456+
componentGroup,
432457
metadataClassName: `${componentInfo.componentName}Metadata`,
433458
hasAttributes: attributes.length > 0,
434-
hasRegions: hasRegions || false,
459+
hasRegions,
435460
hasLoader: false,
436-
regions: hasRegions ? regionConfig.regions || [] : [],
461+
regions,
437462
attributes,
438463
};
439464

@@ -443,11 +468,11 @@ function handleConfirmGenerationStep(args: PageDesignerDecoratorInput, workspace
443468
decoratorCode,
444469
componentName: componentInfo.componentName,
445470
componentId: componentMetadata.id,
446-
componentGroup: componentMetadata.group || 'odyssey_base',
471+
componentGroup,
447472
file: args.component,
448473
attributeCount: attributes.length,
449-
hasRegions: hasRegions || false,
450-
regionCount: hasRegions && regionConfig.regions ? regionConfig.regions.length : 0,
474+
hasRegions,
475+
regionCount,
451476
});
452477

453478
return {

packages/b2c-dx-mcp/src/tools/page-designer-decorator/rules.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/rules.ts

File renamed without changes.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/rules/1-mode-selection.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/rules/1-mode-selection.ts

File renamed without changes.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/rules/2a-auto-mode.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/rules/2a-auto-mode.ts

File renamed without changes.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/rules/2b-0-interactive-overview.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/rules/2b-0-interactive-overview.ts

File renamed without changes.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/rules/2b-1-interactive-analyze.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/rules/2b-1-interactive-analyze.ts

File renamed without changes.

packages/b2c-dx-mcp/src/tools/page-designer-decorator/rules/2b-2-interactive-select-props.ts renamed to packages/b2c-dx-mcp/src/tools/storefrontnext/page-designer-decorator/rules/2b-2-interactive-select-props.ts

File renamed without changes.

0 commit comments

Comments
 (0)