@salesforce/b2c-dx-mcp 0.4.3 → 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.
Files changed (80) hide show
  1. package/README.md +82 -370
  2. package/content/pwav3/components.md +400 -0
  3. package/content/pwav3/config.md +124 -0
  4. package/content/pwav3/data-fetching.md +213 -0
  5. package/content/pwav3/extensibility.md +167 -0
  6. package/content/pwav3/i18n.md +214 -0
  7. package/content/pwav3/quick-reference.md +169 -0
  8. package/content/pwav3/routing.md +107 -0
  9. package/content/pwav3/state-management.md +193 -0
  10. package/content/pwav3/styling.md +248 -0
  11. package/content/pwav3/testing.md +124 -0
  12. package/content/site-theming/theming-accessibility.md +126 -0
  13. package/content/site-theming/theming-questions.md +208 -0
  14. package/content/site-theming/theming-validation.md +174 -0
  15. package/dist/registry.js +1 -1
  16. package/dist/services.d.ts +10 -10
  17. package/dist/services.js +19 -12
  18. package/dist/tools/cartridges/index.js +1 -6
  19. package/dist/tools/index.d.ts +1 -4
  20. package/dist/tools/index.js +1 -4
  21. package/dist/tools/mrt/index.js +1 -6
  22. package/dist/tools/pwav3/index.d.ts +12 -3
  23. package/dist/tools/pwav3/index.js +5 -63
  24. package/dist/tools/pwav3/pwa-kit-development-guidelines.d.ts +9 -0
  25. package/dist/tools/pwav3/pwa-kit-development-guidelines.js +151 -0
  26. package/dist/tools/scapi/index.d.ts +1 -1
  27. package/dist/tools/scapi/index.js +6 -1
  28. package/dist/tools/scapi/scapi-custom-api-scaffold.d.ts +60 -0
  29. package/dist/tools/scapi/scapi-custom-api-scaffold.js +175 -0
  30. package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +24 -0
  31. package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +53 -0
  32. package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +42 -0
  33. package/dist/tools/storefrontnext/figma/figma-to-component/index.js +325 -0
  34. package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +40 -0
  35. package/dist/tools/storefrontnext/figma/generate-component/decision.js +312 -0
  36. package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +9 -0
  37. package/dist/tools/storefrontnext/figma/generate-component/formatter.js +92 -0
  38. package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +114 -0
  39. package/dist/tools/storefrontnext/figma/generate-component/index.js +98 -0
  40. package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +71 -0
  41. package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +260 -0
  42. package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +61 -0
  43. package/dist/tools/storefrontnext/figma/map-tokens/index.js +234 -0
  44. package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +65 -0
  45. package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +268 -0
  46. package/dist/tools/storefrontnext/index.d.ts +17 -0
  47. package/dist/tools/storefrontnext/index.js +10 -60
  48. package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +15 -0
  49. package/dist/tools/storefrontnext/page-designer-decorator/index.js +3 -3
  50. package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js} +3 -3
  51. package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +92 -0
  52. package/dist/tools/storefrontnext/site-theming/color-contrast.js +186 -0
  53. package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +16 -0
  54. package/dist/tools/storefrontnext/site-theming/color-mapping.js +131 -0
  55. package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +11 -0
  56. package/dist/tools/storefrontnext/site-theming/guidance-merger.js +78 -0
  57. package/dist/tools/storefrontnext/site-theming/index.d.ts +14 -0
  58. package/dist/tools/storefrontnext/site-theming/index.js +122 -0
  59. package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +16 -0
  60. package/dist/tools/storefrontnext/site-theming/response-builder.js +316 -0
  61. package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +62 -0
  62. package/dist/tools/storefrontnext/site-theming/theming-store.js +410 -0
  63. package/dist/tools/storefrontnext/site-theming/types.d.ts +35 -0
  64. package/dist/tools/storefrontnext/site-theming/types.js +7 -0
  65. package/oclif.manifest.json +1 -1
  66. package/package.json +9 -6
  67. /package/content/{auth.md → sfnext/auth.md} +0 -0
  68. /package/content/{components.md → sfnext/components.md} +0 -0
  69. /package/content/{config.md → sfnext/config.md} +0 -0
  70. /package/content/{data-fetching.md → sfnext/data-fetching.md} +0 -0
  71. /package/content/{extensions.md → sfnext/extensions.md} +0 -0
  72. /package/content/{i18n.md → sfnext/i18n.md} +0 -0
  73. /package/content/{page-designer.md → sfnext/page-designer.md} +0 -0
  74. /package/content/{performance.md → sfnext/performance.md} +0 -0
  75. /package/content/{pitfalls.md → sfnext/pitfalls.md} +0 -0
  76. /package/content/{quick-reference.md → sfnext/quick-reference.md} +0 -0
  77. /package/content/{state-management.md → sfnext/state-management.md} +0 -0
  78. /package/content/{styling.md → sfnext/styling.md} +0 -0
  79. /package/content/{testing.md → sfnext/testing.md} +0 -0
  80. /package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts} +0 -0
