@salesforce/b2c-dx-mcp 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +47 -27
  2. package/content/page-designer.md +4 -4
  3. package/dist/commands/mcp.d.ts +12 -0
  4. package/dist/commands/mcp.js +16 -4
  5. package/dist/registry.d.ts +4 -4
  6. package/dist/registry.js +10 -10
  7. package/dist/services.d.ts +75 -1
  8. package/dist/services.js +124 -1
  9. package/dist/tools/adapter.d.ts +29 -21
  10. package/dist/tools/adapter.js +34 -24
  11. package/dist/tools/cartridges/index.d.ts +5 -3
  12. package/dist/tools/cartridges/index.js +55 -25
  13. package/dist/tools/index.d.ts +1 -0
  14. package/dist/tools/index.js +1 -0
  15. package/dist/tools/mrt/index.d.ts +2 -2
  16. package/dist/tools/mrt/index.js +29 -10
  17. package/dist/tools/page-designer-decorator/analyzer.d.ts +169 -0
  18. package/dist/tools/page-designer-decorator/analyzer.js +535 -0
  19. package/dist/tools/page-designer-decorator/index.d.ts +252 -0
  20. package/dist/tools/page-designer-decorator/index.js +597 -0
  21. package/dist/tools/page-designer-decorator/rules/1-mode-selection.d.ts +8 -0
  22. package/dist/tools/page-designer-decorator/rules/1-mode-selection.js +65 -0
  23. package/dist/tools/page-designer-decorator/rules/2a-auto-mode.d.ts +13 -0
  24. package/dist/tools/page-designer-decorator/rules/2a-auto-mode.js +87 -0
  25. package/dist/tools/page-designer-decorator/rules/2b-0-interactive-overview.d.ts +4 -0
  26. package/dist/tools/page-designer-decorator/rules/2b-0-interactive-overview.js +55 -0
  27. package/dist/tools/page-designer-decorator/rules/2b-1-interactive-analyze.d.ts +22 -0
  28. package/dist/tools/page-designer-decorator/rules/2b-1-interactive-analyze.js +109 -0
  29. package/dist/tools/page-designer-decorator/rules/2b-2-interactive-select-props.d.ts +21 -0
  30. package/dist/tools/page-designer-decorator/rules/2b-2-interactive-select-props.js +60 -0
  31. package/dist/tools/page-designer-decorator/rules/2b-3-interactive-configure-attrs.d.ts +27 -0
  32. package/dist/tools/page-designer-decorator/rules/2b-3-interactive-configure-attrs.js +68 -0
  33. package/dist/tools/page-designer-decorator/rules/2b-4-interactive-configure-regions.d.ts +4 -0
  34. package/dist/tools/page-designer-decorator/rules/2b-4-interactive-configure-regions.js +65 -0
  35. package/dist/tools/page-designer-decorator/rules/2b-5-interactive-confirm-generation.d.ts +11 -0
  36. package/dist/tools/page-designer-decorator/rules/2b-5-interactive-confirm-generation.js +92 -0
  37. package/dist/tools/page-designer-decorator/rules.d.ts +51 -0
  38. package/dist/tools/page-designer-decorator/rules.js +70 -0
  39. package/dist/tools/page-designer-decorator/templates/decorator-generator.d.ts +116 -0
  40. package/dist/tools/page-designer-decorator/templates/decorator-generator.js +350 -0
  41. package/dist/tools/pwav3/index.d.ts +2 -2
  42. package/dist/tools/pwav3/index.js +13 -13
  43. package/dist/tools/scapi/index.d.ts +10 -2
  44. package/dist/tools/scapi/index.js +5 -56
  45. package/dist/tools/scapi/scapi-custom-apis-status.d.ts +9 -0
  46. package/dist/tools/scapi/scapi-custom-apis-status.js +152 -0
  47. package/dist/tools/scapi/scapi-schemas-list.d.ts +12 -0
  48. package/dist/tools/scapi/scapi-schemas-list.js +248 -0
  49. package/dist/tools/storefrontnext/developer-guidelines.d.ts +2 -2
  50. package/dist/tools/storefrontnext/developer-guidelines.js +3 -3
  51. package/dist/tools/storefrontnext/index.d.ts +2 -2
  52. package/dist/tools/storefrontnext/index.js +13 -13
  53. package/oclif.manifest.json +13 -2
  54. package/package.json +10 -5
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Component analysis result
3
+ */
4
+ export interface ComponentInfo {
5
+ componentName: string;
6
+ interfaceName: null | string;
7
+ hasDecorators: boolean;
8
+ props: PropInfo[];
9
+ exportType: 'default' | 'named';
10
+ filePath: string;
11
+ }
12
+ /**
13
+ * Property information extracted from component interface
14
+ */
15
+ export interface PropInfo {
16
+ name: string;
17
+ type: string;
18
+ optional: boolean;
19
+ isComplex: boolean;
20
+ isUIOnly: boolean;
21
+ }
22
+ /**
23
+ * Type suggestion for attribute configuration
24
+ */
25
+ export interface TypeSuggestion {
26
+ type: string;
27
+ reason: string;
28
+ priority: 'high' | 'low' | 'medium';
29
+ }
30
+ /**
31
+ * Valid SFCC Page Designer attribute types
32
+ */
33
+ export declare const VALID_ATTRIBUTE_TYPES: readonly ["string", "text", "markup", "integer", "boolean", "product", "category", "file", "page", "image", "url", "enum", "custom", "cms_record"];
34
+ /**
35
+ * Infer Page Designer attribute type from TypeScript type
36
+ */
37
+ export declare function inferPageDesignerType(tsType: string): string;
38
+ /**
39
+ * Check if TypeScript type can be auto-inferred
40
+ */
41
+ export declare function isAutoInferredType(tsType: string): boolean;
42
+ /**
43
+ * Check if type is too complex for Page Designer
44
+ */
45
+ export declare function isComplexType(tsType: string): boolean;
46
+ /**
47
+ * Check if property is UI-only
48
+ */
49
+ export declare function isUIOnlyProp(propName: string): boolean;
50
+ /**
51
+ * Generate Page Designer attribute type suggestions for a component prop
52
+ *
53
+ * **Inference Strategy:**
54
+ * Uses naming patterns and TypeScript types to suggest appropriate Page Designer types.
55
+ * This reduces manual configuration by auto-detecting common patterns.
56
+ *
57
+ * **Page Designer Types:**
58
+ * - `string`: Default text input
59
+ * - `url`: URL/link inputs (validates URL format)
60
+ * - `image`: Image asset picker
61
+ * - `html`: Rich text editor
62
+ * - `markup`: HTML/markdown editor
63
+ * - `enum`: Dropdown with predefined values
64
+ * - `boolean`: Checkbox
65
+ * - `number`: Numeric input
66
+ * - `product`: Product picker (SFCC-specific)
67
+ * - `category`: Category picker (SFCC-specific)
68
+ *
69
+ * **Heuristics (by priority):**
70
+ * 1. **High Priority**: Strong patterns (url, image, product)
71
+ * 2. **Medium Priority**: Contextual patterns (html, markup)
72
+ * 3. **Low Priority**: Weak signals (description → markup)
73
+ *
74
+ * Multiple suggestions allow developers to choose the best fit.
75
+ *
76
+ * @param propName - Property name from component interface
77
+ * @param tsType - TypeScript type string
78
+ * @returns Array of type suggestions with reasoning and priority
79
+ *
80
+ * @example
81
+ * // URL detection:
82
+ * generateTypeSuggestions('imageUrl', 'string')
83
+ * // => [{ type: 'url', reason: '...', priority: 'high' }]
84
+ *
85
+ * @example
86
+ * // Image detection:
87
+ * generateTypeSuggestions('heroImage', 'string')
88
+ * // => [{ type: 'image', reason: '...', priority: 'high' }]
89
+ *
90
+ * @example
91
+ * // Multiple suggestions:
92
+ * generateTypeSuggestions('description', 'string')
93
+ * // => [
94
+ * // { type: 'markup', reason: '...', priority: 'low' },
95
+ * // { type: 'html', reason: '...', priority: 'medium' }
96
+ * // ]
97
+ *
98
+ * @example
99
+ * // Product reference:
100
+ * generateTypeSuggestions('product', 'string')
101
+ * // => [{ type: 'product', reason: '...', priority: 'high' }]
102
+ *
103
+ * @public
104
+ */
105
+ export declare function generateTypeSuggestions(propName: string, tsType: string): TypeSuggestion[];
106
+ /**
107
+ * Component analyzer for Page Designer decorator generation
108
+ */
109
+ declare class ComponentAnalyzer {
110
+ private cache;
111
+ analyzeComponent(filePath: string): ComponentInfo;
112
+ clearCache(): void;
113
+ }
114
+ export declare const componentAnalyzer: ComponentAnalyzer;
115
+ /**
116
+ * Resolve component input (name or path) to absolute file path
117
+ *
118
+ * **This is the main entry point for component discovery.**
119
+ *
120
+ * Supports two input modes:
121
+ * 1. **Name-based** (recommended): Just provide the component name
122
+ * 2. **Path-based** (backward compatible): Provide relative path from workspace
123
+ *
124
+ * **Name-based detection:**
125
+ * Input is treated as a name if it:
126
+ * - Does NOT contain path separators (/ or \)
127
+ * - Does NOT have a file extension (.tsx, .ts, etc.)
128
+ *
129
+ * **Path-based detection:**
130
+ * Input is treated as a path if it:
131
+ * - Contains / or \
132
+ * - Has a file extension
133
+ *
134
+ * @param input - Component name or relative path
135
+ * @param workspaceRoot - Absolute path to workspace root
136
+ * @param searchPaths - Additional directories to search (only used for name-based)
137
+ * @returns Absolute file path to component
138
+ * @throws {Error} If component cannot be found, with detailed search information
139
+ *
140
+ * @example
141
+ * // Name-based (finds automatically):
142
+ * resolveComponent('ProductCard', '/workspace')
143
+ * // => '/workspace/src/components/product-tile/ProductCard.tsx'
144
+ *
145
+ * @example
146
+ * // Path-based (backward compatible):
147
+ * resolveComponent('src/components/ProductCard.tsx', '/workspace')
148
+ * // => '/workspace/src/components/ProductCard.tsx'
149
+ *
150
+ * @example
151
+ * // With custom search paths (for monorepos):
152
+ * resolveComponent('Hero', '/workspace', ['packages/retail/src', 'packages/shared'])
153
+ * // => '/workspace/packages/retail/src/components/Hero.tsx'
154
+ *
155
+ * @example
156
+ * // Error handling:
157
+ * try {
158
+ * resolveComponent('NonExistent', '/workspace')
159
+ * } catch (err) {
160
+ * // Error includes:
161
+ * // - List of searched locations
162
+ * // - Tried name variations
163
+ * // - Helpful tips for resolution
164
+ * }
165
+ *
166
+ * @public
167
+ */
168
+ export declare function resolveComponent(input: string, workspaceRoot: string, searchPaths?: string[]): string;
169
+ export {};
@@ -0,0 +1,535 @@
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 { existsSync, readFileSync } from 'node:fs';
7
+ import path from 'node:path';
8
+ import { globSync } from 'glob';
9
+ import { Project, InterfaceDeclaration, PropertySignature } from 'ts-morph';
10
+ // ============================================================================
11
+ // TYPE INFERENCE
12
+ // ============================================================================
13
+ /**
14
+ * Type mapping from TypeScript to SFCC Page Designer attribute types
15
+ */
16
+ const TYPE_MAPPING = {
17
+ String: 'string',
18
+ string: 'string',
19
+ Number: 'integer',
20
+ number: 'integer',
21
+ Boolean: 'boolean',
22
+ boolean: 'boolean',
23
+ Date: 'string',
24
+ URL: 'url',
25
+ CMSRecord: 'cms_record',
26
+ };
27
+ /**
28
+ * Valid SFCC Page Designer attribute types
29
+ */
30
+ export const VALID_ATTRIBUTE_TYPES = [
31
+ 'string',
32
+ 'text',
33
+ 'markup',
34
+ 'integer',
35
+ 'boolean',
36
+ 'product',
37
+ 'category',
38
+ 'file',
39
+ 'page',
40
+ 'image',
41
+ 'url',
42
+ 'enum',
43
+ 'custom',
44
+ 'cms_record',
45
+ ];
46
+ /**
47
+ * Infer Page Designer attribute type from TypeScript type
48
+ */
49
+ export function inferPageDesignerType(tsType) {
50
+ if (TYPE_MAPPING[tsType]) {
51
+ return TYPE_MAPPING[tsType];
52
+ }
53
+ if (tsType.includes('|')) {
54
+ const firstType = tsType.split('|')[0].trim();
55
+ return inferPageDesignerType(firstType);
56
+ }
57
+ if (tsType.includes('[]') || tsType.includes('Array<')) {
58
+ return 'string';
59
+ }
60
+ return 'string';
61
+ }
62
+ /**
63
+ * Check if TypeScript type can be auto-inferred
64
+ */
65
+ export function isAutoInferredType(tsType) {
66
+ return Boolean(TYPE_MAPPING[tsType]);
67
+ }
68
+ /**
69
+ * Check if type is too complex for Page Designer
70
+ */
71
+ export function isComplexType(tsType) {
72
+ return (tsType.includes('{') ||
73
+ tsType.includes('<') ||
74
+ tsType.includes('.') ||
75
+ tsType.includes('=>') ||
76
+ tsType.includes('React.') ||
77
+ tsType.startsWith('('));
78
+ }
79
+ /**
80
+ * Check if property is UI-only
81
+ */
82
+ export function isUIOnlyProp(propName) {
83
+ const uiPatterns = [
84
+ 'classname',
85
+ 'style',
86
+ 'theme',
87
+ 'variant',
88
+ 'size',
89
+ 'color',
90
+ 'loading',
91
+ 'disabled',
92
+ 'readonly',
93
+ 'onclick',
94
+ 'onchange',
95
+ 'onsubmit',
96
+ 'children',
97
+ 'key',
98
+ 'ref',
99
+ ];
100
+ const nameLower = propName.toLowerCase();
101
+ return uiPatterns.some((pattern) => nameLower.includes(pattern));
102
+ }
103
+ /**
104
+ * Generate Page Designer attribute type suggestions for a component prop
105
+ *
106
+ * **Inference Strategy:**
107
+ * Uses naming patterns and TypeScript types to suggest appropriate Page Designer types.
108
+ * This reduces manual configuration by auto-detecting common patterns.
109
+ *
110
+ * **Page Designer Types:**
111
+ * - `string`: Default text input
112
+ * - `url`: URL/link inputs (validates URL format)
113
+ * - `image`: Image asset picker
114
+ * - `html`: Rich text editor
115
+ * - `markup`: HTML/markdown editor
116
+ * - `enum`: Dropdown with predefined values
117
+ * - `boolean`: Checkbox
118
+ * - `number`: Numeric input
119
+ * - `product`: Product picker (SFCC-specific)
120
+ * - `category`: Category picker (SFCC-specific)
121
+ *
122
+ * **Heuristics (by priority):**
123
+ * 1. **High Priority**: Strong patterns (url, image, product)
124
+ * 2. **Medium Priority**: Contextual patterns (html, markup)
125
+ * 3. **Low Priority**: Weak signals (description → markup)
126
+ *
127
+ * Multiple suggestions allow developers to choose the best fit.
128
+ *
129
+ * @param propName - Property name from component interface
130
+ * @param tsType - TypeScript type string
131
+ * @returns Array of type suggestions with reasoning and priority
132
+ *
133
+ * @example
134
+ * // URL detection:
135
+ * generateTypeSuggestions('imageUrl', 'string')
136
+ * // => [{ type: 'url', reason: '...', priority: 'high' }]
137
+ *
138
+ * @example
139
+ * // Image detection:
140
+ * generateTypeSuggestions('heroImage', 'string')
141
+ * // => [{ type: 'image', reason: '...', priority: 'high' }]
142
+ *
143
+ * @example
144
+ * // Multiple suggestions:
145
+ * generateTypeSuggestions('description', 'string')
146
+ * // => [
147
+ * // { type: 'markup', reason: '...', priority: 'low' },
148
+ * // { type: 'html', reason: '...', priority: 'medium' }
149
+ * // ]
150
+ *
151
+ * @example
152
+ * // Product reference:
153
+ * generateTypeSuggestions('product', 'string')
154
+ * // => [{ type: 'product', reason: '...', priority: 'high' }]
155
+ *
156
+ * @public
157
+ */
158
+ export function generateTypeSuggestions(propName, tsType) {
159
+ const suggestions = [];
160
+ const nameLower = propName.toLowerCase();
161
+ // URL patterns
162
+ if (nameLower.includes('url') || nameLower.includes('link') || nameLower.includes('href')) {
163
+ suggestions.push({
164
+ type: 'url',
165
+ reason: 'Property name suggests URL/link',
166
+ priority: 'high',
167
+ });
168
+ }
169
+ // Image patterns
170
+ if (nameLower.includes('image') ||
171
+ nameLower.includes('img') ||
172
+ nameLower.includes('picture') ||
173
+ nameLower.includes('background')) {
174
+ suggestions.push({
175
+ type: 'image',
176
+ reason: 'Property name suggests image asset',
177
+ priority: 'high',
178
+ });
179
+ }
180
+ // Rich text patterns
181
+ if (nameLower.includes('html') ||
182
+ nameLower.includes('richtext') ||
183
+ nameLower.includes('content') ||
184
+ nameLower.includes('body')) {
185
+ suggestions.push({
186
+ type: 'markup',
187
+ reason: 'Property name suggests rich content',
188
+ priority: 'medium',
189
+ });
190
+ }
191
+ // Multi-line text patterns
192
+ if (nameLower.includes('description') || nameLower.includes('bio') || nameLower.includes('message')) {
193
+ suggestions.push({
194
+ type: 'text',
195
+ reason: 'Property name suggests multi-line text',
196
+ priority: 'medium',
197
+ });
198
+ }
199
+ // Array patterns
200
+ if (tsType.includes('[]') || tsType.includes('Array<')) {
201
+ suggestions.push({
202
+ type: 'enum',
203
+ reason: 'Array types work best as enums for selection in Page Designer',
204
+ priority: 'high',
205
+ });
206
+ }
207
+ // Product/Category references
208
+ if (nameLower.includes('product') && !nameLower.includes('products')) {
209
+ suggestions.push({
210
+ type: 'product',
211
+ reason: 'Property name suggests product reference',
212
+ priority: 'high',
213
+ });
214
+ }
215
+ if (nameLower.includes('category')) {
216
+ suggestions.push({
217
+ type: 'category',
218
+ reason: 'Property name suggests category reference',
219
+ priority: 'high',
220
+ });
221
+ }
222
+ return suggestions;
223
+ }
224
+ // ============================================================================
225
+ // COMPONENT FILE PARSING
226
+ // ============================================================================
227
+ /**
228
+ * Extract component name from file content
229
+ */
230
+ function extractComponentName(content) {
231
+ const defaultFunctionMatch = content.match(/export\s+default\s+function\s+(\w+)/);
232
+ if (defaultFunctionMatch) {
233
+ return defaultFunctionMatch[1];
234
+ }
235
+ const namedFunctionMatch = content.match(/export\s+function\s+(\w+)/);
236
+ if (namedFunctionMatch) {
237
+ return namedFunctionMatch[1];
238
+ }
239
+ const namedConstMatch = content.match(/export\s+const\s+(\w+)\s*=/);
240
+ if (namedConstMatch) {
241
+ return namedConstMatch[1];
242
+ }
243
+ return 'Component';
244
+ }
245
+ /**
246
+ * Detect export type
247
+ */
248
+ function detectExportType(content) {
249
+ return content.includes('export default') ? 'default' : 'named';
250
+ }
251
+ /**
252
+ * Parse component file and extract structure
253
+ */
254
+ function parseComponentFile(filePath) {
255
+ const content = readFileSync(filePath, 'utf8');
256
+ const hasDecorators = content.includes('@Component') || content.includes('@PageType');
257
+ if (hasDecorators) {
258
+ return {
259
+ componentName: extractComponentName(content),
260
+ interfaceName: null,
261
+ hasDecorators: true,
262
+ props: [],
263
+ exportType: detectExportType(content),
264
+ filePath,
265
+ };
266
+ }
267
+ const project = new Project({
268
+ useInMemoryFileSystem: true,
269
+ skipAddingFilesFromTsConfig: true,
270
+ });
271
+ const sourceFile = project.createSourceFile(filePath, content);
272
+ const interfaces = sourceFile.getInterfaces();
273
+ const propsInterface = interfaces.find((i) => i.getName().includes('Props'));
274
+ if (!propsInterface) {
275
+ return {
276
+ componentName: extractComponentName(content),
277
+ interfaceName: null,
278
+ hasDecorators: false,
279
+ props: [],
280
+ exportType: detectExportType(content),
281
+ filePath,
282
+ };
283
+ }
284
+ const props = propsInterface.getProperties().map((prop) => {
285
+ const name = prop.getName();
286
+ const type = prop.getType().getText();
287
+ const optional = prop.hasQuestionToken();
288
+ return {
289
+ name,
290
+ type,
291
+ optional,
292
+ isComplex: isComplexType(type),
293
+ isUIOnly: isUIOnlyProp(name),
294
+ };
295
+ });
296
+ return {
297
+ componentName: extractComponentName(content),
298
+ interfaceName: propsInterface.getName(),
299
+ hasDecorators: false,
300
+ props,
301
+ exportType: detectExportType(content),
302
+ filePath,
303
+ };
304
+ }
305
+ // ============================================================================
306
+ // COMPONENT ANALYZER
307
+ // ============================================================================
308
+ /**
309
+ * Component analyzer for Page Designer decorator generation
310
+ */
311
+ class ComponentAnalyzer {
312
+ cache = new Map();
313
+ analyzeComponent(filePath) {
314
+ const cached = this.cache.get(filePath);
315
+ if (cached) {
316
+ return cached;
317
+ }
318
+ const analysis = parseComponentFile(filePath);
319
+ this.cache.set(filePath, analysis);
320
+ return analysis;
321
+ }
322
+ clearCache() {
323
+ this.cache.clear();
324
+ }
325
+ }
326
+ export const componentAnalyzer = new ComponentAnalyzer();
327
+ // ============================================================================
328
+ // COMPONENT RESOLUTION (Name-Based Lookup)
329
+ // ============================================================================
330
+ /**
331
+ * Convert PascalCase or camelCase to kebab-case
332
+ *
333
+ * Used for finding components with different naming conventions.
334
+ * React components are typically PascalCase, but file names may be kebab-case.
335
+ *
336
+ * @param str - String to convert (e.g., "ProductCard", "myComponent")
337
+ * @returns Kebab-case string (e.g., "product-card", "my-component")
338
+ *
339
+ * @example
340
+ * toKebabCase('ProductCard') // => 'product-card'
341
+ * toKebabCase('MyButtonComponent') // => 'my-button-component'
342
+ * toKebabCase('heroSection') // => 'hero-section'
343
+ *
344
+ * @internal
345
+ */
346
+ function toKebabCase(str) {
347
+ return str
348
+ .replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2')
349
+ .replaceAll(/([A-Z])([A-Z][a-z])/g, '$1-$2')
350
+ .toLowerCase();
351
+ }
352
+ /**
353
+ * Search for component file by name using smart discovery patterns
354
+ *
355
+ * **Search Strategy (in priority order):**
356
+ * 1. Common component directories with exact name (PascalCase)
357
+ * 2. Kebab-case variants of the name
358
+ * 3. Index file patterns (for directory-based components)
359
+ * 4. Broader search in src/
360
+ * 5. Custom search paths (if provided)
361
+ *
362
+ * **Why this order:**
363
+ * - Most projects follow conventions (src/components/)
364
+ * - PascalCase is React standard, checked first
365
+ * - Kebab-case is common for file names
366
+ * - Index files are common for complex components
367
+ * - Fallback to broader search if not in standard locations
368
+ *
369
+ * **Disambiguation:**
370
+ * If multiple files match, prefers the shortest path (closest to root).
371
+ * This typically selects the main component over similar named test/story files.
372
+ *
373
+ * @param componentName - Component name without extension (e.g., "ProductCard", "Hero")
374
+ * @param workspaceRoot - Absolute path to workspace root
375
+ * @param customPaths - Additional directories to search (e.g., ["packages/retail/src"])
376
+ * @returns Absolute file path or null if not found
377
+ *
378
+ * @example
379
+ * // Finds: src/components/product-tile/ProductCard.tsx
380
+ * findComponentByName('ProductCard', '/workspace', undefined)
381
+ *
382
+ * @example
383
+ * // Finds: src/components/hero.tsx or src/components/hero/index.tsx
384
+ * findComponentByName('hero', '/workspace', undefined)
385
+ *
386
+ * @example
387
+ * // Searches in custom paths first
388
+ * findComponentByName('ProductCard', '/workspace', ['packages/retail/src'])
389
+ *
390
+ * @internal
391
+ */
392
+ function findComponentByName(componentName, workspaceRoot, customPaths) {
393
+ // Normalize component name (remove file extensions)
394
+ const cleanName = componentName.replace(/\.(tsx?|jsx?)$/, '');
395
+ const kebabName = toKebabCase(cleanName);
396
+ // Search patterns (in order of priority)
397
+ const searchPatterns = [
398
+ // Common component directories (PascalCase)
399
+ `src/components/**/${cleanName}.tsx`,
400
+ `src/components/**/${cleanName}.ts`,
401
+ `app/components/**/${cleanName}.tsx`,
402
+ `components/**/${cleanName}.tsx`,
403
+ // Kebab-case variants
404
+ `src/components/**/${kebabName}.tsx`,
405
+ `app/components/**/${kebabName}.tsx`,
406
+ `components/**/${kebabName}.tsx`,
407
+ // Index file patterns
408
+ `src/components/**/${kebabName}/index.tsx`,
409
+ `app/components/**/${kebabName}/index.tsx`,
410
+ // Anywhere in src/ (broader search)
411
+ `src/**/${cleanName}.tsx`,
412
+ `src/**/${cleanName}.ts`,
413
+ `src/**/${kebabName}.tsx`,
414
+ // Custom search paths (if provided)
415
+ ...(customPaths?.flatMap((path) => [
416
+ `${path}/**/${cleanName}.tsx`,
417
+ `${path}/**/${cleanName}.ts`,
418
+ `${path}/**/${kebabName}.tsx`,
419
+ `${path}/**/${kebabName}/index.tsx`,
420
+ ]) || []),
421
+ ];
422
+ // Search with glob
423
+ for (const pattern of searchPatterns) {
424
+ try {
425
+ const matches = globSync(pattern, {
426
+ cwd: workspaceRoot,
427
+ absolute: true,
428
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/out/**'],
429
+ });
430
+ if (matches.length > 0) {
431
+ // If multiple matches, prefer shortest path (closest to root)
432
+ const sorted = matches.sort((a, b) => a.length - b.length);
433
+ return sorted[0];
434
+ }
435
+ }
436
+ catch {
437
+ // Ignore glob errors and try next pattern
438
+ continue;
439
+ }
440
+ }
441
+ return null;
442
+ }
443
+ /**
444
+ * Resolve component input (name or path) to absolute file path
445
+ *
446
+ * **This is the main entry point for component discovery.**
447
+ *
448
+ * Supports two input modes:
449
+ * 1. **Name-based** (recommended): Just provide the component name
450
+ * 2. **Path-based** (backward compatible): Provide relative path from workspace
451
+ *
452
+ * **Name-based detection:**
453
+ * Input is treated as a name if it:
454
+ * - Does NOT contain path separators (/ or \)
455
+ * - Does NOT have a file extension (.tsx, .ts, etc.)
456
+ *
457
+ * **Path-based detection:**
458
+ * Input is treated as a path if it:
459
+ * - Contains / or \
460
+ * - Has a file extension
461
+ *
462
+ * @param input - Component name or relative path
463
+ * @param workspaceRoot - Absolute path to workspace root
464
+ * @param searchPaths - Additional directories to search (only used for name-based)
465
+ * @returns Absolute file path to component
466
+ * @throws {Error} If component cannot be found, with detailed search information
467
+ *
468
+ * @example
469
+ * // Name-based (finds automatically):
470
+ * resolveComponent('ProductCard', '/workspace')
471
+ * // => '/workspace/src/components/product-tile/ProductCard.tsx'
472
+ *
473
+ * @example
474
+ * // Path-based (backward compatible):
475
+ * resolveComponent('src/components/ProductCard.tsx', '/workspace')
476
+ * // => '/workspace/src/components/ProductCard.tsx'
477
+ *
478
+ * @example
479
+ * // With custom search paths (for monorepos):
480
+ * resolveComponent('Hero', '/workspace', ['packages/retail/src', 'packages/shared'])
481
+ * // => '/workspace/packages/retail/src/components/Hero.tsx'
482
+ *
483
+ * @example
484
+ * // Error handling:
485
+ * try {
486
+ * resolveComponent('NonExistent', '/workspace')
487
+ * } catch (err) {
488
+ * // Error includes:
489
+ * // - List of searched locations
490
+ * // - Tried name variations
491
+ * // - Helpful tips for resolution
492
+ * }
493
+ *
494
+ * @public
495
+ */
496
+ export function resolveComponent(input, workspaceRoot, searchPaths) {
497
+ // Check if input looks like a path (has / or \ or file extension)
498
+ const looksLikePath = input.includes('/') || input.includes('\\') || input.match(/\.(tsx?|jsx?|mjs|cjs|js)$/);
499
+ if (looksLikePath) {
500
+ // Treat as path (backward compatible)
501
+ const fullPath = path.join(workspaceRoot, input);
502
+ if (existsSync(fullPath)) {
503
+ return fullPath;
504
+ }
505
+ throw new Error(`Component file not found at path: ${input}\n\n` +
506
+ `Full path checked: ${fullPath}\n\n` +
507
+ `Tips:\n` +
508
+ ` 1. Use component name instead (e.g., "ProductCard") for automatic discovery\n` +
509
+ ` 2. If components are in a different repo, set --working-directory flag or SFCC_WORKING_DIRECTORY env var`);
510
+ }
511
+ // Treat as component name - search for it
512
+ const found = findComponentByName(input, workspaceRoot, searchPaths);
513
+ if (!found) {
514
+ const searchLocations = [
515
+ 'src/components/**',
516
+ 'app/components/**',
517
+ 'components/**',
518
+ 'src/**',
519
+ ...(searchPaths || []),
520
+ ];
521
+ throw new Error(`Component "${input}" not found.\n\n` +
522
+ `Searched in:\n${searchLocations.map((loc) => ` - ${loc}`).join('\n')}\n\n` +
523
+ `Tried variations:\n` +
524
+ ` - ${input}.tsx\n` +
525
+ ` - ${toKebabCase(input)}.tsx\n` +
526
+ ` - ${toKebabCase(input)}/index.tsx\n\n` +
527
+ `Tips:\n` +
528
+ ` 1. Provide full path: component: "src/components/ProductCard.tsx"\n` +
529
+ ` 2. Add custom search: searchPaths: ["packages/retail/src"]\n` +
530
+ ` 3. Check component name spelling and casing\n` +
531
+ ` 4. If components are in a different repo, set --working-directory flag or SFCC_WORKING_DIRECTORY env var`);
532
+ }
533
+ return found;
534
+ }
535
+ //# sourceMappingURL=analyzer.js.map