@fundamental-ngx/mcp 0.62.0-rc.100

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 (33) hide show
  1. package/README.md +109 -0
  2. package/package.json +21 -0
  3. package/src/data/components.json +61370 -0
  4. package/src/extractors/build-metadata.d.ts +8 -0
  5. package/src/extractors/build-metadata.js +214 -0
  6. package/src/extractors/build-metadata.js.map +1 -0
  7. package/src/extractors/cem-extractor.d.ts +17 -0
  8. package/src/extractors/cem-extractor.js +434 -0
  9. package/src/extractors/cem-extractor.js.map +1 -0
  10. package/src/extractors/changelog-extractor.d.ts +6 -0
  11. package/src/extractors/changelog-extractor.js +118 -0
  12. package/src/extractors/changelog-extractor.js.map +1 -0
  13. package/src/extractors/description-extractor.d.ts +11 -0
  14. package/src/extractors/description-extractor.js +61 -0
  15. package/src/extractors/description-extractor.js.map +1 -0
  16. package/src/extractors/example-extractor.d.ts +19 -0
  17. package/src/extractors/example-extractor.js +70 -0
  18. package/src/extractors/example-extractor.js.map +1 -0
  19. package/src/extractors/token-extractor.d.ts +6 -0
  20. package/src/extractors/token-extractor.js +348 -0
  21. package/src/extractors/token-extractor.js.map +1 -0
  22. package/src/extractors/typedoc-extractor.d.ts +16 -0
  23. package/src/extractors/typedoc-extractor.js +580 -0
  24. package/src/extractors/typedoc-extractor.js.map +1 -0
  25. package/src/index.d.ts +2 -0
  26. package/src/index.js +5 -0
  27. package/src/index.js.map +1 -0
  28. package/src/server.d.ts +1 -0
  29. package/src/server.js +796 -0
  30. package/src/server.js.map +1 -0
  31. package/src/types/component-metadata.d.ts +177 -0
  32. package/src/types/component-metadata.js +24 -0
  33. package/src/types/component-metadata.js.map +1 -0
