@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,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design token extracted from theme CSS (app.css).
|
|
3
|
+
*
|
|
4
|
+
* @property {string} name - CSS custom property name (e.g., "--color-primary")
|
|
5
|
+
* @property {string} value - Raw value (may be var() reference)
|
|
6
|
+
* @property {'dark'|'light'|'shared'} theme - Theme context: 'dark', 'light', or 'shared'
|
|
7
|
+
* @property {'color'|'fontFamily'|'fontSize'|'opacity'|'other'|'radius'|'spacing'} type - Token type for matching: color, spacing, radius, etc.
|
|
8
|
+
* @property {string} [resolvedValue] - Resolved value for var() references (actual hex/value)
|
|
9
|
+
*/
|
|
10
|
+
export interface ThemeToken {
|
|
11
|
+
name: string;
|
|
12
|
+
value: string;
|
|
13
|
+
theme: 'dark' | 'light' | 'shared';
|
|
14
|
+
type: 'color' | 'fontFamily' | 'fontSize' | 'opacity' | 'other' | 'radius' | 'spacing';
|
|
15
|
+
resolvedValue?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parsed theme file with tokens organized by theme context.
|
|
19
|
+
*
|
|
20
|
+
* @property {ThemeToken[]} tokens - All resolved tokens (excludes unresolved var() references)
|
|
21
|
+
* @property {Map<string,ThemeToken>} lightTokens - Map of token name to ThemeToken for light theme
|
|
22
|
+
* @property {Map<string,ThemeToken>} darkTokens - Map of token name to ThemeToken for dark theme
|
|
23
|
+
* @property {Map<string,ThemeToken>} sharedTokens - Map of token name to ThemeToken for shared tokens
|
|
24
|
+
* @property {string[]} warnings - Warnings (e.g., unresolved var() references)
|
|
25
|
+
*/
|
|
26
|
+
export interface ParsedTheme {
|
|
27
|
+
tokens: ThemeToken[];
|
|
28
|
+
lightTokens: Map<string, ThemeToken>;
|
|
29
|
+
darkTokens: Map<string, ThemeToken>;
|
|
30
|
+
sharedTokens: Map<string, ThemeToken>;
|
|
31
|
+
warnings: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Finds the theme file path (app.css) in the workspace.
|
|
35
|
+
*
|
|
36
|
+
* @param workspaceRoot - Workspace root directory to search
|
|
37
|
+
* @returns Absolute path to app.css if found, or null
|
|
38
|
+
*/
|
|
39
|
+
export declare function findThemeFilePath(workspaceRoot?: string): null | string;
|
|
40
|
+
/**
|
|
41
|
+
* Parses theme file and extracts all CSS custom properties.
|
|
42
|
+
*
|
|
43
|
+
* When themeFilePath is not provided, searches for app.css in src/app.css or app.css relative to workspaceRoot.
|
|
44
|
+
*
|
|
45
|
+
* @param themeFilePath - Optional absolute path to theme CSS file
|
|
46
|
+
* @param workspaceRoot - Optional workspace root for theme file discovery when themeFilePath is omitted
|
|
47
|
+
* @returns Parsed theme with tokens organized by light/dark/shared
|
|
48
|
+
* @throws {Error} When theme file is not found
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseThemeFile(themeFilePath?: string, workspaceRoot?: string): ParsedTheme;
|
|
51
|
+
/**
|
|
52
|
+
* Gets all color tokens from parsed theme.
|
|
53
|
+
*
|
|
54
|
+
* @param parsedTheme - Parsed theme from parseThemeFile
|
|
55
|
+
* @returns Array of color-type tokens
|
|
56
|
+
*/
|
|
57
|
+
export declare function getColorTokens(parsedTheme: ParsedTheme): ThemeToken[];
|
|
58
|
+
/**
|
|
59
|
+
* Gets all spacing tokens from parsed theme.
|
|
60
|
+
*
|
|
61
|
+
* @param parsedTheme - Parsed theme from parseThemeFile
|
|
62
|
+
* @returns Array of spacing-type tokens
|
|
63
|
+
*/
|
|
64
|
+
export declare function getSpacingTokens(parsedTheme: ParsedTheme): ThemeToken[];
|
|
65
|
+
/**
|
|
66
|
+
* Gets all radius tokens from parsed theme.
|
|
67
|
+
*
|
|
68
|
+
* @param parsedTheme - Parsed theme from parseThemeFile
|
|
69
|
+
* @returns Array of radius-type tokens
|
|
70
|
+
*/
|
|
71
|
+
export declare function getRadiusTokens(parsedTheme: ParsedTheme): ThemeToken[];
|
|
@@ -0,0 +1,260 @@
|
|
|
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
|
+
* PostCSS dependency: Used to parse theme CSS (app.css) into an AST for reliable extraction of
|
|
8
|
+
* design tokens. Required for: (1) walking @theme at-rules and inline blocks, (2) distinguishing
|
|
9
|
+
* light/dark/shared tokens via selectors (e.g. [data-theme="light"], :root), (3) handling nested
|
|
10
|
+
* rules and var() references. Regex-based parsing would be brittle for real-world theme files.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import postcss, {} from 'postcss';
|
|
15
|
+
/**
|
|
16
|
+
* Determines the type of a CSS custom property based on its name and value
|
|
17
|
+
*/
|
|
18
|
+
function determineTokenType(name, value) {
|
|
19
|
+
const nameLower = name.toLowerCase();
|
|
20
|
+
if (nameLower.includes('color') || value.startsWith('#') || value.startsWith('rgb')) {
|
|
21
|
+
return 'color';
|
|
22
|
+
}
|
|
23
|
+
if (nameLower.includes('radius')) {
|
|
24
|
+
return 'radius';
|
|
25
|
+
}
|
|
26
|
+
if (nameLower.includes('opacity')) {
|
|
27
|
+
return 'opacity';
|
|
28
|
+
}
|
|
29
|
+
if (nameLower.includes('font-size') || nameLower.includes('text-size')) {
|
|
30
|
+
return 'fontSize';
|
|
31
|
+
}
|
|
32
|
+
if (nameLower.includes('font-family') || nameLower.includes('font-face')) {
|
|
33
|
+
return 'fontFamily';
|
|
34
|
+
}
|
|
35
|
+
if (nameLower.includes('spacing') ||
|
|
36
|
+
nameLower.includes('gap') ||
|
|
37
|
+
nameLower.includes('padding') ||
|
|
38
|
+
nameLower.includes('margin')) {
|
|
39
|
+
return 'spacing';
|
|
40
|
+
}
|
|
41
|
+
return 'other';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extracts CSS custom property name from var() reference
|
|
45
|
+
* Example: "var(--primary)" -> "--primary"
|
|
46
|
+
*/
|
|
47
|
+
function extractVarName(value) {
|
|
48
|
+
const match = value.match(/var\(([^)]+)\)/);
|
|
49
|
+
return match ? match[1].trim() : null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves var() references to actual values
|
|
53
|
+
* Returns warnings for any unresolved references
|
|
54
|
+
*/
|
|
55
|
+
function resolveVarReferences(tokens) {
|
|
56
|
+
const warnings = [];
|
|
57
|
+
const tokenMap = new Map();
|
|
58
|
+
for (const token of tokens)
|
|
59
|
+
tokenMap.set(token.name, token);
|
|
60
|
+
for (const token of tokens) {
|
|
61
|
+
if (token.value.includes('var(')) {
|
|
62
|
+
const varName = extractVarName(token.value);
|
|
63
|
+
if (varName) {
|
|
64
|
+
const referencedToken = tokenMap.get(varName);
|
|
65
|
+
if (referencedToken) {
|
|
66
|
+
// Recursively resolve if the referenced token also has a var()
|
|
67
|
+
token.resolvedValue = referencedToken.resolvedValue || referencedToken.value;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Unresolved reference - log warning and skip this token
|
|
71
|
+
warnings.push(`Warning: Token "${token.name}" references undefined variable "${varName}". This token will be excluded from matching.`);
|
|
72
|
+
// Don't set resolvedValue - this token will be filtered out
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
token.resolvedValue = token.value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return warnings;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Extracts custom properties from a PostCSS rule node
|
|
84
|
+
*/
|
|
85
|
+
function extractCustomPropertiesFromRule(rule, theme) {
|
|
86
|
+
const tokens = [];
|
|
87
|
+
rule.walkDecls((decl) => {
|
|
88
|
+
if (decl.prop.startsWith('--')) {
|
|
89
|
+
tokens.push({
|
|
90
|
+
name: decl.prop,
|
|
91
|
+
value: decl.value,
|
|
92
|
+
theme,
|
|
93
|
+
type: determineTokenType(decl.prop, decl.value),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return tokens;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Determines if a selector represents a dark theme context
|
|
101
|
+
*/
|
|
102
|
+
function isDarkThemeSelector(selector) {
|
|
103
|
+
return (selector.includes('.dark') ||
|
|
104
|
+
selector.includes('[data-theme="dark"]') ||
|
|
105
|
+
selector.includes("[data-theme='dark']") ||
|
|
106
|
+
(selector.includes('html:not(.dark)') && selector.includes('inverse')));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Determines if a selector represents a light theme context
|
|
110
|
+
*/
|
|
111
|
+
function isLightThemeSelector(selector) {
|
|
112
|
+
return (selector === ':root' ||
|
|
113
|
+
selector.includes('[data-theme="light"]') ||
|
|
114
|
+
selector.includes("[data-theme='light']") ||
|
|
115
|
+
(selector.includes('html.dark') && selector.includes('inverse')));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parses CSS content to extract theme tokens from different sections using PostCSS
|
|
119
|
+
*/
|
|
120
|
+
function parseCSSContent(cssContent) {
|
|
121
|
+
const allTokens = [];
|
|
122
|
+
// Parse CSS with PostCSS
|
|
123
|
+
const root = postcss.parse(cssContent);
|
|
124
|
+
// Walk through all rules and at-rules
|
|
125
|
+
root.walkAtRules('theme', (atRule) => {
|
|
126
|
+
// Extract @theme inline block (shared tokens)
|
|
127
|
+
if (atRule.params.includes('inline')) {
|
|
128
|
+
atRule.walkDecls((decl) => {
|
|
129
|
+
if (decl.prop.startsWith('--')) {
|
|
130
|
+
allTokens.push({
|
|
131
|
+
name: decl.prop,
|
|
132
|
+
value: decl.value,
|
|
133
|
+
theme: 'shared',
|
|
134
|
+
type: determineTokenType(decl.prop, decl.value),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
// Walk through all rules to find theme-specific tokens
|
|
141
|
+
root.walkRules((rule) => {
|
|
142
|
+
const selector = rule.selector;
|
|
143
|
+
// Determine theme based on selector
|
|
144
|
+
let theme = null;
|
|
145
|
+
if (isDarkThemeSelector(selector)) {
|
|
146
|
+
theme = 'dark';
|
|
147
|
+
}
|
|
148
|
+
else if (isLightThemeSelector(selector)) {
|
|
149
|
+
theme = 'light';
|
|
150
|
+
}
|
|
151
|
+
// Extract tokens if we identified a theme
|
|
152
|
+
if (theme) {
|
|
153
|
+
const tokens = extractCustomPropertiesFromRule(rule, theme);
|
|
154
|
+
allTokens.push(...tokens);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Resolve var() references and collect warnings
|
|
158
|
+
const warnings = resolveVarReferences(allTokens);
|
|
159
|
+
// Filter out tokens with unresolved references
|
|
160
|
+
const resolvedTokens = allTokens.filter((token) => token.resolvedValue !== undefined);
|
|
161
|
+
// Log warnings about skipped tokens
|
|
162
|
+
if (warnings.length > 0 && resolvedTokens.length < allTokens.length) {
|
|
163
|
+
const skippedCount = allTokens.length - resolvedTokens.length;
|
|
164
|
+
warnings.push(`Skipped ${skippedCount} token(s) with unresolved var() references. These will not be available for matching.`);
|
|
165
|
+
}
|
|
166
|
+
// Organize tokens by theme
|
|
167
|
+
const lightTokens = new Map();
|
|
168
|
+
const darkTokens = new Map();
|
|
169
|
+
const sharedTokens = new Map();
|
|
170
|
+
for (const token of resolvedTokens) {
|
|
171
|
+
switch (token.theme) {
|
|
172
|
+
case 'dark': {
|
|
173
|
+
darkTokens.set(token.name, token);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'light': {
|
|
177
|
+
lightTokens.set(token.name, token);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'shared': {
|
|
181
|
+
sharedTokens.set(token.name, token);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
tokens: resolvedTokens,
|
|
188
|
+
lightTokens,
|
|
189
|
+
darkTokens,
|
|
190
|
+
sharedTokens,
|
|
191
|
+
warnings,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Finds the theme file path (app.css) in the workspace.
|
|
196
|
+
*
|
|
197
|
+
* @param workspaceRoot - Workspace root directory to search
|
|
198
|
+
* @returns Absolute path to app.css if found, or null
|
|
199
|
+
*/
|
|
200
|
+
export function findThemeFilePath(workspaceRoot) {
|
|
201
|
+
if (!workspaceRoot) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const possiblePaths = [join(workspaceRoot, 'src/app.css'), join(workspaceRoot, 'app.css')];
|
|
205
|
+
for (const path of possiblePaths) {
|
|
206
|
+
if (existsSync(path)) {
|
|
207
|
+
return path;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Parses theme file and extracts all CSS custom properties.
|
|
214
|
+
*
|
|
215
|
+
* When themeFilePath is not provided, searches for app.css in src/app.css or app.css relative to workspaceRoot.
|
|
216
|
+
*
|
|
217
|
+
* @param themeFilePath - Optional absolute path to theme CSS file
|
|
218
|
+
* @param workspaceRoot - Optional workspace root for theme file discovery when themeFilePath is omitted
|
|
219
|
+
* @returns Parsed theme with tokens organized by light/dark/shared
|
|
220
|
+
* @throws {Error} When theme file is not found
|
|
221
|
+
*/
|
|
222
|
+
export function parseThemeFile(themeFilePath, workspaceRoot) {
|
|
223
|
+
const filePath = themeFilePath ?? findThemeFilePath(workspaceRoot);
|
|
224
|
+
if (!filePath) {
|
|
225
|
+
throw new Error('Theme file (app.css) not found. Please provide the themeFilePath parameter.');
|
|
226
|
+
}
|
|
227
|
+
if (!existsSync(filePath)) {
|
|
228
|
+
throw new Error(`Theme file not found at: ${filePath}`);
|
|
229
|
+
}
|
|
230
|
+
const cssContent = readFileSync(filePath, 'utf8');
|
|
231
|
+
return parseCSSContent(cssContent);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Gets all color tokens from parsed theme.
|
|
235
|
+
*
|
|
236
|
+
* @param parsedTheme - Parsed theme from parseThemeFile
|
|
237
|
+
* @returns Array of color-type tokens
|
|
238
|
+
*/
|
|
239
|
+
export function getColorTokens(parsedTheme) {
|
|
240
|
+
return parsedTheme.tokens.filter((token) => token.type === 'color');
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Gets all spacing tokens from parsed theme.
|
|
244
|
+
*
|
|
245
|
+
* @param parsedTheme - Parsed theme from parseThemeFile
|
|
246
|
+
* @returns Array of spacing-type tokens
|
|
247
|
+
*/
|
|
248
|
+
export function getSpacingTokens(parsedTheme) {
|
|
249
|
+
return parsedTheme.tokens.filter((token) => token.type === 'spacing');
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Gets all radius tokens from parsed theme.
|
|
253
|
+
*
|
|
254
|
+
* @param parsedTheme - Parsed theme from parseThemeFile
|
|
255
|
+
* @returns Array of radius-type tokens
|
|
256
|
+
*/
|
|
257
|
+
export function getRadiusTokens(parsedTheme) {
|
|
258
|
+
return parsedTheme.tokens.filter((token) => token.type === 'radius');
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=css-parser.js.map
|
|
@@ -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
|