@salesforce/b2c-dx-mcp 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -370
- package/content/pwav3/components.md +400 -0
- package/content/pwav3/config.md +124 -0
- package/content/pwav3/data-fetching.md +213 -0
- package/content/pwav3/extensibility.md +167 -0
- package/content/pwav3/i18n.md +214 -0
- package/content/pwav3/quick-reference.md +169 -0
- package/content/pwav3/routing.md +107 -0
- package/content/pwav3/state-management.md +193 -0
- package/content/pwav3/styling.md +248 -0
- package/content/pwav3/testing.md +124 -0
- package/content/site-theming/theming-accessibility.md +126 -0
- package/content/site-theming/theming-questions.md +208 -0
- package/content/site-theming/theming-validation.md +174 -0
- package/dist/registry.js +1 -1
- package/dist/services.d.ts +10 -10
- package/dist/services.js +19 -12
- package/dist/tools/cartridges/index.js +1 -6
- package/dist/tools/index.d.ts +1 -4
- package/dist/tools/index.js +1 -4
- package/dist/tools/mrt/index.js +1 -6
- package/dist/tools/pwav3/index.d.ts +12 -3
- package/dist/tools/pwav3/index.js +5 -63
- package/dist/tools/pwav3/pwa-kit-development-guidelines.d.ts +9 -0
- package/dist/tools/pwav3/pwa-kit-development-guidelines.js +151 -0
- package/dist/tools/scapi/index.d.ts +1 -1
- package/dist/tools/scapi/index.js +6 -1
- package/dist/tools/scapi/scapi-custom-api-scaffold.d.ts +60 -0
- package/dist/tools/scapi/scapi-custom-api-scaffold.js +175 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +24 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +53 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +42 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.js +325 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +40 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.js +312 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +9 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.js +92 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +114 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.js +98 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +71 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +260 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +61 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.js +234 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +65 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +268 -0
- package/dist/tools/storefrontnext/index.d.ts +17 -0
- package/dist/tools/storefrontnext/index.js +10 -60
- package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +15 -0
- package/dist/tools/storefrontnext/page-designer-decorator/index.js +3 -3
- package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js} +3 -3
- package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +92 -0
- package/dist/tools/storefrontnext/site-theming/color-contrast.js +186 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.js +131 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +11 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.js +78 -0
- package/dist/tools/storefrontnext/site-theming/index.d.ts +14 -0
- package/dist/tools/storefrontnext/site-theming/index.js +122 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.js +316 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +62 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.js +410 -0
- package/dist/tools/storefrontnext/site-theming/types.d.ts +35 -0
- package/dist/tools/storefrontnext/site-theming/types.js +7 -0
- package/oclif.manifest.json +1 -1
- package/package.json +8 -5
- /package/content/{auth.md → sfnext/auth.md} +0 -0
- /package/content/{components.md → sfnext/components.md} +0 -0
- /package/content/{config.md → sfnext/config.md} +0 -0
- /package/content/{data-fetching.md → sfnext/data-fetching.md} +0 -0
- /package/content/{extensions.md → sfnext/extensions.md} +0 -0
- /package/content/{i18n.md → sfnext/i18n.md} +0 -0
- /package/content/{page-designer.md → sfnext/page-designer.md} +0 -0
- /package/content/{performance.md → sfnext/performance.md} +0 -0
- /package/content/{pitfalls.md → sfnext/pitfalls.md} +0 -0
- /package/content/{quick-reference.md → sfnext/quick-reference.md} +0 -0
- /package/content/{state-management.md → sfnext/state-management.md} +0 -0
- /package/content/{styling.md → sfnext/styling.md} +0 -0
- /package/content/{testing.md → sfnext/testing.md} +0 -0
- /package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts} +0 -0
|
@@ -0,0 +1,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
|
/**
|
|
@@ -3,62 +3,12 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2
|
|
4
4
|
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
* Storefront Next toolset for B2C Commerce.
|
|
8
|
-
*
|
|
9
|
-
* This toolset provides MCP tools for Storefront Next development.
|
|
10
|
-
*
|
|
11
|
-
* **Implemented Tools:**
|
|
12
|
-
* - `storefront_next_development_guidelines` - Get development guidelines and best practices (GA)
|
|
13
|
-
*
|
|
14
|
-
* **Placeholder Tools (Use `--allow-non-ga-tools` flag to enable):**
|
|
15
|
-
* - `storefront_next_site_theming` - Configure site theming
|
|
16
|
-
* - `storefront_next_figma_to_component_workflow` - Convert Figma to components
|
|
17
|
-
* - `storefront_next_generate_component` - Generate new components
|
|
18
|
-
* - `storefront_next_map_tokens_to_theme` - Map design tokens
|
|
19
|
-
* - `storefront_next_generate_page_designer_metadata` - Generate Page Designer metadata
|
|
20
|
-
*
|
|
21
|
-
* @module tools/storefrontnext
|
|
22
|
-
*/
|
|
23
|
-
import { z } from 'zod';
|
|
24
|
-
import { createToolAdapter, jsonResult } from '../adapter.js';
|
|
25
|
-
import { createDeveloperGuidelinesTool } from './developer-guidelines.js';
|
|
6
|
+
import { createDeveloperGuidelinesTool } from './sfnext-development-guidelines.js';
|
|
26
7
|
import { createPageDesignerDecoratorTool } from './page-designer-decorator/index.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* the actual implementation is available.
|
|
32
|
-
*
|
|
33
|
-
* @param name - Tool name
|
|
34
|
-
* @param description - Tool description
|
|
35
|
-
* @param loadServices - Function that loads configuration and returns Services instance
|
|
36
|
-
* @returns The configured MCP tool
|
|
37
|
-
*/
|
|
38
|
-
function createPlaceholderTool(name, description, loadServices) {
|
|
39
|
-
return createToolAdapter({
|
|
40
|
-
name,
|
|
41
|
-
description: `[PLACEHOLDER] ${description}`,
|
|
42
|
-
toolsets: ['STOREFRONTNEXT'],
|
|
43
|
-
isGA: false,
|
|
44
|
-
requiresInstance: false,
|
|
45
|
-
inputSchema: {
|
|
46
|
-
message: z.string().optional().describe('Optional message to echo'),
|
|
47
|
-
},
|
|
48
|
-
async execute(args) {
|
|
49
|
-
// Placeholder implementation
|
|
50
|
-
const timestamp = new Date().toISOString();
|
|
51
|
-
return {
|
|
52
|
-
tool: name,
|
|
53
|
-
status: 'placeholder',
|
|
54
|
-
message: `This is a placeholder implementation for '${name}'. The actual implementation is coming soon.`,
|
|
55
|
-
input: args,
|
|
56
|
-
timestamp,
|
|
57
|
-
};
|
|
58
|
-
},
|
|
59
|
-
formatOutput: (output) => jsonResult(output),
|
|
60
|
-
}, loadServices);
|
|
61
|
-
}
|
|
8
|
+
import { createSiteThemingTool } from './site-theming/index.js';
|
|
9
|
+
import { createFigmaToComponentTool } from './figma/figma-to-component/index.js';
|
|
10
|
+
import { createGenerateComponentTool } from './figma/generate-component/index.js';
|
|
11
|
+
import { createMapTokensToThemeTool } from './figma/map-tokens/index.js';
|
|
62
12
|
/**
|
|
63
13
|
* Creates all tools for the STOREFRONTNEXT toolset.
|
|
64
14
|
*
|
|
@@ -73,11 +23,11 @@ export function createStorefrontNextTools(loadServices) {
|
|
|
73
23
|
return [
|
|
74
24
|
createDeveloperGuidelinesTool(loadServices),
|
|
75
25
|
createPageDesignerDecoratorTool(loadServices),
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
26
|
+
createSiteThemingTool(loadServices),
|
|
27
|
+
createPageDesignerDecoratorTool(loadServices),
|
|
28
|
+
createFigmaToComponentTool(loadServices),
|
|
29
|
+
createGenerateComponentTool(loadServices),
|
|
30
|
+
createMapTokensToThemeTool(loadServices),
|
|
81
31
|
];
|
|
82
32
|
}
|
|
83
33
|
//# sourceMappingURL=index.js.map
|
|
@@ -226,12 +226,27 @@ export function generateTypeSuggestions(propName, tsType) {
|
|
|
226
226
|
// ============================================================================
|
|
227
227
|
/**
|
|
228
228
|
* Extract component name from file content
|
|
229
|
+
*
|
|
230
|
+
* Priority order:
|
|
231
|
+
* 1. export default function X (inline default function)
|
|
232
|
+
* 2. export default X (default export of named identifier, e.g. export default ProductItem)
|
|
233
|
+
* 3. export function X (first named function export)
|
|
234
|
+
* 4. export const X =
|
|
235
|
+
* 5. fallback: 'Component'
|
|
236
|
+
*
|
|
237
|
+
* Note: (2) must be checked before (3) because files may have both "export function Foo"
|
|
238
|
+
* and "export default Bar" — the default export is the primary component.
|
|
229
239
|
*/
|
|
230
240
|
function extractComponentName(content) {
|
|
231
241
|
const defaultFunctionMatch = content.match(/export\s+default\s+function\s+(\w+)/);
|
|
232
242
|
if (defaultFunctionMatch) {
|
|
233
243
|
return defaultFunctionMatch[1];
|
|
234
244
|
}
|
|
245
|
+
// export default X where X is a named identifier (not "function")
|
|
246
|
+
const defaultNamedMatch = content.match(/export\s+default\s+(?!function\s)(\w+)/);
|
|
247
|
+
if (defaultNamedMatch) {
|
|
248
|
+
return defaultNamedMatch[1];
|
|
249
|
+
}
|
|
235
250
|
const namedFunctionMatch = content.match(/export\s+function\s+(\w+)/);
|
|
236
251
|
if (namedFunctionMatch) {
|
|
237
252
|
return namedFunctionMatch[1];
|
|
@@ -14,7 +14,7 @@ export const pageDesignerDecoratorSchema = z
|
|
|
14
14
|
.object({
|
|
15
15
|
component: z
|
|
16
16
|
.string()
|
|
17
|
-
.describe('Component name (e.g., "
|
|
17
|
+
.describe('Component name (e.g., "ProductItem", "ProductTile") or file path (e.g., "src/components/ProductItem.tsx"). ' +
|
|
18
18
|
'When a name is provided, the tool automatically searches common component directories. ' +
|
|
19
19
|
'For backward compatibility, file paths are also supported.'),
|
|
20
20
|
searchPaths: z
|
|
@@ -530,7 +530,7 @@ export function createPageDesignerDecoratorTool(loadServices) {
|
|
|
530
530
|
name: 'storefront_next_page_designer_decorator',
|
|
531
531
|
description: 'Adds Page Designer decorators (@Component, @AttributeDefinition, @RegionDefinition) to React components. ' +
|
|
532
532
|
'Two modes: autoMode=true for quick setup with defaults, or interactive mode via conversationContext.step. ' +
|
|
533
|
-
'Component discovery uses
|
|
533
|
+
'Component discovery uses --project-directory flag or SFCC_PROJECT_DIRECTORY env var. ' +
|
|
534
534
|
'Auto mode: selects suitable props, infers types, generates code immediately. ' +
|
|
535
535
|
'Interactive mode: multi-step workflow (analyze → select_props → configure_attrs → configure_regions → confirm_generation).',
|
|
536
536
|
inputSchema: pageDesignerDecoratorSchema.shape,
|
|
@@ -543,7 +543,7 @@ export function createPageDesignerDecoratorTool(loadServices) {
|
|
|
543
543
|
// Use projectDirectory from services to ensure we search in the correct project directory
|
|
544
544
|
// This prevents searches in the home folder when MCP clients spawn servers from ~
|
|
545
545
|
const services = loadServices();
|
|
546
|
-
const workspaceRoot = services.
|
|
546
|
+
const workspaceRoot = services.resolveWithProjectDirectory();
|
|
547
547
|
if (validatedArgs.autoMode === undefined && !validatedArgs.conversationContext) {
|
|
548
548
|
const fullPath = resolveComponent(validatedArgs.component, workspaceRoot, validatedArgs.searchPaths);
|
|
549
549
|
const componentInfo = componentAnalyzer.analyzeComponent(fullPath);
|
package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js}
RENAMED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Provides critical development guidelines and best practices for building
|
|
10
10
|
* Storefront Next applications with React Server Components.
|
|
11
11
|
*
|
|
12
|
-
* @module tools/storefrontnext/
|
|
12
|
+
* @module tools/storefrontnext/sfnext-development-guidelines
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync } from 'node:fs';
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
@@ -21,7 +21,7 @@ import { createToolAdapter, textResult } from '../adapter.js';
|
|
|
21
21
|
// regardless of where this module is located in the build output
|
|
22
22
|
const require = createRequire(import.meta.url);
|
|
23
23
|
const packageRoot = path.dirname(require.resolve('@salesforce/b2c-dx-mcp/package.json'));
|
|
24
|
-
const CONTENT_DIR = path.join(packageRoot, 'content');
|
|
24
|
+
const CONTENT_DIR = path.join(packageRoot, 'content', 'sfnext');
|
|
25
25
|
/**
|
|
26
26
|
* Section metadata with key and optional description.
|
|
27
27
|
* Single source of truth for all available sections.
|
|
@@ -137,4 +137,4 @@ export function createDeveloperGuidelinesTool(loadServices) {
|
|
|
137
137
|
formatOutput: (output) => textResult(output),
|
|
138
138
|
}, loadServices);
|
|
139
139
|
}
|
|
140
|
-
//# sourceMappingURL=
|
|
140
|
+
//# sourceMappingURL=sfnext-development-guidelines.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates that a string is a valid 6-digit hex color.
|
|
3
|
+
* @param hex - Hex color string to validate
|
|
4
|
+
* @returns true if valid
|
|
5
|
+
*/
|
|
6
|
+
export declare function isValidHex(hex: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Calculates the relative luminance of a color according to WCAG 2.1
|
|
9
|
+
* @param hex - Hex color string (e.g., "#635BFF")
|
|
10
|
+
* @returns Relative luminance value between 0 and 1
|
|
11
|
+
* @throws Error if hex format is invalid
|
|
12
|
+
*/
|
|
13
|
+
export declare function getLuminance(hex: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Calculates the contrast ratio between two colors according to WCAG 2.1
|
|
16
|
+
* @param color1 - First hex color string
|
|
17
|
+
* @param color2 - Second hex color string
|
|
18
|
+
* @returns Contrast ratio (1:1 to 21:1)
|
|
19
|
+
*/
|
|
20
|
+
export declare function getContrastRatio(color1: string, color2: string): number;
|
|
21
|
+
/**
|
|
22
|
+
* WCAG compliance levels
|
|
23
|
+
*/
|
|
24
|
+
export declare enum WCAGLevel {
|
|
25
|
+
AA = "AA",// 4.5:1 for normal text
|
|
26
|
+
AA_LARGE = "AA_LARGE",// 3:1 for large text
|
|
27
|
+
AAA = "AAA",// 7:1 for normal text
|
|
28
|
+
AAA_LARGE = "AAA_LARGE",// 4.5:1 for large text
|
|
29
|
+
FAIL = "FAIL"
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Determines WCAG compliance level for a contrast ratio
|
|
33
|
+
* @param ratio - Contrast ratio
|
|
34
|
+
* @param isLargeText - Whether this is for large text (18pt+ or 14pt+ bold)
|
|
35
|
+
* @returns WCAG compliance level
|
|
36
|
+
*/
|
|
37
|
+
export declare function getWCAGLevel(ratio: number, isLargeText?: boolean): WCAGLevel;
|
|
38
|
+
/**
|
|
39
|
+
* Result of color contrast validation for a single foreground/background pair.
|
|
40
|
+
*
|
|
41
|
+
* @property {string} color1 - First hex color (typically foreground)
|
|
42
|
+
* @property {string} color2 - Second hex color (typically background)
|
|
43
|
+
* @property {number} ratio - Contrast ratio (1:1 to 21:1)
|
|
44
|
+
* @property {WCAGLevel} wcagLevel - WCAG compliance level
|
|
45
|
+
* @property {boolean} passesAA - Whether the combination meets WCAG AA
|
|
46
|
+
* @property {boolean} passesAAA - Whether the combination meets WCAG AAA
|
|
47
|
+
* @property {boolean} isLargeText - Whether validation used large-text thresholds
|
|
48
|
+
* @property {string} visualAssessment - Readability assessment (excellent, good, acceptable, poor)
|
|
49
|
+
* @property {string} [recommendation] - Optional suggestion when contrast is suboptimal
|
|
50
|
+
*/
|
|
51
|
+
export interface ContrastValidationResult {
|
|
52
|
+
color1: string;
|
|
53
|
+
color2: string;
|
|
54
|
+
ratio: number;
|
|
55
|
+
wcagLevel: WCAGLevel;
|
|
56
|
+
passesAA: boolean;
|
|
57
|
+
passesAAA: boolean;
|
|
58
|
+
isLargeText: boolean;
|
|
59
|
+
visualAssessment: 'acceptable' | 'excellent' | 'good' | 'poor';
|
|
60
|
+
recommendation?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validates contrast between two colors
|
|
64
|
+
* @param color1 - First hex color string
|
|
65
|
+
* @param color2 - Second hex color string
|
|
66
|
+
* @param isLargeText - Whether this is for large text
|
|
67
|
+
* @returns Validation result with contrast ratio and compliance info
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateContrast(color1: string, color2: string, isLargeText?: boolean): ContrastValidationResult;
|
|
70
|
+
/**
|
|
71
|
+
* Validates multiple color combinations for WCAG compliance.
|
|
72
|
+
*
|
|
73
|
+
* @param combinations - Array of foreground/background pairs with optional label and large-text flag
|
|
74
|
+
* @returns Array of validation results, each including the input label if provided
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateColorCombinations(combinations: Array<{
|
|
77
|
+
foreground: string;
|
|
78
|
+
background: string;
|
|
79
|
+
isLargeText?: boolean;
|
|
80
|
+
label?: string;
|
|
81
|
+
}>): Array<ContrastValidationResult & {
|
|
82
|
+
label?: string;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Formats a validation result as a human-readable string for display to users.
|
|
86
|
+
*
|
|
87
|
+
* @param result - Validation result, optionally with a label for the color combination
|
|
88
|
+
* @returns Multi-line string with contrast ratio, WCAG status, and recommendation (if any)
|
|
89
|
+
*/
|
|
90
|
+
export declare function formatValidationResult(result: ContrastValidationResult & {
|
|
91
|
+
label?: string;
|
|
92
|
+
}): string;
|