#
tokens: 34988/50000 4/88 files (page 4/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 of 5. Use http://codebase.md/marianfoo/mcp-sap-docs?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       ├── 00-overview.mdc
│       ├── 10-search-stack.mdc
│       ├── 20-tools-and-apis.mdc
│       ├── 30-tests-and-output.mdc
│       ├── 40-deploy.mdc
│       ├── 50-metadata-config.mdc
│       ├── 60-adding-github-sources.mdc
│       ├── 70-tool-usage-guide.mdc
│       └── 80-abap-integration.mdc
├── .cursorignore
├── .gitattributes
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── config.yml
│   │   ├── missing-documentation.yml
│   │   └── new-documentation-source.yml
│   └── workflows
│       ├── deploy-mcp-sap-docs.yml
│       ├── test-pr.yml
│       └── update-submodules.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── docs
│   ├── ABAP-INTEGRATION-SUMMARY.md
│   ├── ABAP-MULTI-VERSION-INTEGRATION.md
│   ├── ABAP-STANDARD-INTEGRATION.md
│   ├── ABAP-USAGE-GUIDE.md
│   ├── ARCHITECTURE.md
│   ├── COMMUNITY-SEARCH-IMPLEMENTATION.md
│   ├── CONTENT-SIZE-LIMITS.md
│   ├── CURSOR-SETUP.md
│   ├── DEV.md
│   ├── FTS5-IMPLEMENTATION-COMPLETE.md
│   ├── LLM-FRIENDLY-IMPROVEMENTS.md
│   ├── METADATA-CONSOLIDATION.md
│   ├── TEST-SEARCH.md
│   └── TESTS.md
├── ecosystem.config.cjs
├── index.html
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── REMOTE_SETUP.md
├── scripts
│   ├── build-fts.ts
│   ├── build-index.ts
│   ├── check-version.js
│   └── summarize-src.js
├── server.json
├── setup.sh
├── src
│   ├── global.d.ts
│   ├── http-server.ts
│   ├── lib
│   │   ├── BaseServerHandler.ts
│   │   ├── communityBestMatch.ts
│   │   ├── config.ts
│   │   ├── localDocs.ts
│   │   ├── logger.ts
│   │   ├── metadata.ts
│   │   ├── sapHelp.ts
│   │   ├── search.ts
│   │   ├── searchDb.ts
│   │   ├── truncate.ts
│   │   ├── types.ts
│   │   └── url-generation
│   │       ├── abap.ts
│   │       ├── BaseUrlGenerator.ts
│   │       ├── cap.ts
│   │       ├── cloud-sdk.ts
│   │       ├── dsag.ts
│   │       ├── GenericUrlGenerator.ts
│   │       ├── index.ts
│   │       ├── README.md
│   │       ├── sapui5.ts
│   │       ├── utils.ts
│   │       └── wdi5.ts
│   ├── metadata.json
│   ├── server.ts
│   └── streamable-http-server.ts
├── test
│   ├── _utils
│   │   ├── httpClient.js
│   │   └── parseResults.js
│   ├── community-search.ts
│   ├── comprehensive-url-generation.test.ts
│   ├── performance
│   │   └── README.md
│   ├── prompts.test.ts
│   ├── quick-url-test.ts
│   ├── README.md
│   ├── tools
│   │   ├── run-tests.js
│   │   ├── sap_docs_search
│   │   │   ├── search-cap-docs.js
│   │   │   ├── search-cloud-sdk-ai.js
│   │   │   ├── search-cloud-sdk-js.js
│   │   │   └── search-sapui5-docs.js
│   │   ├── search-url-verification.js
│   │   ├── search.generic.spec.js
│   │   └── search.smoke.js
│   ├── url-status.ts
│   └── validate-urls.ts
├── test-community-search.js
├── test-search-interactive.ts
├── test-search.http
├── test-search.ts
├── tsconfig.json
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/metadata.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "version": 1,
  3 |   "updated_at": "2025-01-14",
  4 |   "description": "Centralized configuration for SAP Docs MCP search system",
  5 |   
  6 |   "sources": [
  7 |     {
  8 |       "id": "sapui5",
  9 |       "type": "documentation",
 10 |       "lang": "en",
 11 |       "boost": 0.1,
 12 |       "tags": ["ui5", "frontend", "javascript"],
 13 |       "description": "SAPUI5 framework documentation",
 14 |       "libraryId": "/sapui5",
 15 |       "sourcePath": "sapui5-docs/docs",
 16 |       "baseUrl": "https://ui5.sap.com",
 17 |       "pathPattern": "/#/topic/{file}",
 18 |       "anchorStyle": "custom"
 19 |     },
 20 |     {
 21 |       "id": "cap",
 22 |       "type": "documentation", 
 23 |       "lang": "en",
 24 |       "boost": 0.1,
 25 |       "tags": ["backend", "nodejs", "java", "cds"],
 26 |       "description": "SAP Cloud Application Programming model",
 27 |       "libraryId": "/cap",
 28 |       "sourcePath": "cap-docs",
 29 |       "baseUrl": "https://cap.cloud.sap",
 30 |       "pathPattern": "/docs/{file}",
 31 |       "anchorStyle": "docsify"
 32 |     },
 33 |     {
 34 |       "id": "openui5-api",
 35 |       "type": "api",
 36 |       "lang": "en", 
 37 |       "boost": 0.1,
 38 |       "tags": ["api", "controls", "ui5"],
 39 |       "description": "OpenUI5 API documentation",
 40 |       "libraryId": "/openui5-api",
 41 |       "sourcePath": "openui5/src",
 42 |       "baseUrl": "https://sdk.openui5.org",
 43 |       "pathPattern": "/#/api/{file}",
 44 |       "anchorStyle": "custom"
 45 |     },
 46 |     {
 47 |       "id": "openui5-samples",
 48 |       "type": "samples",
 49 |       "lang": "en",
 50 |       "boost": 0.05,
 51 |       "tags": ["samples", "examples", "ui5"],
 52 |       "description": "OpenUI5 code samples",
 53 |       "libraryId": "/openui5-samples",
 54 |       "sourcePath": "openui5/src",
 55 |       "baseUrl": "https://sdk.openui5.org",
 56 |       "pathPattern": "/entity/{file}",
 57 |       "anchorStyle": "custom"
 58 |     },
 59 |     {
 60 |       "id": "wdi5",
 61 |       "type": "documentation",
 62 |       "lang": "en",
 63 |       "boost": 0.05,
 64 |       "tags": ["testing", "e2e", "webdriver"],
 65 |       "description": "wdi5 testing framework",
 66 |       "libraryId": "/wdi5",
 67 |       "sourcePath": "wdi5/docs",
 68 |       "baseUrl": "https://ui5-community.github.io/wdi5",
 69 |       "pathPattern": "#{file}",
 70 |       "anchorStyle": "docsify"
 71 |     },
 72 |     {
 73 |       "id": "ui5-tooling",
 74 |       "type": "documentation",
 75 |       "lang": "en",
 76 |       "boost": 0.0,
 77 |       "tags": ["tooling", "build", "cli"],
 78 |       "description": "UI5 Tooling documentation",
 79 |       "libraryId": "/ui5-tooling",
 80 |       "sourcePath": "ui5-tooling/docs",
 81 |       "baseUrl": "https://sap.github.io/ui5-tooling/v4",
 82 |       "pathPattern": "/pages/{file}",
 83 |       "anchorStyle": "github"
 84 |     },
 85 |     {
 86 |       "id": "cloud-mta-build-tool", 
 87 |       "type": "documentation",
 88 |       "lang": "en",
 89 |       "boost": 0.0,
 90 |       "tags": ["mta", "build", "deployment"],
 91 |       "description": "Cloud MTA Build Tool",
 92 |       "libraryId": "/cloud-mta-build-tool",
 93 |       "sourcePath": "cloud-mta-build-tool/docs/docs",
 94 |       "baseUrl": "https://sap.github.io/cloud-mta-build-tool",
 95 |       "pathPattern": "/{file}",
 96 |       "anchorStyle": "github"
 97 |     },
 98 |     {
 99 |       "id": "ui5-webcomponents",
100 |       "type": "documentation", 
101 |       "lang": "en",
102 |       "boost": 0.0,
103 |       "tags": ["webcomponents", "ui5"],
104 |       "description": "UI5 Web Components",
105 |       "libraryId": "/ui5-webcomponents",
106 |       "sourcePath": "ui5-webcomponents/docs",
107 |       "baseUrl": "https://sap.github.io/ui5-webcomponents/docs",
108 |       "pathPattern": "/{file}",
109 |       "anchorStyle": "github"
110 |     },
111 |     {
112 |       "id": "cloud-sdk-js",
113 |       "type": "documentation",
114 |       "lang": "en", 
115 |       "boost": 0.05,
116 |       "tags": ["sdk", "javascript", "cloud"],
117 |       "description": "SAP Cloud SDK for JavaScript",
118 |       "libraryId": "/cloud-sdk-js",
119 |       "sourcePath": "cloud-sdk/docs-js",
120 |       "baseUrl": "https://sap.github.io/cloud-sdk/docs/js",
121 |       "pathPattern": "/{file}",
122 |       "anchorStyle": "github"
123 |     },
124 |     {
125 |       "id": "cloud-sdk-java",
126 |       "type": "documentation",
127 |       "lang": "en",
128 |       "boost": 0.05,
129 |       "tags": ["sdk", "java", "cloud"],
130 |       "description": "SAP Cloud SDK for Java",
131 |       "libraryId": "/cloud-sdk-java",
132 |       "sourcePath": "cloud-sdk/docs-java",
133 |       "baseUrl": "https://sap.github.io/cloud-sdk/docs/java",
134 |       "pathPattern": "/{file}",
135 |       "anchorStyle": "github"
136 |     },
137 |     {
138 |       "id": "cloud-sdk-ai-js",
139 |       "type": "documentation",
140 |       "lang": "en",
141 |       "boost": 0.05, 
142 |       "tags": ["ai", "sdk", "javascript"],
143 |       "description": "SAP Cloud SDK AI for JavaScript",
144 |       "libraryId": "/cloud-sdk-ai-js",
145 |       "sourcePath": "cloud-sdk-ai/docs-js",
146 |       "baseUrl": "https://sap.github.io/ai-sdk/docs/js",
147 |       "pathPattern": "/{file}",
148 |       "anchorStyle": "github"
149 |     },
150 |     {
151 |       "id": "cloud-sdk-ai-java",
152 |       "type": "documentation",
153 |       "lang": "en",
154 |       "boost": 0.05,
155 |       "tags": ["ai", "sdk", "java"],
156 |       "description": "SAP Cloud SDK AI for Java",
157 |       "libraryId": "/cloud-sdk-ai-java",
158 |       "sourcePath": "cloud-sdk-ai/docs-java",
159 |       "baseUrl": "https://sap.github.io/ai-sdk/docs/java",
160 |       "pathPattern": "/{file}",
161 |       "anchorStyle": "github"
162 |     },
163 |     {
164 |       "id": "ui5-typescript",
165 |       "type": "documentation",
166 |       "lang": "en",
167 |       "boost": 0.1,
168 |       "tags": ["ui5", "typescript", "types", "frontend"],
169 |       "description": "UI5 TypeScript",
170 |       "libraryId": "/ui5-typescript",
171 |       "sourcePath": "ui5-typescript",
172 |       "baseUrl": "https://github.com/UI5/typescript/blob/gh-pages",
173 |       "pathPattern": "/{file}",
174 |       "anchorStyle": "github"
175 |     },
176 |     {
177 |       "id": "ui5-cc-spreadsheetimporter",
178 |       "type": "documentation", 
179 |       "lang": "en",
180 |       "boost": 0.05,
181 |       "tags": ["ui5", "spreadsheet", "importer", "custom-control"],
182 |       "description": "UI5 CC Spreadsheet Importer",
183 |       "libraryId": "/ui5-cc-spreadsheetimporter",
184 |       "sourcePath": "ui5-cc-spreadsheetimporter/docs",
185 |       "baseUrl": "https://docs.spreadsheet-importer.com",
186 |       "pathPattern": "/pages/{file}/",
187 |       "anchorStyle": "github"
188 |     },
189 |     {
190 |       "id": "abap-cheat-sheets",
191 |       "type": "documentation",
192 |       "lang": "en", 
193 |       "boost": 0.05,
194 |       "tags": ["abap", "syntax", "cheat-sheets", "examples", "backend"],
195 |       "description": "ABAP Cheat Sheets",
196 |       "libraryId": "/abap-cheat-sheets",
197 |       "sourcePath": "abap-cheat-sheets",
198 |       "baseUrl": "https://github.com/SAP-samples/abap-cheat-sheets/blob/main",
199 |       "pathPattern": "/{file}",
200 |       "anchorStyle": "github"
201 |     },
202 |     {
203 |       "id": "sap-styleguides",
204 |       "type": "documentation",
205 |       "lang": "en",
206 |       "boost": 0.06,
207 |       "tags": ["abap", "clean-code", "style-guide", "best-practices", "code-review"],
208 |       "description": "SAP Style Guides",
209 |       "libraryId": "/sap-styleguides", 
210 |       "sourcePath": "sap-styleguides",
211 |       "baseUrl": "https://github.com/SAP/styleguides/blob/main",
212 |       "pathPattern": "/{file}",
213 |       "anchorStyle": "github"
214 |     },
215 |     {
216 |       "id": "dsag-abap-leitfaden",
217 |       "type": "documentation",
218 |       "lang": "de",
219 |       "boost": 0.05,
220 |       "tags": ["abap", "leitfaden", "best-practices", "german", "dsag", "clean-core"],
221 |       "description": "DSAG ABAP Leitfaden",
222 |       "libraryId": "/dsag-abap-leitfaden",
223 |       "sourcePath": "dsag-abap-leitfaden/docs",
224 |       "baseUrl": "https://1dsag.github.io/ABAP-Leitfaden",
225 |       "pathPattern": "/{file}/",
226 |       "anchorStyle": "github"
227 |     },
228 |     {
229 |       "id": "abap-fiori-showcase",
230 |       "type": "documentation",
231 |       "lang": "en",
232 |       "boost": 0.08,
233 |       "tags": ["fiori-elements", "abap", "rap", "annotations", "odata-v4", "showcase"],
234 |       "description": "ABAP Platform Fiori Feature Showcase",
235 |       "libraryId": "/abap-fiori-showcase",
236 |       "sourcePath": "abap-fiori-showcase",
237 |       "baseUrl": "https://github.com/SAP-samples/abap-platform-fiori-feature-showcase/blob/main",
238 |       "pathPattern": "/{file}",
239 |       "anchorStyle": "github"
240 |     },
241 |     {
242 |       "id": "cap-fiori-showcase",
243 |       "type": "documentation",
244 |       "lang": "en",
245 |       "boost": 0.08,
246 |       "tags": ["fiori-elements", "cap", "annotations", "odata-v4", "showcase", "nodejs"],
247 |       "description": "CAP Fiori Elements Feature Showcase",
248 |       "libraryId": "/cap-fiori-showcase",
249 |       "sourcePath": "cap-fiori-showcase",
250 |       "baseUrl": "https://github.com/SAP-samples/fiori-elements-feature-showcase/blob/main",
251 |       "pathPattern": "/{file}",
252 |       "anchorStyle": "github"
253 |     },
254 |     {
255 |       "id": "abap-docs-758",
256 |       "type": "documentation",
257 |       "lang": "en",
258 |       "boost": 0.05,
259 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.58"],
260 |       "description": "Official ABAP Keyword Documentation (7.58)",
261 |       "libraryId": "/abap-docs-758",
262 |       "sourcePath": "abap-docs/docs/7.58/md",
263 |       "baseUrl": "https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US",
264 |       "pathPattern": "/{file}",
265 |       "anchorStyle": "sap-help"
266 |     },
267 |     {
268 |       "id": "abap-docs-757",
269 |       "type": "documentation",
270 |       "lang": "en",
271 |       "boost": 0.02,
272 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.57"],
273 |       "description": "Official ABAP Keyword Documentation (7.57)",
274 |       "libraryId": "/abap-docs-757",
275 |       "sourcePath": "abap-docs/docs/7.57/md",
276 |       "baseUrl": "https://help.sap.com/doc/abapdocu_757_index_htm/7.57/en-US",
277 |       "pathPattern": "/{file}",
278 |       "anchorStyle": "sap-help"
279 |     },
280 |     {
281 |       "id": "abap-docs-756",
282 |       "type": "documentation",
283 |       "lang": "en",
284 |       "boost": 0.01,
285 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.56"],
286 |       "description": "Official ABAP Keyword Documentation (7.56)",
287 |       "libraryId": "/abap-docs-756",
288 |       "sourcePath": "abap-docs/docs/7.56/md",
289 |       "baseUrl": "https://help.sap.com/doc/abapdocu_756_index_htm/7.56/en-US",
290 |       "pathPattern": "/{file}",
291 |       "anchorStyle": "sap-help"
292 |     },
293 |     {
294 |       "id": "abap-docs-755",
295 |       "type": "documentation",
296 |       "lang": "en",
297 |       "boost": 0.01,
298 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.55"],
299 |       "description": "Official ABAP Keyword Documentation (7.55)",
300 |       "libraryId": "/abap-docs-755",
301 |       "sourcePath": "abap-docs/docs/7.55/md",
302 |       "baseUrl": "https://help.sap.com/doc/abapdocu_755_index_htm/7.55/en-US",
303 |       "pathPattern": "/{file}",
304 |       "anchorStyle": "sap-help"
305 |     },
306 |     {
307 |       "id": "abap-docs-754",
308 |       "type": "documentation",
309 |       "lang": "en",
310 |       "boost": 0.01,
311 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.54"],
312 |       "description": "Official ABAP Keyword Documentation (7.54)",
313 |       "libraryId": "/abap-docs-754",
314 |       "sourcePath": "abap-docs/docs/7.54/md",
315 |       "baseUrl": "https://help.sap.com/doc/abapdocu_754_index_htm/7.54/en-US",
316 |       "pathPattern": "/{file}",
317 |       "anchorStyle": "sap-help"
318 |     },
319 |     {
320 |       "id": "abap-docs-753",
321 |       "type": "documentation",
322 |       "lang": "en",
323 |       "boost": 0.01,
324 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.53"],
325 |       "description": "Official ABAP Keyword Documentation (7.53)",
326 |       "libraryId": "/abap-docs-753",
327 |       "sourcePath": "abap-docs/docs/7.53/md",
328 |       "baseUrl": "https://help.sap.com/doc/abapdocu_753_index_htm/7.53/en-US",
329 |       "pathPattern": "/{file}",
330 |       "anchorStyle": "sap-help"
331 |     },
332 |     {
333 |       "id": "abap-docs-752",
334 |       "type": "documentation",
335 |       "lang": "en",
336 |       "boost": 0.01,
337 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "7.52"],
338 |       "description": "Official ABAP Keyword Documentation (7.52)",
339 |       "libraryId": "/abap-docs-752",
340 |       "sourcePath": "abap-docs/docs/7.52/md",
341 |       "baseUrl": "https://help.sap.com/doc/abapdocu_752_index_htm/7.52/en-US",
342 |       "pathPattern": "/{file}",
343 |       "anchorStyle": "sap-help"
344 |     },
345 |     {
346 |       "id": "abap-docs-latest",
347 |       "type": "documentation",
348 |       "lang": "en",
349 |       "boost": 1.0,
350 |       "tags": ["abap", "keyword-documentation", "language-reference", "syntax", "programming", "latest"],
351 |       "description": "Official ABAP Keyword Documentation (Latest)",
352 |       "libraryId": "/abap-docs-latest",
353 |       "sourcePath": "abap-docs/docs/latest/md",
354 |       "baseUrl": "https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US",
355 |       "pathPattern": "/{file}",
356 |       "anchorStyle": "sap-help"
357 |     }
358 |   ],
359 |   
360 |   "synonyms": [
361 |     { "from": "ui5", "to": ["sapui5", "openui5"] },
362 |     { "from": "button", "to": ["btn", "sap.m.Button"] },
363 |     { "from": "table", "to": ["sap.m.Table", "sap.ui.table.Table"] },
364 |     { "from": "wizard", "to": ["sap.m.Wizard"] },
365 |     { "from": "testing", "to": ["wdi5", "e2e", "automation"] },
366 |     { "from": "cds", "to": ["cap", "cloud application programming"] },
367 |     { "from": "odata", "to": ["rest", "api", "service"] },
368 |     { "from": "typescript", "to": ["ts", "types", "type definitions"] },
369 |     { "from": "spreadsheet", "to": ["excel", "csv", "import", "upload"] },
370 |     { "from": "abap", "to": ["advanced business application programming", "sap programming"] },
371 |     { "from": "clean-code", "to": ["clean code", "best practices", "style guide"] },
372 |     { "from": "style-guide", "to": ["styleguide", "coding standards", "best practices"] },
373 |     { "from": "leitfaden", "to": ["guidelines", "guide", "best practices"] },
374 |     { "from": "clean-core", "to": ["clean core", "extensibility", "cloud development"] },
375 |     { "from": "fiori-elements", "to": ["fiori elements", "annotations", "odata"] },
376 |     { "from": "rap", "to": ["restful application programming", "abap restful"] },
377 |     { "from": "annotations", "to": ["ui5 annotations", "odata annotations", "fiori annotations"] }
378 |   ],
379 |   
380 |   "acronyms": {
381 |     "CAP": ["Cloud Application Programming", "cds"],
382 |     "CDS": ["Core Data Services", "cap"],
383 |     "UI5": ["sapui5", "openui5"],
384 |     "BTP": ["Business Technology Platform", "cloud"],
385 |     "MTA": ["Multi-Target Application"],
386 |     "CQL": ["CDS Query Language", "query"],
387 |     "OData": ["Open Data Protocol", "rest", "api"],
388 |     "TS": ["TypeScript", "types"],
389 |     "ABAP": ["Advanced Business Application Programming", "sap programming"],
390 |     "DSAG": ["Deutschsprachige SAP-Anwendergruppe", "german sap user group"],
391 |     "RAP": ["RESTful Application Programming", "abap restful", "business object"]
392 |   },
393 | 
394 |   "contextBoosts": {
395 |     "SAP Cloud SDK": {
396 |       "/cloud-sdk-ai-js": 1.0,
397 |       "/cloud-sdk-ai-java": 1.0,
398 |       "/cloud-sdk-js": 0.8,
399 |       "/cloud-sdk-java": 0.8,
400 |       "/cap": 0.2
401 |     },
402 |     "UI5": {
403 |       "/sapui5": 0.9,
404 |       "/openui5-api": 0.9,
405 |       "/openui5-samples": 0.9,
406 |       "/ui5-typescript": 0.8
407 |     },
408 |     "wdi5": {
409 |       "/wdi5": 1.0,
410 |       "/openui5-api": 0.4,
411 |       "/openui5-samples": 0.4,
412 |       "/sapui5": 0.4
413 |     },
414 |     "UI5 Web Components": {
415 |       "/ui5-webcomponents": 1.0
416 |     },
417 |     "UI5 Tooling": {
418 |       "/ui5-tooling": 1.0
419 |     },
420 |     "Cloud MTA Build Tool": {
421 |       "/cloud-mta-build-tool": 1.0
422 |     },
423 |     "CAP": {
424 |       "/cap": 1.0,
425 |       "/cap-fiori-showcase": 0.9,
426 |       "/sapui5": 0.2
427 |     },
428 |     "TypeScript": {
429 |       "/ui5-typescript": 1.0,
430 |       "/sapui5": 0.4,
431 |       "/openui5-api": 0.4
432 |     },
433 |     "UI5 CC Spreadsheet Importer": {
434 |       "/ui5-cc-spreadsheetimporter": 1.0,
435 |       "/sapui5": 0.3,
436 |       "/openui5-api": 0.3
437 |     },
438 |     "ABAP": {
439 |       "/abap-docs-latest": 1.0,
440 |       "/abap-cheat-sheets": 0.8,
441 |       "/sap-styleguides": 0.7,
442 |       "/dsag-abap-leitfaden": 0.6,
443 |       "/abap-docs-758": 0.05,
444 |       "/abap-docs-757": 0.02,
445 |       "/abap-docs-756": 0.01,
446 |       "/abap-docs-755": 0.01,
447 |       "/abap-docs-754": 0.01,
448 |       "/abap-docs-753": 0.01,
449 |       "/abap-docs-752": 0.01,
450 |       "/cap": 0.2
451 |     },
452 |     "Clean Code": {
453 |       "/sap-styleguides": 1.0,
454 |       "/dsag-abap-leitfaden": 0.8,
455 |       "/abap-cheat-sheets": 0.4
456 |     },
457 |     "DSAG": {
458 |       "/dsag-abap-leitfaden": 1.0,
459 |       "/abap-cheat-sheets": 0.5,
460 |       "/sap-styleguides": 0.3
461 |     },
462 |     "Fiori Elements": {
463 |       "/abap-fiori-showcase": 1.0,
464 |       "/cap-fiori-showcase": 1.0,
465 |       "/sapui5": 0.6,
466 |       "/cap": 0.5
467 |     },
468 |     "RAP": {
469 |       "/abap-fiori-showcase": 1.0,
470 |       "/abap-cheat-sheets": 0.6,
471 |       "/cap": 0.3
472 |     },
473 |     "7.58": {
474 |       "/abap-docs-758": 2.0,
475 |       "/abap-docs-latest": 0.3
476 |     },
477 |     "7.57": {
478 |       "/abap-docs-757": 2.0,
479 |       "/abap-docs-latest": 0.3
480 |     },
481 |     "7.56": {
482 |       "/abap-docs-756": 2.0,
483 |       "/abap-docs-latest": 0.3
484 |     },
485 |     "7.55": {
486 |       "/abap-docs-755": 2.0,
487 |       "/abap-docs-latest": 0.3
488 |     },
489 |     "7.54": {
490 |       "/abap-docs-754": 2.0,
491 |       "/abap-docs-latest": 0.3
492 |     },
493 |     "7.53": {
494 |       "/abap-docs-753": 2.0,
495 |       "/abap-docs-latest": 0.3
496 |     },
497 |     "7.52": {
498 |       "/abap-docs-752": 2.0,
499 |       "/abap-docs-latest": 0.3
500 |     },
501 |     "latest": {
502 |       "/abap-docs-latest": 1.5,
503 |       "/abap-docs-758": 0.1
504 |     }
505 |   },
506 | 
507 |   "libraryMappings": {
508 |     "sapui5": "sapui5",
509 |     "cap": "cap",
510 |     "cloud-sdk-js": "cloud-sdk-js",
511 |     "cloud-sdk-ai-js": "cloud-sdk-ai-js",
512 |     "openui5-api": "sapui5",
513 |     "openui5-samples": "sapui5",
514 |     "wdi5": "wdi5",
515 |     "ui5-tooling": "ui5-tooling",
516 |     "cloud-mta-build-tool": "cloud-mta-build-tool",
517 |     "ui5-webcomponents": "ui5-webcomponents",
518 |     "cloud-sdk-java": "cloud-sdk-java",
519 |     "cloud-sdk-ai-java": "cloud-sdk-ai-java",
520 |     "ui5-typescript": "ui5-typescript",
521 |     "ui5-cc-spreadsheetimporter": "ui5-cc-spreadsheetimporter",
522 |     "abap-cheat-sheets": "abap-cheat-sheets",
523 |     "sap-styleguides": "sap-styleguides",
524 |     "dsag-abap-leitfaden": "dsag-abap-leitfaden",
525 |     "abap-fiori-showcase": "abap-fiori-showcase",
526 |     "cap-fiori-showcase": "cap-fiori-showcase",
527 |     "abap-docs-758": "abap-docs",
528 |     "abap-docs-757": "abap-docs",
529 |     "abap-docs-756": "abap-docs",
530 |     "abap-docs-755": "abap-docs",
531 |     "abap-docs-754": "abap-docs",
532 |     "abap-docs-753": "abap-docs",
533 |     "abap-docs-752": "abap-docs",
534 |     "abap-docs-latest": "abap-docs"
535 |   },
536 | 
537 |   "contextEmojis": {
538 |     "CAP": "🏗️",
539 |     "wdi5": "🧪", 
540 |     "UI5": "🎨",
541 |     "UI5 Web Components": "🕹️",
542 |     "SAP Cloud SDK": "🌐",
543 |     "UI5 Tooling": "🔧",
544 |     "Cloud MTA Build Tool": "🚢",
545 |     "TypeScript": "📝",
546 |     "UI5 CC Spreadsheet Importer": "📊",
547 |     "ABAP": "💻",
548 |     "Clean Code": "✨",
549 |     "DSAG": "🇩🇪",
550 |     "Fiori Elements": "📱",
551 |     "RAP": "⚡",
552 |     "MIXED": "🔀"
553 |   }
554 | }
555 | 
```

--------------------------------------------------------------------------------
/test/comprehensive-url-generation.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Comprehensive URL Generation Test Suite
  3 |  * 
  4 |  * This test suite validates the URL generation system for SAP documentation sources.
  5 |  * It tests both the main generateDocumentationUrl function and individual generator classes
  6 |  * for 10+ different documentation sources including CAP, Cloud SDK, UI5, wdi5, etc.
  7 |  * 
  8 |  * Key Features:
  9 |  * - Reads from real source files when available (automatic path mapping)
 10 |  * - Falls back to test data when source files don't exist
 11 |  * - Uses real configuration from metadata.json (no hardcoded configs)
 12 |  * - Comprehensive coverage of all URL generation patterns
 13 |  * - Debug mode available with DEBUG_TESTS=true environment variable
 14 |  * 
 15 |  * Running Tests:
 16 |  * - npm run test:url-generation           # Run URL generation tests
 17 |  * - npm run test:url-generation:debug     # Run with debug output
 18 |  * - DEBUG_TESTS=true npx vitest run test/comprehensive-url-generation.test.ts
 19 |  * 
 20 |  * Architecture:
 21 |  * The system uses an abstract BaseUrlGenerator class with source-specific implementations
 22 |  * for different documentation platforms. Each generator handles its own URL patterns,
 23 |  * frontmatter parsing, and path transformations.
 24 |  */
 25 | 
 26 | import { describe, it, expect } from 'vitest';
 27 | import { 
 28 |   generateDocumentationUrl,
 29 |   CloudSdkUrlGenerator,
 30 |   SapUi5UrlGenerator,
 31 |   CapUrlGenerator,
 32 |   Wdi5UrlGenerator,
 33 |   DsagUrlGenerator,
 34 |   GenericUrlGenerator
 35 | } from '../src/lib/url-generation/index.js';
 36 | import { AbapUrlGenerator, generateAbapUrl } from '../src/lib/url-generation/abap.js';
 37 | import { DocUrlConfig, getDocUrlConfig } from '../src/lib/metadata.js';
 38 | 
 39 | describe('Comprehensive URL Generation System', () => {
 40 |   
 41 |   /**
 42 |    * Retrieves URL configuration from metadata.json for a given library
 43 |    * @param libraryId - The library identifier (e.g., '/cloud-sdk-js')
 44 |    * @returns Configuration object with baseUrl, pathPattern, and anchorStyle
 45 |    * @throws Error if no configuration is found
 46 |    */
 47 |   function getConfigForLibrary(libraryId: string): DocUrlConfig {
 48 |     const config = getDocUrlConfig(libraryId);
 49 |     if (!config) {
 50 |       throw new Error(`No configuration found for library: ${libraryId}`);
 51 |     }
 52 |     return config;
 53 |   }
 54 | 
 55 |   /**
 56 |    * Maps libraryId + relFile to actual source file path in the filesystem
 57 |    * Handles different repository structures and path transformations
 58 |    * @param libraryId - The library identifier 
 59 |    * @param relFile - The relative file path within the library
 60 |    * @returns Full path to the actual source file
 61 |    * @throws Error if no path mapping exists for the library
 62 |    */
 63 |   function getSourceFilePath(libraryId: string, relFile: string): string {
 64 |     const pathMappings: Record<string, { basePath: string; transform?: (relFile: string) => string }> = {
 65 |       '/cap': { basePath: 'sources/cap-docs' },
 66 |       '/cloud-mta-build-tool': { basePath: 'sources/cloud-mta-build-tool' },
 67 |       '/cloud-sdk-js': { basePath: 'sources/cloud-sdk/docs-js' },
 68 |       '/cloud-sdk-ai-js': { basePath: 'sources/cloud-sdk-ai/docs-js' },
 69 |       '/openui5-api': { 
 70 |         basePath: 'sources/openui5',
 71 |         transform: (relFile) => {
 72 |           // Transform src/sap/m/Button.js → src/sap.m/src/sap/m/Button.js
 73 |           const match = relFile.match(/^src\/sap\/([^\/]+)\/(.+)$/);
 74 |           if (match) {
 75 |             const [, module, file] = match;
 76 |             return `src/sap.${module}/src/sap/${module}/${file}`;
 77 |           }
 78 |           return relFile;
 79 |         }
 80 |       },
 81 |       '/openui5-samples': { basePath: 'sources/openui5' },
 82 |       '/sapui5': { basePath: 'sources/sapui5-docs/docs' },
 83 |       '/ui5-tooling': { basePath: 'sources/ui5-tooling/docs' },
 84 |       '/ui5-webcomponents': { basePath: 'sources/ui5-webcomponents/docs' },
 85 |       '/wdi5': { basePath: 'sources/wdi5/docs' },
 86 |       '/ui5-typescript': { basePath: 'sources/ui5-typescript' },
 87 |       '/ui5-cc-spreadsheetimporter': { basePath: 'sources/ui5-cc-spreadsheetimporter/docs' },
 88 |       '/abap-cheat-sheets': { basePath: 'sources/abap-cheat-sheets' },
 89 |       '/sap-styleguides': { basePath: 'sources/sap-styleguides' },
 90 |       '/dsag-abap-leitfaden': { basePath: 'sources/dsag-abap-leitfaden/docs' },
 91 |       '/abap-fiori-showcase': { basePath: 'sources/abap-fiori-showcase' },
 92 |       '/cap-fiori-showcase': { basePath: 'sources/cap-fiori-showcase' }
 93 |     };
 94 | 
 95 |     const mapping = pathMappings[libraryId];
 96 |     if (!mapping) {
 97 |       throw new Error(`No source path mapping found for library: ${libraryId}`);
 98 |     }
 99 | 
100 |     const transformedRelFile = mapping.transform ? mapping.transform(relFile) : relFile;
101 |     return `${mapping.basePath}/${transformedRelFile}`;
102 |   }
103 | 
104 |   /**
105 |    * Reads file content from actual source files with graceful fallback
106 |    * @param libraryId - The library identifier (e.g., '/cloud-sdk-js')
107 |    * @param relFile - The relative file path within the library
108 |    * @returns File content as string, or null if file doesn't exist
109 |    */
110 |   function readFileContent(libraryId: string, relFile: string): string | null {
111 |     const fs = require('fs');
112 |     const path = require('path');
113 |     
114 |     try {
115 |       const sourceFilePath = getSourceFilePath(libraryId, relFile);
116 |       const fullPath = path.resolve(sourceFilePath);
117 |       return fs.readFileSync(fullPath, 'utf8');
118 |     } catch (error: any) {
119 |       console.warn(`Could not read file for ${libraryId}/${relFile}:`, error.message);
120 |       // Return null to trigger fallback to test data
121 |       return null;
122 |     }
123 |   }
124 |   
125 |   /**
126 |    * Test cases for comprehensive URL generation testing
127 |    * 
128 |    * Each test case defines:
129 |    * - name: Human-readable test description
130 |    * - libraryId: Library identifier from metadata.json
131 |    * - relFile: Relative file path within the library (used for path mapping)
132 |    * - expectedUrl: Expected generated URL for validation
133 |    * - frontmatter: Fallback YAML frontmatter (used when real file not found)
134 |    * - content: Fallback content (used when real file not found)
135 |    * 
136 |    * The system will attempt to read real source files first, falling back to
137 |    * the provided frontmatter/content if the file doesn't exist.
138 |    */
139 |   const testCases = [
140 |     {
141 |       name: 'CAP - CDS Log Documentation',
142 |       libraryId: '/cap',
143 |       relFile: 'node.js/cds-log.md',
144 |       expectedUrl: 'https://cap.cloud.sap/docs/#/node.js/cds-log',
145 |       frontmatter: '---\nid: cds-log\ntitle: Logging\n---\n',
146 |       content: '# Logging\n\nCAP provides structured logging capabilities...'
147 |     },
148 |     {
149 |       name: 'Cloud MTA Build Tool - Download Page',
150 |       libraryId: '/cloud-mta-build-tool',
151 |       relFile: 'docs/download.md',
152 |       expectedUrl: 'https://sap.github.io/cloud-mta-build-tool/download',
153 |       frontmatter: '',
154 |       content: '\nYou can install the Cloud MTA Build Tool...'
155 |     },
156 |     {
157 |       name: 'Cloud SDK JS - Kubernetes Migration',
158 |       libraryId: '/cloud-sdk-js',
159 |       relFile: 'environments/migrate-sdk-application-from-btp-cf-to-kubernetes.mdx',
160 |       expectedUrl: 'https://sap.github.io/cloud-sdk/docs/js/environments/kubernetes',
161 |       frontmatter: '---\nid: kubernetes\ntitle: Migrate your App from SAP BTP CF to Kubernetes\n---\n',
162 |       content: '# Migrate a Cloud Foundry Application to a Kubernetes Cluster\n\nThis guide details...'
163 |     },
164 |     {
165 |       name: 'Cloud SDK AI JS - Orchestration',
166 |       libraryId: '/cloud-sdk-ai-js',
167 |       relFile: 'langchain/orchestration.mdx',
168 |       expectedUrl: 'https://sap.github.io/ai-sdk/docs/js/langchain/orchestration',
169 |       frontmatter: '---\nid: orchestration\ntitle: Orchestration Integration\n---\n',
170 |       content: '# Orchestration Integration\n\nThe @sap-ai-sdk/langchain packages provides...'
171 |     },
172 |     {
173 |       name: 'OpenUI5 API - Button Control',
174 |       libraryId: '/openui5-api',
175 |       relFile: 'src/sap/m/Button.js',
176 |       expectedUrl: 'https://sdk.openui5.org/#/api/sap.m.Button',
177 |       frontmatter: '',
178 |       content: 'sap.ui.define([\n  "./library",\n  "sap/ui/core/Control",\n  // Button control implementation'
179 |     },
180 |     {
181 |       name: 'OpenUI5 Samples - ButtonWithBadge',
182 |       libraryId: '/openui5-samples',
183 |       relFile: 'src/sap.m/test/sap/m/demokit/sample/ButtonWithBadge/Component.js',
184 |       expectedUrl: 'https://sdk.openui5.org/entity/sap.m.Button/sample/sap.m.sample.ButtonWithBadge',
185 |       frontmatter: '',
186 |       content: 'sap.ui.define([\n  "sap/ui/core/UIComponent"\n], function (UIComponent) {\n  // Sample implementation'
187 |     },
188 |          {
189 |        name: 'SAPUI5 - Multi-Selection Navigation',
190 |        libraryId: '/sapui5',
191 |        relFile: '06_SAP_Fiori_Elements/multi-selection-for-intent-based-navigation-640cabf.md',
192 |        expectedUrl: 'https://ui5.sap.com/#/topic/640cabfd35c3469aacf31be28924d50d',
193 |        frontmatter: '---\nid: 640cabfd35c3469aacf31be28924d50d\ntopic: 640cabfd35c3469aacf31be28924d50d\ntitle: Multi-Selection for Intent-Based Navigation\n---\n',
194 |        content: '# Multi-Selection for Intent-Based Navigation\n\nThis feature allows...'
195 |      },
196 |     {
197 |       name: 'UI5 Tooling - Builder Documentation',
198 |       libraryId: '/ui5-tooling',
199 |       relFile: 'pages/Builder.md',
200 |       expectedUrl: 'https://sap.github.io/ui5-tooling/v4/pages/Builder#ui5-builder',
201 |       frontmatter: '',
202 |       content: '# UI5 Builder\n\nThe UI5 Builder module takes care of building your project...'
203 |     },
204 |     {
205 |       name: 'UI5 Web Components - Configuration',
206 |       libraryId: '/ui5-webcomponents',
207 |       relFile: '2-advanced/01-configuration.md',
208 |       expectedUrl: 'https://sap.github.io/ui5-webcomponents/docs/01-configuration#configuration',
209 |       frontmatter: '',
210 |       content: '# Configuration\n\nThis section explains how you can configure UI5 Web Components...'
211 |     },
212 |     {
213 |       name: 'wdi5 - Locators Documentation',
214 |       libraryId: '/wdi5',
215 |       relFile: 'locators.md',
216 |       expectedUrl: 'https://ui5-community.github.io/wdi5/#/locators',
217 |       frontmatter: '---\nid: locators\ntitle: Locators\n---\n',
218 |       content: '# Locators\n\nwdi5 provides various locators for UI5 controls...'
219 |     },
220 |     {
221 |       name: 'UI5 TypeScript - FAQ Documentation',
222 |       libraryId: '/ui5-typescript',
223 |       relFile: 'faq.md',
224 |       expectedUrl: 'https://github.com/UI5/typescript/blob/gh-pages/faq#faq---frequently-asked-questions-for-the-ui5-type-definitions',
225 |       frontmatter: '',
226 |       content: '# FAQ - Frequently Asked Questions for the UI5 Type Definitions\n\nWhile the [main page](README.md) answers the high-level questions...'
227 |     },
228 |     {
229 |       name: 'UI5 CC Spreadsheet Importer - Checks Documentation',
230 |       libraryId: '/ui5-cc-spreadsheetimporter',
231 |       relFile: 'pages/Checks.md',
232 |       expectedUrl: 'https://docs.spreadsheet-importer.com/pages/Checks/#error-types',
233 |       frontmatter: '',
234 |       content: '## Error Types\n\nThe following types of errors are handled by the UI5 Spreadsheet Upload Control...'
235 |     },
236 |     {
237 |       name: 'ABAP Cheat Sheets - Internal Tables',
238 |       libraryId: '/abap-cheat-sheets',
239 |       relFile: '01_Internal_Tables.md',
240 |       expectedUrl: 'https://github.com/SAP-samples/abap-cheat-sheets/blob/main/01_Internal_Tables#internal-tables',
241 |       frontmatter: '',
242 |       content: '# Internal Tables\n\nThis cheat sheet contains a selection of syntax examples and notes on internal tables...'
243 |     },
244 |     {
245 |       name: 'SAP Style Guides - Clean ABAP',
246 |       libraryId: '/sap-styleguides',
247 |       relFile: 'clean-abap/CleanABAP.md',
248 |       expectedUrl: 'https://github.com/SAP/styleguides/blob/main/CleanABAP#clean-abap',
249 |       frontmatter: '',
250 |       content: '# Clean ABAP\n\n> [**中文**](CleanABAP_zh.md)\n\nThis style guide presents the essentials of clean ABAP...'
251 |     },
252 |     {
253 |       name: 'DSAG ABAP Leitfaden - Clean Core',
254 |       libraryId: '/dsag-abap-leitfaden',
255 |       relFile: 'clean-core/what-is-clean-core.md',
256 |       expectedUrl: 'https://1dsag.github.io/ABAP-Leitfaden/clean-core/what-is-clean-core/#was-ist-clean-core',
257 |       frontmatter: '',
258 |       content: '# Was ist Clean Core?\n\nClean Core ist ein Konzept von SAP, das darauf abzielt...'
259 |     },
260 |     {
261 |       name: 'ABAP Platform Fiori Feature Showcase - General Features',
262 |       libraryId: '/abap-fiori-showcase',
263 |       relFile: '01_general_features.md',
264 |       expectedUrl: 'https://github.com/SAP-samples/abap-platform-fiori-feature-showcase/blob/main/01_general_features#general-features',
265 |       frontmatter: '',
266 |       content: '# General Features\n\nThis section describes the features that are generally used throughout...'
267 |     },
268 |     {
269 |       name: 'CAP Fiori Elements Feature Showcase - README',
270 |       libraryId: '/cap-fiori-showcase',
271 |       relFile: 'README.md',
272 |       expectedUrl: 'https://github.com/SAP-samples/fiori-elements-feature-showcase/blob/main/README#sap-fiori-elements-for-odata-v4-feature-showcase',
273 |       frontmatter: '',
274 |       content: '# SAP Fiori Elements for OData V4 Feature Showcase\n\nThis app showcases different features of SAP Fiori elements...'
275 |     }
276 |     // Note: Some sources like CAP, Cloud SDK AI, wdi5, etc. may need different file mappings
277 |     // or fallback to mock content if actual files don't exist in expected locations
278 |   ];
279 | 
280 |   describe('Main URL Generation Function', () => {
281 |     testCases.forEach(({ name, libraryId, relFile, expectedUrl, frontmatter, content }) => {
282 |       it(`should generate correct URL for ${name}`, () => {
283 |         // Step 1: Get configuration from metadata.json
284 |         const config = getConfigForLibrary(libraryId);
285 |         
286 |         // Step 2: Try to read from actual source file first, fallback to test data
287 |         let fileContent = readFileContent(libraryId, relFile);
288 |         let contentSource = 'real file';
289 |         
290 |         if (!fileContent) {
291 |           // Fallback to hardcoded test data when real file is not available
292 |           fileContent = frontmatter ? `${frontmatter}\n${content}` : content;
293 |           contentSource = 'test data';
294 |         }
295 |         
296 |         // For debugging: log which content source was used
297 |         if (process.env.DEBUG_TESTS === 'true') {
298 |           console.log(`\n[${name}] Using ${contentSource}`);
299 |           console.log(`File path: ${libraryId}/${relFile}`);
300 |           console.log(`Content preview: ${fileContent.slice(0, 100)}...`);
301 |         }
302 |         
303 |         // Step 3: Generate URL using the URL generation system
304 |         const result = generateDocumentationUrl(libraryId, relFile, fileContent, config);
305 |         
306 |         // Step 4: Validate the result
307 |         expect(result).toBe(expectedUrl);
308 |       });
309 |     });
310 |   });
311 | 
312 |   describe('Individual Generator Classes', () => {
313 |     
314 |     describe('CloudSdkUrlGenerator', () => {
315 |       it('should generate URLs using frontmatter ID', () => {
316 |         const config = getConfigForLibrary('/cloud-sdk-js');
317 |         const generator = new CloudSdkUrlGenerator('/cloud-sdk-js', config);
318 |         const content = '---\nid: kubernetes\n---\n# Migration Guide';
319 |         
320 |         const result = generator.generateUrl({
321 |           libraryId: '/cloud-sdk-js',
322 |           relFile: 'environments/migrate.mdx',
323 |           content,
324 |           config
325 |         });
326 |         
327 |         expect(result).toBe('https://sap.github.io/cloud-sdk/docs/js/environments/kubernetes');
328 |       });
329 | 
330 |       it('should handle AI SDK variants differently', () => {
331 |         const config = getConfigForLibrary('/cloud-sdk-ai-js');
332 |         const generator = new CloudSdkUrlGenerator('/cloud-sdk-ai-js', config);
333 |         const content = '---\nid: orchestration\n---\n# Orchestration';
334 |         
335 |         const result = generator.generateUrl({
336 |           libraryId: '/cloud-sdk-ai-js',
337 |           relFile: 'langchain/orchestration.mdx',
338 |           content,
339 |           config
340 |         });
341 |         
342 |         expect(result).toBe('https://sap.github.io/ai-sdk/docs/js/langchain/orchestration');
343 |       });
344 |     });
345 | 
346 |     describe('SapUi5UrlGenerator', () => {
347 |       it('should generate topic-based URLs for SAPUI5', () => {
348 |         const config = getConfigForLibrary('/sapui5');
349 |         const generator = new SapUi5UrlGenerator('/sapui5', config);
350 |         const content = '---\nid: 123e4567-e89b-12d3-a456-426614174000\n---\n# Topic Content';
351 |         
352 |         const result = generator.generateUrl({
353 |           libraryId: '/sapui5',
354 |           relFile: 'docs/topic.md',
355 |           content,
356 |           config
357 |         });
358 |         
359 |         expect(result).toBe('https://ui5.sap.com/#/topic/123e4567-e89b-12d3-a456-426614174000');
360 |       });
361 | 
362 |       it('should generate API URLs for OpenUI5 controls', () => {
363 |         const config = getConfigForLibrary('/openui5-api');
364 |         const generator = new SapUi5UrlGenerator('/openui5-api', config);
365 |         const content = 'sap.ui.define([\n  "sap/m/Button"\n], function(Button) {';
366 |         
367 |         const result = generator.generateUrl({
368 |           libraryId: '/openui5-api',
369 |           relFile: 'src/sap/m/Button.js',
370 |           content,
371 |           config
372 |         });
373 |         
374 |         expect(result).toBe('https://sdk.openui5.org/#/api/sap.m.Button');
375 |       });
376 |     });
377 | 
378 |     describe('CapUrlGenerator', () => {
379 |       it('should generate docsify-style URLs', () => {
380 |         const config = getConfigForLibrary('/cap');
381 |         const generator = new CapUrlGenerator('/cap', config);
382 |         const content = '---\nid: getting-started\n---\n# Getting Started';
383 |         
384 |         const result = generator.generateUrl({
385 |           libraryId: '/cap',
386 |           relFile: 'guides/getting-started.md',
387 |           content,
388 |           config
389 |         });
390 |         
391 |         expect(result).toBe('https://cap.cloud.sap/docs/#/guides/getting-started');
392 |       });
393 | 
394 |       it('should handle CDS-specific sections', () => {
395 |         const config = getConfigForLibrary('/cap');
396 |         const generator = new CapUrlGenerator('/cap', config);
397 |         const content = '---\nslug: cds-types\n---\n# CDS Types';
398 |         
399 |         const result = generator.generateUrl({
400 |           libraryId: '/cap',
401 |           relFile: 'cds/types.md',
402 |           content,
403 |           config
404 |         });
405 |         
406 |         expect(result).toBe('https://cap.cloud.sap/docs/#/cds/cds-types');
407 |       });
408 |     });
409 | 
410 |     describe('Wdi5UrlGenerator', () => {
411 |       it('should generate docsify-style URLs for wdi5', () => {
412 |         const config = getConfigForLibrary('/wdi5');
413 |         const generator = new Wdi5UrlGenerator('/wdi5', config);
414 |         const content = '---\nid: locators\n---\n# Locators';
415 |         
416 |         const result = generator.generateUrl({
417 |           libraryId: '/wdi5',
418 |           relFile: 'locators.md',
419 |           content,
420 |           config
421 |         });
422 |         
423 |         expect(result).toBe('https://ui5-community.github.io/wdi5/#/locators');
424 |       });
425 | 
426 |       it('should handle configuration-specific sections', () => {
427 |         const config = getConfigForLibrary('/wdi5');
428 |         const generator = new Wdi5UrlGenerator('/wdi5', config);
429 |         const content = '---\nid: basic-config\n---\n# Basic Configuration';
430 |         
431 |         const result = generator.generateUrl({
432 |           libraryId: '/wdi5',
433 |           relFile: 'configuration/basic.md',
434 |           content,
435 |           config
436 |         });
437 |         
438 |         expect(result).toBe('https://ui5-community.github.io/wdi5/#/configuration/basic-config');
439 |       });
440 |     });
441 | 
442 |     describe('DsagUrlGenerator', () => {
443 |       it('should generate GitHub Pages URLs with path transformation', () => {
444 |         const config = getConfigForLibrary('/dsag-abap-leitfaden');
445 |         const generator = new DsagUrlGenerator('/dsag-abap-leitfaden', config);
446 |         const content = '# Was ist Clean Core?\n\nClean Core ist ein Konzept von SAP...';
447 |         
448 |         const result = generator.generateUrl({
449 |           libraryId: '/dsag-abap-leitfaden',
450 |           relFile: 'clean-core/what-is-clean-core.md',
451 |           content,
452 |           config
453 |         });
454 |         
455 |         expect(result).toBe('https://1dsag.github.io/ABAP-Leitfaden/clean-core/what-is-clean-core/#was-ist-clean-core');
456 |       });
457 | 
458 |       it('should handle root-level documentation', () => {
459 |         const config = getConfigForLibrary('/dsag-abap-leitfaden');
460 |         const generator = new DsagUrlGenerator('/dsag-abap-leitfaden', config);
461 |         const content = '# ABAP Leitfaden\n\nDer DSAG ABAP Leitfaden...';
462 |         
463 |         const result = generator.generateUrl({
464 |           libraryId: '/dsag-abap-leitfaden',
465 |           relFile: 'README.md',
466 |           content,
467 |           config
468 |         });
469 |         
470 |         expect(result).toBe('https://1dsag.github.io/ABAP-Leitfaden/README/#abap-leitfaden');
471 |       });
472 |     });
473 | 
474 |     describe('GenericUrlGenerator', () => {
475 |       it('should handle generic sources with frontmatter', () => {
476 |         const config = getConfigForLibrary('/ui5-tooling'); // Use a real generic source
477 |         const generator = new GenericUrlGenerator('/ui5-tooling', config);
478 |         const content = '---\nid: test-doc\n---\n# Test Document';
479 |         
480 |         const result = generator.generateUrl({
481 |           libraryId: '/ui5-tooling',
482 |           relFile: 'pages/test.md',
483 |           content,
484 |           config
485 |         });
486 |         
487 |         expect(result).toBe('https://sap.github.io/ui5-tooling/v4/pages/test-doc#test-document');
488 |       });
489 | 
490 |       it('should fallback to filename when no frontmatter', () => {
491 |         const config = getConfigForLibrary('/ui5-tooling'); // Use a real generic source
492 |         const generator = new GenericUrlGenerator('/ui5-tooling', config);
493 |         const content = '# Test Document\n\nSome content...';
494 |         
495 |         const result = generator.generateUrl({
496 |           libraryId: '/ui5-tooling',
497 |           relFile: 'pages/test.md',
498 |           content,
499 |           config
500 |         });
501 |         
502 |         expect(result).toBe('https://sap.github.io/ui5-tooling/v4/pages/test#test-document');
503 |       });
504 |     });
505 | 
506 |     describe('AbapUrlGenerator', () => {
507 |       it('should generate correct cloud URLs for latest version', () => {
508 |         // Mock config for ABAP documentation
509 |         const config: DocUrlConfig = {
510 |           baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
511 |           pathPattern: '/latest/en-US/{filename}',
512 |           anchorStyle: 'lowercase-with-dashes'
513 |         };
514 |         
515 |         const generator = new AbapUrlGenerator('/abap-docs-latest', config);
516 |         
517 |         const result = generator.generateSourceSpecificUrl({
518 |           libraryId: '/abap-docs-latest',
519 |           relFile: 'abeninline_declarations.md',
520 |           content: '# Inline Declarations',
521 |           config
522 |         });
523 |         
524 |         expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abeninline_declarations.html');
525 |       });
526 | 
527 |       it('should generate correct cloud URLs for version 9.16', () => {
528 |         const config: DocUrlConfig = {
529 |           baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
530 |           pathPattern: '/9.16/en-US/{filename}',
531 |           anchorStyle: 'lowercase-with-dashes'
532 |         };
533 |         
534 |         const generator = new AbapUrlGenerator('/abap-docs-916', config);
535 |         
536 |         const result = generator.generateSourceSpecificUrl({
537 |           libraryId: '/abap-docs-916',
538 |           relFile: 'md/abapselect.md',
539 |           content: '# SELECT Statement',
540 |           config
541 |         });
542 |         
543 |         expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abapselect.html');
544 |       });
545 | 
546 |       it('should generate correct cloud URLs for S/4HANA 2025 version 8.10', () => {
547 |         const config: DocUrlConfig = {
548 |           baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
549 |           pathPattern: '/8.10/en-US/{filename}',
550 |           anchorStyle: 'lowercase-with-dashes'
551 |         };
552 |         
553 |         const generator = new AbapUrlGenerator('/abap-docs-810', config);
554 |         
555 |         const result = generator.generateSourceSpecificUrl({
556 |           libraryId: '/abap-docs-810',
557 |           relFile: 'abaploop.md',
558 |           content: '# LOOP Statement',
559 |           config
560 |         });
561 |         
562 |         expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abaploop.html');
563 |       });
564 | 
565 |       it('should generate correct legacy URLs for version 7.58', () => {
566 |         const config: DocUrlConfig = {
567 |           baseUrl: 'https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US',
568 |           pathPattern: '/7.58/en-US/{filename}',
569 |           anchorStyle: 'lowercase-with-dashes'
570 |         };
571 |         
572 |         const generator = new AbapUrlGenerator('/abap-docs-758', config);
573 |         
574 |         const result = generator.generateSourceSpecificUrl({
575 |           libraryId: '/abap-docs-758',
576 |           relFile: 'md/abapdata.md',
577 |           content: '# DATA Statement',
578 |           config
579 |         });
580 |         
581 |         expect(result).toBe('https://help.sap.com/doc/abapdocu_758_index_htm/7.58/en-US/abapdata.html');
582 |       });
583 | 
584 |       it('should handle anchors correctly', () => {
585 |         const config: DocUrlConfig = {
586 |           baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
587 |           pathPattern: '/latest/en-US/{filename}',
588 |           anchorStyle: 'lowercase-with-dashes'
589 |         };
590 |         
591 |         const generator = new AbapUrlGenerator('/abap-docs-latest', config);
592 |         
593 |         const result = generator.generateSourceSpecificUrl({
594 |           libraryId: '/abap-docs-latest',
595 |           relFile: 'abeninline_declarations.md',
596 |           content: '# Inline Declarations',
597 |           config,
598 |           anchor: 'syntax'
599 |         });
600 |         
601 |         expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abeninline_declarations.html#syntax');
602 |       });
603 | 
604 |       it('should correctly extract version from library ID', () => {
605 |         const testCases = [
606 |           { libraryId: '/abap-docs-758', expected: '7.58' },
607 |           { libraryId: '/abap-docs-latest', expected: 'latest' },
608 |           { libraryId: '/abap-docs-916', expected: '9.16' },
609 |           { libraryId: '/abap-docs-810', expected: '8.10' }
610 |         ];
611 | 
612 |         testCases.forEach(({ libraryId, expected }) => {
613 |           const config: DocUrlConfig = {
614 |             baseUrl: 'https://example.com',
615 |             pathPattern: `/${expected}/en-US/{filename}`,
616 |             anchorStyle: 'lowercase-with-dashes'
617 |           };
618 |           
619 |           const generator = new AbapUrlGenerator(libraryId, config);
620 |           
621 |           // Test the version extraction by checking the generated URL
622 |           const result = generator.generateSourceSpecificUrl({
623 |             libraryId,
624 |             relFile: 'test.md',
625 |             content: '# Test',
626 |             config
627 |           });
628 |           
629 |           if (expected === 'latest' || parseFloat(expected) >= 9.1 || parseFloat(expected) >= 8.1) {
630 |             expect(result).toContain('abapdocu_cp_index_htm/CLOUD');
631 |           } else {
632 |             const versionCode = expected.replace('.', '');
633 |             expect(result).toContain(`abapdocu_${versionCode}_index_htm/${expected}`);
634 |           }
635 |         });
636 |       });
637 | 
638 |       it('should use .html extension instead of .htm for file extension', () => {
639 |         const config: DocUrlConfig = {
640 |           baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
641 |           pathPattern: '/latest/en-US/{filename}',
642 |           anchorStyle: 'lowercase-with-dashes'
643 |         };
644 |         
645 |         const generator = new AbapUrlGenerator('/abap-docs-latest', config);
646 |         
647 |         const result = generator.generateSourceSpecificUrl({
648 |           libraryId: '/abap-docs-latest',
649 |           relFile: 'abeninline_declarations.md',
650 |           content: '# Inline Declarations',
651 |           config
652 |         });
653 |         
654 |         // Should use .html file extension (not .htm file extension)
655 |         expect(result).toContain('abeninline_declarations.html');
656 |         expect(result).toMatch(/\.html$/);
657 |         expect(result).not.toMatch(/\.htm$/);
658 |       });
659 | 
660 |       it('should point to latest cloud version instead of legacy 7.58 version', () => {
661 |         const config: DocUrlConfig = {
662 |           baseUrl: 'https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US',
663 |           pathPattern: '/latest/en-US/{filename}',
664 |           anchorStyle: 'lowercase-with-dashes'
665 |         };
666 |         
667 |         const generator = new AbapUrlGenerator('/abap-docs-latest', config);
668 |         
669 |         const result = generator.generateSourceSpecificUrl({
670 |           libraryId: '/abap-docs-latest',
671 |           relFile: 'abeninline_declarations.md',
672 |           content: '# Inline Declarations',
673 |           config
674 |         });
675 |         
676 |         // Should use the new cloud URL pattern instead of the old 7.58 pattern
677 |         expect(result).toContain('abapdocu_cp_index_htm/CLOUD');
678 |         expect(result).not.toContain('abapdocu_758_index_htm/7.58');
679 |         expect(result).not.toContain('abapdocu_latest_index_htm/latest');
680 |         
681 |         // The full URL should match the expected cloud pattern
682 |         expect(result).toBe('https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/abeninline_declarations.html');
683 |       });
684 |     });
685 |   });
686 | 
687 |   describe('Error Handling', () => {
688 |     it('should return null for missing config', () => {
689 |       const result = generateDocumentationUrl('/unknown', 'file.md', 'content', null as any);
690 |       expect(result).toBeNull();
691 |     });
692 | 
693 |     it('should handle malformed frontmatter gracefully', () => {
694 |       // Test with a non-existent library ID that will use the generic generator
695 |       const config = getConfigForLibrary('/ui5-tooling'); // Use a real config for fallback testing
696 |       
697 |       const content = '---\ninvalid: yaml: content:\n---\n# Content';
698 |       const result = generateDocumentationUrl('/ui5-tooling', 'test.md', content, config);
699 |       
700 |       expect(result).not.toBeNull();
701 |     });
702 |   });
703 | 
704 |   describe('URL Pattern Validation', () => {
705 |     testCases.forEach(({ name, expectedUrl }) => {
706 |       it(`should generate valid URL format for ${name}`, () => {
707 |         expect(expectedUrl).toMatch(/^https?:\/\//);
708 |         expect(() => new URL(expectedUrl)).not.toThrow();
709 |       });
710 |     });
711 |   });
712 | });
713 | 
```