@@ -0,0 +1,8 @@
1
+ import { ComponentCatalog } from '../types/component-metadata';
2
+ /**
3
+ * Build the complete MCP metadata catalog.
4
+ *
5
+ * Orchestrates all extractors and writes the unified components.json.
6
+ * Can be run as a standalone script or via an NX executor.
7
+ */
8
+ export declare function buildMetadata(basePath: string, outputPath: string): Promise<ComponentCatalog>;
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildMetadata = buildMetadata;
37
+ const promises_1 = require("fs/promises");
38
+ const path_1 = require("path");
39
+ const cem_extractor_1 = require("./cem-extractor");
40
+ const description_extractor_1 = require("./description-extractor");
41
+ const example_extractor_1 = require("./example-extractor");
42
+ const typedoc_extractor_1 = require("./typedoc-extractor");
43
+ /** Map npm package name to the docs directory alias used in example paths. */
44
+ const LIBRARY_TO_DIR = {
45
+ '@fundamental-ngx/core': 'core',
46
+ '@fundamental-ngx/platform': 'platform',
47
+ '@fundamental-ngx/btp': 'btp',
48
+ '@fundamental-ngx/cx': 'cx',
49
+ '@fundamental-ngx/cdk': 'cdk'
50
+ };
51
+ /** Derive the example directory name from a selector.
52
+ * "fd-date-picker" → "date-picker"
53
+ * "fdp-table" → "table"
54
+ * "button[fd-button], a[fd-button]" → "button"
55
+ * "[fdOverflowLayout]" → "overflow-layout"
56
+ */
57
+ function selectorToDir(selector) {
58
+ // Handle attribute selectors like "button[fd-button], a[fd-button]" or "[fdOverflowLayout]"
59
+ const attrMatch = selector.match(/\[(fd[pbk]?)-([^\]]+)\]/);
60
+ if (attrMatch) {
61
+ return attrMatch[2];
62
+ }
63
+ // Handle camelCase attribute selectors like "[fdOverflowLayout]"
64
+ const camelMatch = selector.match(/\[fd([A-Z][^\]]*)\]/);
65
+ if (camelMatch) {
66
+ // Convert camelCase to kebab-case: "OverflowLayout" → "overflow-layout"
67
+ return camelMatch[1]
68
+ .replace(/([A-Z])/g, '-$1')
69
+ .toLowerCase()
70
+ .replace(/^-/, '');
71
+ }
72
+ // Simple prefix selectors: "fd-button" → "button"
73
+ return selector
74
+ .split(',')[0]
75
+ .trim()
76
+ .replace(/^(fd[pbk]?|cx|ui5)-/, '');
77
+ }
78
+ /**
79
+ * Build the complete MCP metadata catalog.
80
+ *
81
+ * Orchestrates all extractors and writes the unified components.json.
82
+ * Can be run as a standalone script or via an NX executor.
83
+ */
84
+ async function buildMetadata(basePath, outputPath) {
85
+ console.log('Starting metadata extraction...');
86
+ const startTime = Date.now();
87
+ // Run CEM and TypeDoc extraction in parallel
88
+ const [cemComponents, typeDocComponents] = await Promise.all([
89
+ (0, cem_extractor_1.extractAllUi5Components)(basePath).then((components) => {
90
+ console.log(` CEM: extracted ${components.length} UI5 components`);
91
+ return components;
92
+ }),
93
+ (0, typedoc_extractor_1.extractAllTypeDocComponents)(basePath).then((components) => {
94
+ console.log(` TypeDoc: extracted ${components.length} hand-written components`);
95
+ return components;
96
+ })
97
+ ]);
98
+ // Merge all components
99
+ const allComponents = [...cemComponents, ...typeDocComponents];
100
+ // Enrich with examples
101
+ const examples = await (0, example_extractor_1.extractExamples)(basePath);
102
+ console.log(` Examples: found ${examples.size} components with examples`);
103
+ // Attach examples to matching components
104
+ let enrichedCount = 0;
105
+ for (const component of allComponents) {
106
+ const libDir = LIBRARY_TO_DIR[component.library];
107
+ if (!libDir) {
108
+ continue;
109
+ }
110
+ const compDir = selectorToDir(component.selector);
111
+ const key = `${libDir}/${compDir}`;
112
+ const compExamples = examples.get(key);
113
+ if (compExamples && compExamples.length > 0) {
114
+ component.examples = compExamples.map((ex) => ({
115
+ name: ex.name,
116
+ description: ex.description,
117
+ typescript: ex.typescript,
118
+ html: ex.html
119
+ }));
120
+ enrichedCount++;
121
+ }
122
+ }
123
+ console.log(` Examples: enriched ${enrichedCount} components`);
124
+ // Enrich with docs descriptions (for components missing JSDoc)
125
+ const descriptions = await (0, description_extractor_1.extractDescriptions)(basePath);
126
+ console.log(` Descriptions: found ${descriptions.size} docs descriptions`);
127
+ let descriptionEnrichedCount = 0;
128
+ for (const component of allComponents) {
129
+ // Skip components that already have a real description (from JSDoc)
130
+ if (component.description && !component.description.match(/^\w[\w ]+ (?:component|directive) \(/)) {
131
+ continue;
132
+ }
133
+ const libDir = LIBRARY_TO_DIR[component.library];
134
+ if (!libDir) {
135
+ continue;
136
+ }
137
+ const compDir = selectorToDir(component.selector);
138
+ const key = `${libDir}/${compDir}`;
139
+ const docsDesc = descriptions.get(key);
140
+ if (docsDesc) {
141
+ component.description = docsDesc;
142
+ descriptionEnrichedCount++;
143
+ }
144
+ }
145
+ console.log(` Descriptions: enriched ${descriptionEnrichedCount} components`);
146
+ // Sort alphabetically by selector for consistent output
147
+ allComponents.sort((a, b) => a.selector.localeCompare(b.selector));
148
+ const catalog = {
149
+ generatedAt: new Date().toISOString(),
150
+ version: '0.62.0-rc.67',
151
+ components: allComponents
152
+ };
153
+ // Write output
154
+ await (0, promises_1.mkdir)((0, path_1.dirname)(outputPath), { recursive: true });
155
+ await (0, promises_1.writeFile)(outputPath, JSON.stringify(catalog, null, 2), 'utf-8');
156
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
157
+ console.log(`\nMetadata extraction complete in ${elapsed}s`);
158
+ console.log(` Total components: ${allComponents.length}`);
159
+ console.log(` Output: ${outputPath}`);
160
+ return catalog;
161
+ }
162
+ /**
163
+ * CLI entry point for running extraction standalone.
164
+ * Usage: npx ts-node libs/mcp-server/src/extractors/build-metadata.ts [--dry-run]
165
+ */
166
+ async function main() {
167
+ const basePath = (0, path_1.resolve)(__dirname, '../../../../');
168
+ const outputPath = (0, path_1.resolve)(__dirname, '../data/components.json');
169
+ const isDryRun = process.argv.includes('--dry-run');
170
+ try {
171
+ const catalog = await buildMetadata(basePath, isDryRun ? '/dev/null' : outputPath);
172
+ if (isDryRun) {
173
+ // In dry-run mode, compare with existing file
174
+ const { readFile } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
175
+ try {
176
+ const existing = await readFile(outputPath, 'utf-8');
177
+ const existingCatalog = JSON.parse(existing);
178
+ if (existingCatalog.components.length !== catalog.components.length) {
179
+ console.error(`\nSTALE: Component count changed (${existingCatalog.components.length} → ${catalog.components.length})`);
180
+ process.exit(1);
181
+ }
182
+ // Compare component names/selectors
183
+ const existingSelectors = new Set(existingCatalog.components.map((c) => c.selector));
184
+ const newSelectors = new Set(catalog.components.map((c) => c.selector));
185
+ const added = catalog.components.filter((c) => !existingSelectors.has(c.selector));
186
+ const removed = existingCatalog.components.filter((c) => !newSelectors.has(c.selector));
187
+ if (added.length > 0 || removed.length > 0) {
188
+ if (added.length) {
189
+ console.error(` Added: ${added.map((c) => c.selector).join(', ')}`);
190
+ }
191
+ if (removed.length) {
192
+ console.error(` Removed: ${removed.map((c) => c.selector).join(', ')}`);
193
+ }
194
+ console.error('\nSTALE: Run `nx run mcp-server:extract-metadata` to update.');
195
+ process.exit(1);
196
+ }
197
+ console.log('\ncomponents.json is up to date.');
198
+ }
199
+ catch {
200
+ console.error('\nSTALE: components.json does not exist. Run `nx run mcp-server:extract-metadata`.');
201
+ process.exit(1);
202
+ }
203
+ }
204
+ }
205
+ catch (error) {
206
+ console.error('Metadata extraction failed:', error);
207
+ process.exit(1);
208
+ }
209
+ }
210
+ // Run if executed directly
211
+ if (require.main === module) {
212
+ main();
213
+ }
214
+ //# sourceMappingURL=build-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-metadata.js","sourceRoot":"","sources":["../../../../../libs/mcp-server/src/extractors/build-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { ComponentMetadata, Library } from '../types/component-metadata';
2
+ /**
3
+ * Extract component metadata from a single Custom Elements Manifest JSON file.
4
+ *
5
+ * @param cemFilePath Absolute path to a `custom-elements-internal.json` file.
6
+ * @param library The fundamental-ngx library this CEM belongs to.
7
+ * @returns Array of normalized `ComponentMetadata` entries.
8
+ */
9
+ export declare function extractFromCem(cemFilePath: string, library: Library): Promise<ComponentMetadata[]>;
10
+ /**
11
+ * Convenience function that extracts metadata from all three UI5 Web Components
12
+ * CEM files and returns a merged array.
13
+ *
14
+ * @param basePath Absolute path to the repository root (where `node_modules` lives).
15
+ * @returns Combined array of `ComponentMetadata` for all UI5 packages.
16
+ */
17
+ export declare function extractAllUi5Components(basePath: string): Promise<ComponentMetadata[]>;
@@ -0,0 +1,434 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractFromCem = extractFromCem;
4
+ exports.extractAllUi5Components = extractAllUi5Components;
5
+ const promises_1 = require("fs/promises");
6
+ const path_1 = require("path");
7
+ const CEM_SOURCES = [
8
+ {
9
+ cemPath: '@ui5/webcomponents/dist/custom-elements-internal.json',
10
+ library: '@fundamental-ngx/ui5-webcomponents'
11
+ },
12
+ {
13
+ cemPath: '@ui5/webcomponents-fiori/dist/custom-elements-internal.json',
14
+ library: '@fundamental-ngx/ui5-webcomponents-fiori'
15
+ },
16
+ {
17
+ cemPath: '@ui5/webcomponents-ai/dist/custom-elements-internal.json',
18
+ library: '@fundamental-ngx/ui5-webcomponents-ai'
19
+ }
20
+ ];
21
+ // ---------------------------------------------------------------------------
22
+ // Public API
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Extract component metadata from a single Custom Elements Manifest JSON file.
26
+ *
27
+ * @param cemFilePath Absolute path to a `custom-elements-internal.json` file.
28
+ * @param library The fundamental-ngx library this CEM belongs to.
29
+ * @returns Array of normalized `ComponentMetadata` entries.
30
+ */
31
+ async function extractFromCem(cemFilePath, library) {
32
+ const raw = await (0, promises_1.readFile)(cemFilePath, 'utf-8');
33
+ const cem = JSON.parse(raw);
34
+ const enumMap = buildEnumMap(cem);
35
+ const components = [];
36
+ for (const mod of cem.modules) {
37
+ for (const decl of mod.declarations ?? []) {
38
+ if (!isPublicCustomElement(decl)) {
39
+ continue;
40
+ }
41
+ const component = mapDeclaration(decl, mod.path, library, enumMap);
42
+ components.push(component);
43
+ }
44
+ }
45
+ return components;
46
+ }
47
+ /**
48
+ * Convenience function that extracts metadata from all three UI5 Web Components
49
+ * CEM files and returns a merged array.
50
+ *
51
+ * @param basePath Absolute path to the repository root (where `node_modules` lives).
52
+ * @returns Combined array of `ComponentMetadata` for all UI5 packages.
53
+ */
54
+ async function extractAllUi5Components(basePath) {
55
+ const results = [];
56
+ for (const source of CEM_SOURCES) {
57
+ const cemFilePath = (0, path_1.resolve)(basePath, 'node_modules', source.cemPath);
58
+ try {
59
+ const components = await extractFromCem(cemFilePath, source.library);
60
+ results.push(...components);
61
+ }
62
+ catch {
63
+ // CEM file may not exist (e.g. ai package not installed)
64
+ }
65
+ }
66
+ return results;
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // Internal helpers
70
+ // ---------------------------------------------------------------------------
71
+ /** Type guard: declaration is a public, non-abstract custom element. */
72
+ function isPublicCustomElement(decl) {
73
+ if (decl.kind !== 'class') {
74
+ return false;
75
+ }
76
+ const cls = decl;
77
+ return (cls.customElement === true &&
78
+ !!cls.tagName &&
79
+ cls._ui5privacy !== 'private' &&
80
+ cls._ui5privacy !== 'protected' &&
81
+ cls._ui5abstract !== true);
82
+ }
83
+ /** Build a lookup from enum name to its member values. */
84
+ function buildEnumMap(cem) {
85
+ const map = new Map();
86
+ for (const mod of cem.modules) {
87
+ for (const decl of mod.declarations ?? []) {
88
+ if (decl.kind === 'enum') {
89
+ const enumDecl = decl;
90
+ const values = (enumDecl.members ?? []).map((m) => m.name);
91
+ map.set(enumDecl.name, values);
92
+ }
93
+ }
94
+ }
95
+ return map;
96
+ }
97
+ /** Try to resolve enum values from a type reference. */
98
+ function resolveEnumValues(type, enumMap) {
99
+ if (!type) {
100
+ return undefined;
101
+ }
102
+ // Check direct references first
103
+ if (type.references?.length) {
104
+ for (const ref of type.references) {
105
+ const refValues = enumMap.get(ref.name);
106
+ if (refValues?.length) {
107
+ return refValues;
108
+ }
109
+ }
110
+ }
111
+ // Fall back to matching the type text against known enum names
112
+ const typeName = type.text.replace(/\s*\|.*$/, '').trim();
113
+ const typeValues = enumMap.get(typeName);
114
+ if (typeValues?.length) {
115
+ return typeValues;
116
+ }
117
+ // Handle inline union types like '"Auto" | "Accent1" | "Accent2"'
118
+ if (type.text.includes('"') && type.text.includes('|')) {
119
+ const matches = type.text.match(/"([^"]+)"/g);
120
+ if (matches?.length) {
121
+ return matches.map((m) => m.replace(/"/g, ''));
122
+ }
123
+ }
124
+ return undefined;
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Declaration → ComponentMetadata mapping
128
+ // ---------------------------------------------------------------------------
129
+ function mapDeclaration(decl, modulePath, library, enumMap) {
130
+ const rawDescription = decl.description ?? '';
131
+ return {
132
+ name: decl.name,
133
+ selector: decl.tagName,
134
+ library,
135
+ category: inferCategory(modulePath, library),
136
+ description: cleanCemDescription(rawDescription),
137
+ since: decl._ui5since,
138
+ deprecated: normalizeDeprecated(decl.deprecated),
139
+ inputs: mapInputs(decl.members, enumMap),
140
+ outputs: mapOutputs(decl.events),
141
+ slots: mapSlots(decl.slots),
142
+ methods: mapMethods(decl.members),
143
+ cssProperties: mapCssProperties(decl.cssProperties),
144
+ keyboardHandling: extractSection(rawDescription, 'Keyboard Handling'),
145
+ sourceFile: modulePath,
146
+ source: 'cem'
147
+ };
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Input mapping
151
+ // ---------------------------------------------------------------------------
152
+ function mapInputs(members, enumMap) {
153
+ if (!members) {
154
+ return [];
155
+ }
156
+ return members
157
+ .filter((m) => m.kind === 'field' && m.privacy === 'public' && !m.readonly)
158
+ .map((m) => ({
159
+ name: m.name,
160
+ type: m.type?.text ?? 'unknown',
161
+ defaultValue: m.default,
162
+ description: m.description ?? '',
163
+ required: m.default === undefined && !isOptionalType(m.type?.text),
164
+ enumValues: resolveEnumValues(m.type, enumMap),
165
+ since: m._ui5since,
166
+ deprecated: normalizeDeprecated(m.deprecated)
167
+ }));
168
+ }
169
+ /** Returns true if the type string indicates the value is optional. */
170
+ function isOptionalType(typeText) {
171
+ if (!typeText) {
172
+ return false;
173
+ }
174
+ return typeText.includes('undefined') || typeText.includes('null') || typeText.endsWith('?');
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Output mapping
178
+ // ---------------------------------------------------------------------------
179
+ function mapOutputs(events) {
180
+ if (!events) {
181
+ return [];
182
+ }
183
+ return events
184
+ .filter((e) => e._ui5privacy !== 'private' && e._ui5privacy !== 'protected')
185
+ .map((e) => ({
186
+ name: toUi5OutputName(e.name),
187
+ type: e.type?.text ?? 'CustomEvent',
188
+ description: e.description ?? '',
189
+ detail: mapEventDetail(e._ui5parameters),
190
+ since: e._ui5since,
191
+ deprecated: normalizeDeprecated(e.deprecated)
192
+ }));
193
+ }
194
+ /**
195
+ * Convert a DOM event name to the Angular output name used by the UI5 wrappers.
196
+ * Prefix with "ui5" and PascalCase the rest.
197
+ *
198
+ * Examples:
199
+ * - "click" → "ui5Click"
200
+ * - "selection-change" → "ui5SelectionChange"
201
+ * - "close" → "ui5Close"
202
+ */
203
+ function toUi5OutputName(eventName) {
204
+ const pascal = eventName
205
+ .split('-')
206
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
207
+ .join('');
208
+ return `ui5${pascal}`;
209
+ }
210
+ function mapEventDetail(params) {
211
+ if (!params?.length) {
212
+ return undefined;
213
+ }
214
+ return params
215
+ .filter((p) => p._ui5privacy !== 'private' && p._ui5privacy !== 'protected')
216
+ .map((p) => ({
217
+ name: p.name,
218
+ type: p.type?.text ?? 'unknown',
219
+ description: p.description ?? ''
220
+ }));
221
+ }
222
+ // ---------------------------------------------------------------------------
223
+ // Slot mapping
224
+ // ---------------------------------------------------------------------------
225
+ function mapSlots(slots) {
226
+ if (!slots) {
227
+ return [];
228
+ }
229
+ return slots
230
+ .filter((s) => s._ui5privacy !== 'private' && s._ui5privacy !== 'protected')
231
+ .map((s) => ({
232
+ name: s.name,
233
+ description: s.description ?? '',
234
+ acceptedTypes: extractAcceptedTypes(s._ui5type),
235
+ since: s._ui5since,
236
+ deprecated: normalizeDeprecated(s.deprecated)
237
+ }));
238
+ }
239
+ /** Extract accepted type names from a slot's _ui5type field. */
240
+ function extractAcceptedTypes(type) {
241
+ if (!type) {
242
+ return undefined;
243
+ }
244
+ // References give the most accurate type names
245
+ if (type.references?.length) {
246
+ return type.references.map((ref) => ref.name);
247
+ }
248
+ // Fall back to parsing the text, e.g. "Array<HTMLElement>" → ["HTMLElement"]
249
+ const genericMatch = type.text.match(/Array<(.+)>/);
250
+ if (genericMatch) {
251
+ return genericMatch[1].split('|').map((t) => t.trim());
252
+ }
253
+ return [type.text];
254
+ }
255
+ // ---------------------------------------------------------------------------
256
+ // Method mapping
257
+ // ---------------------------------------------------------------------------
258
+ function mapMethods(members) {
259
+ if (!members) {
260
+ return [];
261
+ }
262
+ return members
263
+ .filter((m) => m.kind === 'method' && m.privacy === 'public')
264
+ .map((m) => ({
265
+ name: m.name,
266
+ returnType: m.return?.type?.text ?? 'void',
267
+ description: m.description ?? '',
268
+ params: mapMethodParams(m.parameters),
269
+ deprecated: normalizeDeprecated(m.deprecated)
270
+ }));
271
+ }
272
+ function mapMethodParams(params) {
273
+ if (!params) {
274
+ return [];
275
+ }
276
+ return params.map((p) => ({
277
+ name: p.name,
278
+ type: p.type?.text ?? 'unknown',
279
+ description: p.description ?? '',
280
+ optional: p.name.endsWith('?') || (p.type?.text?.includes('undefined') ?? false)
281
+ }));
282
+ }
283
+ // ---------------------------------------------------------------------------
284
+ // CSS property mapping
285
+ // ---------------------------------------------------------------------------
286
+ function mapCssProperties(props) {
287
+ if (!props) {
288
+ return [];
289
+ }
290
+ return props.map((p) => ({
291
+ name: p.name,
292
+ description: p.description ?? '',
293
+ defaultValue: p.default
294
+ }));
295
+ }
296
+ // ---------------------------------------------------------------------------
297
+ // Description cleaning
298
+ // ---------------------------------------------------------------------------
299
+ /** Normalize a CEM `deprecated` field (string message or boolean) to a string or undefined. */
300
+ function normalizeDeprecated(value) {
301
+ if (typeof value === 'string') {
302
+ return value;
303
+ }
304
+ if (value === true) {
305
+ return 'Deprecated';
306
+ }
307
+ return undefined;
308
+ }
309
+ /**
310
+ * Extract the content of a named `### Section` from a CEM markdown description.
311
+ * Returns `undefined` if the section is not found.
312
+ *
313
+ * @param sectionName Plain text section name (must not contain regex special characters).
314
+ */
315
+ function extractSection(markdown, sectionName) {
316
+ const escaped = sectionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
317
+ const pattern = new RegExp(`###\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n###\\s|$)`);
318
+ const match = markdown.match(pattern);
319
+ if (!match) {
320
+ return undefined;
321
+ }
322
+ return match[1].trim() || undefined;
323
+ }
324
+ /**
325
+ * Clean a CEM markdown description into plain-text suitable for MCP output.
326
+ *
327
+ * 1. Extract the "### Overview" section content (if present)
328
+ * 2. Otherwise use everything before the first `###` header
329
+ * 3. Strip known boilerplate sections (ES6 Module Import, Keyboard Handling)
330
+ * 4. Remove markdown formatting
331
+ */
332
+ function cleanCemDescription(raw) {
333
+ if (!raw) {
334
+ return '';
335
+ }
336
+ // Try to extract Overview section
337
+ let text = extractSection(raw, 'Overview');
338
+ if (!text) {
339
+ // No ### Overview — use text before the first ### header
340
+ const firstHeader = raw.indexOf('###');
341
+ text = firstHeader > 0 ? raw.slice(0, firstHeader).trim() : raw;
342
+ }
343
+ // Remove remaining ### sections that may have leaked through
344
+ text = text.replace(/###\s+[\s\S]*$/m, '').trim();
345
+ // Strip markdown formatting
346
+ text = text
347
+ .replace(/`([^`]+)`/g, '$1') // inline code
348
+ .replace(/\*\*([^*]+)\*\*/g, '$1') // bold
349
+ .replace(/\*([^*]+)\*/g, '$1') // italic
350
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // links
351
+ .replace(/\n{2,}/g, '\n') // collapse blank lines
352
+ .trim();
353
+ return text;
354
+ }
355
+ // ---------------------------------------------------------------------------
356
+ // Category inference
357
+ // ---------------------------------------------------------------------------
358
+ /** Infer a human-readable category from the module path and library. */
359
+ function inferCategory(modulePath, library) {
360
+ switch (library) {
361
+ case '@fundamental-ngx/ui5-webcomponents-fiori':
362
+ return 'Fiori';
363
+ case '@fundamental-ngx/ui5-webcomponents-ai':
364
+ return 'AI';
365
+ default:
366
+ break;
367
+ }
368
+ // For the base @ui5/webcomponents package, try to derive category from path
369
+ // Module paths look like "dist/Button.js", "dist/CalendarDate.js"
370
+ const fileName = modulePath.replace(/^dist\//, '').replace(/\.js$/, '');
371
+ // Group by common prefixes
372
+ const categoryPrefixes = {
373
+ Avatar: 'User & Identity',
374
+ Badge: 'Indicators',
375
+ Bar: 'Layout',
376
+ Breadcrumbs: 'Navigation',
377
+ BusyIndicator: 'Indicators',
378
+ Button: 'Actions',
379
+ Calendar: 'Date & Time',
380
+ Card: 'Data Display',
381
+ Carousel: 'Data Display',
382
+ CheckBox: 'Form',
383
+ ColorPalette: 'Form',
384
+ ColorPicker: 'Form',
385
+ ComboBox: 'Form',
386
+ DatePicker: 'Date & Time',
387
+ DateRangePicker: 'Date & Time',
388
+ DateTimePicker: 'Date & Time',
389
+ Dialog: 'Popover & Dialog',
390
+ DragDrop: 'Utilities',
391
+ FileUploader: 'Form',
392
+ Form: 'Form',
393
+ Icon: 'Data Display',
394
+ Input: 'Form',
395
+ Label: 'Form',
396
+ Link: 'Navigation',
397
+ List: 'Data Display',
398
+ Menu: 'Navigation',
399
+ MessageStrip: 'Feedback',
400
+ MultiComboBox: 'Form',
401
+ MultiInput: 'Form',
402
+ Panel: 'Layout',
403
+ Popover: 'Popover & Dialog',
404
+ Progress: 'Indicators',
405
+ Radio: 'Form',
406
+ RangeSlider: 'Form',
407
+ RatingIndicator: 'Form',
408
+ ResponsivePopover: 'Popover & Dialog',
409
+ SegmentedButton: 'Actions',
410
+ Select: 'Form',
411
+ Slider: 'Form',
412
+ SplitButton: 'Actions',
413
+ StepInput: 'Form',
414
+ Switch: 'Form',
415
+ Tab: 'Navigation',
416
+ Table: 'Data Display',
417
+ TextArea: 'Form',
418
+ TimePicker: 'Date & Time',
419
+ Title: 'Data Display',
420
+ Toast: 'Feedback',
421
+ ToggleButton: 'Actions',
422
+ Token: 'Data Display',
423
+ Toolbar: 'Actions',
424
+ Tree: 'Data Display',
425
+ Tag: 'Data Display'
426
+ };
427
+ for (const [prefix, category] of Object.entries(categoryPrefixes)) {
428
+ if (fileName.startsWith(prefix)) {
429
+ return category;
430
+ }
431
+ }
432
+ return 'UI5 Component';
433
+ }
434
+ //# sourceMappingURL=cem-extractor.js.map