@@ -0,0 +1,186 @@
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
+ * WCAG 2.1 color contrast utilities for accessibility validation.
8
+ *
9
+ * Provides luminance calculation, contrast ratio computation, and WCAG compliance
10
+ * checking for theming and color validation in Storefront Next.
11
+ *
12
+ * @module tools/storefrontnext/site-theming/color-contrast
13
+ */
14
+ /**
15
+ * WCAG 2.1 constants for contrast ratio calculation
16
+ * These values are specified in the WCAG 2.1 standard
17
+ */
18
+ const WCAG_CONTRAST_OFFSET = 0.05; // Offset added to luminance values in contrast ratio formula
19
+ // Linear RGB conversion constants (sRGB to linear RGB)
20
+ const LINEAR_RGB_THRESHOLD = 0.039_28; // Threshold for linear RGB conversion
21
+ const LINEAR_RGB_DIVISOR = 12.92; // Divisor for values below threshold
22
+ const GAMMA_CORRECTION_OFFSET = 0.055; // Offset for gamma correction
23
+ const GAMMA_CORRECTION_DIVISOR = 1.055; // Divisor for gamma correction
24
+ const GAMMA_EXPONENT = 2.4; // Gamma exponent for sRGB
25
+ // Relative luminance weights (WCAG 2.1 standard)
26
+ const LUMINANCE_RED_WEIGHT = 0.2126;
27
+ const LUMINANCE_GREEN_WEIGHT = 0.7152;
28
+ const LUMINANCE_BLUE_WEIGHT = 0.0722;
29
+ /** Valid 6-digit hex color pattern (with optional # prefix) */
30
+ const HEX_PATTERN = /^#?[0-9A-Fa-f]{6}$/;
31
+ /**
32
+ * Validates that a string is a valid 6-digit hex color.
33
+ * @param hex - Hex color string to validate
34
+ * @returns true if valid
35
+ */
36
+ export function isValidHex(hex) {
37
+ return typeof hex === 'string' && HEX_PATTERN.test(hex.trim());
38
+ }
39
+ /**
40
+ * Calculates the relative luminance of a color according to WCAG 2.1
41
+ * @param hex - Hex color string (e.g., "#635BFF")
42
+ * @returns Relative luminance value between 0 and 1
43
+ * @throws Error if hex format is invalid
44
+ */
45
+ export function getLuminance(hex) {
46
+ const trimmed = hex.trim();
47
+ if (!HEX_PATTERN.test(trimmed)) {
48
+ throw new Error(`Invalid hex color: "${hex}". Expected 6-digit hex (e.g., #635BFF).`);
49
+ }
50
+ const cleanHex = trimmed.replace('#', '');
51
+ // Parse RGB values
52
+ const r = Number.parseInt(cleanHex.slice(0, 2), 16) / 255;
53
+ const g = Number.parseInt(cleanHex.slice(2, 4), 16) / 255;
54
+ const b = Number.parseInt(cleanHex.slice(4, 6), 16) / 255;
55
+ // Convert to linear RGB
56
+ const [rs, gs, bs] = [r, g, b].map((c) => {
57
+ return c <= LINEAR_RGB_THRESHOLD
58
+ ? c / LINEAR_RGB_DIVISOR
59
+ : ((c + GAMMA_CORRECTION_OFFSET) / GAMMA_CORRECTION_DIVISOR) ** GAMMA_EXPONENT;
60
+ });
61
+ // Calculate relative luminance
62
+ return LUMINANCE_RED_WEIGHT * rs + LUMINANCE_GREEN_WEIGHT * gs + LUMINANCE_BLUE_WEIGHT * bs;
63
+ }
64
+ /**
65
+ * Calculates the contrast ratio between two colors according to WCAG 2.1
66
+ * @param color1 - First hex color string
67
+ * @param color2 - Second hex color string
68
+ * @returns Contrast ratio (1:1 to 21:1)
69
+ */
70
+ export function getContrastRatio(color1, color2) {
71
+ const l1 = getLuminance(color1);
72
+ const l2 = getLuminance(color2);
73
+ const lighter = Math.max(l1, l2);
74
+ const darker = Math.min(l1, l2);
75
+ return (lighter + WCAG_CONTRAST_OFFSET) / (darker + WCAG_CONTRAST_OFFSET);
76
+ }
77
+ /**
78
+ * WCAG compliance levels
79
+ */
80
+ export var WCAGLevel;
81
+ (function (WCAGLevel) {
82
+ WCAGLevel["AA"] = "AA";
83
+ WCAGLevel["AA_LARGE"] = "AA_LARGE";
84
+ WCAGLevel["AAA"] = "AAA";
85
+ WCAGLevel["AAA_LARGE"] = "AAA_LARGE";
86
+ WCAGLevel["FAIL"] = "FAIL";
87
+ })(WCAGLevel || (WCAGLevel = {}));
88
+ /**
89
+ * Determines WCAG compliance level for a contrast ratio
90
+ * @param ratio - Contrast ratio
91
+ * @param isLargeText - Whether this is for large text (18pt+ or 14pt+ bold)
92
+ * @returns WCAG compliance level
93
+ */
94
+ export function getWCAGLevel(ratio, isLargeText = false) {
95
+ if (isLargeText) {
96
+ if (ratio >= 4.5) {
97
+ return WCAGLevel.AAA_LARGE;
98
+ }
99
+ if (ratio >= 3) {
100
+ return WCAGLevel.AA_LARGE;
101
+ }
102
+ return WCAGLevel.FAIL;
103
+ }
104
+ if (ratio >= 7) {
105
+ return WCAGLevel.AAA;
106
+ }
107
+ if (ratio >= 4.5) {
108
+ return WCAGLevel.AA;
109
+ }
110
+ return WCAGLevel.FAIL;
111
+ }
112
+ /**
113
+ * Validates contrast between two colors
114
+ * @param color1 - First hex color string
115
+ * @param color2 - Second hex color string
116
+ * @param isLargeText - Whether this is for large text
117
+ * @returns Validation result with contrast ratio and compliance info
118
+ */
119
+ export function validateContrast(color1, color2, isLargeText = false) {
120
+ const ratio = getContrastRatio(color1, color2);
121
+ const wcagLevel = getWCAGLevel(ratio, isLargeText);
122
+ const passesAA = ratio >= (isLargeText ? 3 : 4.5);
123
+ const passesAAA = ratio >= (isLargeText ? 4.5 : 7);
124
+ // Visual assessment based on ratio
125
+ let visualAssessment;
126
+ let recommendation;
127
+ if (ratio >= 7) {
128
+ visualAssessment = 'excellent';
129
+ }
130
+ else if (ratio >= 5) {
131
+ visualAssessment = 'good';
132
+ }
133
+ else if (ratio >= 4.5) {
134
+ visualAssessment = 'acceptable';
135
+ recommendation =
136
+ 'Meets minimum WCAG AA but may be difficult to read, especially for body text. Consider using a darker/lighter color for better readability.';
137
+ }
138
+ else {
139
+ visualAssessment = 'poor';
140
+ recommendation =
141
+ 'Does not meet WCAG AA standards. Text will be difficult to read. Strongly recommend using a color with better contrast.';
142
+ }
143
+ return {
144
+ color1,
145
+ color2,
146
+ ratio,
147
+ wcagLevel,
148
+ passesAA,
149
+ passesAAA,
150
+ isLargeText,
151
+ visualAssessment,
152
+ recommendation,
153
+ };
154
+ }
155
+ /**
156
+ * Validates multiple color combinations for WCAG compliance.
157
+ *
158
+ * @param combinations - Array of foreground/background pairs with optional label and large-text flag
159
+ * @returns Array of validation results, each including the input label if provided
160
+ */
161
+ export function validateColorCombinations(combinations) {
162
+ return combinations.map((combo) => ({
163
+ ...validateContrast(combo.foreground, combo.background, combo.isLargeText ?? false),
164
+ label: combo.label,
165
+ }));
166
+ }
167
+ /**
168
+ * Formats a validation result as a human-readable string for display to users.
169
+ *
170
+ * @param result - Validation result, optionally with a label for the color combination
171
+ * @returns Multi-line string with contrast ratio, WCAG status, and recommendation (if any)
172
+ */
173
+ export function formatValidationResult(result) {
174
+ const label = result.label ? `${result.label}: ` : '';
175
+ const textType = result.isLargeText ? 'large text' : 'normal text';
176
+ const wcagStatus = result.passesAAA ? '✅ AAA' : result.passesAA ? '✅ AA' : '❌ FAIL';
177
+ let output = `${label}${result.color1} on ${result.color2}\n`;
178
+ output += ` Contrast Ratio: ${result.ratio.toFixed(2)}:1\n`;
179
+ output += ` WCAG ${textType}: ${wcagStatus}\n`;
180
+ output += ` Visual Assessment: ${result.visualAssessment.toUpperCase()}\n`;
181
+ if (result.recommendation) {
182
+ output += ` ⚠️ ${result.recommendation}\n`;
183
+ }
184
+ return output;
185
+ }
186
+ //# sourceMappingURL=color-contrast.js.map
@@ -0,0 +1,16 @@
1
+ /** A foreground/background color pair for contrast validation */
2
+ export type ColorCombination = {
3
+ foreground: string;
4
+ background: string;
5
+ label: string;
6
+ isLargeText?: boolean;
7
+ };
8
+ /**
9
+ * Builds foreground/background color combinations from a semantic color mapping.
10
+ * Derives pairs for text-on-background, button text, links, etc.
11
+ */
12
+ export declare function buildColorCombinations(colorMapping: Record<string, string>): ColorCombination[];
13
+ /**
14
+ * Appends WCAG color contrast validation results to the given instructions string.
15
+ */
16
+ export declare function appendValidationSection(internalInstructions: string, combinations: ColorCombination[]): string;
@@ -0,0 +1,131 @@
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
+ * Derives foreground/background color combinations from a color mapping and
8
+ * appends WCAG validation results to response text.
9
+ *
10
+ * @module tools/storefrontnext/site-theming/color-mapping
11
+ */
12
+ import { validateColorCombinations, formatValidationResult, isValidHex } from './color-contrast.js';
13
+ function tryTextCombo(key, color, keyLower, ctx) {
14
+ if (keyLower.includes('text') && keyLower.includes('light') && isValidHex(ctx.lightBg)) {
15
+ return { foreground: color, background: ctx.lightBg, label: `${key}: ${color} on light background (${ctx.lightBg})` };
16
+ }
17
+ if (keyLower.includes('text') && keyLower.includes('dark') && isValidHex(ctx.darkBg)) {
18
+ return { foreground: color, background: ctx.darkBg, label: `${key}: ${color} on dark background (${ctx.darkBg})` };
19
+ }
20
+ const isButtonText = keyLower === 'buttontext' || (keyLower.includes('button') && keyLower.includes('text'));
21
+ if (isButtonText && isValidHex(ctx.buttonBg)) {
22
+ return {
23
+ foreground: color,
24
+ background: ctx.buttonBg,
25
+ label: `${key}: ${color} on button background (${ctx.buttonBg})`,
26
+ };
27
+ }
28
+ if (keyLower.includes('link') && isValidHex(ctx.lightBg)) {
29
+ return { foreground: color, background: ctx.lightBg, label: `${key}: ${color} on light background (${ctx.lightBg})` };
30
+ }
31
+ return null;
32
+ }
33
+ function tryBackgroundCombo(key, color, ctx) {
34
+ const foregroundKey = key.replace(/Background|Bg/i, 'Text') || key.replace(/Background|Bg/i, 'Foreground');
35
+ const foreground = ctx.colorMapping[foregroundKey] || ctx.colorMapping[`${key.replace(/Background|Bg/i, '')}Text`];
36
+ if (foreground?.startsWith('#') && isValidHex(foreground)) {
37
+ return { foreground, background: color, label: `${foregroundKey || 'text'} (${foreground}) on ${key} (${color})` };
38
+ }
39
+ return null;
40
+ }
41
+ function tryTextForegroundCombo(key, color, keyLower, ctx) {
42
+ const backgroundKey = key.replace(/Text|Foreground/i, 'Background') || key.replace(/Text|Foreground/i, 'Bg');
43
+ let background = ctx.colorMapping[backgroundKey];
44
+ let backgroundLabel = backgroundKey;
45
+ if (!background) {
46
+ background = keyLower.includes('button') ? ctx.buttonBg : keyLower.includes('dark') ? ctx.darkBg : ctx.lightBg;
47
+ backgroundLabel = keyLower.includes('button')
48
+ ? 'button background'
49
+ : keyLower.includes('dark')
50
+ ? 'dark background'
51
+ : 'light background';
52
+ }
53
+ if (background?.startsWith('#') && isValidHex(background)) {
54
+ return { foreground: color, background, label: `${key} (${color}) on ${backgroundLabel} (${background})` };
55
+ }
56
+ return null;
57
+ }
58
+ function tryComboForEntry(key, color, ctx) {
59
+ const keyLower = key.toLowerCase();
60
+ const textCombo = tryTextCombo(key, color, keyLower, ctx);
61
+ if (textCombo)
62
+ return textCombo;
63
+ if (keyLower.includes('background') || keyLower.includes('bg'))
64
+ return tryBackgroundCombo(key, color, ctx);
65
+ if (keyLower.includes('text') || keyLower.includes('foreground'))
66
+ return tryTextForegroundCombo(key, color, keyLower, ctx);
67
+ return null;
68
+ }
69
+ /**
70
+ * Builds foreground/background color combinations from a semantic color mapping.
71
+ * Derives pairs for text-on-background, button text, links, etc.
72
+ */
73
+ export function buildColorCombinations(colorMapping) {
74
+ const ctx = {
75
+ colorMapping,
76
+ lightBg: colorMapping.lightBackground || colorMapping.background || '#FFFFFF',
77
+ darkBg: colorMapping.darkBackground || '#18181B',
78
+ buttonBg: colorMapping.buttonBackground || colorMapping.primary || '#0A2540',
79
+ };
80
+ const combinations = [];
81
+ for (const [key, color] of Object.entries(colorMapping)) {
82
+ if (!color || !color.startsWith('#') || !isValidHex(color))
83
+ continue;
84
+ const combo = tryComboForEntry(key, color, ctx);
85
+ if (combo)
86
+ combinations.push(combo);
87
+ }
88
+ if (combinations.length === 0) {
89
+ const whiteBg = '#FFFFFF';
90
+ const darkBgFallback = '#18181B';
91
+ for (const [key, color] of Object.entries(colorMapping)) {
92
+ if (!color || !color.startsWith('#') || !isValidHex(color))
93
+ continue;
94
+ if (key.toLowerCase().includes('background') || key.toLowerCase().includes('bg'))
95
+ continue;
96
+ combinations.push({ foreground: color, background: whiteBg, label: `${key} (${color}) on white background` }, { foreground: color, background: darkBgFallback, label: `${key} (${color}) on dark background` });
97
+ }
98
+ }
99
+ return combinations;
100
+ }
101
+ /**
102
+ * Appends WCAG color contrast validation results to the given instructions string.
103
+ */
104
+ export function appendValidationSection(internalInstructions, combinations) {
105
+ if (combinations.length === 0) {
106
+ return internalInstructions;
107
+ }
108
+ const results = validateColorCombinations(combinations);
109
+ let output = internalInstructions;
110
+ for (const result of results) {
111
+ output += formatValidationResult(result);
112
+ output += '\n';
113
+ }
114
+ const hasIssues = results.some((r) => !r.passesAA || r.visualAssessment === 'poor' || r.visualAssessment === 'acceptable');
115
+ if (hasIssues) {
116
+ output += '### ⚠️ VALIDATION SUMMARY\n\n';
117
+ output += '**Issues found that should be addressed:**\n\n';
118
+ for (const result of results.filter((r) => !r.passesAA || r.visualAssessment === 'poor' || r.visualAssessment === 'acceptable')) {
119
+ output += `- ${result.label || 'Color combination'}: ${result.recommendation || 'Needs improvement'}\n`;
120
+ }
121
+ output += '\n';
122
+ output +=
123
+ '**You MUST present these findings to the user BEFORE implementing and wait for their confirmation.**\n\n';
124
+ }
125
+ else {
126
+ output += '### ✅ VALIDATION SUMMARY\n\n';
127
+ output += 'All color combinations meet WCAG AA standards and have good visual assessment.\n\n';
128
+ }
129
+ return output;
130
+ }
131
+ //# sourceMappingURL=color-mapping.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Merges multiple ThemingGuidance objects from different theming files.
3
+ *
4
+ * @module tools/storefrontnext/site-theming/guidance-merger
5
+ */
6
+ import type { ThemingGuidance } from './theming-store.js';
7
+ /**
8
+ * Merges multiple ThemingGuidance objects into one.
9
+ * Questions are deduplicated by ID; guidelines, rules, workflows, and validations are combined.
10
+ */
11
+ export declare function mergeGuidance(guidanceArray: ThemingGuidance[]): ThemingGuidance;
@@ -0,0 +1,78 @@
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
+ function mergeWorkflows(guidanceArray) {
7
+ const workflows = guidanceArray.filter((g) => g.workflow);
8
+ if (workflows.length === 0)
9
+ return undefined;
10
+ const merged = {
11
+ steps: [],
12
+ extractionInstructions: workflows[0].workflow?.extractionInstructions,
13
+ preImplementationChecklist: workflows[0].workflow?.preImplementationChecklist,
14
+ };
15
+ for (const g of workflows) {
16
+ if (g.workflow?.steps)
17
+ merged.steps.push(...g.workflow.steps);
18
+ if (!merged.extractionInstructions && g.workflow?.extractionInstructions) {
19
+ merged.extractionInstructions = g.workflow.extractionInstructions;
20
+ }
21
+ if (!merged.preImplementationChecklist && g.workflow?.preImplementationChecklist) {
22
+ merged.preImplementationChecklist = g.workflow.preImplementationChecklist;
23
+ }
24
+ }
25
+ return merged;
26
+ }
27
+ function mergeValidations(guidanceArray) {
28
+ const validations = guidanceArray.filter((g) => g.validation);
29
+ if (validations.length === 0)
30
+ return undefined;
31
+ const joinField = (field) => validations
32
+ .map((g) => g.validation?.[field])
33
+ .filter((x) => typeof x === 'string')
34
+ .join('\n\n');
35
+ return {
36
+ colorValidation: joinField('colorValidation'),
37
+ fontValidation: joinField('fontValidation'),
38
+ generalValidation: joinField('generalValidation'),
39
+ requirements: joinField('requirements'),
40
+ };
41
+ }
42
+ function buildQuestionMap(guidanceArray) {
43
+ const questionMap = new Map();
44
+ for (const guidance of guidanceArray) {
45
+ for (const q of guidance.questions) {
46
+ if (!questionMap.has(q.id))
47
+ questionMap.set(q.id, q);
48
+ }
49
+ }
50
+ return questionMap;
51
+ }
52
+ function buildMergedMetadata(guidanceArray) {
53
+ return {
54
+ filePath: guidanceArray.map((g) => g.metadata.filePath).join(', '),
55
+ fileName: guidanceArray.map((g) => g.metadata.fileName).join(', '),
56
+ loadedAt: new Date(),
57
+ };
58
+ }
59
+ /**
60
+ * Merges multiple ThemingGuidance objects into one.
61
+ * Questions are deduplicated by ID; guidelines, rules, workflows, and validations are combined.
62
+ */
63
+ export function mergeGuidance(guidanceArray) {
64
+ if (guidanceArray.length === 0)
65
+ throw new Error('Cannot merge empty guidance array');
66
+ if (guidanceArray.length === 1)
67
+ return guidanceArray[0];
68
+ const questionMap = buildQuestionMap(guidanceArray);
69
+ return {
70
+ questions: [...questionMap.values()],
71
+ guidelines: guidanceArray.flatMap((g) => g.guidelines),
72
+ rules: guidanceArray.flatMap((g) => g.rules),
73
+ metadata: buildMergedMetadata(guidanceArray),
74
+ workflow: mergeWorkflows(guidanceArray),
75
+ validation: mergeValidations(guidanceArray),
76
+ };
77
+ }
78
+ //# sourceMappingURL=guidance-merger.js.map
@@ -0,0 +1,14 @@
1
+ import type { McpTool } from '../../../utils/index.js';
2
+ import type { Services } from '../../../services.js';
3
+ export type { ColorEntry, ColorMapping, CollectedAnswers, ConversationContext, FontEntry, SiteThemingInput, } from './types.js';
4
+ /**
5
+ * Creates the site theming MCP tool for Storefront Next.
6
+ *
7
+ * The tool guides theming changes (colors, fonts, visual styling) and validates color
8
+ * combinations for WCAG accessibility. It must be called before implementing any
9
+ * theming changes.
10
+ *
11
+ * @param loadServices - Function that loads configuration and returns Services instance
12
+ * @returns The configured MCP tool
13
+ */
14
+ export declare function createSiteThemingTool(loadServices: () => Services): McpTool;
@@ -0,0 +1,122 @@
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
+ * Site Theming tool for Storefront Next.
8
+ *
9
+ * Provides theming guidelines, guided questions, and automatic WCAG color contrast
10
+ * validation. Call this tool first when users request brand colors or theme changes.
11
+ *
12
+ * @module tools/storefrontnext/site-theming
13
+ */
14
+ import { z } from 'zod';
15
+ import { createToolAdapter, textResult, errorResult } from '../../adapter.js';
16
+ import { siteThemingStore } from './theming-store.js';
17
+ import { mergeGuidance } from './guidance-merger.js';
18
+ import { generateResponse } from './response-builder.js';
19
+ /**
20
+ * Creates the site theming MCP tool for Storefront Next.
21
+ *
22
+ * The tool guides theming changes (colors, fonts, visual styling) and validates color
23
+ * combinations for WCAG accessibility. It must be called before implementing any
24
+ * theming changes.
25
+ *
26
+ * @param loadServices - Function that loads configuration and returns Services instance
27
+ * @returns The configured MCP tool
28
+ */
29
+ export function createSiteThemingTool(loadServices) {
30
+ return createToolAdapter({
31
+ name: 'storefront_next_site_theming',
32
+ description: '⚠️ MANDATORY: Call this tool FIRST before implementing any theming changes. ' +
33
+ 'Provides theming guidelines, questions, and automatic validation. ' +
34
+ 'CRITICAL RULES: Call immediately when user requests theming (even if colors/fonts provided). ' +
35
+ 'NEVER implement without calling this tool first. NEVER skip question-answer workflow. ' +
36
+ 'MUST ask questions and WAIT for responses. ' +
37
+ 'VALIDATION GATE: After collecting answers, call tool again with colorMapping to trigger validation. ' +
38
+ 'DEFAULT FILES: theming-questions, theming-validation, theming-accessibility. ' +
39
+ 'Use fileKeys to add custom files. ' +
40
+ 'WORKFLOW: Call tool → Ask questions → Call with colorMapping (validation) → Present findings → Wait confirmation → Implement',
41
+ toolsets: ['STOREFRONTNEXT'],
42
+ isGA: false,
43
+ requiresInstance: false,
44
+ inputSchema: {
45
+ fileKeys: z
46
+ .array(z.string())
47
+ .optional()
48
+ .describe('Array of file keys to add to the default set. If provided, guidance from all specified files will be merged with defaults: theming-questions, theming-validation, theming-accessibility. Available keys can be listed by calling the tool without parameters.'),
49
+ conversationContext: z
50
+ .object({
51
+ currentStep: z
52
+ .string()
53
+ .optional()
54
+ .describe('Current step in the theming conversation (e.g., "collecting-colors", "collecting-fonts")'),
55
+ collectedAnswers: z
56
+ .record(z.string(), z.any())
57
+ .optional()
58
+ .describe('Previously collected answers from the user'),
59
+ questionsAsked: z.array(z.string()).optional().describe('List of questions that have already been asked'),
60
+ })
61
+ .optional()
62
+ .describe('Context from previous conversation rounds'),
63
+ },
64
+ async execute(args, context) {
65
+ siteThemingStore.initialize(context.services.resolveWithProjectDirectory());
66
+ const defaultFileKeys = ['theming-questions', 'theming-validation', 'theming-accessibility'];
67
+ let fileKeys;
68
+ if (args.fileKeys && args.fileKeys.length > 0) {
69
+ const allKeys = [...defaultFileKeys, ...args.fileKeys];
70
+ fileKeys = [...new Set(allKeys)];
71
+ }
72
+ else {
73
+ fileKeys = defaultFileKeys;
74
+ }
75
+ const hasContext = args.conversationContext &&
76
+ (args.conversationContext.collectedAnswers ||
77
+ args.conversationContext.questionsAsked ||
78
+ args.conversationContext.currentStep);
79
+ if (!args.fileKeys && !hasContext) {
80
+ const availableKeys = siteThemingStore.getKeys();
81
+ if (availableKeys.length === 0) {
82
+ return {
83
+ text: 'No theming files have been loaded. Please ensure theming files are configured at server startup.',
84
+ isError: false,
85
+ };
86
+ }
87
+ return {
88
+ text: `Available theming files:\n\n${availableKeys.map((key) => `- ${key}`).join('\n')}\n\nDefault files (always used): theming-questions, theming-validation, theming-accessibility\n\nUse the \`fileKeys\` parameter to add additional files. User-provided files are merged with the defaults.`,
89
+ isError: false,
90
+ };
91
+ }
92
+ const guidanceArray = [];
93
+ const missingKeys = [];
94
+ for (const key of fileKeys) {
95
+ const guidance = siteThemingStore.get(key);
96
+ if (guidance) {
97
+ guidanceArray.push(guidance);
98
+ }
99
+ else {
100
+ missingKeys.push(key);
101
+ }
102
+ }
103
+ if (guidanceArray.length === 0 || missingKeys.length > 0) {
104
+ const availableKeys = siteThemingStore.getKeys();
105
+ const keysList = fileKeys.length === 1 ? `key "${fileKeys[0]}"` : `keys: ${fileKeys.join(', ')}`;
106
+ const missingList = missingKeys.length === 1 ? `"${missingKeys[0]}"` : missingKeys.join(', ');
107
+ return {
108
+ text: `Theming file(s) with ${keysList} not found.\n\nMissing: ${missingList}\nAvailable keys: ${availableKeys.join(', ')}\n\nFiles are loaded at server startup. To add more files, configure them via the THEMING_FILES environment variable or update the server initialization.`,
109
+ isError: true,
110
+ };
111
+ }
112
+ const guidance = fileKeys.length > 1 ? mergeGuidance(guidanceArray) : guidanceArray[0];
113
+ const response = generateResponse(guidance, args.conversationContext);
114
+ return {
115
+ text: response,
116
+ isError: false,
117
+ };
118
+ },
119
+ formatOutput: (output) => output.isError ? errorResult(output.text) : textResult(output.text),
120
+ }, loadServices);
121
+ }
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Builds the theming tool response from guidance and conversation context.
3
+ *
4
+ * @module tools/storefrontnext/site-theming/response-builder
5
+ */
6
+ import type { ThemingGuidance } from './theming-store.js';
7
+ import type { ConversationContext } from './types.js';
8
+ /**
9
+ * Returns questions relevant to the current conversation state, filtered and sorted.
10
+ */
11
+ export declare function getRelevantQuestions(guidance: ThemingGuidance, context?: ConversationContext): ThemingGuidance['questions'];
12
+ export declare function hasProvidedThemingInfo(context?: ConversationContext): boolean;
13
+ /**
14
+ * Generates the full theming tool response from guidance and conversation context.
15
+ */
16
+ export declare function generateResponse(guidance: ThemingGuidance, context?: ConversationContext): string;