--------------------------------------------------------------------------------
/src/lib/BaseServerHandler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Base Server Handler - Shared functionality for MCP servers
  3 |  * Eliminates code duplication between stdio and HTTP server implementations
  4 |  * 
  5 |  * IMPORTANT FOR LLMs/AI ASSISTANTS:
  6 |  * =================================
  7 |  * The function names in this MCP server may appear with different prefixes depending on your MCP client:
  8 |  * - Simple names: search, fetch, sap_community_search, sap_help_search, sap_help_get
  9 |  * - Prefixed names: mcp_sap-docs-remote_search, mcp_sap-docs-remote_fetch, etc.
 10 |  * 
 11 |  * Try the simple names first, then the prefixed versions if they don't work.
 12 |  * 
 13 |  * Note: sap_docs_search and sap_docs_get are legacy aliases for backward compatibility.
 14 |  */
 15 | 
 16 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 17 | import {
 18 |   ListResourcesRequestSchema,
 19 |   ReadResourceRequestSchema,
 20 |   CallToolRequestSchema,
 21 |   ListToolsRequestSchema,
 22 |   ListPromptsRequestSchema,
 23 |   GetPromptRequestSchema
 24 | } from "@modelcontextprotocol/sdk/types.js";
 25 | import {
 26 |   searchLibraries,
 27 |   fetchLibraryDocumentation,
 28 |   listDocumentationResources,
 29 |   readDocumentationResource,
 30 |   searchCommunity
 31 | } from "./localDocs.js";
 32 | import { searchSapHelp, getSapHelpContent } from "./sapHelp.js";
 33 | 
 34 | import { SearchResponse } from "./types.js";
 35 | import { logger } from "./logger.js";
 36 | import { search } from "./search.js";
 37 | import { CONFIG } from "./config.js";
 38 | import { loadMetadata, getDocUrlConfig } from "./metadata.js";
 39 | import { generateDocumentationUrl, formatSearchResult } from "./url-generation/index.js";
 40 | 
 41 | /**
 42 |  * Helper functions for creating structured JSON responses compatible with ChatGPT and all MCP clients
 43 |  */
 44 | 
 45 | interface SearchResult {
 46 |   id: string;
 47 |   title: string;
 48 |   url: string;
 49 |   snippet?: string;
 50 |   score?: number;
 51 |   metadata?: Record<string, any>;
 52 | }
 53 | 
 54 | interface DocumentResult {
 55 |   id: string;
 56 |   title: string;
 57 |   text: string;
 58 |   url: string;
 59 |   metadata?: Record<string, any>;
 60 | }
 61 | 
 62 | /**
 63 |  * Create structured JSON response for search results (ChatGPT-compatible)
 64 |  */
 65 | function createSearchResponse(results: SearchResult[]): any {
 66 |   // Clean the results to avoid JSON serialization issues in MCP protocol
 67 |   const cleanedResults = results.map(result => ({
 68 |     // ChatGPT requires: id, title, url (other fields optional)
 69 |     id: result.id,
 70 |     title: result.title ? result.title.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n') : result.title,
 71 |     url: result.url,
 72 |     // Additional fields for enhanced functionality
 73 |     snippet: result.snippet ? result.snippet.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n') : result.snippet,
 74 |     score: result.score,
 75 |     metadata: result.metadata
 76 |   }));
 77 |   
 78 |   // ChatGPT expects: { "results": [...] } in JSON-encoded text content
 79 |   return {
 80 |     content: [
 81 |       {
 82 |         type: "text",
 83 |         text: JSON.stringify({ results: cleanedResults })
 84 |       }
 85 |     ]
 86 |   };
 87 | }
 88 | 
 89 | /**
 90 |  * Create structured JSON response for document fetch (ChatGPT-compatible)
 91 |  */
 92 | function createDocumentResponse(document: DocumentResult): any {
 93 |   // Clean the text content to avoid JSON serialization issues in MCP protocol
 94 |   const cleanedDocument = {
 95 |     // ChatGPT requires: id, title, text, url, metadata
 96 |     id: document.id,
 97 |     title: document.title,
 98 |     text: document.text
 99 |       .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control chars except \n, \r, \t
100 |       .replace(/\r\n/g, '\n') // Normalize line endings
101 |       .replace(/\r/g, '\n'), // Convert remaining \r to \n
102 |     url: document.url,
103 |     metadata: document.metadata
104 |   };
105 |   
106 |   // ChatGPT expects document object as JSON-encoded text content
107 |   return {
108 |     content: [
109 |       {
110 |         type: "text", 
111 |         text: JSON.stringify(cleanedDocument)
112 |       }
113 |     ]
114 |   };
115 | }
116 | 
117 | /**
118 |  * Create error response in structured JSON format
119 |  */
120 | function createErrorResponse(error: string, requestId?: string): any {
121 |   return {
122 |     content: [
123 |       {
124 |         type: "text",
125 |         text: JSON.stringify({ 
126 |           error,
127 |           requestId: requestId || 'unknown'
128 |         })
129 |       }
130 |     ]
131 |   };
132 | }
133 | 
134 | export interface ServerConfig {
135 |   name: string;
136 |   description: string;
137 |   version: string;
138 | }
139 | 
140 | /**
141 |  * Helper function to extract client metadata from request
142 |  */
143 | function extractClientMetadata(request: any): Record<string, any> {
144 |   const metadata: Record<string, any> = {};
145 |   
146 |   // Try to extract available metadata from the request
147 |   if (request.meta) {
148 |     metadata.meta = request.meta;
149 |   }
150 |   
151 |   // Extract any client identification from headers or other sources
152 |   if (request.headers) {
153 |     metadata.headers = request.headers;
154 |   }
155 |   
156 |   // Extract transport information if available
157 |   if (request.transport) {
158 |     metadata.transport = request.transport;
159 |   }
160 |   
161 |   // Extract session or connection info
162 |   if (request.id) {
163 |     metadata.requestId = request.id;
164 |   }
165 |   
166 |   return metadata;
167 | }
168 | 
169 | /**
170 |  * Base Server Handler Class
171 |  * Provides shared functionality for all MCP server implementations
172 |  */
173 | export class BaseServerHandler {
174 |   
175 |   /**
176 |    * Configure server with shared resource and tool handlers
177 |    */
178 |   static configureServer(srv: Server): void {
179 |     // Only setup resource handlers if resources capability is enabled
180 |     // DISABLED: Resources capability causes 60,000+ resources which breaks Cursor
181 |     // this.setupResourceHandlers(srv);
182 |     this.setupToolHandlers(srv);
183 | 
184 |     const capabilities = (srv as unknown as { _capabilities?: { prompts?: object } })._capabilities;
185 |     if (capabilities?.prompts) {
186 |       this.setupPromptHandlers(srv);
187 |     }
188 |   }
189 | 
190 |   /**
191 |    * Setup resource handlers (shared between all server types)
192 |    */
193 |   private static setupResourceHandlers(srv: Server): void {
194 |     // List available resources
195 |     srv.setRequestHandler(ListResourcesRequestSchema, async () => {
196 |       const resources = await listDocumentationResources();
197 |       return { resources };
198 |     });
199 | 
200 |     // Read resource contents
201 |     srv.setRequestHandler(ReadResourceRequestSchema, async (request) => {
202 |       const { uri } = request.params;
203 |       try {
204 |         return await readDocumentationResource(uri);
205 |       } catch (error: any) {
206 |         return {
207 |           contents: [{
208 |             uri,
209 |             mimeType: "text/plain",
210 |             text: `Error reading resource: ${error.message}`
211 |           }]
212 |         };
213 |       }
214 |     });
215 |   }
216 | 
217 |   /**
218 |    * Setup tool handlers (shared between all server types)
219 |    */
220 |   private static setupToolHandlers(srv: Server): void {
221 |     // List available tools
222 |     srv.setRequestHandler(ListToolsRequestSchema, async () => {
223 |       return {
224 |         tools: [
225 |           {
226 |             name: "sap_community_search", 
227 |             description: `SEARCH SAP COMMUNITY: sap_community_search(query="search terms")
228 | 
229 | FUNCTION NAME: sap_community_search (or mcp_sap-docs-remote_sap_community_search)
230 | 
231 | FINDS: Blog posts, discussions, solutions from SAP Community
232 | INCLUDES: Engagement data (kudos), ranked by "Best Match"
233 | 
234 | TYPICAL WORKFLOW:
235 | 1. sap_community_search(query="your problem + error code")
236 | 2. fetch(id="community-12345") for full posts
237 | 
238 | BEST FOR TROUBLESHOOTING:
239 | • Include error codes: "415 error", "500 error"
240 | • Be specific: "CAP action binary upload 415"
241 | • Use real scenarios: "wizard implementation issues"`,
242 |             inputSchema: {
243 |               type: "object",
244 |               properties: {
245 |                 query: {
246 |                   type: "string",
247 |                   description: "Search terms for SAP Community. Include error codes and specific technical details.",
248 |                   examples: [
249 |                     "CAP action parameter binary file upload 415 error",
250 |                     "wizard implementation best practices",
251 |                     "fiori elements authentication",
252 |                     "UI5 deployment issues",
253 |                     "wdi5 test automation problems"
254 |                   ]
255 |                 }
256 |               },
257 |               required: ["query"]
258 |             }
259 |           },
260 |           {
261 |             name: "sap_help_search",
262 |             description: `SEARCH SAP HELP PORTAL: sap_help_search(query="product + topic")
263 | 
264 | FUNCTION NAME: sap_help_search (or mcp_sap-docs-remote_sap_help_search)
265 | 
266 | SEARCHES: Official SAP Help Portal (help.sap.com)
267 | COVERS: Product guides, implementation guides, technical documentation
268 | 
269 | TYPICAL WORKFLOW:
270 | 1. sap_help_search(query="product name + configuration topic")
271 | 2. sap_help_get(result_id="sap-help-12345abc")
272 | 
273 | BEST PRACTICES:
274 | • Include product names: "S/4HANA", "BTP", "Fiori"
275 | • Add specific tasks: "configuration", "setup", "deployment"
276 | • Use official SAP terminology`,
277 |             inputSchema: {
278 |               type: "object",
279 |               properties: {
280 |                 query: {
281 |                   type: "string",
282 |                   description: "Search terms for SAP Help Portal. Include product names and specific topics.",
283 |                   examples: [
284 |                     "S/4HANA configuration",
285 |                     "Fiori Launchpad setup", 
286 |                     "BTP integration",
287 |                     "ABAP development guide",
288 |                     "SAP Analytics Cloud setup"
289 |                   ]
290 |                 }
291 |               },
292 |               required: ["query"]
293 |             }
294 |           },
295 |           {
296 |             name: "sap_help_get", 
297 |             description: `GET SAP HELP PAGE: sap_help_get(result_id="sap-help-12345abc")
298 | 
299 | FUNCTION NAME: sap_help_get (or mcp_sap-docs-remote_sap_help_get)
300 | 
301 | RETRIEVES: Complete SAP Help Portal page content
302 | REQUIRES: Exact result_id from sap_help_search
303 | 
304 | USAGE PATTERN:
305 | 1. Get ID from sap_help_search results  
306 | 2. Use exact ID (don't modify the format)
307 | 3. Receive full page content + metadata`,
308 |             inputSchema: {
309 |               type: "object",
310 |               properties: {
311 |                 result_id: {
312 |                   type: "string",
313 |                   description: "Exact ID from sap_help_search results. Copy the ID exactly as returned.",
314 |                   examples: [
315 |                     "sap-help-12345abc",
316 |                     "sap-help-98765def"
317 |                   ]
318 |                 }
319 |               },
320 |               required: ["result_id"]
321 |             }
322 |           },
323 |           {
324 |             name: "search",
325 |             description: `SEARCH SAP DOCS: search(query="search terms")
326 | 
327 | FUNCTION NAME: search
328 | 
329 | COVERS: ABAP (all versions), UI5, CAP, wdi5, OpenUI5 APIs, Cloud SDK
330 | AUTO-DETECTS: ABAP versions from query (e.g. "LOOP 7.57", defaults to 7.58)
331 | 
332 | TYPICAL WORKFLOW:
333 | 1. search(query="your search terms") 
334 | 2. fetch(id="result_id_from_step_1")
335 | 
336 | QUERY TIPS:
337 | • Be specific: "CAP action binary parameter" not just "CAP"
338 | • Include error codes: "415 error CAP action"
339 | • Use technical terms: "LargeBinary MediaType XMLHttpRequest"
340 | • For ABAP: Include version like "7.58" or "latest"`,
341 |             inputSchema: {
342 |               type: "object",
343 |               properties: {
344 |                 query: {
345 |                   type: "string",
346 |                   description: "Search terms using natural language. Be specific and include technical terms.",
347 |                   examples: [
348 |                     "CAP binary data LargeBinary MediaType",
349 |                     "UI5 button properties",
350 |                     "wdi5 testing locators", 
351 |                     "ABAP SELECT statements 7.58",
352 |                     "415 error CAP action parameter"
353 |                   ]
354 |                 }
355 |               },
356 |               required: ["query"]
357 |             }
358 |           },
359 |           {
360 |             name: "fetch",
361 |             description: `GET SPECIFIC DOCS: fetch(id="result_id")
362 | 
363 | FUNCTION NAME: fetch
364 | 
365 | RETRIEVES: Full content from search results
366 | WORKS WITH: Document IDs returned by search
367 | 
368 | ChatGPT COMPATIBLE:
369 | • Uses "id" parameter (required by ChatGPT)
370 | • Returns structured JSON content
371 | • Includes full document text and metadata`,
372 |             inputSchema: {
373 |               type: "object",
374 |               properties: {
375 |                 id: {
376 |                   type: "string",
377 |                   description: "Unique document ID from search results. Use exact IDs returned by search.",
378 |                   examples: [
379 |                     "/cap/guides/domain-modeling",
380 |                     "/sapui5/controls/button-properties", 
381 |                     "/openui5-api/sap/m/Button",
382 |                     "/abap-docs-758/inline-declarations",
383 |                     "community-12345"
384 |                   ]
385 |                 }
386 |               },
387 |               required: ["id"]
388 |             }
389 |           },
390 | 
391 |         ]
392 |       };
393 |     });
394 | 
395 |     // Handle tool execution
396 |     srv.setRequestHandler(CallToolRequestSchema, async (request) => {
397 |       const { name, arguments: args } = request.params;
398 |       const clientMetadata = extractClientMetadata(request);
399 | 
400 |       if (name === "sap_docs_search" || name === "search") {
401 |         const { query } = args as { query: string };
402 |         
403 |         // Enhanced logging with timing
404 |         const timing = logger.logToolStart(name, query, clientMetadata);
405 |         
406 |         try {
407 |           // Use hybrid search with reranking
408 |           const results = await search(query, { 
409 |             k: CONFIG.RETURN_K 
410 |           });
411 |           
412 |           const topResults = results;
413 |           
414 |           if (topResults.length === 0) {
415 |             logger.logToolSuccess(name, timing.requestId, timing.startTime, 0, { fallback: false });
416 |             return createErrorResponse(
417 |               `No results for "${query}". Try UI5 controls ("button", "table"), CAP topics ("actions", "binary"), testing ("wdi5", "e2e"), ABAP with versions ("SELECT 7.58"), or include error codes ("415 error").`,
418 |               timing.requestId
419 |             );
420 |           }
421 |           
422 |           // Transform results to ChatGPT-compatible format with id, title, url
423 |           const searchResults: SearchResult[] = topResults.map((r, index) => {
424 |             // Extract library_id and topic from document ID
425 |             const libraryIdMatch = r.id.match(/^(\/[^\/]+)/);
426 |             const libraryId = libraryIdMatch ? libraryIdMatch[1] : (r.sourceId ? `/${r.sourceId}` : r.id);
427 |             const topic = r.id.startsWith(libraryId) ? r.id.slice(libraryId.length + 1) : '';
428 |             
429 |             const config = getDocUrlConfig(libraryId);
430 |             const docUrl = config ? generateDocumentationUrl(libraryId, '', r.text, config) : null;
431 |             
432 |             return {
433 |               // ChatGPT-required format: id, title, url
434 |               id: r.id,  // Use full document ID as required by ChatGPT
435 |               title: r.text.split('\n')[0] || r.id,
436 |               url: docUrl || `#${r.id}`,
437 |               // Additional fields for backward compatibility
438 |               library_id: libraryId,
439 |               topic: topic,
440 |               snippet: r.text ? r.text.substring(0, CONFIG.EXCERPT_LENGTH_MAIN) + '...' : '',
441 |               score: r.finalScore,
442 |               metadata: {
443 |                 source: r.sourceId || 'sap-docs',
444 |                 library: libraryId,
445 |                 bm25Score: r.bm25,
446 |                 rank: index + 1
447 |               }
448 |             };
449 |           });
450 |           
451 |           logger.logToolSuccess(name, timing.requestId, timing.startTime, topResults.length, { fallback: false });
452 |           
453 |           return createSearchResponse(searchResults);
454 |         } catch (error) {
455 |           logger.logToolError(name, timing.requestId, timing.startTime, error, false);
456 |           logger.info('Attempting fallback to original search after hybrid search failure');
457 |           
458 |           // Fallback to original search
459 |           try {
460 |             const res: SearchResponse = await searchLibraries(query);
461 |             
462 |             if (!res.results.length) {
463 |               logger.logToolSuccess(name, timing.requestId, timing.startTime, 0, { fallback: true });
464 |               return createErrorResponse(
465 |                 res.error || `No fallback results for "${query}". Try UI5 controls ("button", "table"), CAP topics ("actions", "binary"), testing ("wdi5", "e2e"), ABAP with versions ("SELECT 7.58"), or include error codes.`,
466 |                 timing.requestId
467 |               );
468 |             }
469 |             
470 |             // Transform fallback results to structured format
471 |             const fallbackResults: SearchResult[] = res.results.map((r, index) => ({
472 |               id: r.id || `fallback-${index}`,
473 |               title: r.title || 'SAP Documentation',
474 |               url: r.url || `#${r.id}`,
475 |               snippet: r.description ? r.description.substring(0, 200) + '...' : '',
476 |               metadata: {
477 |                 source: 'fallback-search',
478 |                 rank: index + 1
479 |               }
480 |             }));
481 |             
482 |             logger.logToolSuccess(name, timing.requestId, timing.startTime, res.results.length, { fallback: true });
483 |             
484 |             return createSearchResponse(fallbackResults);
485 |           } catch (fallbackError) {
486 |             logger.logToolError(name, timing.requestId, timing.startTime, fallbackError, true);
487 |             return createErrorResponse(
488 |               `Search temporarily unavailable. Wait 30 seconds and retry, try sap_community_search instead, or use more specific search terms.`,
489 |               timing.requestId
490 |             );
491 |           }
492 |         }
493 |       }
494 | 
495 |       if (name === "sap_community_search") {
496 |         const { query } = args as { query: string };
497 |         
498 |         // Enhanced logging with timing
499 |         const timing = logger.logToolStart(name, query, clientMetadata);
500 |         
501 |         try {
502 |           const res: SearchResponse = await searchCommunity(query);
503 |           
504 |           if (!res.results.length) {
505 |             logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
506 |             return createErrorResponse(
507 |               res.error || `No SAP Community posts found for "${query}". Try different keywords or check your connection.`,
508 |               timing.requestId
509 |             );
510 |           }
511 |           
512 |           // Transform community search results to ChatGPT-compatible format
513 |           const communityResults: SearchResult[] = res.results.map((r: any, index) => ({
514 |             // ChatGPT-required format: id, title, url
515 |             id: r.id || `community-${index}`,
516 |             title: r.title || 'SAP Community Post',
517 |             url: r.url || `#${r.id}`,
518 |             // Additional fields for enhanced functionality
519 |             library_id: r.library_id || `community-${index}`,
520 |             topic: r.topic || '',
521 |             snippet: r.snippet || (r.description ? r.description.substring(0, 200) + '...' : ''),
522 |             score: r.score || 0,
523 |             metadata: r.metadata || {
524 |               source: 'sap-community',
525 |               likes: r.likes,
526 |               author: r.author,
527 |               postTime: r.postTime,
528 |               rank: index + 1
529 |             }
530 |           }));
531 |           
532 |           logger.logToolSuccess(name, timing.requestId, timing.startTime, res.results.length);
533 |           
534 |           return createSearchResponse(communityResults);
535 |         } catch (error) {
536 |           logger.logToolError(name, timing.requestId, timing.startTime, error);
537 |           return createErrorResponse(
538 |             `SAP Community search service temporarily unavailable. Please try again later.`,
539 |             timing.requestId
540 |           );
541 |         }
542 |       }
543 | 
544 |       if (name === "sap_docs_get" || name === "fetch") {
545 |         // Handle both old format (library_id) and new ChatGPT format (id)
546 |         const library_id = (args as any).library_id || (args as any).id;
547 |         const topic = (args as any).topic || "";
548 |         
549 |         if (!library_id) {
550 |           const timing = logger.logToolStart(name, 'missing_id', clientMetadata);
551 |           logger.logToolError(name, timing.requestId, timing.startTime, new Error('Missing id parameter'));
552 |           return createErrorResponse(
553 |             `Missing required parameter: id. Please provide a document ID from search results.`,
554 |             timing.requestId
555 |           );
556 |         }
557 |         
558 |         // Enhanced logging with timing
559 |         const searchKey = library_id + (topic ? `/${topic}` : '');
560 |         const timing = logger.logToolStart(name, searchKey, clientMetadata);
561 |         
562 |         try {
563 |           const text = await fetchLibraryDocumentation(library_id, topic);
564 |           
565 |           if (!text) {
566 |             logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
567 |             return createErrorResponse(
568 |               `Nothing found for ${library_id}`,
569 |               timing.requestId
570 |             );
571 |           }
572 |           
573 |           // Transform document content to ChatGPT-compatible format
574 |           const config = getDocUrlConfig(library_id);
575 |           const docUrl = config ? generateDocumentationUrl(library_id, '', text, config) : null;
576 |           const document: DocumentResult = {
577 |             id: library_id,
578 |             title: library_id.replace(/^\//, '').replace(/\//g, ' > ') + (topic ? ` (${topic})` : ''),
579 |             text: text,
580 |             url: docUrl || `#${library_id}`,
581 |             metadata: {
582 |               source: 'sap-docs',
583 |               library: library_id,
584 |               topic: topic || undefined,
585 |               contentLength: text.length
586 |             }
587 |           };
588 |           
589 |           logger.logToolSuccess(name, timing.requestId, timing.startTime, 1, { 
590 |             contentLength: text.length,
591 |             libraryId: library_id,
592 |             topic: topic || undefined
593 |           });
594 |           
595 |           return createDocumentResponse(document);
596 |         } catch (error) {
597 |           logger.logToolError(name, timing.requestId, timing.startTime, error);
598 |           return createErrorResponse(
599 |             `Error retrieving documentation for ${library_id}. Please try again later.`,
600 |             timing.requestId
601 |           );
602 |         }
603 |       }
604 | 
605 |       if (name === "sap_help_search") {
606 |         const { query } = args as { query: string };
607 |         
608 |         // Enhanced logging with timing
609 |         const timing = logger.logToolStart(name, query, clientMetadata);
610 |         
611 |         try {
612 |           const res: SearchResponse = await searchSapHelp(query);
613 |           
614 |           if (!res.results.length) {
615 |             logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
616 |             return createErrorResponse(
617 |               res.error || `No SAP Help results found for "${query}". Try different keywords or check your connection.`,
618 |               timing.requestId
619 |             );
620 |           }
621 |           
622 |           // Transform SAP Help search results to ChatGPT-compatible format
623 |           const helpResults: SearchResult[] = res.results.map((r, index) => ({
624 |             // ChatGPT-required format: id, title, url
625 |             id: r.id || `sap-help-${index}`,
626 |             title: r.title || 'SAP Help Document',
627 |             url: r.url || `#${r.id}`,
628 |             // Additional fields for enhanced functionality
629 |             snippet: r.description ? r.description.substring(0, 200) + '...' : '',
630 |             metadata: {
631 |               source: 'sap-help',
632 |               totalSnippets: r.totalSnippets,
633 |               rank: index + 1
634 |             }
635 |           }));
636 |           
637 |           logger.logToolSuccess(name, timing.requestId, timing.startTime, res.results.length);
638 |           
639 |           return createSearchResponse(helpResults);
640 |         } catch (error) {
641 |           logger.logToolError(name, timing.requestId, timing.startTime, error);
642 |           return createErrorResponse(
643 |             `SAP Help search service temporarily unavailable. Please try again later.`,
644 |             timing.requestId
645 |           );
646 |         }
647 |       }
648 | 
649 |       if (name === "sap_help_get") {
650 |         const { result_id } = args as { result_id: string };
651 |         
652 |         // Enhanced logging with timing
653 |         const timing = logger.logToolStart(name, result_id, clientMetadata);
654 |         
655 |         try {
656 |           const content = await getSapHelpContent(result_id);
657 |           
658 |           // Transform SAP Help content to structured format
659 |           const document: DocumentResult = {
660 |             id: result_id,
661 |             title: `SAP Help Document (${result_id})`,
662 |             text: content,
663 |             url: `https://help.sap.com/#${result_id}`,
664 |             metadata: {
665 |               source: 'sap-help',
666 |               resultId: result_id,
667 |               contentLength: content.length
668 |             }
669 |           };
670 |           
671 |           logger.logToolSuccess(name, timing.requestId, timing.startTime, 1, { 
672 |             contentLength: content.length,
673 |             resultId: result_id
674 |           });
675 |           
676 |           return createDocumentResponse(document);
677 |         } catch (error) {
678 |           logger.logToolError(name, timing.requestId, timing.startTime, error);
679 |           return createErrorResponse(
680 |             `Error retrieving SAP Help content. Please try again later.`,
681 |             timing.requestId
682 |           );
683 |         }
684 |       }
685 | 
686 | 
687 | 
688 |       throw new Error(`Unknown tool: ${name}`);
689 |     });
690 |   }
691 | 
692 |   /**
693 |    * Setup prompt handlers (shared between all server types)
694 |    */
695 |   private static setupPromptHandlers(srv: Server): void {
696 |     // List available prompts
697 |     srv.setRequestHandler(ListPromptsRequestSchema, async () => {
698 |       return {
699 |         prompts: [
700 |           {
701 |             name: "sap_search_help",
702 |             title: "SAP Documentation Search Helper",
703 |             description: "Helps users construct effective search queries for SAP documentation",
704 |             arguments: [
705 |               {
706 |                 name: "domain",
707 |                 description: "SAP domain (UI5, CAP, ABAP, etc.)",
708 |                 required: false
709 |               },
710 |               {
711 |                 name: "context",
712 |                 description: "Specific context or technology area",
713 |                 required: false
714 |               }
715 |             ]
716 |           },
717 |           {
718 |             name: "sap_troubleshoot",
719 |             title: "SAP Issue Troubleshooting Guide",
720 |             description: "Guides users through troubleshooting common SAP development issues",
721 |             arguments: [
722 |               {
723 |                 name: "error_message",
724 |                 description: "Error message or symptom description",
725 |                 required: false
726 |               },
727 |               {
728 |                 name: "technology",
729 |                 description: "SAP technology stack (UI5, CAP, ABAP, etc.)",
730 |                 required: false
731 |               }
732 |             ]
733 |           }
734 |         ]
735 |       };
736 |     });
737 | 
738 |     // Get specific prompt
739 |     srv.setRequestHandler(GetPromptRequestSchema, async (request) => {
740 |       const { name, arguments: args } = request.params;
741 |       
742 |       switch (name) {
743 |         case "sap_search_help":
744 |           const domain = args?.domain || "general SAP";
745 |           const context = args?.context || "development";
746 |           
747 |           return {
748 |             description: `Search helper for ${domain} documentation`,
749 |             messages: [
750 |               {
751 |                 role: "user",
752 |                 content: {
753 |                   type: "text",
754 |                   text: `I need help searching ${domain} documentation for ${context}. What search terms should I use to find the most relevant results?
755 | 
756 | Here are some tips for effective SAP documentation searches:
757 | 
758 | **For UI5/Frontend:**
759 | - Include specific control names (e.g., "Table", "Button", "ObjectPage")
760 | - Mention UI5 version if relevant
761 | - Use terms like "properties", "events", "aggregations"
762 | 
763 | **For CAP/Backend:**
764 | - Include CDS concepts (e.g., "entity", "service", "annotation")
765 | - Mention specific features (e.g., "authentication", "authorization", "events")
766 | - Use terms like "deployment", "configuration"
767 | 
768 | **For ABAP:**
769 | - Include version number (e.g., "7.58", "latest")
770 | - Use specific statement types (e.g., "SELECT", "LOOP", "MODIFY")
771 | - Include object types (e.g., "class", "method", "interface")
772 | 
773 | **General Tips:**
774 | - Be specific rather than broad
775 | - Include error codes if troubleshooting
776 | - Use technical terms rather than business descriptions
777 | - Combine multiple related terms
778 | 
779 | What specific topic are you looking for help with?`
780 |                 }
781 |               }
782 |             ]
783 |           };
784 | 
785 |         case "sap_troubleshoot":
786 |           const errorMessage = args?.error_message || "an issue";
787 |           const technology = args?.technology || "SAP";
788 |           
789 |           return {
790 |             description: `Troubleshooting guide for ${technology}`,
791 |             messages: [
792 |               {
793 |                 role: "user", 
794 |                 content: {
795 |                   type: "text",
796 |                   text: `I'm experiencing ${errorMessage} with ${technology}. Let me help you troubleshoot this systematically.
797 | 
798 | **Step 1: Information Gathering**
799 | - What is the exact error message or symptom?
800 | - When does this occur (during development, runtime, deployment)?
801 | - What were you trying to accomplish?
802 | - What technology stack are you using?
803 | 
804 | **Step 2: Initial Search Strategy**
805 | Let me search the SAP documentation for similar issues:
806 | 
807 | **For UI5 Issues:**
808 | - Search for the exact error message
809 | - Include control or component names
810 | - Look for browser console errors
811 | 
812 | **For CAP Issues:**
813 | - Check service definitions and annotations
814 | - Look for deployment configuration
815 | - Verify database connections
816 | 
817 | **For ABAP Issues:**
818 | - Include ABAP version in search
819 | - Look for syntax or runtime errors
820 | - Check object dependencies
821 | 
822 | **Step 3: Common Solutions**
823 | Based on the issue type, I'll search for:
824 | - Official SAP documentation
825 | - Community discussions
826 | - Code examples and samples
827 | 
828 | Please provide more details about your specific issue, and I'll search for relevant solutions.`
829 |                 }
830 |               }
831 |             ]
832 |           };
833 | 
834 |         default:
835 |           throw new Error(`Unknown prompt: ${name}`);
836 |       }
837 |     });
838 |   }
839 | 
840 |   /**
841 |    * Initialize metadata system (shared initialization logic)
842 |    */
843 |   static initializeMetadata(): void {
844 |     logger.info('Initializing BM25 search system...');
845 |     try {
846 |       loadMetadata();
847 |       logger.info('Search system ready with metadata');
848 |     } catch (error) {
849 |       logger.warn('Metadata loading failed, using defaults', { error: String(error) });
850 |       logger.info('Search system ready');
851 |     }
852 |   }
853 | }
854 | 
```

--------------------------------------------------------------------------------
/scripts/build-index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // Build pipeline step 1: Creates dist/data/index.json (bundle of all docs from submodules)
  2 | import fg from "fast-glob";
  3 | import fs from "fs/promises";
  4 | import path, { join } from "path";
  5 | import matter from "gray-matter";
  6 | 
  7 | interface DocEntry {
  8 |   id: string;              // "/sapui5/<rel-path>", "/cap/<rel-path>", "/openui5-api/<rel-path>", or "/openui5-samples/<rel-path>"
  9 |   title: string;
 10 |   description: string;
 11 |   snippetCount: number;
 12 |   relFile: string;         // path relative to sources/…
 13 |   type?: "markdown" | "jsdoc" | "sample" | "markdown-section";  // type of documentation
 14 |   controlName?: string;    // extracted UI5 control name (e.g., "Wizard", "Button")
 15 |   namespace?: string;      // UI5 namespace (e.g., "sap.m", "sap.f")
 16 |   keywords?: string[];     // searchable keywords and tags
 17 |   properties?: string[];   // control properties for API docs
 18 |   events?: string[];       // control events for API docs
 19 |   aggregations?: string[]; // control aggregations for API docs
 20 |   parentDocument?: string; // for sections, the ID of the parent document
 21 |   sectionStartLine?: number; // for sections, the line number where the section starts
 22 |   headingLevel?: number;   // for sections, the heading level (2=##, 3=###, 4=####)
 23 | }
 24 | 
 25 | interface LibraryBundle {
 26 |   id: string;              // "/sapui5" | "/cap" | "/openui5-api" | "/openui5-samples"
 27 |   name: string;            // "SAPUI5", "CAP", "OpenUI5 API", "OpenUI5 Samples"
 28 |   description: string;
 29 |   docs: DocEntry[];
 30 | }
 31 | 
 32 | interface SourceConfig {
 33 |   repoName: string;
 34 |   absDir: string;
 35 |   id: string;
 36 |   name: string;
 37 |   description: string;
 38 |   filePattern: string;
 39 |   exclude?: string;
 40 |   type: "markdown" | "jsdoc" | "sample";
 41 | }
 42 | 
 43 | const SOURCES: SourceConfig[] = [
 44 |   {
 45 |     repoName: "sapui5-docs",
 46 |     absDir: join("sources", "sapui5-docs", "docs"),
 47 |     id: "/sapui5",
 48 |     name: "SAPUI5",
 49 |     description: "Official SAPUI5 Markdown documentation",
 50 |     filePattern: "**/*.md",
 51 |     type: "markdown" as const
 52 |   },
 53 |   {
 54 |     repoName: "cap-docs",
 55 |     absDir: join("sources", "cap-docs"),
 56 |     id: "/cap",
 57 |     name: "SAP Cloud Application Programming Model (CAP)",
 58 |     description: "CAP (Capire) reference & guides",
 59 |     filePattern: "**/*.md",
 60 |     type: "markdown" as const
 61 |   },
 62 |   {
 63 |     repoName: "openui5",
 64 |     absDir: join("sources", "openui5", "src"),
 65 |     id: "/openui5-api",
 66 |     name: "OpenUI5 API",
 67 |     description: "OpenUI5 Control API documentation and JSDoc",
 68 |     filePattern: "**/src/**/*.js",
 69 |     exclude: "**/test/**/*",
 70 |     type: "jsdoc" as const
 71 |   },
 72 |   {
 73 |     repoName: "openui5",
 74 |     absDir: join("sources", "openui5", "src"),
 75 |     id: "/openui5-samples",
 76 |     name: "OpenUI5 Samples", 
 77 |     description: "OpenUI5 demokit sample applications and code examples",
 78 |     filePattern: "**/demokit/sample/**/*.{js,xml,json,html}",
 79 |     type: "sample" as const
 80 |   },
 81 |   {
 82 |     repoName: "wdi5",
 83 |     absDir: join("sources", "wdi5", "docs"),
 84 |     id: "/wdi5",
 85 |     name: "wdi5",
 86 |     description: "wdi5 end-to-end test framework documentation",
 87 |     filePattern: "**/*.md",
 88 |     type: "markdown" as const
 89 |   },
 90 |   {
 91 |     repoName: "ui5-tooling",
 92 |     absDir: join("sources", "ui5-tooling", "docs"),
 93 |     id: "/ui5-tooling",
 94 |     name: "UI5 Tooling ",
 95 |     description: "UI5 Tooling documentation",
 96 |     filePattern: "**/*.md",
 97 |     type: "markdown" as const
 98 |   },
 99 |   {
100 |     repoName: "cloud-mta-build-tool",
101 |     absDir: join("sources", "cloud-mta-build-tool", "docs", "docs"),
102 |     id: "/cloud-mta-build-tool",
103 |     name: "Cloud MTA Build Tool",
104 |     description: "Cloud MTA Build Tool documentation",
105 |     filePattern: "**/*.md",
106 |     type: "markdown" as const
107 |   },
108 |   {
109 |     repoName: "ui5-webcomponents",
110 |     absDir: join("sources", "ui5-webcomponents", "docs"),
111 |     id: "/ui5-webcomponents",
112 |     name: "UI5 Web Components",
113 |     description: "UI5 Web Components documentation",
114 |     filePattern: "**/*.md",
115 |     type: "markdown" as const
116 |   },
117 |   {
118 |     repoName: "cloud-sdk",
119 |     absDir: join("sources", "cloud-sdk", "docs-js"),
120 |     id: "/cloud-sdk-js",
121 |     name: "Cloud SDK (JavaScript)",
122 |     description: "Cloud SDK (JavaScript) documentation",
123 |     filePattern: "**/*.mdx",
124 |     type: "markdown" as const
125 |   },
126 |   {
127 |     repoName: "cloud-sdk",
128 |     absDir: join("sources", "cloud-sdk", "docs-java"),
129 |     id: "/cloud-sdk-java",
130 |     name: "Cloud SDK (Java)",
131 |     description: "Cloud SDK (Java) documentation",
132 |     filePattern: "**/*.mdx",
133 |     type: "markdown" as const
134 |   },
135 |   {
136 |     repoName: "cloud-sdk-ai",
137 |     absDir: join("sources", "cloud-sdk-ai", "docs-js"),
138 |     id: "/cloud-sdk-ai-js",
139 |     name: "Cloud SDK AI (JavaScript)",
140 |     description: "Cloud SDK AI (JavaScript) documentation",
141 |     filePattern: "**/*.mdx",
142 |     type: "markdown" as const
143 |   },
144 |   {
145 |     repoName: "cloud-sdk-ai",
146 |     absDir: join("sources", "cloud-sdk-ai", "docs-java"),
147 |     id: "/cloud-sdk-ai-java",
148 |     name: "Cloud SDK AI (Java)",
149 |     description: "Cloud SDK AI (Java) documentation",
150 |     filePattern: "**/*.mdx",
151 |     type: "markdown" as const
152 |   },
153 |   {
154 |     repoName: "ui5-typescript",
155 |     absDir: join("sources", "ui5-typescript"),
156 |     id: "/ui5-typescript",
157 |     name: "UI5 TypeScript",
158 |     description: "Official entry point to anything TypeScript related for UI5",
159 |     filePattern: "*.md",
160 |     type: "markdown" as const
161 |   },
162 |   {
163 |     repoName: "ui5-cc-spreadsheetimporter",
164 |     absDir: join("sources", "ui5-cc-spreadsheetimporter", "docs"),
165 |     id: "/ui5-cc-spreadsheetimporter",
166 |     name: "UI5 CC Spreadsheet Importer",
167 |     description: "UI5 Custom Control for importing spreadsheet data",
168 |     filePattern: "**/*.md",
169 |     type: "markdown" as const
170 |   },
171 |   {
172 |     repoName: "abap-cheat-sheets",
173 |     absDir: join("sources", "abap-cheat-sheets"),
174 |     id: "/abap-cheat-sheets",
175 |     name: "ABAP Cheat Sheets",
176 |     description: "Comprehensive ABAP syntax examples and cheat sheets",
177 |     filePattern: "*.md",
178 |     type: "markdown" as const
179 |   },
180 |   {
181 |     repoName: "sap-styleguides",
182 |     absDir: join("sources", "sap-styleguides"),
183 |     id: "/sap-styleguides",
184 |     name: "SAP Style Guides",
185 |     description: "SAP coding style guides and best practices including Clean ABAP",
186 |     filePattern: "**/*.md",
187 |     type: "markdown" as const
188 |   },
189 |   {
190 |     repoName: "dsag-abap-leitfaden",
191 |     absDir: join("sources", "dsag-abap-leitfaden", "docs"),
192 |     id: "/dsag-abap-leitfaden",
193 |     name: "DSAG ABAP Leitfaden",
194 |     description: "German ABAP guidelines and best practices by DSAG",
195 |     filePattern: "**/*.md",
196 |     type: "markdown" as const
197 |   },
198 |   {
199 |     repoName: "abap-fiori-showcase",
200 |     absDir: join("sources", "abap-fiori-showcase"),
201 |     id: "/abap-fiori-showcase",
202 |     name: "ABAP Platform Fiori Feature Showcase",
203 |     description: "Annotation-driven SAP Fiori Elements features for OData V4 using ABAP RAP",
204 |     filePattern: "*.md",
205 |     type: "markdown" as const
206 |   },
207 |   {
208 |     repoName: "cap-fiori-showcase", 
209 |     absDir: join("sources", "cap-fiori-showcase"),
210 |     id: "/cap-fiori-showcase",
211 |     name: "CAP Fiori Elements Feature Showcase",
212 |     description: "SAP Fiori Elements features and annotations showcase using CAP",
213 |     filePattern: "*.md",
214 |     type: "markdown" as const
215 |   },
216 |   {
217 |     repoName: "abap-docs",
218 |     absDir: join("sources", "abap-docs", "docs", "7.58", "md"),
219 |     id: "/abap-docs-758",
220 |     name: "ABAP Keyword Documentation (7.58)",
221 |     description: "Official ABAP language reference and syntax documentation (version 7.58) - individual files optimized for LLM consumption",
222 |     filePattern: "*.md",
223 |     type: "markdown" as const
224 |   },
225 |   {
226 |     repoName: "abap-docs",
227 |     absDir: join("sources", "abap-docs", "docs", "7.57", "md"),
228 |     id: "/abap-docs-757",
229 |     name: "ABAP Keyword Documentation (7.57)",
230 |     description: "Official ABAP language reference and syntax documentation (version 7.57) - individual files optimized for LLM consumption",
231 |     filePattern: "*.md",
232 |     type: "markdown" as const
233 |   },
234 |   {
235 |     repoName: "abap-docs",
236 |     absDir: join("sources", "abap-docs", "docs", "7.56", "md"),
237 |     id: "/abap-docs-756",
238 |     name: "ABAP Keyword Documentation (7.56)",
239 |     description: "Official ABAP language reference and syntax documentation (version 7.56) - individual files optimized for LLM consumption",
240 |     filePattern: "*.md",
241 |     type: "markdown" as const
242 |   },
243 |   {
244 |     repoName: "abap-docs",
245 |     absDir: join("sources", "abap-docs", "docs", "7.55", "md"),
246 |     id: "/abap-docs-755",
247 |     name: "ABAP Keyword Documentation (7.55)",
248 |     description: "Official ABAP language reference and syntax documentation (version 7.55) - individual files optimized for LLM consumption",
249 |     filePattern: "*.md",
250 |     type: "markdown" as const
251 |   },
252 |   {
253 |     repoName: "abap-docs",
254 |     absDir: join("sources", "abap-docs", "docs", "7.54", "md"),
255 |     id: "/abap-docs-754",
256 |     name: "ABAP Keyword Documentation (7.54)",
257 |     description: "Official ABAP language reference and syntax documentation (version 7.54) - individual files optimized for LLM consumption",
258 |     filePattern: "*.md",
259 |     type: "markdown" as const
260 |   },
261 |   {
262 |     repoName: "abap-docs",
263 |     absDir: join("sources", "abap-docs", "docs", "7.53", "md"),
264 |     id: "/abap-docs-753",
265 |     name: "ABAP Keyword Documentation (7.53)",
266 |     description: "Official ABAP language reference and syntax documentation (version 7.53) - individual files optimized for LLM consumption",
267 |     filePattern: "*.md",
268 |     type: "markdown" as const
269 |   },
270 |   {
271 |     repoName: "abap-docs",
272 |     absDir: join("sources", "abap-docs", "docs", "7.52", "md"),
273 |     id: "/abap-docs-752",
274 |     name: "ABAP Keyword Documentation (7.52)",
275 |     description: "Official ABAP language reference and syntax documentation (version 7.52) - individual files optimized for LLM consumption",
276 |     filePattern: "*.md",
277 |     type: "markdown" as const
278 |   },
279 |   {
280 |     repoName: "abap-docs",
281 |     absDir: join("sources", "abap-docs", "docs", "latest", "md"),
282 |     id: "/abap-docs-latest",
283 |     name: "ABAP Keyword Documentation (Latest)",
284 |     description: "Official ABAP language reference and syntax documentation (latest version) - individual files optimized for LLM consumption",
285 |     filePattern: "*.md",
286 |     type: "markdown" as const
287 |   }
288 | ];
289 | 
290 | // Extract meaningful content from ABAP documentation files
291 | function extractAbapContent(content: string, filename: string): { title: string; description: string; snippetCount: number } {
292 |   const lines = content.split(/\r?\n/);
293 |   
294 |   // Skip attribution header (first few lines with "📖 Official SAP Documentation")
295 |   let contentStart = 0;
296 |   for (let i = 0; i < lines.length; i++) {
297 |     if (lines[i].includes('📖 Official SAP Documentation') || lines[i].startsWith('> **📖')) {
298 |       // Skip until we find the actual content (after attribution and separators)
299 |       for (let j = i; j < lines.length; j++) {
300 |         if (lines[j].trim() === '' || lines[j].includes('* * *') || lines[j].includes('---')) {
301 |           continue;
302 |         }
303 |         if (!lines[j].startsWith('>')) {
304 |           contentStart = j;
305 |           break;
306 |         }
307 |       }
308 |       break;
309 |     }
310 |   }
311 |   
312 |   // Find the actual title (first non-metadata heading)
313 |   let title = filename.replace('.md', '').replace('aben', '');
314 |   for (let i = contentStart; i < lines.length; i++) {
315 |     const line = lines[i].trim();
316 |     if (line && !line.startsWith('AS ABAP Release') && !line.startsWith('[ABAP -') && !line.startsWith('[![') && !line.includes('Mail Feedback')) {
317 |       if (line.match(/^[A-Z][a-zA-Z\s]+$/)) {
318 |         // Found a proper title (like "Inline Declarations")
319 |         title = line;
320 |         contentStart = i + 1;
321 |         break;
322 |       }
323 |     }
324 |   }
325 |   
326 |   // Extract meaningful description from content
327 |   const contentLines = lines.slice(contentStart);
328 |   const meaningfulLines = [];
329 |   
330 |   for (const line of contentLines) {
331 |     const trimmed = line.trim();
332 |     
333 |     // Skip empty lines, separators, and navigation
334 |     if (!trimmed || trimmed === '---' || trimmed === '* * *' || trimmed.startsWith('[ABAP -') || trimmed.includes('Mail Feedback')) {
335 |       continue;
336 |     }
337 |     
338 |     // Skip metadata lines
339 |     if (trimmed.startsWith('AS ABAP Release') || trimmed.includes('©Copyright')) {
340 |       continue;
341 |     }
342 |     
343 |     // Stop at "Continue" or "Programming Guideline" sections
344 |     if (trimmed.startsWith('Continue') || trimmed.startsWith('Programming Guideline')) {
345 |       break;
346 |     }
347 |     
348 |     meaningfulLines.push(trimmed);
349 |     
350 |     // Stop when we have enough content for a good description
351 |     if (meaningfulLines.join(' ').length > 300) {
352 |       break;
353 |     }
354 |   }
355 |   
356 |   // Build description from meaningful content
357 |   let description = meaningfulLines.join(' ').trim();
358 |   
359 |   // If description is too short, add version info
360 |   if (description.length < 50) {
361 |     const versionMatch = filename.match(/abap-docs-(\d+)/);
362 |     const version = versionMatch ? versionMatch[1] : '7.58';
363 |     description = `${title} - ABAP ${version} language reference`;
364 |   }
365 |   
366 |   // Extract ABAP-specific terms for better searchability
367 |   const abapTerms: string[] = [];
368 |   const descriptionLower = description.toLowerCase();
369 |   
370 |   // Common ABAP statement keywords
371 |   const statements = ['data', 'final', 'field-symbol', 'select', 'loop', 'if', 'try', 'catch', 'class', 'method'];
372 |   statements.forEach(stmt => {
373 |     if (descriptionLower.includes(stmt)) {
374 |       abapTerms.push(stmt);
375 |     }
376 |   });
377 |   
378 |   // Add statement context if found
379 |   if (abapTerms.length > 0) {
380 |     description += ` | Statements: ${abapTerms.join(', ')}`;
381 |   }
382 |   
383 |   // Count code snippets (ABAP typically has fewer but more meaningful ones)
384 |   const snippetCount = (content.match(/```/g)?.length || 0) / 2;
385 |   
386 |   return {
387 |     title,
388 |     description: description.substring(0, 400), // Allow longer descriptions for ABAP
389 |     snippetCount
390 |   };
391 | }
392 | 
393 | // Extract information from sample files (JS, XML, JSON, HTML)
394 | function extractSampleInfo(content: string, filePath: string) {
395 |   const fileName = path.basename(filePath);
396 |   const fileExt = path.extname(filePath);
397 |   const sampleDir = path.dirname(filePath);
398 |   
399 |   // Extract control name from the path (e.g., "Button", "Wizard", "Table")
400 |   const pathParts = sampleDir.split('/');
401 |   const sampleIndex = pathParts.findIndex(part => part === 'sample');
402 |   const controlName = sampleIndex >= 0 && sampleIndex < pathParts.length - 1 
403 |     ? pathParts[sampleIndex + 1] 
404 |     : path.basename(sampleDir);
405 |   
406 |   let title = `${controlName} Sample - ${fileName}`;
407 |   let description = `Sample implementation of ${controlName} control`;
408 |   let snippetCount = 0;
409 |   
410 |   // Extract specific information based on file type
411 |   if (fileExt === '.js') {
412 |     // JavaScript sample files
413 |     const jsContent = content.toLowerCase();
414 |     
415 |     // Look for common UI5 patterns
416 |     if (jsContent.includes('controller')) {
417 |       title = `${controlName} Sample Controller`;
418 |       description = `Controller implementation for ${controlName} sample`;
419 |     } else if (jsContent.includes('component')) {
420 |       title = `${controlName} Sample Component`;
421 |       description = `Component definition for ${controlName} sample`;
422 |     }
423 |     
424 |     // Count meaningful code patterns
425 |     const codePatterns = [
426 |       /function\s*\(/g,
427 |       /onPress\s*:/g,
428 |       /on[A-Z][a-zA-Z]*\s*:/g,
429 |       /\.attach[A-Z][a-zA-Z]*/g,
430 |       /new\s+sap\./g
431 |     ];
432 |     
433 |     snippetCount = codePatterns.reduce((count, pattern) => {
434 |       return count + (content.match(pattern)?.length || 0);
435 |     }, 0);
436 |     
437 |   } else if (fileExt === '.xml') {
438 |     // XML view files
439 |     title = `${controlName} Sample View`;
440 |     description = `XML view implementation for ${controlName} sample`;
441 |     
442 |     // Count XML controls and bindings
443 |     const xmlPatterns = [
444 |       /<[a-zA-Z][^>]*>/g,
445 |       /\{[^}]+\}/g,  // bindings
446 |       /press=/g,
447 |       /text=/g
448 |     ];
449 |     
450 |     snippetCount = xmlPatterns.reduce((count, pattern) => {
451 |       return count + (content.match(pattern)?.length || 0);
452 |     }, 0);
453 |     
454 |   } else if (fileExt === '.json') {
455 |     // Manifest or model files
456 |     if (fileName.includes('manifest')) {
457 |       title = `${controlName} Sample Manifest`;
458 |       description = `Application manifest for ${controlName} sample`;
459 |     } else {
460 |       title = `${controlName} Sample Data`;
461 |       description = `Sample data model for ${controlName} control`;
462 |     }
463 |     
464 |     try {
465 |       const jsonObj = JSON.parse(content);
466 |       snippetCount = Object.keys(jsonObj).length;
467 |     } catch {
468 |       snippetCount = 1;
469 |     }
470 |     
471 |   } else if (fileExt === '.html') {
472 |     // HTML files
473 |     title = `${controlName} Sample HTML`;
474 |     description = `HTML page for ${controlName} sample`;
475 |     
476 |     const htmlPatterns = [
477 |       /<script[^>]*>/g,
478 |       /<div[^>]*>/g,
479 |       /data-sap-ui-/g
480 |     ];
481 |     
482 |     snippetCount = htmlPatterns.reduce((count, pattern) => {
483 |       return count + (content.match(pattern)?.length || 0);
484 |     }, 0);
485 |   }
486 |   
487 |   // Add library information from path
488 |   const libraryMatch = filePath.match(/src\/([^\/]+)\/test/);
489 |   if (libraryMatch) {
490 |     const library = libraryMatch[1];
491 |     description += ` (${library} library)`;
492 |   }
493 |   
494 |   return {
495 |     title,
496 |     description,
497 |     snippetCount: Math.max(1, snippetCount) // Ensure at least 1
498 |   };
499 | }
500 | 
501 | // Extract JSDoc information from JavaScript files with enhanced metadata
502 | function extractJSDocInfo(content: string, fileName: string) {
503 |   const lines = content.split(/\r?\n/);
504 |   
505 |   // Try to find the main class/control definition
506 |   const classMatch = content.match(/\.extend\s*\(\s*["']([^"']+)["']/);
507 |   const fullControlName = classMatch ? classMatch[1] : path.basename(fileName, ".js");
508 |   
509 |   // Extract namespace and control name
510 |   const namespaceMatch = fullControlName.match(/^(sap\.[^.]+)\.(.*)/);
511 |   const namespace = namespaceMatch ? namespaceMatch[1] : '';
512 |   const controlName = namespaceMatch ? namespaceMatch[2] : fullControlName;
513 |   
514 |   // Extract main class JSDoc comment
515 |   const jsdocMatch = content.match(/\/\*\*\s*([\s\S]*?)\*\//);
516 |   let description = "";
517 |   
518 |   if (jsdocMatch) {
519 |     // Clean up JSDoc comment and extract description
520 |     const jsdocContent = jsdocMatch[1]
521 |       .split('\n')
522 |       .map(line => line.replace(/^\s*\*\s?/, ''))
523 |       .join('\n')
524 |       .trim();
525 |     
526 |     // Extract the main description (everything before @tags)
527 |     const firstAtIndex = jsdocContent.indexOf('@');
528 |     description = firstAtIndex > -1 
529 |       ? jsdocContent.substring(0, firstAtIndex).trim()
530 |       : jsdocContent;
531 |     
532 |     // Clean up common JSDoc patterns
533 |     description = description
534 |       .replace(/^\s*Constructor for a new.*$/m, '')
535 |       .replace(/^\s*@param.*$/gm, '')
536 |       .replace(/^\s*@.*$/gm, '')
537 |       .replace(/\n\s*\n/g, '\n')
538 |       .trim();
539 |   }
540 |   
541 |   // Extract properties, events, aggregations with better parsing
542 |   const properties: string[] = [];
543 |   const events: string[] = [];
544 |   const aggregations: string[] = [];
545 |   const keywords: string[] = [];
546 |   
547 |   // Extract properties
548 |   const propertiesSection = content.match(/properties\s*:\s*\{([\s\S]*?)\n\s*\}/);
549 |   if (propertiesSection) {
550 |     const propMatches = propertiesSection[1].matchAll(/(\w+)\s*:\s*\{/g);
551 |     for (const match of propMatches) {
552 |       properties.push(match[1]);
553 |     }
554 |   }
555 |   
556 |   // Extract events  
557 |   const eventsSection = content.match(/events\s*:\s*\{([\s\S]*?)\n\s*\}/);
558 |   if (eventsSection) {
559 |     const eventMatches = eventsSection[1].matchAll(/(\w+)\s*:\s*\{/g);
560 |     for (const match of eventMatches) {
561 |       events.push(match[1]);
562 |     }
563 |   }
564 |   
565 |   // Extract aggregations
566 |   const aggregationsSection = content.match(/aggregations\s*:\s*\{([\s\S]*?)\n\s*\}/);
567 |   if (aggregationsSection) {
568 |     const aggMatches = aggregationsSection[1].matchAll(/(\w+)\s*:\s*\{/g);
569 |     for (const match of aggMatches) {
570 |       aggregations.push(match[1]);
571 |     }
572 |   }
573 |   
574 |   // Generate keywords based on control name and content
575 |   keywords.push(controlName.toLowerCase());
576 |   if (namespace) keywords.push(namespace);
577 |   if (fullControlName !== controlName) keywords.push(fullControlName);
578 |   
579 |   // Add common UI5 control keywords based on control name
580 |   const controlLower = controlName.toLowerCase();
581 |   if (controlLower.includes('wizard')) keywords.push('wizard', 'step', 'multi-step', 'process');
582 |   if (controlLower.includes('button')) keywords.push('button', 'click', 'press', 'action');
583 |   if (controlLower.includes('table')) keywords.push('table', 'grid', 'data', 'row', 'column');
584 |   if (controlLower.includes('dialog')) keywords.push('dialog', 'popup', 'modal', 'overlay');
585 |   if (controlLower.includes('input')) keywords.push('input', 'field', 'text', 'form');
586 |   if (controlLower.includes('list')) keywords.push('list', 'item', 'collection');
587 |   if (controlLower.includes('panel')) keywords.push('panel', 'container', 'layout');
588 |   if (controlLower.includes('page')) keywords.push('page', 'navigation', 'view');
589 |   
590 |   // Add property/event-based keywords
591 |   if (properties.includes('text')) keywords.push('text');
592 |   if (properties.includes('value')) keywords.push('value');
593 |   if (events.includes('press')) keywords.push('press', 'click');
594 |   if (events.includes('change')) keywords.push('change', 'update');
595 |   
596 |   // Count code blocks and property definitions
597 |   const codeBlockCount = (content.match(/```/g)?.length || 0) / 2;
598 |   const propertyCount = properties.length + events.length + aggregations.length;
599 |   
600 |   return {
601 |     title: fullControlName,
602 |     description: description || `OpenUI5 control: ${fullControlName}`,
603 |     snippetCount: Math.max(1, codeBlockCount + Math.floor(propertyCount / 3)),
604 |     controlName,
605 |     namespace,
606 |     keywords: [...new Set(keywords)],
607 |     properties,
608 |     events,
609 |     aggregations
610 |   };
611 | }
612 | 
613 | function extractMarkdownSections(content: string, lines: string[], src: any, relFile: string, docs: DocEntry[]) {
614 |   const sections: { title: string; content: string; startLine: number; level: number }[] = [];
615 |   let currentSection: { title: string; content: string; startLine: number; level: number } | null = null;
616 |   
617 |   for (let i = 0; i < lines.length; i++) {
618 |     const line = lines[i];
619 |     
620 |     // Check for headings (##, ###, ####)
621 |     let headingLevel = 0;
622 |     let headingText = '';
623 |     
624 |     if (line.startsWith('#### ')) {
625 |       headingLevel = 4;
626 |       headingText = line.slice(5).trim();
627 |     } else if (line.startsWith('### ')) {
628 |       headingLevel = 3;
629 |       headingText = line.slice(4).trim();
630 |     } else if (line.startsWith('## ')) {
631 |       headingLevel = 2;
632 |       headingText = line.slice(3).trim();
633 |     }
634 |     
635 |     if (headingLevel > 0) {
636 |       // Save previous section if it exists
637 |       if (currentSection) {
638 |         sections.push(currentSection);
639 |       }
640 |       
641 |       // Start new section
642 |       currentSection = {
643 |         title: headingText,
644 |         content: '',
645 |         startLine: i,
646 |         level: headingLevel
647 |       };
648 |     } else if (currentSection) {
649 |       // Add content to current section
650 |       currentSection.content += line + '\n';
651 |     }
652 |   }
653 |   
654 |   // Add the last section
655 |   if (currentSection) {
656 |     sections.push(currentSection);
657 |   }
658 |   
659 |   // Create separate docs entries for meaningful sections
660 |   for (const section of sections) {
661 |     // Skip very short sections or those with placeholder titles
662 |     if (section.content.trim().length < 100 || section.title.length < 3) {
663 |       continue;
664 |     }
665 |     
666 |     // Generate description from section content, including code blocks for better searchability
667 |     const contentLines = section.content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
668 |     
669 |     // Extract code blocks content for technical terms
670 |     const codeBlocks = section.content.match(/```[\s\S]*?```/g) || [];
671 |     const codeContent = codeBlocks
672 |       .map(block => block.replace(/```[\w]*\n?/g, '').replace(/```/g, ''))
673 |       .join(' ')
674 |       .replace(/\s+/g, ' ')
675 |       .trim();
676 |     
677 |     // Combine description with code content for better indexing
678 |     let description = contentLines.slice(0, 3).join(' ').trim() || section.title;
679 |     
680 |     // Include important technical terms from code blocks (like annotation qualifiers)
681 |     if (codeContent) {
682 |       // Extract meaningful technical terms (identifiers, annotation qualifiers, etc.)
683 |       const technicalTerms = (codeContent.match(/[@#]?\w+(?:\.\w+)*(?:#\w+)?/g) || [])
684 |         .filter((term: string) => term.length > 3 && !['true', 'false', 'null', 'undefined', 'function', 'return'].includes(term.toLowerCase()))
685 |         .slice(0, 10); // Limit to prevent bloating
686 |       
687 |       if (technicalTerms.length > 0) {
688 |         description += ' ' + technicalTerms.join(' ');
689 |       }
690 |     }
691 |     
692 |     // Count code snippets in this section
693 |     const snippetCount = (section.content.match(/```/g)?.length || 0) / 2;
694 |     
695 |     // Create section entry
696 |     const sectionId = `${src.id}/${relFile.replace(/\.md$/, "")}#${section.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;
697 |     
698 |     docs.push({
699 |       id: sectionId,
700 |       title: section.title,
701 |       description: description.substring(0, 300) + (description.length > 300 ? '...' : ''),
702 |       snippetCount,
703 |       relFile,
704 |       type: 'markdown-section' as any,
705 |       parentDocument: `${src.id}/${relFile.replace(/\.md$/, "")}`,
706 |       sectionStartLine: section.startLine,
707 |       headingLevel: section.level
708 |     });
709 |   }
710 | }
711 | 
712 | async function main() {
713 |   await fs.mkdir("dist/data", { recursive: true });
714 |   const all: Record<string, LibraryBundle> = {};
715 | 
716 |   for (const src of SOURCES) {
717 |     const patterns = [src.filePattern];
718 |     if (src.exclude) {
719 |       patterns.push(`!${src.exclude}`);
720 |     }
721 |     const files = await fg(patterns, { cwd: src.absDir, absolute: true });
722 | 
723 |     const docs: DocEntry[] = [];
724 | 
725 |     for (const absPath of files) {
726 |       const rel = path.relative(src.absDir, absPath).replace(/\\/g, "/");
727 |       const raw = await fs.readFile(absPath, "utf8");
728 | 
729 | 
730 |       let title: string;
731 |       let description: string;
732 |       let snippetCount: number;
733 |       let id: string;
734 | 
735 |       if (src.type === "markdown") {
736 |         // Handle markdown files with error handling for malformed frontmatter
737 |         let frontmatter, content;
738 |         try {
739 |           const parsed = matter(raw);
740 |           frontmatter = parsed.data;
741 |           content = parsed.content;
742 |         } catch (yamlError: any) {
743 |           console.warn(`YAML parsing failed for ${rel}, using fallback:`, yamlError?.message || yamlError);
744 |           // Fallback: extract content without frontmatter
745 |           const lines = raw.split('\n');
746 |           const contentStartIndex = lines.findIndex((line, index) => line.trim() === '---' && index > 0) + 1;
747 |           frontmatter = {};
748 |           content = contentStartIndex > 0 ? lines.slice(contentStartIndex).join('\n') : raw;
749 |         }
750 |         const lines = content.split(/\r?\n/);
751 | 
752 |         // Use frontmatter for title and description (works for ABAP and other sources)
753 |         title = frontmatter?.title || 
754 |                 lines.find((l) => l.startsWith("# "))?.slice(2).trim() ||
755 |                 path.basename(rel, ".md");
756 |         
757 |         // Enhanced description from frontmatter or content
758 |         if (frontmatter?.description) {
759 |           description = frontmatter.description;
760 |         } else if (frontmatter?.synopsis && content.includes("{{ $frontmatter.synopsis }}")) {
761 |           description = frontmatter.synopsis;
762 |         } else {
763 |           // Fallback to content extraction
764 |           const rawDescription = lines.find((l) => l.trim() && !l.startsWith("#"))?.trim() || "";
765 |           description = rawDescription;
766 |         }
767 |         
768 |         snippetCount = (content.match(/```/g)?.length || 0) / 2;
769 |         
770 |         id = `${src.id}/${rel.replace(/\.md$/, "")}`;
771 |         
772 |         // Extract individual sections as separate entries for all markdown docs
773 |         if (content.includes('##')) {
774 |           extractMarkdownSections(content, lines, src, rel, docs);
775 |         }
776 |       } else if (src.type === "jsdoc") {
777 |         // Handle JavaScript files with JSDoc
778 |         const jsDocInfo = extractJSDocInfo(raw, path.basename(absPath));
779 |         title = jsDocInfo.title;
780 |         description = jsDocInfo.description;
781 |         snippetCount = jsDocInfo.snippetCount;
782 |         id = `${src.id}/${rel.replace(/\.js$/, "")}`;
783 |         
784 |         // Skip files that don't look like UI5 controls
785 |         if (!raw.includes('.extend') || !raw.includes('metadata')) {
786 |           continue;
787 |         }
788 |         
789 |         docs.push({ 
790 |           id, 
791 |           title, 
792 |           description, 
793 |           snippetCount, 
794 |           relFile: rel,
795 |           type: src.type,
796 |           controlName: jsDocInfo.controlName,
797 |           namespace: jsDocInfo.namespace,
798 |           keywords: jsDocInfo.keywords,
799 |           properties: jsDocInfo.properties,
800 |           events: jsDocInfo.events,
801 |           aggregations: jsDocInfo.aggregations
802 |         });
803 |         
804 |       } else if (src.type === "sample") {
805 |         // Handle sample files (JS, XML, JSON, HTML)
806 |         const sampleInfo = extractSampleInfo(raw, rel);
807 |         title = sampleInfo.title;
808 |         description = sampleInfo.description;
809 |         snippetCount = sampleInfo.snippetCount;
810 |         id = `${src.id}/${rel.replace(/\.(js|xml|json|html)$/, "")}`;
811 |         
812 |         // Skip empty files or non-meaningful samples
813 |         if (raw.trim().length < 50) {
814 |           continue;
815 |         }
816 |         
817 |         // Extract control name from sample path for better searchability
818 |         const pathParts = rel.split('/');
819 |         const sampleIndex = pathParts.findIndex(part => part === 'sample');
820 |         const controlName = sampleIndex >= 0 && sampleIndex < pathParts.length - 1 
821 |           ? pathParts[sampleIndex + 1] 
822 |           : path.basename(path.dirname(rel));
823 |         
824 |         // Generate sample keywords
825 |         const keywords = [controlName.toLowerCase(), 'sample', 'example'];
826 |         if (rel.includes('.xml')) keywords.push('view', 'xml');
827 |         if (rel.includes('.js')) keywords.push('controller', 'javascript');
828 |         if (rel.includes('.json')) keywords.push('model', 'data', 'configuration');
829 |         if (rel.includes('manifest')) keywords.push('manifest', 'app');
830 |         
831 |         docs.push({ 
832 |           id, 
833 |           title, 
834 |           description, 
835 |           snippetCount, 
836 |           relFile: rel,
837 |           type: src.type,
838 |           controlName,
839 |           keywords: [...new Set(keywords)]
840 |         });
841 |         
842 |       } else {
843 |         continue; // Skip unknown file types
844 |       }
845 | 
846 |       // For markdown files, still use the basic structure
847 |       if (src.type === "markdown") {
848 | 
849 |         docs.push({ 
850 |           id, 
851 |           title, 
852 |           description, 
853 |           snippetCount, 
854 |           relFile: rel,
855 |           type: src.type
856 |         });
857 |       }
858 |     }
859 | 
860 |     const bundle: LibraryBundle = {
861 |       id: src.id,
862 |       name: src.name,
863 |       description: src.description,
864 |       docs
865 |     };
866 | 
867 |     all[src.id] = bundle;
868 |     await fs.writeFile(
869 |       path.join("dist", "data", `data${src.id}.json`.replace(/\//g, "_")),
870 |       JSON.stringify(bundle, null, 2)
871 |     );
872 |   }
873 | 
874 |   await fs.writeFile("dist/data/index.json", JSON.stringify(all, null, 2));
875 |   console.log("✅  Index built with", Object.keys(all).length, "libraries.");
876 | }
877 | 
878 | main(); 
```
Page 4/5FirstPrevNextLast