@salesforce/b2c-dx-mcp 0.4.4 → 0.4.6

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 (84) 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/commands/mcp.d.ts +3 -3
  16. package/dist/commands/mcp.js +7 -7
  17. package/dist/registry.js +1 -1
  18. package/dist/services.d.ts +15 -15
  19. package/dist/services.js +21 -14
  20. package/dist/tools/adapter.d.ts +2 -2
  21. package/dist/tools/adapter.js +2 -2
  22. package/dist/tools/cartridges/index.js +1 -6
  23. package/dist/tools/index.d.ts +1 -4
  24. package/dist/tools/index.js +1 -4
  25. package/dist/tools/mrt/index.js +4 -9
  26. package/dist/tools/pwav3/index.d.ts +12 -3
  27. package/dist/tools/pwav3/index.js +5 -63
  28. package/dist/tools/pwav3/pwa-kit-development-guidelines.d.ts +9 -0
  29. package/dist/tools/pwav3/pwa-kit-development-guidelines.js +151 -0
  30. package/dist/tools/scapi/index.d.ts +1 -1
  31. package/dist/tools/scapi/index.js +6 -1
  32. package/dist/tools/scapi/scapi-custom-api-scaffold.d.ts +60 -0
  33. package/dist/tools/scapi/scapi-custom-api-scaffold.js +175 -0
  34. package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +24 -0
  35. package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +53 -0
  36. package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +42 -0
  37. package/dist/tools/storefrontnext/figma/figma-to-component/index.js +325 -0
  38. package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +40 -0
  39. package/dist/tools/storefrontnext/figma/generate-component/decision.js +312 -0
  40. package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +9 -0
  41. package/dist/tools/storefrontnext/figma/generate-component/formatter.js +92 -0
  42. package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +114 -0
  43. package/dist/tools/storefrontnext/figma/generate-component/index.js +98 -0
  44. package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +71 -0
  45. package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +260 -0
  46. package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +61 -0
  47. package/dist/tools/storefrontnext/figma/map-tokens/index.js +234 -0
  48. package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +65 -0
  49. package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +268 -0
  50. package/dist/tools/storefrontnext/index.d.ts +17 -0
  51. package/dist/tools/storefrontnext/index.js +10 -60
  52. package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +15 -0
  53. package/dist/tools/storefrontnext/page-designer-decorator/index.js +3 -3
  54. package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js} +3 -3
  55. package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +92 -0
  56. package/dist/tools/storefrontnext/site-theming/color-contrast.js +186 -0
  57. package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +16 -0
  58. package/dist/tools/storefrontnext/site-theming/color-mapping.js +131 -0
  59. package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +11 -0
  60. package/dist/tools/storefrontnext/site-theming/guidance-merger.js +78 -0
  61. package/dist/tools/storefrontnext/site-theming/index.d.ts +14 -0
  62. package/dist/tools/storefrontnext/site-theming/index.js +122 -0
  63. package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +16 -0
  64. package/dist/tools/storefrontnext/site-theming/response-builder.js +316 -0
  65. package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +62 -0
  66. package/dist/tools/storefrontnext/site-theming/theming-store.js +410 -0
  67. package/dist/tools/storefrontnext/site-theming/types.d.ts +35 -0
  68. package/dist/tools/storefrontnext/site-theming/types.js +7 -0
  69. package/oclif.manifest.json +8 -5
  70. package/package.json +9 -6
  71. /package/content/{auth.md → sfnext/auth.md} +0 -0
  72. /package/content/{components.md → sfnext/components.md} +0 -0
  73. /package/content/{config.md → sfnext/config.md} +0 -0
  74. /package/content/{data-fetching.md → sfnext/data-fetching.md} +0 -0
  75. /package/content/{extensions.md → sfnext/extensions.md} +0 -0
  76. /package/content/{i18n.md → sfnext/i18n.md} +0 -0
  77. /package/content/{page-designer.md → sfnext/page-designer.md} +0 -0
  78. /package/content/{performance.md → sfnext/performance.md} +0 -0
  79. /package/content/{pitfalls.md → sfnext/pitfalls.md} +0 -0
  80. /package/content/{quick-reference.md → sfnext/quick-reference.md} +0 -0
  81. /package/content/{state-management.md → sfnext/state-management.md} +0 -0
  82. /package/content/{styling.md → sfnext/styling.md} +0 -0
  83. /package/content/{testing.md → sfnext/testing.md} +0 -0
  84. /package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts} +0 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Map tokens tool for Figma-to-component workflow.
