@salesforce/b2c-dx-mcp 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +9 -6
- /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,410 @@
|
|
|
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
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { join, dirname, basename } from 'node:path';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { getLogger } from '@salesforce/b2c-tooling-sdk/logging';
|
|
10
|
+
// Resolve the site-theming content directory from the package root
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const packageRoot = dirname(require.resolve('@salesforce/b2c-dx-mcp/package.json'));
|
|
13
|
+
const SITE_THEMING_CONTENT_DIR = join(packageRoot, 'content', 'site-theming');
|
|
14
|
+
const logger = getLogger();
|
|
15
|
+
function parseWorkflowSection(content) {
|
|
16
|
+
const workflowMatch = content.match(/##\s*🔄\s*WORKFLOW[^#]*(?=##|$)/is);
|
|
17
|
+
if (!workflowMatch)
|
|
18
|
+
return undefined;
|
|
19
|
+
const workflowContent = workflowMatch[0].replace(/##\s*🔄\s*WORKFLOW[^\n]*\n?/i, '').trim();
|
|
20
|
+
const stepMatches = workflowContent.match(/^\d+\.\s+(.+)$/gm);
|
|
21
|
+
const steps = stepMatches ? stepMatches.map((step) => step.replace(/^\d+\.\s+/, '').trim()) : [];
|
|
22
|
+
const extractionMatch = workflowContent.match(/###\s*📝\s*EXTRACTION[^#]*(?=###|$)/is);
|
|
23
|
+
const extractionInstructions = extractionMatch
|
|
24
|
+
? extractionMatch[0].replace(/###\s*📝\s*EXTRACTION[^\n]*\n?/i, '').trim()
|
|
25
|
+
: undefined;
|
|
26
|
+
const checklistMatch = workflowContent.match(/###\s*✅\s*PRE-IMPLEMENTATION[^#]*(?=###|$)/is);
|
|
27
|
+
const preImplementationChecklist = checklistMatch
|
|
28
|
+
? checklistMatch[0].replace(/###\s*✅\s*PRE-IMPLEMENTATION[^\n]*\n?/i, '').trim()
|
|
29
|
+
: undefined;
|
|
30
|
+
if (steps.length > 0 || extractionInstructions || preImplementationChecklist) {
|
|
31
|
+
return { steps, extractionInstructions, preImplementationChecklist };
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function parseValidationSection(content) {
|
|
36
|
+
const validationMatch = content.match(/##\s*✅\s*VALIDATION[^#]*(?=##|$)/is);
|
|
37
|
+
if (!validationMatch)
|
|
38
|
+
return undefined;
|
|
39
|
+
const validationContent = validationMatch[0].replace(/##\s*✅\s*VALIDATION[^\n]*\n?/i, '').trim();
|
|
40
|
+
const colorValidationMatch = validationContent.match(/###\s*A\.\s*Color[^#]*(?=###|$)/is);
|
|
41
|
+
const colorValidation = colorValidationMatch
|
|
42
|
+
? colorValidationMatch[0].replace(/###\s*A\.\s*Color[^\n]*\n?/i, '').trim()
|
|
43
|
+
: undefined;
|
|
44
|
+
const fontValidationMatch = validationContent.match(/###\s*B\.\s*Font[^#]*(?=###|$)/is);
|
|
45
|
+
const fontValidation = fontValidationMatch
|
|
46
|
+
? fontValidationMatch[0].replace(/###\s*B\.\s*Font[^\n]*\n?/i, '').trim()
|
|
47
|
+
: undefined;
|
|
48
|
+
const generalValidationMatch = validationContent.match(/###\s*C\.\s*General[^#]*(?=###|$)/is);
|
|
49
|
+
const generalValidation = generalValidationMatch
|
|
50
|
+
? generalValidationMatch[0].replace(/###\s*C\.\s*General[^\n]*\n?/i, '').trim()
|
|
51
|
+
: undefined;
|
|
52
|
+
const requirementsMatch = validationContent.match(/###\s*IMPORTANT[^#]*(?=###|$)/is);
|
|
53
|
+
const requirements = requirementsMatch
|
|
54
|
+
? requirementsMatch[0].replace(/###\s*IMPORTANT[^\n]*\n?/i, '').trim()
|
|
55
|
+
: undefined;
|
|
56
|
+
if (colorValidation || fontValidation || generalValidation || requirements) {
|
|
57
|
+
return { colorValidation, fontValidation, generalValidation, requirements };
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
function extractRuleItems(content, pattern, type) {
|
|
62
|
+
const rules = [];
|
|
63
|
+
let match;
|
|
64
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
65
|
+
const items = match[1]
|
|
66
|
+
.split('\n')
|
|
67
|
+
.filter((line) => line.trim().startsWith('-'))
|
|
68
|
+
.map((line) => line.replace(/^-\s*/, '').trim());
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
rules.push({ type, description: item });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return rules;
|
|
74
|
+
}
|
|
75
|
+
function generateColorQuestions(guidance, content, generateId) {
|
|
76
|
+
const allGuidelines = guidance.guidelines;
|
|
77
|
+
const allRules = guidance.rules;
|
|
78
|
+
const colorGuidelines = allGuidelines.filter((g) => g.content.toLowerCase().includes('color') ||
|
|
79
|
+
g.content.toLowerCase().includes('hex') ||
|
|
80
|
+
g.title.toLowerCase().includes('color'));
|
|
81
|
+
const colorRules = allRules.filter((r) => r.description.toLowerCase().includes('color') ||
|
|
82
|
+
r.description.toLowerCase().includes('background-color') ||
|
|
83
|
+
r.description.toLowerCase().includes('border-color'));
|
|
84
|
+
if (colorGuidelines.length === 0 && colorRules.length === 0)
|
|
85
|
+
return [];
|
|
86
|
+
const questions = [];
|
|
87
|
+
if (allGuidelines.some((g) => g.content.toLowerCase().includes('exact hex') || g.content.toLowerCase().includes('hex code'))) {
|
|
88
|
+
questions.push({
|
|
89
|
+
id: generateId('color'),
|
|
90
|
+
question: 'What are the exact hex color values you want to use? (Please provide hex codes, e.g., #635BFF)',
|
|
91
|
+
category: 'colors',
|
|
92
|
+
required: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (allGuidelines.some((g) => g.content.toLowerCase().includes('color type mapping') ||
|
|
96
|
+
g.content.toLowerCase().includes('color mapping') ||
|
|
97
|
+
g.content.toLowerCase().includes('primary vs secondary') ||
|
|
98
|
+
g.content.toLowerCase().includes('brand vs accent'))) {
|
|
99
|
+
questions.push({
|
|
100
|
+
id: generateId('color'),
|
|
101
|
+
question: 'How should these colors be mapped? (e.g., which color should be primary vs secondary, brand vs accent)',
|
|
102
|
+
category: 'colors',
|
|
103
|
+
required: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (allGuidelines.some((g) => g.content.toLowerCase().includes('color combinations') || g.content.toLowerCase().includes('propose color'))) {
|
|
107
|
+
questions.push({
|
|
108
|
+
id: generateId('color'),
|
|
109
|
+
question: 'Which color should be used for primary actions vs secondary actions?',
|
|
110
|
+
category: 'colors',
|
|
111
|
+
required: false,
|
|
112
|
+
}, {
|
|
113
|
+
id: generateId('color'),
|
|
114
|
+
question: 'What should be the hover state colors?',
|
|
115
|
+
category: 'colors',
|
|
116
|
+
required: false,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (content.toLowerCase().includes('dark') && content.toLowerCase().includes('light')) {
|
|
120
|
+
questions.push({
|
|
121
|
+
id: generateId('color'),
|
|
122
|
+
question: 'Do you want to support both light and dark themes? If yes, what colors should be used for each?',
|
|
123
|
+
category: 'colors',
|
|
124
|
+
required: false,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return questions;
|
|
128
|
+
}
|
|
129
|
+
function generateFontQuestions(guidance, generateId) {
|
|
130
|
+
const allGuidelines = guidance.guidelines;
|
|
131
|
+
const fontGuidelines = allGuidelines.filter((g) => g.content.toLowerCase().includes('font') ||
|
|
132
|
+
g.content.toLowerCase().includes('typography') ||
|
|
133
|
+
g.title.toLowerCase().includes('font'));
|
|
134
|
+
const fontRules = guidance.rules.filter((r) => r.description.toLowerCase().includes('font') ||
|
|
135
|
+
r.description.toLowerCase().includes('font-weight') ||
|
|
136
|
+
r.description.toLowerCase().includes('font-size'));
|
|
137
|
+
if (fontGuidelines.length === 0 && fontRules.length === 0)
|
|
138
|
+
return [];
|
|
139
|
+
const questions = [];
|
|
140
|
+
if (allGuidelines.some((g) => g.content.toLowerCase().includes('exact font') || g.content.toLowerCase().includes('font name'))) {
|
|
141
|
+
questions.push({
|
|
142
|
+
id: generateId('font'),
|
|
143
|
+
question: 'What is the exact font family name you want to use? (e.g., "sohne-var")',
|
|
144
|
+
category: 'typography',
|
|
145
|
+
required: true,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (allGuidelines.some((g) => g.content.toLowerCase().includes('font availability') ||
|
|
149
|
+
g.content.toLowerCase().includes('custom font') ||
|
|
150
|
+
g.content.toLowerCase().includes('google fonts'))) {
|
|
151
|
+
questions.push({
|
|
152
|
+
id: generateId('font'),
|
|
153
|
+
question: 'Is this a custom font that needs to be loaded, or should I use a Google Fonts equivalent?',
|
|
154
|
+
category: 'typography',
|
|
155
|
+
required: true,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (allGuidelines.some((g) => g.content.toLowerCase().includes('headings and body') ||
|
|
159
|
+
g.content.toLowerCase().includes('font apply') ||
|
|
160
|
+
g.content.toLowerCase().includes('font usage'))) {
|
|
161
|
+
questions.push({
|
|
162
|
+
id: generateId('font'),
|
|
163
|
+
question: 'Should this font apply to both headings and body text, or just one?',
|
|
164
|
+
category: 'typography',
|
|
165
|
+
required: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return questions;
|
|
169
|
+
}
|
|
170
|
+
function generateLayoutQuestions(guidance, content, generateId) {
|
|
171
|
+
const layoutGuidelines = guidance.guidelines.filter((g) => g.content.toLowerCase().includes('layout') ||
|
|
172
|
+
g.content.toLowerCase().includes('positioning') ||
|
|
173
|
+
g.title.toLowerCase().includes('layout'));
|
|
174
|
+
if (layoutGuidelines.length === 0)
|
|
175
|
+
return [];
|
|
176
|
+
const allowsLayout = content.toLowerCase().includes('layout changes') && content.toLowerCase().includes('explicitly requested');
|
|
177
|
+
if (!allowsLayout)
|
|
178
|
+
return [];
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
id: generateId('general'),
|
|
182
|
+
question: 'Do you need any layout changes, or only visual styling (colors, fonts, etc.)?',
|
|
183
|
+
category: 'general',
|
|
184
|
+
required: false,
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
function generateQuestionsFromGuidelines(guidance, content) {
|
|
189
|
+
let counter = 0;
|
|
190
|
+
const generateId = (cat) => `${cat}-${++counter}`;
|
|
191
|
+
return [
|
|
192
|
+
...generateColorQuestions(guidance, content, generateId),
|
|
193
|
+
...generateFontQuestions(guidance, generateId),
|
|
194
|
+
...generateLayoutQuestions(guidance, content, generateId),
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
function extractQuestionLines(content) {
|
|
198
|
+
const lines = content.split('\n').filter((line) => {
|
|
199
|
+
const t = line.trim();
|
|
200
|
+
return t.endsWith('?') && t.length > 10;
|
|
201
|
+
});
|
|
202
|
+
return lines
|
|
203
|
+
.map((line) => line
|
|
204
|
+
.replace(/^[-*•]\s*/, '')
|
|
205
|
+
.replace(/^\d+\.\s*/, '')
|
|
206
|
+
.trim())
|
|
207
|
+
.filter((c) => c.length > 10 && c.endsWith('?'));
|
|
208
|
+
}
|
|
209
|
+
function mergeQuestionsIntoGuidance(guidance, content, generated, extracted) {
|
|
210
|
+
const colorQs = extracted.filter((q) => q.toLowerCase().includes('color') ||
|
|
211
|
+
q.toLowerCase().includes('primary') ||
|
|
212
|
+
q.toLowerCase().includes('accent') ||
|
|
213
|
+
q.toLowerCase().includes('brand') ||
|
|
214
|
+
q.toLowerCase().includes('theme'));
|
|
215
|
+
const fontQs = extracted.filter((q) => q.toLowerCase().includes('font') || q.toLowerCase().includes('typography'));
|
|
216
|
+
const generalQs = extracted.filter((q) => !q.toLowerCase().includes('color') &&
|
|
217
|
+
!q.toLowerCase().includes('primary') &&
|
|
218
|
+
!q.toLowerCase().includes('accent') &&
|
|
219
|
+
!q.toLowerCase().includes('brand') &&
|
|
220
|
+
!q.toLowerCase().includes('theme') &&
|
|
221
|
+
!q.toLowerCase().includes('font') &&
|
|
222
|
+
!q.toLowerCase().includes('typography'));
|
|
223
|
+
let counter = generated.length;
|
|
224
|
+
const genId = (cat) => `${cat}-${++counter}`;
|
|
225
|
+
guidance.questions.push(...generated);
|
|
226
|
+
for (const [i, q] of colorQs.entries()) {
|
|
227
|
+
guidance.questions.push({
|
|
228
|
+
id: genId('color'),
|
|
229
|
+
question: q,
|
|
230
|
+
category: 'colors',
|
|
231
|
+
required: i === 0 && generated.filter((x) => x.category === 'colors').length === 0,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
for (const [i, q] of fontQs.entries()) {
|
|
235
|
+
guidance.questions.push({
|
|
236
|
+
id: genId('font'),
|
|
237
|
+
question: q,
|
|
238
|
+
category: 'typography',
|
|
239
|
+
required: i === 0 && generated.filter((x) => x.category === 'typography').length === 0,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
for (const q of generalQs) {
|
|
243
|
+
guidance.questions.push({ id: genId('general'), question: q, category: 'general', required: false });
|
|
244
|
+
}
|
|
245
|
+
if (guidance.questions.length === 0) {
|
|
246
|
+
const lower = content.toLowerCase();
|
|
247
|
+
if (lower.includes('color')) {
|
|
248
|
+
guidance.questions.push({
|
|
249
|
+
id: 'color-primary',
|
|
250
|
+
question: 'What colors should be used for theming? (Please provide hex codes)',
|
|
251
|
+
category: 'colors',
|
|
252
|
+
required: true,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (lower.includes('font') || lower.includes('typography')) {
|
|
256
|
+
guidance.questions.push({
|
|
257
|
+
id: 'font-family',
|
|
258
|
+
question: 'What font family should be used? (Please provide exact font name)',
|
|
259
|
+
category: 'typography',
|
|
260
|
+
required: true,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Parses an .md/.mdc file and extracts theming questions and guidelines
|
|
267
|
+
*/
|
|
268
|
+
function parseThemingMDC(content, filePath) {
|
|
269
|
+
const guidance = {
|
|
270
|
+
questions: [],
|
|
271
|
+
guidelines: [],
|
|
272
|
+
rules: [],
|
|
273
|
+
metadata: {
|
|
274
|
+
filePath,
|
|
275
|
+
fileName: basename(filePath),
|
|
276
|
+
loadedAt: new Date(),
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
const workflow = parseWorkflowSection(content);
|
|
280
|
+
if (workflow)
|
|
281
|
+
guidance.workflow = workflow;
|
|
282
|
+
const validation = parseValidationSection(content);
|
|
283
|
+
if (validation)
|
|
284
|
+
guidance.validation = validation;
|
|
285
|
+
const criticalSections = content.match(/##\s*⚠️\s*CRITICAL[^#]*/gi) || [];
|
|
286
|
+
const specificationSections = content.match(/##\s*📋[^#]*/gi) || [];
|
|
287
|
+
for (const section of criticalSections) {
|
|
288
|
+
const titleMatch = section.match(/##\s*⚠️\s*CRITICAL:\s*(.+?)\n/);
|
|
289
|
+
const title = titleMatch ? titleMatch[1].trim() : 'Critical Rule';
|
|
290
|
+
guidance.guidelines.push({
|
|
291
|
+
category: 'critical',
|
|
292
|
+
title,
|
|
293
|
+
content: section.replace(/##\s*⚠️\s*CRITICAL[^\n]*\n/, '').trim(),
|
|
294
|
+
critical: true,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
for (const section of specificationSections) {
|
|
298
|
+
const titleMatch = section.match(/##\s*📋\s*(.+?)\n/);
|
|
299
|
+
const title = titleMatch ? titleMatch[1].trim() : 'Specification Rule';
|
|
300
|
+
guidance.guidelines.push({
|
|
301
|
+
category: 'specification',
|
|
302
|
+
title,
|
|
303
|
+
content: section.replace(/##\s*📋[^\n]*\n/, '').trim(),
|
|
304
|
+
critical: false,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const doRules = extractRuleItems(content, /###\s*What\s+TO\s+Change:([^#]*)/gi, 'do');
|
|
308
|
+
const dontRules = extractRuleItems(content, /###\s*What\s+NOT\s+to\s+Change:([^#]*)/gi, 'dont');
|
|
309
|
+
guidance.rules.push(...doRules, ...dontRules);
|
|
310
|
+
const generatedQuestions = generateQuestionsFromGuidelines(guidance, content);
|
|
311
|
+
const extractedQuestions = extractQuestionLines(content);
|
|
312
|
+
mergeQuestionsIntoGuidance(guidance, content, generatedQuestions, extractedQuestions);
|
|
313
|
+
return guidance;
|
|
314
|
+
}
|
|
315
|
+
class ThemingStore {
|
|
316
|
+
initializedForRoot = null;
|
|
317
|
+
store = new Map();
|
|
318
|
+
get(fileKey) {
|
|
319
|
+
return this.store.get(fileKey);
|
|
320
|
+
}
|
|
321
|
+
getKeys() {
|
|
322
|
+
return [...this.store.keys()];
|
|
323
|
+
}
|
|
324
|
+
has(fileKey) {
|
|
325
|
+
return this.store.has(fileKey);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Initialize store with default files from content/site-theming.
|
|
329
|
+
* Uses workspaceRoot for THEMING_FILES env paths (relative to project).
|
|
330
|
+
* Skips re-loading when already initialized for the same root.
|
|
331
|
+
*/
|
|
332
|
+
initialize(workspaceRoot, options) {
|
|
333
|
+
const root = workspaceRoot ?? process.cwd();
|
|
334
|
+
if (this.initializedForRoot === root) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (this.initializedForRoot !== null) {
|
|
338
|
+
this.store.clear();
|
|
339
|
+
}
|
|
340
|
+
this.initializedForRoot = root;
|
|
341
|
+
const contentDir = options?.contentDirOverride ?? SITE_THEMING_CONTENT_DIR;
|
|
342
|
+
const defaultFileKeys = ['theming-questions', 'theming-validation', 'theming-accessibility'];
|
|
343
|
+
const extensions = ['.md', '.mdc'];
|
|
344
|
+
for (const key of defaultFileKeys) {
|
|
345
|
+
let filePath = null;
|
|
346
|
+
for (const ext of extensions) {
|
|
347
|
+
const candidate = join(contentDir, `${key}${ext}`);
|
|
348
|
+
if (existsSync(candidate)) {
|
|
349
|
+
filePath = candidate;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (filePath) {
|
|
354
|
+
try {
|
|
355
|
+
this.loadFile(key, filePath);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
359
|
+
logger.warn(`Could not load theming file ${filePath}: ${errorMessage}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const themingFilesEnv = process.env.THEMING_FILES;
|
|
364
|
+
if (themingFilesEnv) {
|
|
365
|
+
try {
|
|
366
|
+
this.loadThemingFilesFromEnv(themingFilesEnv, root);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
370
|
+
logger.warn(`Could not parse THEMING_FILES environment variable: ${errorMessage}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
loadFile(fileKey, filePath) {
|
|
375
|
+
try {
|
|
376
|
+
if (!existsSync(filePath)) {
|
|
377
|
+
throw new Error(`File not found: ${filePath}`);
|
|
378
|
+
}
|
|
379
|
+
const content = readFileSync(filePath, 'utf8');
|
|
380
|
+
const guidance = parseThemingMDC(content, filePath);
|
|
381
|
+
this.store.set(fileKey, guidance);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
385
|
+
throw new Error(`Failed to load theming file ${filePath}: ${errorMessage}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
loadThemingFilesFromEnv(envValue, root) {
|
|
389
|
+
const files = JSON.parse(envValue);
|
|
390
|
+
for (const { key, path: filePath } of files) {
|
|
391
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(root, filePath);
|
|
392
|
+
this.tryLoadEnvFile(key, fullPath);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
tryLoadEnvFile(key, fullPath) {
|
|
396
|
+
if (!existsSync(fullPath)) {
|
|
397
|
+
logger.warn(`Theming file not found: ${fullPath} (key: ${key})`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
this.loadFile(key, fullPath);
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
405
|
+
logger.warn(`Could not load theming file ${fullPath} (key: ${key}): ${errorMessage}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
export const siteThemingStore = new ThemingStore();
|
|
410
|
+
//# sourceMappingURL=theming-store.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the site theming tool.
|
|
3
|
+
*
|
|
4
|
+
* @module tools/storefrontnext/site-theming/types
|
|
5
|
+
*/
|
|
6
|
+
/** Mapping of semantic color roles (e.g. lightText, buttonBackground) to hex values */
|
|
7
|
+
export type ColorMapping = Record<string, string>;
|
|
8
|
+
/** User-provided color with optional type label */
|
|
9
|
+
export interface ColorEntry {
|
|
10
|
+
hex?: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
}
|
|
13
|
+
/** User-provided font with optional type label */
|
|
14
|
+
export interface FontEntry {
|
|
15
|
+
name?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Collected answers from the theming conversation */
|
|
19
|
+
export interface CollectedAnswers {
|
|
20
|
+
colors?: ColorEntry[];
|
|
21
|
+
fonts?: FontEntry[];
|
|
22
|
+
colorMapping?: ColorMapping;
|
|
23
|
+
[questionId: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** Conversation context passed to the theming tool */
|
|
26
|
+
export interface ConversationContext {
|
|
27
|
+
currentStep?: string;
|
|
28
|
+
collectedAnswers?: CollectedAnswers;
|
|
29
|
+
questionsAsked?: string[];
|
|
30
|
+
}
|
|
31
|
+
/** Input schema for the site theming tool */
|
|
32
|
+
export interface SiteThemingInput {
|
|
33
|
+
fileKeys?: string[];
|
|
34
|
+
conversationContext?: ConversationContext;
|
|
35
|
+
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/b2c-dx-mcp",
|
|
3
3
|
"description": "MCP server for B2C Commerce developer experience tools",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.5",
|
|
5
5
|
"author": "Salesforce",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
7
|
"repository": "SalesforceCommerceCloud/b2c-developer-tooling",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"salesforce",
|
|
@@ -78,8 +78,9 @@
|
|
|
78
78
|
"glob": "13.0.0",
|
|
79
79
|
"ts-morph": "^27.0.0",
|
|
80
80
|
"yaml": "2.8.1",
|
|
81
|
+
"postcss": "8.5.6",
|
|
81
82
|
"zod": "3.25.76",
|
|
82
|
-
"@salesforce/b2c-tooling-sdk": "0.5.
|
|
83
|
+
"@salesforce/b2c-tooling-sdk": "0.5.4"
|
|
83
84
|
},
|
|
84
85
|
"devDependencies": {
|
|
85
86
|
"@eslint/compat": "^1",
|
|
@@ -122,9 +123,11 @@
|
|
|
122
123
|
"inspect": "mcp-inspector node bin/run.js --toolsets all --allow-non-ga-tools",
|
|
123
124
|
"inspect:dev": "mcp-inspector node --conditions development bin/dev.js --toolsets all --allow-non-ga-tools",
|
|
124
125
|
"pretest": "tsc --noEmit -p test",
|
|
125
|
-
"test": "c8 mocha --forbid-only \"test/**/*.test.ts\"",
|
|
126
|
-
"test:ci": "c8 mocha --forbid-only --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"",
|
|
127
|
-
"test:agent": "mocha --forbid-only --reporter min \"test/**/*.test.ts\"",
|
|
126
|
+
"test": "c8 mocha --forbid-only --ignore 'test/e2e/**' \"test/**/*.test.ts\"",
|
|
127
|
+
"test:ci": "c8 mocha --forbid-only --reporter json --reporter-option output=test-results.json --ignore 'test/e2e/**' \"test/**/*.test.ts\"",
|
|
128
|
+
"test:agent": "mocha --forbid-only --reporter min --ignore 'test/e2e/**' \"test/**/*.test.ts\"",
|
|
129
|
+
"test:e2e": "mocha --forbid-only \"test/e2e/**/*.test.ts\"",
|
|
130
|
+
"test:e2e:ci": "mocha --forbid-only --reporter json --reporter-option output=test-results-e2e.json \"test/e2e/**/*.test.ts\"",
|
|
128
131
|
"coverage": "c8 report",
|
|
129
132
|
"posttest": "pnpm run lint"
|
|
130
133
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts}
RENAMED
|
File without changes
|