3
+ *
4
+ * Maps Figma design tokens to Storefront Next theme tokens in app.css with exact/fuzzy matching.
5
+ *
6
+ * @module tools/storefrontnext/figma/map-tokens
7
+ */
8
+ import { z } from 'zod';
9
+ import type { McpTool } from '../../../../utils/index.js';
10
+ import type { Services } from '../../../../services.js';
11
+ export declare const mapTokensToThemeSchema: z.ZodObject<{
12
+ figmaTokens: z.ZodArray<z.ZodObject<{
13
+ name: z.ZodString;
14
+ value: z.ZodString;
15
+ type: z.ZodEnum<["color", "spacing", "radius", "opacity", "fontSize", "fontFamily", "other"]>;
16
+ description: z.ZodOptional<z.ZodString>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ type: "color" | "fontFamily" | "fontSize" | "opacity" | "other" | "radius" | "spacing";
19
+ name: string;
20
+ value: string;
21
+ description?: string | undefined;
22
+ }, {
23
+ type: "color" | "fontFamily" | "fontSize" | "opacity" | "other" | "radius" | "spacing";
24
+ name: string;
25
+ value: string;
26
+ description?: string | undefined;
27
+ }>, "many">;
28
+ themeFilePath: z.ZodOptional<z.ZodString>;
29
+ }, "strict", z.ZodTypeAny, {
30
+ figmaTokens: {
31
+ type: "color" | "fontFamily" | "fontSize" | "opacity" | "other" | "radius" | "spacing";
32
+ name: string;
33
+ value: string;
34
+ description?: string | undefined;
35
+ }[];
36
+ themeFilePath?: string | undefined;
37
+ }, {
38
+ figmaTokens: {
39
+ type: "color" | "fontFamily" | "fontSize" | "opacity" | "other" | "radius" | "spacing";
40
+ name: string;
41
+ value: string;
42
+ description?: string | undefined;
43
+ }[];
44
+ themeFilePath?: string | undefined;
45
+ }>;
46
+ export type MapTokensToThemeInput = z.infer<typeof mapTokensToThemeSchema>;
47
+ /**
48
+ * Maps Figma design tokens to existing theme tokens in app.css.
49
+ *
50
+ * @param args - Figma tokens array and optional theme file path
51
+ * @param workspaceRoot - Optional workspace root for theme file discovery; used when themeFilePath is not provided
52
+ * @returns Formatted mapping report with exact/fuzzy matches, confidence scores, and usage instructions, or error message on failure
53
+ */
54
+ export declare function mapFigmaTokensToTheme(args: MapTokensToThemeInput, workspaceRoot?: string): string;
55
+ /**
56
+ * Creates the storefront_next_map_tokens_to_theme MCP tool.
57
+ *
58
+ * @param loadServices - Function that loads configuration and returns Services instance
59
+ * @returns MCP tool for token mapping
60
+ */
61
+ export declare function createMapTokensToThemeTool(loadServices: () => Services): McpTool;
@@ -0,0 +1,234 @@
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
+ * Map tokens tool for Figma-to-component workflow.
8
+ *
9
+ * Maps Figma design tokens to Storefront Next theme tokens in app.css with exact/fuzzy matching.
10
+ *
11
+ * @module tools/storefrontnext/figma/map-tokens
12
+ */
13
+ import { z } from 'zod';
14
+ import { createToolAdapter, textResult } from '../../../adapter.js';
15
+ import { parseThemeFile } from './css-parser.js';
16
+ import { matchTokens } from './token-matcher.js';
17
+ export const mapTokensToThemeSchema = z
18
+ .object({
19
+ figmaTokens: z
20
+ .array(z.object({
21
+ name: z.string().describe('Token name from Figma (e.g., "Primary/Blue", "Spacing/Large")'),
22
+ value: z.string().describe('Token value (e.g., "#2563eb", "16px", "0.5rem")'),
23
+ type: z
24
+ .enum(['color', 'spacing', 'radius', 'opacity', 'fontSize', 'fontFamily', 'other'])
25
+ .describe('Type of the token'),
26
+ description: z.string().optional().describe('Optional description from Figma'),
27
+ }))
28
+ .describe('Array of design tokens extracted from Figma'),
29
+ themeFilePath: z
30
+ .string()
31
+ .optional()
32
+ .describe('Optional absolute path to theme CSS file. If not provided, will search for app.css in common locations.'),
33
+ })
34
+ .strict();
35
+ function formatTokenMatch(match) {
36
+ let output = `### ${match.figmaToken.name}\n\n`;
37
+ output += `- **Figma Value**: \`${match.figmaToken.value}\`\n`;
38
+ output += `- **Type**: ${match.figmaToken.type}\n`;
39
+ if (match.figmaToken.description) {
40
+ output += `- **Description**: ${match.figmaToken.description}\n`;
41
+ }
42
+ output += `\n#### Match Result\n\n`;
43
+ output += `- **Match Type**: ${match.matchType}\n`;
44
+ output += `- **Confidence**: ${match.confidence}%\n`;
45
+ if (match.matchedToken) {
46
+ output += `- **Matched Token**: \`${match.matchedToken.name}\`\n`;
47
+ output += `- **Token Value**: \`${match.matchedToken.value}\`\n`;
48
+ output += `- **Resolved Value**: \`${match.matchedToken.resolvedValue || match.matchedToken.value}\`\n`;
49
+ output += `- **Theme**: ${match.matchedToken.theme}\n`;
50
+ }
51
+ output += `- **Reason**: ${match.reason}\n\n`;
52
+ if (match.suggestions && match.suggestions.length > 0) {
53
+ output += `#### Suggestions\n\n`;
54
+ for (const [index, suggestion] of match.suggestions.entries()) {
55
+ output += `${index + 1}. **${suggestion.tokenName}**\n`;
56
+ output += ` - Value: \`${suggestion.value}\`\n`;
57
+ output += ` - Theme: ${suggestion.theme}\n`;
58
+ output += ` - Reason: ${suggestion.reason}\n`;
59
+ if (suggestion.insertAfter) {
60
+ output += ` - Insert after: \`${suggestion.insertAfter}\`\n`;
61
+ }
62
+ output += `\n`;
63
+ }
64
+ }
65
+ return output;
66
+ }
67
+ function generateSummary(matches) {
68
+ const exactMatches = matches.filter((m) => m.matchType === 'exact');
69
+ const fuzzyMatches = matches.filter((m) => m.matchType === 'fuzzy');
70
+ const noMatches = matches.filter((m) => m.matchType === 'none');
71
+ const highConfidence = matches.filter((m) => m.confidence >= 70 && m.matchedToken);
72
+ const lowConfidence = matches.filter((m) => m.confidence < 70 && m.confidence > 0);
73
+ let summary = `## Summary\n\n`;
74
+ summary += `- **Total Tokens**: ${matches.length}\n`;
75
+ summary += `- **Exact Matches**: ${exactMatches.length}\n`;
76
+ summary += `- **Fuzzy Matches**: ${fuzzyMatches.length}\n`;
77
+ summary += `- **No Matches**: ${noMatches.length}\n`;
78
+ summary += `- **High Confidence (≥70%)**: ${highConfidence.length}\n`;
79
+ summary += `- **Low Confidence (<70%)**: ${lowConfidence.length}\n\n`;
80
+ if (exactMatches.length > 0) {
81
+ summary += `### ✅ Exact Matches (Use these tokens directly)\n\n`;
82
+ for (const match of exactMatches) {
83
+ if (match.matchedToken) {
84
+ summary += `- \`${match.figmaToken.name}\` → \`${match.matchedToken.name}\`\n`;
85
+ }
86
+ }
87
+ summary += `\n`;
88
+ }
89
+ if (highConfidence.length > 0) {
90
+ summary += `### ⚠️ High Confidence Fuzzy Matches (Review and confirm)\n\n`;
91
+ for (const match of highConfidence) {
92
+ if (match.matchedToken) {
93
+ summary += `- \`${match.figmaToken.name}\` → \`${match.matchedToken.name}\` (${match.confidence}%)\n`;
94
+ }
95
+ }
96
+ summary += `\n`;
97
+ }
98
+ if (lowConfidence.length > 0) {
99
+ summary += `### ⚠️ Low Confidence Matches (Verify before using)\n\n`;
100
+ for (const match of lowConfidence) {
101
+ if (match.matchedToken) {
102
+ summary += `- \`${match.figmaToken.name}\` → \`${match.matchedToken.name}\` (${match.confidence}%)\n`;
103
+ }
104
+ }
105
+ summary += `\n`;
106
+ }
107
+ if (noMatches.length > 0) {
108
+ summary += `### ❌ No Matches (New tokens needed)\n\n`;
109
+ for (const match of noMatches) {
110
+ summary += `- \`${match.figmaToken.name}\`: ${match.figmaToken.value}\n`;
111
+ if (match.suggestions && match.suggestions.length > 0) {
112
+ summary += ` - Suggested: \`${match.suggestions[0].tokenName}\`\n`;
113
+ }
114
+ }
115
+ summary += `\n`;
116
+ }
117
+ return summary;
118
+ }
119
+ function generateRecommendations(matches) {
120
+ const needsNewTokens = matches.filter((m) => m.matchType === 'none');
121
+ const needsReview = matches.filter((m) => m.matchType === 'fuzzy' && m.confidence < 70);
122
+ if (needsNewTokens.length === 0 && needsReview.length === 0) {
123
+ return `## ✅ Recommendations\n\nAll tokens have been matched with high confidence. You can proceed with using the matched tokens in your component.\n\n`;
124
+ }
125
+ let recommendations = `## 📝 Recommendations\n\n`;
126
+ if (needsNewTokens.length > 0) {
127
+ recommendations += `### Create New Tokens\n\n`;
128
+ recommendations += `The following tokens from Figma don't have matches in your theme. Consider adding them to your \`app.css\` file:\n\n`;
129
+ for (const match of needsNewTokens) {
130
+ if (match.suggestions && match.suggestions.length > 0) {
131
+ const suggestion = match.suggestions[0];
132
+ recommendations += `\`\`\`css\n`;
133
+ recommendations += `/* Add to ${suggestion.theme === 'both' ? ':root and .dark' : suggestion.theme === 'light' ? ':root' : '.dark'} section */\n`;
134
+ recommendations += `${suggestion.tokenName}: ${suggestion.value};\n`;
135
+ recommendations += `\`\`\`\n\n`;
136
+ }
137
+ }
138
+ }
139
+ if (needsReview.length > 0) {
140
+ recommendations += `### Review Low Confidence Matches\n\n`;
141
+ recommendations += `The following matches have confidence below 70%. Please review and confirm they are correct before using:\n\n`;
142
+ for (const match of needsReview) {
143
+ if (match.matchedToken) {
144
+ recommendations += `- **${match.figmaToken.name}** (${match.figmaToken.value})\n`;
145
+ recommendations += ` - Matched: \`${match.matchedToken.name}\` (${match.matchedToken.resolvedValue})\n`;
146
+ recommendations += ` - Confidence: ${match.confidence}%\n`;
147
+ recommendations += ` - Reason: ${match.reason}\n\n`;
148
+ }
149
+ }
150
+ }
151
+ return recommendations;
152
+ }
153
+ /**
154
+ * Maps Figma design tokens to existing theme tokens in app.css.
155
+ *
156
+ * @param args - Figma tokens array and optional theme file path
157
+ * @param workspaceRoot - Optional workspace root for theme file discovery; used when themeFilePath is not provided
158
+ * @returns Formatted mapping report with exact/fuzzy matches, confidence scores, and usage instructions, or error message on failure
159
+ */
160
+ export function mapFigmaTokensToTheme(args, workspaceRoot) {
161
+ try {
162
+ const parsedTheme = parseThemeFile(args.themeFilePath, workspaceRoot);
163
+ const figmaTokens = args.figmaTokens.map((token) => ({
164
+ name: token.name,
165
+ value: token.value,
166
+ type: token.type,
167
+ description: token.description,
168
+ }));
169
+ const matches = matchTokens(figmaTokens, parsedTheme);
170
+ let response = `# Figma Design Tokens → StorefrontNext Theme Mapping\n\n`;
171
+ if (parsedTheme.warnings.length > 0) {
172
+ response += `## ⚠️ Warnings\n\n`;
173
+ for (const warning of parsedTheme.warnings) {
174
+ response += `- ${warning}\n`;
175
+ }
176
+ response += `\n`;
177
+ }
178
+ response += generateSummary(matches);
179
+ response += `## Detailed Mapping Results\n\n`;
180
+ for (const match of matches) {
181
+ response += formatTokenMatch(match);
182
+ }
183
+ response += generateRecommendations(matches);
184
+ response += `## 💡 Usage Instructions\n\n`;
185
+ response += `### Using Matched Tokens in Components\n\n`;
186
+ response += `For exact and high-confidence matches, use the token directly in your Tailwind classes:\n\n`;
187
+ response += `\`\`\`tsx\n`;
188
+ response += `// Instead of hardcoded colors\n`;
189
+ response += `<div className="bg-[#2563eb]">\n\n`;
190
+ response += `// Use theme tokens\n`;
191
+ response += `<div className="bg-primary">\n`;
192
+ response += `\`\`\`\n\n`;
193
+ response += `### Creating New Tokens\n\n`;
194
+ response += `If you need to add new tokens, add them to your \`app.css\` file in both light and dark theme sections:\n\n`;
195
+ response += `\`\`\`css\n`;
196
+ response += `:root {\n`;
197
+ response += ` --your-new-token: #value;\n`;
198
+ response += `}\n\n`;
199
+ response += `.dark {\n`;
200
+ response += ` --your-new-token: #dark-value;\n`;
201
+ response += `}\n`;
202
+ response += `\`\`\`\n\n`;
203
+ return response;
204
+ }
205
+ catch (error) {
206
+ const errorMessage = error instanceof Error ? error.message : String(error);
207
+ return `# Error: Token Mapping Failed\n\n${errorMessage}\n\nPlease ensure the theme file path is correct and accessible.`;
208
+ }
209
+ }
210
+ /**
211
+ * Creates the storefront_next_map_tokens_to_theme MCP tool.
212
+ *
213
+ * @param loadServices - Function that loads configuration and returns Services instance
214
+ * @returns MCP tool for token mapping
215
+ */
216
+ export function createMapTokensToThemeTool(loadServices) {
217
+ return createToolAdapter({
218
+ name: 'storefront_next_map_tokens_to_theme',
219
+ description: 'Maps Figma design tokens to existing StorefrontNext theme tokens in app.css. ' +
220
+ 'Analyzes Figma design tokens (colors, spacing, radius, etc.) and finds exact matches, ' +
221
+ 'provides fuzzy matches with confidence scores, suggests new token names for unmatched values, ' +
222
+ 'and recommends where to add new tokens in the CSS file. ' +
223
+ 'Use this tool after retrieving design variables from Figma MCP to ensure components use theme tokens instead of hardcoded values.',
224
+ toolsets: ['STOREFRONTNEXT'],
225
+ isGA: false,
226
+ requiresInstance: false,
227
+ inputSchema: mapTokensToThemeSchema.shape,
228
+ async execute(args, context) {
229
+ return mapFigmaTokensToTheme(args, context.services.resolveWithProjectDirectory());
230
+ },
231
+ formatOutput: (output) => textResult(output),
232
+ }, loadServices);
233
+ }
234
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,65 @@
1
+ import type { ThemeToken, ParsedTheme } from './css-parser.js';
2
+ /**
3
+ * Design token extracted from Figma.
4
+ *
5
+ * @property {string} name - Token name from Figma (e.g., "Primary/Blue", "Spacing/Large")
6
+ * @property {string} value - Token value (e.g., "#2563eb", "16px", "0.5rem")
7
+ * @property {'color'|'fontFamily'|'fontSize'|'opacity'|'other'|'radius'|'spacing'} type - Token type for matching logic
8
+ * @property {string} [description] - Optional description from Figma
9
+ */
10
+ export interface FigmaToken {
11
+ name: string;
12
+ value: string;
13
+ type: 'color' | 'fontFamily' | 'fontSize' | 'opacity' | 'other' | 'radius' | 'spacing';
14
+ description?: string;
15
+ }
16
+ /**
17
+ * Result of matching a Figma token to theme tokens.
18
+ *
19
+ * @property {FigmaToken} figmaToken - The Figma token that was matched
20
+ * @property {ThemeToken} [matchedToken] - Best-matching theme token (if found)
21
+ * @property {number} confidence - Match confidence (0-100)
22
+ * @property {'exact'|'fuzzy'|'none'} matchType - 'exact', 'fuzzy', or 'none'
23
+ * @property {string} reason - Human-readable explanation of the match
24
+ * @property {TokenSuggestion[]} [suggestions] - Suggested new tokens or alternatives (when no match or fuzzy match)
25
+ */
26
+ export interface TokenMatch {
27
+ figmaToken: FigmaToken;
28
+ matchedToken?: ThemeToken;
29
+ confidence: number;
30
+ matchType: 'exact' | 'fuzzy' | 'none';
31
+ reason: string;
32
+ suggestions?: TokenSuggestion[];
33
+ }
34
+ /**
35
+ * Suggestion for a new or alternative theme token.
36
+ *
37
+ * @property {string} tokenName - Suggested CSS custom property name
38
+ * @property {string} value - Token value
39
+ * @property {'both'|'dark'|'light'} theme - Which theme(s) to add to: 'both', 'dark', or 'light'
40
+ * @property {string} reason - Explanation for the suggestion
41
+ * @property {string} [insertAfter] - Optional token name to insert after in the theme file
42
+ */
43
+ export interface TokenSuggestion {
44
+ tokenName: string;
45
+ value: string;
46
+ theme: 'both' | 'dark' | 'light';
47
+ reason: string;
48
+ insertAfter?: string;
49
+ }
50
+ /**
51
+ * Matches a single Figma token to existing theme tokens.
52
+ *
53
+ * @param figmaToken - Figma design token to match
54
+ * @param parsedTheme - Parsed theme from app.css
55
+ * @returns TokenMatch with exact, fuzzy, or no match and optional suggestions
56
+ */
57
+ export declare function matchToken(figmaToken: FigmaToken, parsedTheme: ParsedTheme): TokenMatch;
58
+ /**
59
+ * Matches multiple Figma tokens to existing theme tokens.
60
+ *
61
+ * @param figmaTokens - Array of Figma design tokens to match
62
+ * @param parsedTheme - Parsed theme from app.css
63
+ * @returns Array of TokenMatch results, one per input token
64
+ */
65
+ export declare function matchTokens(figmaTokens: FigmaToken[], parsedTheme: ParsedTheme): TokenMatch[];
@@ -0,0 +1,268 @@
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
+ * Normalizes hex colors to lowercase 6-digit format
8
+ */
9
+ function normalizeHexColor(hex) {
10
+ let normalized = hex.toLowerCase().trim();
11
+ // Remove # if present
12
+ if (normalized.startsWith('#')) {
13
+ normalized = normalized.slice(1);
14
+ }
15
+ // Expand 3-digit hex to 6-digit
16
+ if (normalized.length === 3) {
17
+ normalized = [...normalized].map((c) => c + c).join('');
18
+ }
19
+ return normalized;
20
+ }
21
+ /**
22
+ * Calculates color distance between two hex colors (0-100, lower is closer)
23
+ */
24
+ function calculateColorDistance(hex1, hex2) {
25
+ const r1 = Number.parseInt(hex1.slice(0, 2), 16);
26
+ const g1 = Number.parseInt(hex1.slice(2, 4), 16);
27
+ const b1 = Number.parseInt(hex1.slice(4, 6), 16);
28
+ const r2 = Number.parseInt(hex2.slice(0, 2), 16);
29
+ const g2 = Number.parseInt(hex2.slice(2, 4), 16);
30
+ const b2 = Number.parseInt(hex2.slice(4, 6), 16);
31
+ // Euclidean distance normalized to 0-100 scale
32
+ const distance = Math.hypot(r1 - r2, g1 - g2, b1 - b2);
33
+ // Max distance is sqrt(255^2 * 3) ≈ 441
34
+ return (distance / 441) * 100;
35
+ }
36
+ /**
37
+ * Calculates string similarity between two strings (0-100, higher is more similar)
38
+ * Uses Levenshtein distance algorithm
39
+ */
40
+ function calculateStringSimilarity(str1, str2) {
41
+ const s1 = str1.toLowerCase();
42
+ const s2 = str2.toLowerCase();
43
+ // Exact match
44
+ if (s1 === s2)
45
+ return 100;
46
+ // Contains match bonus
47
+ if (s1.includes(s2) || s2.includes(s1)) {
48
+ return 80 + (Math.min(s1.length, s2.length) / Math.max(s1.length, s2.length)) * 20;
49
+ }
50
+ // Levenshtein distance
51
+ const matrix = [];
52
+ const len1 = s1.length;
53
+ const len2 = s2.length;
54
+ for (let i = 0; i <= len1; i++) {
55
+ matrix[i] = [i];
56
+ }
57
+ for (let j = 0; j <= len2; j++) {
58
+ matrix[0][j] = j;
59
+ }
60
+ for (let i = 1; i <= len1; i++) {
61
+ for (let j = 1; j <= len2; j++) {
62
+ const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
63
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
64
+ }
65
+ }
66
+ const distance = matrix[len1][len2];
67
+ const maxLen = Math.max(len1, len2);
68
+ return ((maxLen - distance) / maxLen) * 100;
69
+ }
70
+ /**
71
+ * Extracts semantic meaning from token name
72
+ */
73
+ function extractSemantics(name) {
74
+ const parts = name.toLowerCase().replace(/^--/, '').split(/[-_]/);
75
+ const semantics = [];
76
+ // Color semantics
77
+ const colorKeywords = new Set([
78
+ 'accent',
79
+ 'background',
80
+ 'border',
81
+ 'destructive',
82
+ 'error',
83
+ 'foreground',
84
+ 'info',
85
+ 'muted',
86
+ 'primary',
87
+ 'secondary',
88
+ 'success',
89
+ 'text',
90
+ 'warning',
91
+ ]);
92
+ const lightDark = new Set(['dark', 'light']);
93
+ const colorNames = new Set(['black', 'blue', 'gray', 'green', 'orange', 'purple', 'red', 'white', 'yellow']);
94
+ for (const part of parts) {
95
+ if (colorKeywords.has(part)) {
96
+ semantics.push(`semantic:${part}`);
97
+ }
98
+ if (lightDark.has(part)) {
99
+ semantics.push(`theme:${part}`);
100
+ }
101
+ if (colorNames.has(part)) {
102
+ semantics.push(`color:${part}`);
103
+ }
104
+ }
105
+ return semantics;
106
+ }
107
+ /**
108
+ * Finds exact color match
109
+ */
110
+ function findExactColorMatch(figmaValue, parsedTheme) {
111
+ const normalizedFigma = normalizeHexColor(figmaValue);
112
+ for (const token of parsedTheme.tokens) {
113
+ if (token.type === 'color' && token.resolvedValue) {
114
+ const normalizedToken = normalizeHexColor(token.resolvedValue);
115
+ if (normalizedFigma === normalizedToken) {
116
+ return token;
117
+ }
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ /**
123
+ * Finds fuzzy matches based on color similarity and name similarity
124
+ */
125
+ function findFuzzyMatches(figmaToken, parsedTheme) {
126
+ const matches = [];
127
+ // Filter tokens by type
128
+ const relevantTokens = parsedTheme.tokens.filter((token) => token.type === figmaToken.type);
129
+ const figmaSemantics = extractSemantics(figmaToken.name);
130
+ const normalizedFigmaValue = figmaToken.type === 'color' && figmaToken.value.startsWith('#')
131
+ ? normalizeHexColor(figmaToken.value)
132
+ : figmaToken.value;
133
+ for (const token of relevantTokens) {
134
+ let score = 0;
135
+ // Name similarity (40% weight)
136
+ const nameSimilarity = calculateStringSimilarity(figmaToken.name, token.name);
137
+ score += nameSimilarity * 0.4;
138
+ // Semantic similarity (30% weight)
139
+ const tokenSemantics = extractSemantics(token.name);
140
+ const semanticMatches = figmaSemantics.filter((s) => tokenSemantics.includes(s)).length;
141
+ const semanticScore = figmaSemantics.length > 0 ? (semanticMatches / figmaSemantics.length) * 100 : 0;
142
+ score += semanticScore * 0.3;
143
+ // Value similarity (30% weight)
144
+ if (figmaToken.type === 'color' && token.resolvedValue) {
145
+ const normalizedTokenValue = normalizeHexColor(token.resolvedValue);
146
+ const colorDistance = calculateColorDistance(normalizedFigmaValue, normalizedTokenValue);
147
+ const colorSimilarity = Math.max(0, 100 - colorDistance);
148
+ score += colorSimilarity * 0.3;
149
+ }
150
+ if (score > 20) {
151
+ // Only include matches with score > 20
152
+ matches.push({ token, score });
153
+ }
154
+ }
155
+ // Sort by score descending
156
+ return matches.sort((a, b) => b.score - a.score);
157
+ }
158
+ /**
159
+ * Generates suggestions for new tokens if no good match found
160
+ */
161
+ function generateTokenSuggestions(figmaToken, parsedTheme) {
162
+ const suggestions = [];
163
+ // Analyze existing token naming patterns
164
+ const existingNames = parsedTheme.tokens.filter((t) => t.type === figmaToken.type).map((t) => t.name);
165
+ // Extract common prefixes
166
+ const hasColorPrefix = existingNames.some((n) => n.startsWith('--color-'));
167
+ const hasRadiusPrefix = existingNames.some((n) => n.startsWith('--radius-'));
168
+ // Generate token name based on Figma token name
169
+ let suggestedName = figmaToken.name.toLowerCase().replaceAll(/[^a-z0-9-]/g, '-');
170
+ // Add appropriate prefix if not present
171
+ if (figmaToken.type === 'color' && !suggestedName.startsWith('--color-') && hasColorPrefix) {
172
+ suggestedName = `--color-${suggestedName.replace(/^--/, '')}`;
173
+ }
174
+ else if (figmaToken.type === 'radius' && !suggestedName.startsWith('--radius-') && hasRadiusPrefix) {
175
+ suggestedName = `--radius-${suggestedName.replace(/^--/, '')}`;
176
+ }
177
+ else if (!suggestedName.startsWith('--')) {
178
+ suggestedName = `--${suggestedName}`;
179
+ }
180
+ // Find a good place to insert
181
+ const similarTokens = existingNames.filter((name) => {
182
+ const similarity = calculateStringSimilarity(name, suggestedName);
183
+ return similarity > 30;
184
+ });
185
+ const insertAfter = similarTokens.length > 0 ? similarTokens[0] : undefined;
186
+ // For colors, suggest both light and dark values
187
+ if (figmaToken.type === 'color') {
188
+ suggestions.push({
189
+ tokenName: suggestedName,
190
+ value: figmaToken.value,
191
+ theme: 'both',
192
+ reason: `New token suggestion based on Figma token "${figmaToken.name}"`,
193
+ insertAfter,
194
+ });
195
+ }
196
+ else {
197
+ suggestions.push({
198
+ tokenName: suggestedName,
199
+ value: figmaToken.value,
200
+ theme: 'light',
201
+ reason: `New token suggestion based on Figma token "${figmaToken.name}"`,
202
+ insertAfter,
203
+ });
204
+ }
205
+ return suggestions;
206
+ }
207
+ /**
208
+ * Matches a single Figma token to existing theme tokens.
209
+ *
210
+ * @param figmaToken - Figma design token to match
211
+ * @param parsedTheme - Parsed theme from app.css
212
+ * @returns TokenMatch with exact, fuzzy, or no match and optional suggestions
213
+ */
214
+ export function matchToken(figmaToken, parsedTheme) {
215
+ // Try exact match first (only for colors with hex values)
216
+ if (figmaToken.type === 'color' && figmaToken.value.startsWith('#')) {
217
+ const exactMatch = findExactColorMatch(figmaToken.value, parsedTheme);
218
+ if (exactMatch) {
219
+ return {
220
+ figmaToken,
221
+ matchedToken: exactMatch,
222
+ confidence: 100,
223
+ matchType: 'exact',
224
+ reason: `Exact color match: ${figmaToken.value} matches ${exactMatch.name}`,
225
+ };
226
+ }
227
+ }
228
+ // Try fuzzy matching
229
+ const fuzzyMatches = findFuzzyMatches(figmaToken, parsedTheme);
230
+ if (fuzzyMatches.length > 0 && fuzzyMatches[0].score >= 50) {
231
+ const bestMatch = fuzzyMatches[0];
232
+ return {
233
+ figmaToken,
234
+ matchedToken: bestMatch.token,
235
+ confidence: Math.round(bestMatch.score),
236
+ matchType: 'fuzzy',
237
+ reason: `Fuzzy match based on name similarity and semantic meaning`,
238
+ suggestions: fuzzyMatches.length > 1
239
+ ? fuzzyMatches.slice(1, 4).map((m) => ({
240
+ tokenName: m.token.name,
241
+ value: m.token.value,
242
+ theme: m.token.theme === 'shared' ? 'both' : m.token.theme,
243
+ reason: `Alternative match (confidence: ${Math.round(m.score)}%)`,
244
+ }))
245
+ : undefined,
246
+ };
247
+ }
248
+ // No good match found, generate suggestions
249
+ const suggestions = generateTokenSuggestions(figmaToken, parsedTheme);
250
+ return {
251
+ figmaToken,
252
+ confidence: 0,
253
+ matchType: 'none',
254
+ reason: 'No matching token found. Consider creating a new token.',
255
+ suggestions,
256
+ };
257
+ }
258
+ /**
259
+ * Matches multiple Figma tokens to existing theme tokens.
260
+ *
261
+ * @param figmaTokens - Array of Figma design tokens to match
262
+ * @param parsedTheme - Parsed theme from app.css
263
+ * @returns Array of TokenMatch results, one per input token
264
+ */
265
+ export function matchTokens(figmaTokens, parsedTheme) {
266
+ return figmaTokens.map((token) => matchToken(token, parsedTheme));
267
+ }
268
+ //# sourceMappingURL=token-matcher.js.map
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Storefront Next toolset for B2C Commerce.
3
+ *
4
+ * This toolset provides MCP tools for Storefront Next development.
5
+ *
6
+ * **Implemented Tools:**
7
+ * - `storefront_next_development_guidelines` - Get development guidelines and best practices
8
+ * - `storefront_next_page_designer_decorator` - Add Page Designer decorators to React components
9
+ * - `storefront_next_site_theming` - Get theming guidelines, questions, and validation
10
+ * - `storefront_next_figma_to_component_workflow` - Convert Figma to components
11
+ * - `storefront_next_generate_component` - Generate new components
12
+ * - `storefront_next_map_tokens_to_theme` - Map design tokens
13
+ *
14
+ * Note: mrt_bundle_push is defined in the MRT toolset and appears in STOREFRONTNEXT.
15
+ *
16
+ * @module tools/storefrontnext
17
+ */
1
18
  import type { McpTool } from '../../utils/index.js';
2
19
  import type { Services } from '../../services.js';
3
20
  /**