catalyst-relay 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1744 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createClient: () => createClient,
24
+ err: () => err,
25
+ ok: () => ok
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/types/result.ts
30
+ function ok(value) {
31
+ return [value, null];
32
+ }
33
+ function err(error) {
34
+ return [null, error];
35
+ }
36
+
37
+ // src/core/utils/xml.ts
38
+ var import_xmldom = require("@xmldom/xmldom");
39
+ function safeParseXml(xmlString) {
40
+ if (!xmlString || xmlString.trim().length === 0) {
41
+ return err(new Error("Empty XML string provided"));
42
+ }
43
+ try {
44
+ const parser = new import_xmldom.DOMParser();
45
+ const doc = parser.parseFromString(xmlString, "text/xml");
46
+ const parseError = doc.getElementsByTagName("parsererror");
47
+ if (parseError.length > 0) {
48
+ const errorNode = parseError[0];
49
+ const errorText = errorNode?.textContent || "Unknown XML parsing error";
50
+ return err(new Error(`XML parsing failed: ${errorText}`));
51
+ }
52
+ return ok(doc);
53
+ } catch (error) {
54
+ if (error instanceof Error) {
55
+ return err(error);
56
+ }
57
+ return err(new Error("Unknown XML parsing error"));
58
+ }
59
+ }
60
+ function extractLockHandle(xml) {
61
+ if (!xml) {
62
+ return err(new Error("Empty XML provided"));
63
+ }
64
+ const [doc, parseErr] = safeParseXml(xml);
65
+ if (parseErr) {
66
+ return err(parseErr);
67
+ }
68
+ const lockHandleElements = doc.getElementsByTagName("LOCK_HANDLE");
69
+ if (lockHandleElements.length === 0) {
70
+ return err(new Error("LOCK_HANDLE element not found in XML"));
71
+ }
72
+ const lockHandleElement = lockHandleElements[0];
73
+ const lockHandle = lockHandleElement?.textContent;
74
+ if (!lockHandle || lockHandle.trim().length === 0) {
75
+ return err(new Error("LOCK_HANDLE element is empty"));
76
+ }
77
+ return ok(lockHandle.trim());
78
+ }
79
+ function extractError(xml) {
80
+ if (!xml) {
81
+ return "No error message found";
82
+ }
83
+ const [doc, parseErr] = safeParseXml(xml);
84
+ if (parseErr) {
85
+ return "Failed to parse error XML";
86
+ }
87
+ const messageElements = doc.getElementsByTagName("message");
88
+ if (messageElements.length === 0) {
89
+ return "No message found";
90
+ }
91
+ const messageElement = messageElements[0];
92
+ const message = messageElement?.textContent;
93
+ return message || "No message found";
94
+ }
95
+ function escapeXml(str) {
96
+ if (!str) {
97
+ return "";
98
+ }
99
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
100
+ }
101
+ function dictToAbapXml(data, root = "DATA") {
102
+ const innerElements = Object.entries(data).map(([key, value]) => {
103
+ if (value) {
104
+ return `<${key}>${escapeXml(value)}</${key}>`;
105
+ }
106
+ return `<${key}/>`;
107
+ }).join("\n ");
108
+ return `<?xml version="1.0" encoding="UTF-8"?>
109
+ <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
110
+ <asx:values>
111
+ <${root}>
112
+ ${innerElements}
113
+ </${root}>
114
+ </asx:values>
115
+ </asx:abap>`;
116
+ }
117
+
118
+ // src/core/utils/sql.ts
119
+ var SqlValidationError = class extends Error {
120
+ constructor(message) {
121
+ super(message);
122
+ this.name = "SqlValidationError";
123
+ }
124
+ };
125
+ function validateSqlInput(input, maxLength = 1e4) {
126
+ if (typeof input !== "string") {
127
+ return err(new SqlValidationError("Input must be a string"));
128
+ }
129
+ if (input.length > maxLength) {
130
+ return err(new SqlValidationError(`Input exceeds maximum length of ${maxLength}`));
131
+ }
132
+ const dangerousPatterns = [
133
+ {
134
+ pattern: /\b(DROP|DELETE|INSERT|UPDATE|ALTER|CREATE|TRUNCATE)\s+/i,
135
+ description: "DDL/DML keywords (DROP, DELETE, INSERT, etc.)"
136
+ },
137
+ {
138
+ pattern: /;[\s]*\w/,
139
+ description: "Statement termination followed by another statement"
140
+ },
141
+ {
142
+ pattern: /--[\s]*\w/,
143
+ description: "SQL comments with content"
144
+ },
145
+ {
146
+ pattern: /\/\*.*?\*\//,
147
+ description: "Block comments"
148
+ },
149
+ {
150
+ pattern: /\bEXEC(UTE)?\s*\(/i,
151
+ description: "Procedure execution"
152
+ },
153
+ {
154
+ pattern: /\bSP_\w+/i,
155
+ description: "Stored procedures"
156
+ },
157
+ {
158
+ pattern: /\bXP_\w+/i,
159
+ description: "Extended stored procedures"
160
+ },
161
+ {
162
+ pattern: /\bUNION\s+(ALL\s+)?SELECT/i,
163
+ description: "Union-based injection"
164
+ },
165
+ {
166
+ pattern: /@@\w+/,
167
+ description: "System variables"
168
+ },
169
+ {
170
+ pattern: /\bDECLARE\s+@/i,
171
+ description: "Variable declarations"
172
+ },
173
+ {
174
+ pattern: /\bCAST\s*\(/i,
175
+ description: "Type casting"
176
+ },
177
+ {
178
+ pattern: /\bCONVERT\s*\(/i,
179
+ description: "Type conversion"
180
+ }
181
+ ];
182
+ for (const { pattern, description } of dangerousPatterns) {
183
+ if (pattern.test(input)) {
184
+ return err(new SqlValidationError(
185
+ `Input contains potentially dangerous SQL pattern: ${description}`
186
+ ));
187
+ }
188
+ }
189
+ const specialCharMatches = input.match(/[;'"\\]/g);
190
+ const specialCharCount = specialCharMatches ? specialCharMatches.length : 0;
191
+ if (specialCharCount > 5) {
192
+ return err(new SqlValidationError("Input contains excessive special characters"));
193
+ }
194
+ const singleQuoteCount = (input.match(/'/g) || []).length;
195
+ if (singleQuoteCount % 2 !== 0) {
196
+ return err(new SqlValidationError("Unbalanced single quotes detected"));
197
+ }
198
+ const doubleQuoteCount = (input.match(/"/g) || []).length;
199
+ if (doubleQuoteCount % 2 !== 0) {
200
+ return err(new SqlValidationError("Unbalanced double quotes detected"));
201
+ }
202
+ return ok(true);
203
+ }
204
+
205
+ // src/core/utils/csrf.ts
206
+ var FETCH_CSRF_TOKEN = "fetch";
207
+ var CSRF_TOKEN_HEADER = "x-csrf-token";
208
+
209
+ // src/core/utils/headers.ts
210
+ var BASE_HEADERS = {
211
+ "X-sap-adt-sessiontype": "stateful",
212
+ "User-Agent": "Eclipse/4.34.0 ADT/3.46.0",
213
+ "X-sap-adt-profiling": "server-time"
214
+ };
215
+ var DEFAULT_TIMEOUT = 3e4;
216
+ function buildRequestHeaders(baseHeaders, customHeaders, auth, csrfToken) {
217
+ const headers = {
218
+ ...baseHeaders,
219
+ ...customHeaders ?? {}
220
+ };
221
+ if (auth?.type === "basic") {
222
+ const credentials = btoa(`${auth.username}:${auth.password}`);
223
+ headers["Authorization"] = `Basic ${credentials}`;
224
+ }
225
+ if (csrfToken && csrfToken !== FETCH_CSRF_TOKEN && !customHeaders?.[CSRF_TOKEN_HEADER]) {
226
+ headers[CSRF_TOKEN_HEADER] = csrfToken;
227
+ }
228
+ return headers;
229
+ }
230
+ function extractCsrfToken(headers) {
231
+ const token = headers.get(CSRF_TOKEN_HEADER) || headers.get(CSRF_TOKEN_HEADER.toLowerCase());
232
+ if (!token || token === FETCH_CSRF_TOKEN) {
233
+ return null;
234
+ }
235
+ return token;
236
+ }
237
+
238
+ // src/core/utils/logging.ts
239
+ var isActive = false;
240
+ function debug(message) {
241
+ if (isActive) {
242
+ console.log(`[DEBUG] ${message}`);
243
+ }
244
+ }
245
+ function debugError(message, cause) {
246
+ if (!isActive) return;
247
+ console.error(`[DEBUG] ${message}`);
248
+ if (cause !== void 0) {
249
+ console.error(`[DEBUG] Cause:`, cause);
250
+ }
251
+ }
252
+
253
+ // src/types/config.ts
254
+ var import_zod = require("zod");
255
+ var clientConfigSchema = import_zod.z.object({
256
+ url: import_zod.z.string().url(),
257
+ client: import_zod.z.string().min(1).max(3),
258
+ auth: import_zod.z.discriminatedUnion("type", [
259
+ import_zod.z.object({
260
+ type: import_zod.z.literal("basic"),
261
+ username: import_zod.z.string().min(1),
262
+ password: import_zod.z.string().min(1)
263
+ }),
264
+ import_zod.z.object({
265
+ type: import_zod.z.literal("saml"),
266
+ username: import_zod.z.string().min(1),
267
+ password: import_zod.z.string().min(1),
268
+ provider: import_zod.z.string().optional()
269
+ }),
270
+ import_zod.z.object({
271
+ type: import_zod.z.literal("sso"),
272
+ certificate: import_zod.z.string().optional()
273
+ })
274
+ ]),
275
+ timeout: import_zod.z.number().positive().optional(),
276
+ insecure: import_zod.z.boolean().optional()
277
+ });
278
+
279
+ // src/core/session/login.ts
280
+ async function fetchCsrfToken(state, request) {
281
+ const endpoint = state.config.auth.type === "saml" ? "/sap/bc/adt/core/http/sessions" : "/sap/bc/adt/compatibility/graph";
282
+ const contentType = state.config.auth.type === "saml" ? "application/vnd.sap.adt.core.http.session.v3+xml" : "application/xml";
283
+ const headers = {
284
+ [CSRF_TOKEN_HEADER]: FETCH_CSRF_TOKEN,
285
+ "Content-Type": contentType,
286
+ "Accept": contentType
287
+ };
288
+ const [response, requestErr] = await request({
289
+ method: "GET",
290
+ path: endpoint,
291
+ headers
292
+ });
293
+ if (requestErr) {
294
+ return err(new Error(`Failed to fetch CSRF token: ${requestErr.message}`));
295
+ }
296
+ if (!response.ok) {
297
+ const text = await response.text();
298
+ return err(new Error(`CSRF token fetch failed with status ${response.status}: ${text}`));
299
+ }
300
+ const token = extractCsrfToken(response.headers);
301
+ debug(`CSRF token from headers: ${token ? token.substring(0, 20) + "..." : "null"}`);
302
+ if (!token) {
303
+ debug("Response headers:");
304
+ response.headers.forEach((value, key) => debug(` ${key}: ${value.substring(0, 50)}`));
305
+ return err(new Error("No CSRF token returned in response headers"));
306
+ }
307
+ state.csrfToken = token;
308
+ debug(`Stored CSRF token in state: ${state.csrfToken?.substring(0, 20)}...`);
309
+ return ok(token);
310
+ }
311
+ async function login(state, request) {
312
+ if (state.config.auth.type === "saml") {
313
+ return err(new Error("SAML authentication not yet implemented"));
314
+ }
315
+ if (state.config.auth.type === "sso") {
316
+ return err(new Error("SSO authentication not yet implemented"));
317
+ }
318
+ const [token, tokenErr] = await fetchCsrfToken(state, request);
319
+ if (tokenErr) {
320
+ return err(new Error(`Login failed: ${tokenErr.message}`));
321
+ }
322
+ const username = state.config.auth.type === "basic" ? state.config.auth.username : "";
323
+ const session = {
324
+ sessionId: token,
325
+ username,
326
+ expiresAt: Date.now() + 8 * 60 * 60 * 1e3
327
+ };
328
+ state.session = session;
329
+ return ok(session);
330
+ }
331
+ async function logout(state, request) {
332
+ const [response, requestErr] = await request({
333
+ method: "POST",
334
+ path: "/sap/public/bc/icf/logoff"
335
+ });
336
+ if (requestErr) {
337
+ return err(new Error(`Logout failed: ${requestErr.message}`));
338
+ }
339
+ if (!response.ok) {
340
+ const text = await response.text();
341
+ return err(new Error(`Logout failed with status ${response.status}: ${text}`));
342
+ }
343
+ state.csrfToken = null;
344
+ state.session = null;
345
+ return ok(void 0);
346
+ }
347
+ async function sessionReset(state, request) {
348
+ await logout(state, request);
349
+ state.csrfToken = null;
350
+ state.session = null;
351
+ const [, loginErr] = await login(state, request);
352
+ if (loginErr) {
353
+ return err(loginErr);
354
+ }
355
+ return ok(void 0);
356
+ }
357
+
358
+ // src/core/adt/types.ts
359
+ var OBJECT_CONFIG_MAP = {
360
+ "asddls": {
361
+ endpoint: "ddic/ddl/sources",
362
+ nameSpace: 'xmlns:ddl="http://www.sap.com/adt/ddic/ddlsources"',
363
+ rootName: "ddl:ddlSource",
364
+ type: "DDLS/DF",
365
+ label: "View" /* VIEW */,
366
+ extension: "asddls",
367
+ dpEndpoint: "cds",
368
+ dpParam: "ddlSourceName"
369
+ },
370
+ "asdcls": {
371
+ endpoint: "acm/dcl/sources",
372
+ nameSpace: 'xmlns:dcl="http://www.sap.com/adt/acm/dclsources"',
373
+ rootName: "dcl:dclSource",
374
+ type: "DCLS/DL",
375
+ label: "Access Control" /* ACCESS_CONTROL */,
376
+ extension: "asdcls"
377
+ },
378
+ "aclass": {
379
+ endpoint: "oo/classes",
380
+ nameSpace: 'xmlns:class="http://www.sap.com/adt/oo/classes"',
381
+ rootName: "class:abapClass",
382
+ type: "CLAS/OC",
383
+ label: "Class" /* CLASS */,
384
+ extension: "aclass"
385
+ },
386
+ "astabldt": {
387
+ endpoint: "ddic/tables",
388
+ nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
389
+ rootName: "blue:blueSource",
390
+ type: "TABL/DT",
391
+ label: "Table" /* TABLE */,
392
+ extension: "astabldt",
393
+ dpEndpoint: "ddic",
394
+ dpParam: "ddicEntityName"
395
+ },
396
+ "asprog": {
397
+ endpoint: "programs/programs",
398
+ nameSpace: 'xmlns:program="http://www.sap.com/adt/programs/programs"',
399
+ rootName: "program:abapProgram",
400
+ type: "PROG/P",
401
+ label: "ABAP Program" /* PROGRAM */,
402
+ extension: "asprog"
403
+ }
404
+ };
405
+ function getConfigByExtension(extension) {
406
+ return OBJECT_CONFIG_MAP[extension] ?? null;
407
+ }
408
+ function getConfigByType(type) {
409
+ for (const config of Object.values(OBJECT_CONFIG_MAP)) {
410
+ if (config.type === type) {
411
+ return config;
412
+ }
413
+ }
414
+ return null;
415
+ }
416
+ function getAllTypes() {
417
+ return Object.values(OBJECT_CONFIG_MAP).map((config) => config.type);
418
+ }
419
+
420
+ // src/core/adt/helpers.ts
421
+ async function checkResponse(response, requestErr, operation) {
422
+ if (requestErr) return [null, requestErr];
423
+ if (!response) return [null, new Error(`${operation}: No response`)];
424
+ if (!response.ok) {
425
+ const text = await response.text();
426
+ const errorMsg = extractError(text);
427
+ if (errorMsg === "No message found" || errorMsg === "Failed to parse error XML") {
428
+ return [null, new Error(`${operation}: HTTP ${response.status} - ${text.substring(0, 500)}`)];
429
+ }
430
+ return [null, new Error(`${operation}: ${errorMsg}`)];
431
+ }
432
+ return [await response.text(), null];
433
+ }
434
+ function requireConfig(extension) {
435
+ const config = getConfigByExtension(extension);
436
+ if (!config) return [null, new Error(`Unsupported extension: ${extension}`)];
437
+ return [config, null];
438
+ }
439
+
440
+ // src/core/adt/read.ts
441
+ async function readObject(client, object) {
442
+ const [config, configErr] = requireConfig(object.extension);
443
+ if (configErr) return err(configErr);
444
+ const [response, requestErr] = await client.request({
445
+ method: "GET",
446
+ path: `/sap/bc/adt/${config.endpoint}/${object.name}/source/main`,
447
+ headers: { "Accept": "text/plain" }
448
+ });
449
+ const [content, checkErr] = await checkResponse(
450
+ response,
451
+ requestErr,
452
+ `Failed to read ${config.label} ${object.name}`
453
+ );
454
+ if (checkErr) return err(checkErr);
455
+ const result = {
456
+ name: object.name,
457
+ extension: object.extension,
458
+ package: "",
459
+ content
460
+ };
461
+ return ok(result);
462
+ }
463
+
464
+ // src/core/adt/lock.ts
465
+ async function lockObject(client, object) {
466
+ const [config, configErr] = requireConfig(object.extension);
467
+ if (configErr) return err(configErr);
468
+ const [response, requestErr] = await client.request({
469
+ method: "POST",
470
+ path: `/sap/bc/adt/${config.endpoint}/${object.name}/source/main`,
471
+ params: {
472
+ "_action": "LOCK",
473
+ "accessMode": "MODIFY"
474
+ },
475
+ headers: {
476
+ "Accept": "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result"
477
+ }
478
+ });
479
+ const [text, checkErr] = await checkResponse(
480
+ response,
481
+ requestErr,
482
+ `Failed to lock ${config.label} ${object.name}`
483
+ );
484
+ if (checkErr) return err(checkErr);
485
+ const [lockHandle, extractErr] = extractLockHandle(text);
486
+ if (extractErr) {
487
+ return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
488
+ }
489
+ debug(`Lock acquired: handle=${lockHandle}`);
490
+ return ok(lockHandle);
491
+ }
492
+ async function unlockObject(client, object, lockHandle) {
493
+ const [config, configErr] = requireConfig(object.extension);
494
+ if (configErr) return err(configErr);
495
+ const [response, requestErr] = await client.request({
496
+ method: "POST",
497
+ path: `/sap/bc/adt/${config.endpoint}/${object.name}/source/main`,
498
+ params: {
499
+ "_action": "UNLOCK",
500
+ "lockHandle": lockHandle
501
+ }
502
+ });
503
+ const [_, checkErr] = await checkResponse(
504
+ response,
505
+ requestErr,
506
+ `Failed to unlock ${config.label} ${object.name}`
507
+ );
508
+ if (checkErr) return err(checkErr);
509
+ return ok(void 0);
510
+ }
511
+
512
+ // src/core/adt/create.ts
513
+ async function createObject(client, object, packageName, transport, username) {
514
+ const [config, configErr] = requireConfig(object.extension);
515
+ if (configErr) return err(configErr);
516
+ const description = object.description ?? "";
517
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
518
+ <${config.rootName} ${config.nameSpace}
519
+ xmlns:adtcore="http://www.sap.com/adt/core"
520
+ adtcore:description="${escapeXml(description)}"
521
+ adtcore:language="EN"
522
+ adtcore:name="${object.name.toUpperCase()}"
523
+ adtcore:type="${config.type}"
524
+ adtcore:responsible="${username.toUpperCase()}">
525
+
526
+ <adtcore:packageRef adtcore:name="${packageName}"/>
527
+
528
+ </${config.rootName}>`;
529
+ const params = {};
530
+ if (transport) {
531
+ params["corrNr"] = transport;
532
+ }
533
+ const [response, requestErr] = await client.request({
534
+ method: "POST",
535
+ path: `/sap/bc/adt/${config.endpoint}`,
536
+ params,
537
+ headers: { "Content-Type": "application/*" },
538
+ body: body.trim()
539
+ });
540
+ const [_, checkErr] = await checkResponse(
541
+ response,
542
+ requestErr,
543
+ `Failed to create ${config.label} ${object.name}`
544
+ );
545
+ if (checkErr) return err(checkErr);
546
+ return ok(void 0);
547
+ }
548
+
549
+ // src/core/adt/update.ts
550
+ async function updateObject(client, object, lockHandle, transport) {
551
+ const [config, configErr] = requireConfig(object.extension);
552
+ if (configErr) return err(configErr);
553
+ const params = {
554
+ "lockHandle": lockHandle
555
+ };
556
+ if (transport) {
557
+ params["corrNr"] = transport;
558
+ }
559
+ debug(`Update ${object.name}: content length=${object.content?.length ?? 0}`);
560
+ const [response, requestErr] = await client.request({
561
+ method: "PUT",
562
+ path: `/sap/bc/adt/${config.endpoint}/${object.name}/source/main`,
563
+ params,
564
+ headers: { "Content-Type": "*/*" },
565
+ body: object.content
566
+ });
567
+ debug(`Update response: ${response?.status ?? "no response"}, err=${requestErr?.message ?? "none"}`);
568
+ const [_, checkErr] = await checkResponse(
569
+ response,
570
+ requestErr,
571
+ `Failed to update ${config.label} ${object.name}`
572
+ );
573
+ if (checkErr) return err(checkErr);
574
+ return ok(void 0);
575
+ }
576
+
577
+ // src/core/adt/delete.ts
578
+ async function deleteObject(client, object, lockHandle, transport) {
579
+ const [config, configErr] = requireConfig(object.extension);
580
+ if (configErr) return err(configErr);
581
+ const params = {
582
+ "lockHandle": lockHandle
583
+ };
584
+ if (transport) {
585
+ params["corrNr"] = transport;
586
+ }
587
+ const [response, requestErr] = await client.request({
588
+ method: "DELETE",
589
+ path: `/sap/bc/adt/${config.endpoint}/${object.name}/source/main`,
590
+ params,
591
+ headers: { "Accept": "text/plain" }
592
+ });
593
+ const [_, checkErr] = await checkResponse(
594
+ response,
595
+ requestErr,
596
+ `Failed to delete ${config.label} ${object.name}`
597
+ );
598
+ if (checkErr) return err(checkErr);
599
+ return ok(void 0);
600
+ }
601
+
602
+ // src/core/adt/activation.ts
603
+ async function activateObjects(client, objects) {
604
+ if (objects.length === 0) {
605
+ return ok([]);
606
+ }
607
+ const extension = objects[0].extension;
608
+ const config = getConfigByExtension(extension);
609
+ if (!config) return err(new Error(`Unsupported extension: ${extension}`));
610
+ for (const obj of objects) {
611
+ if (obj.extension !== extension) {
612
+ return err(new Error("All objects must have the same extension for batch activation"));
613
+ }
614
+ }
615
+ const objectRefs = objects.map((obj) => `<adtcore:objectReference
616
+ adtcore:uri="/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}"
617
+ adtcore:type="${config.type}"
618
+ adtcore:name="${obj.name}"
619
+ adtcore:description="*"/>`).join("\n ");
620
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
621
+ <adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
622
+ ${objectRefs}
623
+ </adtcore:objectReferences>`;
624
+ const [response, requestErr] = await client.request({
625
+ method: "POST",
626
+ path: "/sap/bc/adt/activation",
627
+ params: {
628
+ "method": "activate",
629
+ "preAuditRequested": "true"
630
+ },
631
+ headers: {
632
+ "Content-Type": "application/xml",
633
+ "Accept": "application/xml"
634
+ },
635
+ body
636
+ });
637
+ if (requestErr) {
638
+ return err(requestErr);
639
+ }
640
+ const text = await response.text();
641
+ debug(`Activation response status: ${response.status}`);
642
+ debug(`Activation response: ${text.substring(0, 500)}`);
643
+ if (!response.ok) {
644
+ const errorMsg = extractError(text);
645
+ return err(new Error(`Activation failed: ${errorMsg}`));
646
+ }
647
+ const [results, parseErr] = extractActivationErrors(objects, text, extension);
648
+ if (parseErr) {
649
+ return err(parseErr);
650
+ }
651
+ return ok(results);
652
+ }
653
+ function extractActivationErrors(objects, xml, _extension) {
654
+ const [doc, parseErr] = safeParseXml(xml);
655
+ if (parseErr) {
656
+ return err(parseErr);
657
+ }
658
+ const errorMap = /* @__PURE__ */ new Map();
659
+ objects.forEach((obj) => errorMap.set(obj.name.toLowerCase(), []));
660
+ const msgElements = doc.getElementsByTagName("msg");
661
+ const startRegex = /#start=(\d+),(\d+)/;
662
+ for (let i = 0; i < msgElements.length; i++) {
663
+ const msg = msgElements[i];
664
+ if (!msg) continue;
665
+ const type = msg.getAttribute("type");
666
+ if (type === "W") continue;
667
+ const objDescr = msg.getAttribute("objDescr");
668
+ const href = msg.getAttribute("href");
669
+ if (!objDescr || !href) continue;
670
+ let line;
671
+ let column;
672
+ const match = startRegex.exec(href);
673
+ if (match && match[1] && match[2]) {
674
+ line = parseInt(match[1], 10);
675
+ column = parseInt(match[2], 10);
676
+ }
677
+ if (!line || !column) continue;
678
+ const matchingObj = objects.find(
679
+ (obj) => objDescr.toLowerCase().includes(obj.name.toLowerCase())
680
+ );
681
+ if (!matchingObj) continue;
682
+ const shortTextElements = msg.getElementsByTagName("txt");
683
+ for (let j = 0; j < shortTextElements.length; j++) {
684
+ const txt = shortTextElements[j];
685
+ if (!txt) continue;
686
+ const text = txt.textContent;
687
+ if (!text) continue;
688
+ const message = {
689
+ severity: type === "E" ? "error" : "warning",
690
+ text,
691
+ line,
692
+ column
693
+ };
694
+ const messages = errorMap.get(matchingObj.name.toLowerCase()) || [];
695
+ messages.push(message);
696
+ errorMap.set(matchingObj.name.toLowerCase(), messages);
697
+ }
698
+ }
699
+ const results = objects.map((obj) => {
700
+ const messages = errorMap.get(obj.name.toLowerCase()) || [];
701
+ const hasErrors = messages.some((m) => m.severity === "error");
702
+ return {
703
+ name: obj.name,
704
+ extension: obj.extension,
705
+ status: hasErrors ? "error" : messages.length > 0 ? "warning" : "success",
706
+ messages
707
+ };
708
+ });
709
+ return ok(results);
710
+ }
711
+
712
+ // src/core/adt/tree.ts
713
+ async function getTree(client, query) {
714
+ const internalQuery = {};
715
+ if (query.package) {
716
+ internalQuery.PACKAGE = {
717
+ name: query.package.startsWith("..") ? query.package : `..${query.package}`,
718
+ hasChildrenOfSameFacet: false
719
+ };
720
+ }
721
+ const [result, resultErr] = await getTreeInternal(client, internalQuery, "*");
722
+ if (resultErr) {
723
+ return err(resultErr);
724
+ }
725
+ return ok(result.nodes);
726
+ }
727
+ async function getTreeInternal(client, query, searchPattern) {
728
+ const body = constructTreeBody(query, searchPattern);
729
+ const [response, requestErr] = await client.request({
730
+ method: "POST",
731
+ path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
732
+ headers: {
733
+ "Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
734
+ "Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
735
+ },
736
+ body
737
+ });
738
+ if (requestErr) {
739
+ return err(requestErr);
740
+ }
741
+ if (!response.ok) {
742
+ const text2 = await response.text();
743
+ const errorMsg = extractError(text2);
744
+ return err(new Error(`Tree discovery failed: ${errorMsg}`));
745
+ }
746
+ const text = await response.text();
747
+ const [result, parseErr] = parseTreeResponse(text);
748
+ if (parseErr) {
749
+ return err(parseErr);
750
+ }
751
+ return ok(result);
752
+ }
753
+ function constructTreeBody(query, searchPattern) {
754
+ const facets = [];
755
+ const specified = {};
756
+ const sortedFacets = ["PACKAGE", "GROUP", "TYPE", "API"];
757
+ for (const facet of sortedFacets) {
758
+ const value = query[facet];
759
+ if (value) {
760
+ specified[facet] = value.name;
761
+ if (!value.hasChildrenOfSameFacet) {
762
+ facets.push(facet);
763
+ }
764
+ } else {
765
+ facets.push(facet);
766
+ }
767
+ }
768
+ const specifiedXml = Object.entries(specified).map(([facet, name]) => ` <vfs:preselection facet="${facet.toLowerCase()}">
769
+ <vfs:value>${name}</vfs:value>
770
+ </vfs:preselection>`).join("\n");
771
+ const facetsXml = facets.map((facet) => ` <vfs:facet>${facet.toLowerCase()}</vfs:facet>`).join("\n");
772
+ return `<?xml version="1.0" encoding="UTF-8"?>
773
+ <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="${searchPattern}">
774
+ ${specifiedXml}
775
+ <vfs:facetorder>
776
+ ${facetsXml}
777
+ </vfs:facetorder>
778
+ </vfs:virtualFoldersRequest>`;
779
+ }
780
+ function parseTreeResponse(xml) {
781
+ const [doc, parseErr] = safeParseXml(xml);
782
+ if (parseErr) {
783
+ return err(parseErr);
784
+ }
785
+ const nodes = [];
786
+ const packages = [];
787
+ const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
788
+ for (let i = 0; i < virtualFolders.length; i++) {
789
+ const vf = virtualFolders[i];
790
+ if (!vf) continue;
791
+ const facet = vf.getAttribute("facet");
792
+ const name = vf.getAttribute("name");
793
+ if (facet === "PACKAGE" && name) {
794
+ const desc = vf.getAttribute("description");
795
+ const pkg = {
796
+ name: name.startsWith("..") ? name.substring(2) : name
797
+ };
798
+ if (desc) {
799
+ pkg.description = desc;
800
+ }
801
+ packages.push(pkg);
802
+ }
803
+ if (!name || !facet) continue;
804
+ nodes.push({
805
+ name: name.startsWith("..") ? name.substring(2) : name,
806
+ type: "folder",
807
+ hasChildren: vf.getAttribute("hasChildrenOfSameFacet") === "true"
808
+ });
809
+ }
810
+ const objects = doc.getElementsByTagName("vfs:object");
811
+ for (let i = 0; i < objects.length; i++) {
812
+ const obj = objects[i];
813
+ if (!obj) continue;
814
+ const name = obj.getAttribute("name");
815
+ const type = obj.getAttribute("type");
816
+ if (!name || !type) continue;
817
+ const config = getConfigByType(type);
818
+ if (!config) continue;
819
+ nodes.push({
820
+ name,
821
+ type: "object",
822
+ objectType: config.label,
823
+ extension: config.extension
824
+ });
825
+ }
826
+ return ok({ nodes, packages });
827
+ }
828
+
829
+ // src/core/adt/packages.ts
830
+ async function getPackages(client) {
831
+ const [treeResult, treeErr] = await getTreeInternal(client, {}, "*");
832
+ if (treeErr) {
833
+ return err(treeErr);
834
+ }
835
+ return ok(treeResult.packages);
836
+ }
837
+
838
+ // src/core/adt/transports.ts
839
+ async function getTransports(client, packageName) {
840
+ const contentType = "application/vnd.sap.as+xml; charset=UTF-8; dataname=com.sap.adt.transport.service.checkData";
841
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
842
+ <asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
843
+ <asx:values>
844
+ <DATA>
845
+ <PGMID></PGMID>
846
+ <OBJECT></OBJECT>
847
+ <OBJECTNAME></OBJECTNAME>
848
+ <DEVCLASS>${packageName}</DEVCLASS>
849
+ <SUPER_PACKAGE></SUPER_PACKAGE>
850
+ <OPERATION>I</OPERATION>
851
+ <URI>/sap/bc/adt/ddic/ddl/sources/transport_check</URI>
852
+ </DATA>
853
+ </asx:values>
854
+ </asx:abap>`;
855
+ const [response, requestErr] = await client.request({
856
+ method: "POST",
857
+ path: "/sap/bc/adt/cts/transportchecks",
858
+ headers: {
859
+ "Accept": contentType,
860
+ "Content-Type": contentType
861
+ },
862
+ body
863
+ });
864
+ if (requestErr) {
865
+ return err(requestErr);
866
+ }
867
+ if (!response.ok) {
868
+ const text2 = await response.text();
869
+ const errorMsg = extractError(text2);
870
+ return err(new Error(`Failed to fetch transports for ${packageName}: ${errorMsg}`));
871
+ }
872
+ const text = await response.text();
873
+ const [transports, parseErr] = extractTransports(text);
874
+ if (parseErr) {
875
+ return err(parseErr);
876
+ }
877
+ return ok(transports);
878
+ }
879
+ function extractTransports(xml) {
880
+ const [doc, parseErr] = safeParseXml(xml);
881
+ if (parseErr) {
882
+ return err(parseErr);
883
+ }
884
+ const transports = [];
885
+ const reqHeaders = doc.getElementsByTagName("REQ_HEADER");
886
+ for (let i = 0; i < reqHeaders.length; i++) {
887
+ const header = reqHeaders[i];
888
+ if (!header) continue;
889
+ const trkorrElement = header.getElementsByTagName("TRKORR")[0];
890
+ const userElement = header.getElementsByTagName("AS4USER")[0];
891
+ const textElement = header.getElementsByTagName("AS4TEXT")[0];
892
+ if (!trkorrElement || !userElement || !textElement) continue;
893
+ const id = trkorrElement.textContent;
894
+ const owner = userElement.textContent;
895
+ const description = textElement.textContent;
896
+ if (!id || !owner || !description) continue;
897
+ transports.push({
898
+ id,
899
+ owner,
900
+ description,
901
+ status: "modifiable"
902
+ });
903
+ }
904
+ return ok(transports);
905
+ }
906
+
907
+ // src/core/adt/queryBuilder.ts
908
+ function buildWhereClauses(filters) {
909
+ if (!filters || filters.length === 0) {
910
+ return "";
911
+ }
912
+ const clauses = filters.map((filter) => {
913
+ const { column, operator, value } = filter;
914
+ switch (operator) {
915
+ case "eq":
916
+ return `${column} = ${formatValue(value)}`;
917
+ case "ne":
918
+ return `${column} != ${formatValue(value)}`;
919
+ case "gt":
920
+ return `${column} > ${formatValue(value)}`;
921
+ case "ge":
922
+ return `${column} >= ${formatValue(value)}`;
923
+ case "lt":
924
+ return `${column} < ${formatValue(value)}`;
925
+ case "le":
926
+ return `${column} <= ${formatValue(value)}`;
927
+ case "like":
928
+ return `${column} LIKE ${formatValue(value)}`;
929
+ case "in":
930
+ if (Array.isArray(value)) {
931
+ const values = value.map((v) => formatValue(v)).join(", ");
932
+ return `${column} IN (${values})`;
933
+ }
934
+ return `${column} IN (${formatValue(value)})`;
935
+ default:
936
+ return "";
937
+ }
938
+ }).filter((c) => c);
939
+ if (clauses.length === 0) {
940
+ return "";
941
+ }
942
+ return ` WHERE ${clauses.join(" AND ")}`;
943
+ }
944
+ function buildOrderByClauses(orderBy) {
945
+ if (!orderBy || orderBy.length === 0) {
946
+ return "";
947
+ }
948
+ const clauses = orderBy.map((o) => `${o.column} ${o.direction.toUpperCase()}`);
949
+ return ` ORDER BY ${clauses.join(", ")}`;
950
+ }
951
+ function formatValue(value) {
952
+ if (value === null) {
953
+ return "NULL";
954
+ }
955
+ if (typeof value === "string") {
956
+ return `'${value.replace(/'/g, "''")}'`;
957
+ }
958
+ if (typeof value === "boolean") {
959
+ return value ? "1" : "0";
960
+ }
961
+ return String(value);
962
+ }
963
+
964
+ // src/core/adt/previewParser.ts
965
+ function parseDataPreview(xml, maxRows, isTable) {
966
+ const [doc, parseErr] = safeParseXml(xml);
967
+ if (parseErr) {
968
+ return err(parseErr);
969
+ }
970
+ const namespace = "http://www.sap.com/adt/dataPreview";
971
+ const metadataElements = doc.getElementsByTagNameNS(namespace, "metadata");
972
+ const columns = [];
973
+ for (let i = 0; i < metadataElements.length; i++) {
974
+ const meta = metadataElements[i];
975
+ if (!meta) continue;
976
+ const nameAttr = isTable ? "name" : "camelCaseName";
977
+ const name = meta.getAttributeNS(namespace, nameAttr) || meta.getAttribute("name");
978
+ const dataType = meta.getAttributeNS(namespace, "colType") || meta.getAttribute("colType");
979
+ if (!name || !dataType) continue;
980
+ columns.push({ name, dataType });
981
+ }
982
+ if (columns.length === 0) {
983
+ return err(new Error("No columns found in preview response"));
984
+ }
985
+ const dataSetElements = doc.getElementsByTagNameNS(namespace, "dataSet");
986
+ const columnData = Array.from({ length: columns.length }, () => []);
987
+ for (let i = 0; i < dataSetElements.length; i++) {
988
+ const dataSet = dataSetElements[i];
989
+ if (!dataSet) continue;
990
+ const dataElements = dataSet.getElementsByTagNameNS(namespace, "data");
991
+ for (let j = 0; j < dataElements.length; j++) {
992
+ const data = dataElements[j];
993
+ if (!data) continue;
994
+ const value = data.textContent?.trim() || "";
995
+ columnData[i].push(value);
996
+ }
997
+ }
998
+ const rows = [];
999
+ const rowCount = columnData[0]?.length || 0;
1000
+ for (let i = 0; i < Math.min(rowCount, maxRows); i++) {
1001
+ const row = [];
1002
+ for (let j = 0; j < columns.length; j++) {
1003
+ row.push(columnData[j][i]);
1004
+ }
1005
+ rows.push(row);
1006
+ }
1007
+ const dataFrame = {
1008
+ columns,
1009
+ rows,
1010
+ totalRows: rowCount
1011
+ };
1012
+ return ok(dataFrame);
1013
+ }
1014
+
1015
+ // src/core/adt/data.ts
1016
+ async function previewData(client, query) {
1017
+ const extension = query.objectType === "table" ? "astabldt" : "asddls";
1018
+ const config = getConfigByExtension(extension);
1019
+ if (!config || !config.dpEndpoint || !config.dpParam) {
1020
+ return err(new Error(`Data preview not supported for object type: ${query.objectType}`));
1021
+ }
1022
+ const limit = query.limit ?? 100;
1023
+ const whereClauses = buildWhereClauses(query.filters);
1024
+ const orderByClauses = buildOrderByClauses(query.orderBy);
1025
+ const sqlQuery = `select * from ${query.objectName}${whereClauses}${orderByClauses}`;
1026
+ const [, validationErr] = validateSqlInput(sqlQuery);
1027
+ if (validationErr) {
1028
+ return err(new Error(`SQL validation failed: ${validationErr.message}`));
1029
+ }
1030
+ debug(`Data preview: endpoint=${config.dpEndpoint}, param=${config.dpParam}=${query.objectName}`);
1031
+ debug(`SQL: ${sqlQuery}`);
1032
+ const [response, requestErr] = await client.request({
1033
+ method: "POST",
1034
+ path: `/sap/bc/adt/datapreview/${config.dpEndpoint}`,
1035
+ params: {
1036
+ "rowNumber": limit,
1037
+ [config.dpParam]: query.objectName
1038
+ },
1039
+ headers: {
1040
+ "Accept": "application/vnd.sap.adt.datapreview.table.v1+xml",
1041
+ "Content-Type": "text/plain"
1042
+ },
1043
+ body: sqlQuery
1044
+ });
1045
+ if (requestErr) {
1046
+ return err(requestErr);
1047
+ }
1048
+ if (!response.ok) {
1049
+ const text2 = await response.text();
1050
+ debug(`Data preview error response: ${text2.substring(0, 500)}`);
1051
+ const errorMsg = extractError(text2);
1052
+ return err(new Error(`Data preview failed: ${errorMsg}`));
1053
+ }
1054
+ const text = await response.text();
1055
+ const [dataFrame, parseErr] = parseDataPreview(text, limit, query.objectType === "table");
1056
+ if (parseErr) {
1057
+ return err(parseErr);
1058
+ }
1059
+ return ok(dataFrame);
1060
+ }
1061
+
1062
+ // src/core/adt/distinct.ts
1063
+ var MAX_ROW_COUNT = 5e4;
1064
+ async function getDistinctValues(client, objectName, column, objectType = "view") {
1065
+ const extension = objectType === "table" ? "astabldt" : "asddls";
1066
+ const config = getConfigByExtension(extension);
1067
+ if (!config || !config.dpEndpoint || !config.dpParam) {
1068
+ return err(new Error(`Data preview not supported for object type: ${objectType}`));
1069
+ }
1070
+ const columnName = column.toUpperCase();
1071
+ const sqlQuery = `SELECT ${columnName} AS value, COUNT(*) AS count FROM ${objectName} GROUP BY ${columnName}`;
1072
+ const [, validationErr] = validateSqlInput(sqlQuery);
1073
+ if (validationErr) {
1074
+ return err(new Error(`SQL validation failed: ${validationErr.message}`));
1075
+ }
1076
+ const [response, requestErr] = await client.request({
1077
+ method: "POST",
1078
+ path: `/sap/bc/adt/datapreview/${config.dpEndpoint}`,
1079
+ params: {
1080
+ "rowNumber": MAX_ROW_COUNT,
1081
+ [config.dpParam]: objectName
1082
+ },
1083
+ headers: {
1084
+ "Accept": "application/vnd.sap.adt.datapreview.table.v1+xml"
1085
+ },
1086
+ body: sqlQuery
1087
+ });
1088
+ if (requestErr) {
1089
+ return err(requestErr);
1090
+ }
1091
+ if (!response.ok) {
1092
+ const text2 = await response.text();
1093
+ const errorMsg = extractError(text2);
1094
+ return err(new Error(`Distinct values query failed: ${errorMsg}`));
1095
+ }
1096
+ const text = await response.text();
1097
+ const [doc, parseErr] = safeParseXml(text);
1098
+ if (parseErr) {
1099
+ return err(parseErr);
1100
+ }
1101
+ const dataSets = doc.getElementsByTagNameNS("http://www.sap.com/adt/dataPreview", "dataSet");
1102
+ const values = [];
1103
+ for (let i = 0; i < dataSets.length; i++) {
1104
+ const dataSet = dataSets[i];
1105
+ if (!dataSet) continue;
1106
+ const dataElements = dataSet.getElementsByTagNameNS("http://www.sap.com/adt/dataPreview", "data");
1107
+ if (dataElements.length < 2) continue;
1108
+ const value = dataElements[0]?.textContent ?? "";
1109
+ const countText = dataElements[1]?.textContent?.trim() ?? "0";
1110
+ values.push({
1111
+ value,
1112
+ count: parseInt(countText, 10)
1113
+ });
1114
+ }
1115
+ const result = {
1116
+ column,
1117
+ values
1118
+ };
1119
+ return ok(result);
1120
+ }
1121
+
1122
+ // src/core/adt/count.ts
1123
+ async function countRows(client, objectName, objectType) {
1124
+ const extension = objectType === "table" ? "astabldt" : "asddls";
1125
+ const config = getConfigByExtension(extension);
1126
+ if (!config || !config.dpEndpoint || !config.dpParam) {
1127
+ return err(new Error(`Data preview not supported for object type: ${objectType}`));
1128
+ }
1129
+ const sqlQuery = `SELECT COUNT(*) AS count FROM ${objectName}`;
1130
+ const [, validationErr] = validateSqlInput(sqlQuery);
1131
+ if (validationErr) {
1132
+ return err(new Error(`SQL validation failed: ${validationErr.message}`));
1133
+ }
1134
+ const [response, requestErr] = await client.request({
1135
+ method: "POST",
1136
+ path: `/sap/bc/adt/datapreview/${config.dpEndpoint}`,
1137
+ params: {
1138
+ "rowNumber": 1,
1139
+ [config.dpParam]: objectName
1140
+ },
1141
+ headers: {
1142
+ "Accept": "application/vnd.sap.adt.datapreview.table.v1+xml"
1143
+ },
1144
+ body: sqlQuery
1145
+ });
1146
+ if (requestErr) {
1147
+ return err(requestErr);
1148
+ }
1149
+ if (!response.ok) {
1150
+ const text2 = await response.text();
1151
+ const errorMsg = extractError(text2);
1152
+ return err(new Error(`Row count query failed: ${errorMsg}`));
1153
+ }
1154
+ const text = await response.text();
1155
+ const [doc, parseErr] = safeParseXml(text);
1156
+ if (parseErr) {
1157
+ return err(parseErr);
1158
+ }
1159
+ const dataElements = doc.getElementsByTagNameNS("http://www.sap.com/adt/dataPreview", "data");
1160
+ if (dataElements.length === 0) {
1161
+ return err(new Error("No count value returned"));
1162
+ }
1163
+ const countText = dataElements[0]?.textContent?.trim();
1164
+ if (!countText) {
1165
+ return err(new Error("Empty count value returned"));
1166
+ }
1167
+ const count = parseInt(countText, 10);
1168
+ if (isNaN(count)) {
1169
+ return err(new Error("Invalid count value returned"));
1170
+ }
1171
+ return ok(count);
1172
+ }
1173
+
1174
+ // src/core/adt/searchObjects.ts
1175
+ async function searchObjects(client, query, types) {
1176
+ const searchPattern = query || "*";
1177
+ const objectTypes = types && types.length > 0 ? types : getAllTypes();
1178
+ const params = [
1179
+ ["operation", "quickSearch"],
1180
+ ["query", searchPattern],
1181
+ ["maxResults", "10001"]
1182
+ ];
1183
+ for (const type of objectTypes) {
1184
+ params.push(["objectType", type]);
1185
+ }
1186
+ const urlParams = new URLSearchParams();
1187
+ for (const [key, value] of params) {
1188
+ urlParams.append(key, value);
1189
+ }
1190
+ const [response, requestErr] = await client.request({
1191
+ method: "GET",
1192
+ path: `/sap/bc/adt/repository/informationsystem/search?${urlParams.toString()}`
1193
+ });
1194
+ if (requestErr) {
1195
+ return err(requestErr);
1196
+ }
1197
+ if (!response.ok) {
1198
+ const text2 = await response.text();
1199
+ const errorMsg = extractError(text2);
1200
+ return err(new Error(`Search failed: ${errorMsg}`));
1201
+ }
1202
+ const text = await response.text();
1203
+ const [results, parseErr] = parseSearchResults(text);
1204
+ if (parseErr) {
1205
+ return err(parseErr);
1206
+ }
1207
+ return ok(results);
1208
+ }
1209
+ function parseSearchResults(xml) {
1210
+ const [doc, parseErr] = safeParseXml(xml);
1211
+ if (parseErr) {
1212
+ return err(parseErr);
1213
+ }
1214
+ const results = [];
1215
+ const objectRefs = doc.getElementsByTagNameNS("http://www.sap.com/adt/core", "objectReference");
1216
+ for (let i = 0; i < objectRefs.length; i++) {
1217
+ const obj = objectRefs[i];
1218
+ if (!obj) continue;
1219
+ const name = obj.getAttributeNS("http://www.sap.com/adt/core", "name") || obj.getAttribute("adtcore:name");
1220
+ const type = obj.getAttributeNS("http://www.sap.com/adt/core", "type") || obj.getAttribute("adtcore:type");
1221
+ const description = obj.getAttributeNS("http://www.sap.com/adt/core", "description") || obj.getAttribute("adtcore:description");
1222
+ if (!name || !type) continue;
1223
+ const config = getConfigByType(type);
1224
+ if (!config) continue;
1225
+ const packageRef = obj.getElementsByTagNameNS("http://www.sap.com/adt/core", "packageRef")[0];
1226
+ const packageName = packageRef ? packageRef.getAttributeNS("http://www.sap.com/adt/core", "name") || packageRef.getAttribute("adtcore:name") : "";
1227
+ const result = {
1228
+ name,
1229
+ extension: config.extension,
1230
+ package: packageName || "",
1231
+ objectType: config.label
1232
+ };
1233
+ if (description) {
1234
+ result.description = description;
1235
+ }
1236
+ results.push(result);
1237
+ }
1238
+ return ok(results);
1239
+ }
1240
+
1241
+ // src/core/adt/whereUsed.ts
1242
+ async function findWhereUsed(client, object) {
1243
+ const config = getConfigByExtension(object.extension);
1244
+ if (!config) {
1245
+ return err(new Error(`Unsupported extension: ${object.extension}`));
1246
+ }
1247
+ const uri = `/sap/bc/adt/${config.endpoint}/${object.name}`;
1248
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1249
+ <usagereferences:usageReferenceRequest xmlns:usagereferences="http://www.sap.com/adt/ris/usageReferences">
1250
+ <usagereferences:affectedObjects/>
1251
+ </usagereferences:usageReferenceRequest>`;
1252
+ const [response, requestErr] = await client.request({
1253
+ method: "POST",
1254
+ path: "/sap/bc/adt/repository/informationsystem/usageReferences",
1255
+ params: {
1256
+ "uri": uri,
1257
+ "ris_request_type": "usageReferences"
1258
+ },
1259
+ headers: {
1260
+ "Content-Type": "application/vnd.sap.adt.repository.usagereferences.request.v1+xml",
1261
+ "Accept": "application/vnd.sap.adt.repository.usagereferences.result.v1+xml"
1262
+ },
1263
+ body
1264
+ });
1265
+ if (requestErr) {
1266
+ return err(requestErr);
1267
+ }
1268
+ if (!response.ok) {
1269
+ const text2 = await response.text();
1270
+ const errorMsg = extractError(text2);
1271
+ return err(new Error(`Where-used query failed: ${errorMsg}`));
1272
+ }
1273
+ const text = await response.text();
1274
+ const [dependencies, parseErr] = parseWhereUsed(text);
1275
+ if (parseErr) {
1276
+ return err(parseErr);
1277
+ }
1278
+ return ok(dependencies);
1279
+ }
1280
+ function parseWhereUsed(xml) {
1281
+ const [doc, parseErr] = safeParseXml(xml);
1282
+ if (parseErr) {
1283
+ return err(parseErr);
1284
+ }
1285
+ const dependencies = [];
1286
+ const referencedObjects = doc.getElementsByTagNameNS(
1287
+ "http://www.sap.com/adt/ris/usageReferences",
1288
+ "referencedObject"
1289
+ );
1290
+ for (let i = 0; i < referencedObjects.length; i++) {
1291
+ const refObj = referencedObjects[i];
1292
+ if (!refObj) continue;
1293
+ const adtObject = refObj.getElementsByTagNameNS(
1294
+ "http://www.sap.com/adt/ris/usageReferences",
1295
+ "adtObject"
1296
+ )[0];
1297
+ if (!adtObject) continue;
1298
+ const name = adtObject.getAttributeNS("http://www.sap.com/adt/core", "name") || adtObject.getAttribute("adtcore:name");
1299
+ const type = adtObject.getAttributeNS("http://www.sap.com/adt/core", "type") || adtObject.getAttribute("adtcore:type");
1300
+ if (!name || !type) continue;
1301
+ const config = getConfigByType(type);
1302
+ if (!config) continue;
1303
+ const packageRef = adtObject.getElementsByTagNameNS("http://www.sap.com/adt/core", "packageRef")[0];
1304
+ const packageName = packageRef ? packageRef.getAttributeNS("http://www.sap.com/adt/core", "name") || packageRef.getAttribute("adtcore:name") : "";
1305
+ dependencies.push({
1306
+ name,
1307
+ extension: config.extension,
1308
+ package: packageName || "",
1309
+ usageType: "reference"
1310
+ });
1311
+ }
1312
+ return ok(dependencies);
1313
+ }
1314
+
1315
+ // src/core/adt/createTransport.ts
1316
+ async function createTransport(client, config) {
1317
+ const body = dictToAbapXml({
1318
+ DEVCLASS: config.package,
1319
+ REQUEST_TEXT: config.description,
1320
+ REF: "",
1321
+ OPERATION: "I"
1322
+ });
1323
+ const [response, requestErr] = await client.request({
1324
+ method: "POST",
1325
+ path: "/sap/bc/adt/cts/transports",
1326
+ headers: {
1327
+ "Content-Type": "application/vnd.sap.as+xml; charset=UTF-8; dataname=com.sap.adt.CreateCorrectionRequest",
1328
+ "Accept": "text/plain"
1329
+ },
1330
+ body
1331
+ });
1332
+ if (requestErr) return err(requestErr);
1333
+ if (!response.ok) {
1334
+ const text2 = await response.text();
1335
+ const errorMsg = extractError(text2);
1336
+ return err(new Error(`Failed to create transport for ${config.package}: ${errorMsg}`));
1337
+ }
1338
+ const text = await response.text();
1339
+ const transportId = text.trim().split("/").pop();
1340
+ if (!transportId) {
1341
+ return err(new Error("Failed to parse transport ID from response"));
1342
+ }
1343
+ return ok(transportId);
1344
+ }
1345
+
1346
+ // src/core/adt/gitDiff.ts
1347
+ var import_diff = require("diff");
1348
+ function computeDiff(serverLines, localLines) {
1349
+ const changes = (0, import_diff.diffArrays)(serverLines, localLines);
1350
+ const hunks = [];
1351
+ let diffIndex = 0;
1352
+ let localIndex = 0;
1353
+ for (let i = 0; i < changes.length; i++) {
1354
+ const change = changes[i];
1355
+ if (!change) continue;
1356
+ if (!change.added && !change.removed) {
1357
+ const count = change.count ?? change.value.length;
1358
+ diffIndex += count;
1359
+ localIndex += count;
1360
+ continue;
1361
+ }
1362
+ if (change.removed) {
1363
+ const nextChange = changes[i + 1];
1364
+ if (nextChange?.added) {
1365
+ const serverChanges = change.value;
1366
+ const localChanges = nextChange.value;
1367
+ const modHunk = {
1368
+ type: "modification",
1369
+ length: serverChanges.length + localChanges.length,
1370
+ diffStart: diffIndex,
1371
+ localStart: localIndex,
1372
+ changes: [serverChanges, localChanges]
1373
+ };
1374
+ hunks.push(modHunk);
1375
+ diffIndex += serverChanges.length + localChanges.length;
1376
+ localIndex += localChanges.length;
1377
+ i++;
1378
+ continue;
1379
+ }
1380
+ const deletionHunk = {
1381
+ type: "deletion",
1382
+ length: change.value.length,
1383
+ diffStart: diffIndex,
1384
+ localStart: localIndex,
1385
+ changes: change.value
1386
+ };
1387
+ hunks.push(deletionHunk);
1388
+ diffIndex += change.value.length;
1389
+ continue;
1390
+ }
1391
+ if (!change.added) continue;
1392
+ const additionHunk = {
1393
+ type: "addition",
1394
+ length: change.value.length,
1395
+ diffStart: diffIndex,
1396
+ localStart: localIndex,
1397
+ changes: change.value
1398
+ };
1399
+ hunks.push(additionHunk);
1400
+ diffIndex += change.value.length;
1401
+ localIndex += change.value.length;
1402
+ }
1403
+ return hunks;
1404
+ }
1405
+ async function gitDiff(client, object) {
1406
+ const [serverObj, readErr] = await readObject(client, {
1407
+ name: object.name,
1408
+ extension: object.extension
1409
+ });
1410
+ if (readErr) {
1411
+ return err(new Error(`${object.name} does not exist on server`));
1412
+ }
1413
+ const config = getConfigByExtension(object.extension);
1414
+ const label = config?.label ?? object.extension;
1415
+ const serverLines = serverObj.content.split("\n");
1416
+ const localLines = object.content.split("\n");
1417
+ const diffs = computeDiff(serverLines, localLines);
1418
+ return ok({
1419
+ name: serverObj.name,
1420
+ extension: serverObj.extension,
1421
+ label,
1422
+ diffs
1423
+ });
1424
+ }
1425
+
1426
+ // src/core/client.ts
1427
+ var import_undici = require("undici");
1428
+ function buildParams(baseParams, clientNum) {
1429
+ const params = new URLSearchParams();
1430
+ if (baseParams) {
1431
+ for (const [key, value] of Object.entries(baseParams)) {
1432
+ params.append(key, String(value));
1433
+ }
1434
+ }
1435
+ params.append("sap-client", clientNum);
1436
+ return params;
1437
+ }
1438
+ function buildUrl2(baseUrl, path, params) {
1439
+ const url = new URL(path, baseUrl);
1440
+ if (params) {
1441
+ for (const [key, value] of params.entries()) {
1442
+ url.searchParams.append(key, value);
1443
+ }
1444
+ }
1445
+ return url.toString();
1446
+ }
1447
+ var ADTClientImpl = class {
1448
+ state;
1449
+ requestor;
1450
+ agent;
1451
+ constructor(config) {
1452
+ this.state = {
1453
+ config,
1454
+ session: null,
1455
+ csrfToken: null,
1456
+ cookies: /* @__PURE__ */ new Map()
1457
+ };
1458
+ this.requestor = { request: this.request.bind(this) };
1459
+ if (config.insecure) {
1460
+ this.agent = new import_undici.Agent({
1461
+ connect: {
1462
+ rejectUnauthorized: false,
1463
+ checkServerIdentity: () => void 0
1464
+ // Skip hostname verification
1465
+ }
1466
+ });
1467
+ }
1468
+ }
1469
+ get session() {
1470
+ return this.state.session;
1471
+ }
1472
+ // --- Private helpers ---
1473
+ storeCookies(response) {
1474
+ const setCookieHeader = response.headers.get("set-cookie");
1475
+ if (!setCookieHeader) return;
1476
+ const cookieStrings = setCookieHeader.split(/,(?=\s*\w+=)/);
1477
+ for (const cookieStr of cookieStrings) {
1478
+ const match = cookieStr.match(/^([^=]+)=([^;]*)/);
1479
+ if (match && match[1] && match[2]) {
1480
+ this.state.cookies.set(match[1].trim(), match[2].trim());
1481
+ }
1482
+ }
1483
+ }
1484
+ buildCookieHeader() {
1485
+ if (this.state.cookies.size === 0) return null;
1486
+ return Array.from(this.state.cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
1487
+ }
1488
+ // Core HTTP request function with CSRF token injection and automatic retry on 403 errors
1489
+ async request(options) {
1490
+ const { method, path, params, headers: customHeaders, body } = options;
1491
+ const { config } = this.state;
1492
+ debug(`Request ${method} ${path} - CSRF token in state: ${this.state.csrfToken?.substring(0, 20) || "null"}...`);
1493
+ const headers = buildRequestHeaders(
1494
+ BASE_HEADERS,
1495
+ customHeaders,
1496
+ config.auth,
1497
+ this.state.csrfToken
1498
+ );
1499
+ debug(`CSRF header being sent: ${headers["x-csrf-token"]?.substring(0, 20) || "none"}...`);
1500
+ const cookieHeader = this.buildCookieHeader();
1501
+ if (cookieHeader) {
1502
+ headers["Cookie"] = cookieHeader;
1503
+ debug(`Cookies being sent: ${cookieHeader.substring(0, 50)}...`);
1504
+ }
1505
+ const urlParams = buildParams(params, config.client);
1506
+ const url = buildUrl2(config.url, path, urlParams);
1507
+ const fetchOptions = {
1508
+ method,
1509
+ headers,
1510
+ signal: AbortSignal.timeout(config.timeout ?? DEFAULT_TIMEOUT)
1511
+ };
1512
+ if (this.agent) {
1513
+ fetchOptions.dispatcher = this.agent;
1514
+ }
1515
+ if (body) {
1516
+ fetchOptions.body = body;
1517
+ }
1518
+ try {
1519
+ debug(`Fetching URL: ${url}`);
1520
+ debug(`Insecure mode: ${!!this.agent}`);
1521
+ const response = await (0, import_undici.fetch)(url, fetchOptions);
1522
+ this.storeCookies(response);
1523
+ if (response.status === 403) {
1524
+ const text = await response.text();
1525
+ if (text.includes("CSRF token validation failed")) {
1526
+ const [newToken, tokenErr] = await fetchCsrfToken(this.state, this.request.bind(this));
1527
+ if (tokenErr) {
1528
+ return err(new Error(`CSRF token refresh failed: ${tokenErr.message}`));
1529
+ }
1530
+ headers[CSRF_TOKEN_HEADER] = newToken;
1531
+ const retryCookieHeader = this.buildCookieHeader();
1532
+ if (retryCookieHeader) {
1533
+ headers["Cookie"] = retryCookieHeader;
1534
+ }
1535
+ debug(`Retrying with new CSRF token: ${newToken.substring(0, 20)}...`);
1536
+ const retryResponse = await (0, import_undici.fetch)(url, { ...fetchOptions, headers });
1537
+ this.storeCookies(retryResponse);
1538
+ return ok(retryResponse);
1539
+ }
1540
+ return ok(new Response(text, {
1541
+ status: response.status,
1542
+ statusText: response.statusText,
1543
+ headers: response.headers
1544
+ }));
1545
+ }
1546
+ if (response.status === 500) {
1547
+ const text = await response.text();
1548
+ const [, resetErr] = await sessionReset(this.state, this.request.bind(this));
1549
+ if (resetErr) {
1550
+ return err(new Error(`Session reset failed: ${resetErr.message}`));
1551
+ }
1552
+ return ok(new Response(text, {
1553
+ status: response.status,
1554
+ statusText: response.statusText,
1555
+ headers: response.headers
1556
+ }));
1557
+ }
1558
+ return ok(response);
1559
+ } catch (error) {
1560
+ if (error instanceof Error) {
1561
+ debugError(`Fetch error: ${error.name}: ${error.message}`, error.cause);
1562
+ if ("code" in error) {
1563
+ debugError(`Error code: ${error.code}`);
1564
+ }
1565
+ return err(error);
1566
+ }
1567
+ return err(new Error(`Network error: ${String(error)}`));
1568
+ }
1569
+ }
1570
+ // --- Lifecycle ---
1571
+ async login() {
1572
+ return login(this.state, this.request.bind(this));
1573
+ }
1574
+ async logout() {
1575
+ return logout(this.state, this.request.bind(this));
1576
+ }
1577
+ // --- CRAUD Operations ---
1578
+ async read(objects) {
1579
+ if (!this.state.session) return err(new Error("Not logged in"));
1580
+ const results = [];
1581
+ for (const obj of objects) {
1582
+ const [result, readErr] = await readObject(this.requestor, obj);
1583
+ if (readErr) return err(readErr);
1584
+ results.push(result);
1585
+ }
1586
+ return ok(results);
1587
+ }
1588
+ async create(object, packageName, transport) {
1589
+ if (!this.state.session) return err(new Error("Not logged in"));
1590
+ const [, createErr] = await createObject(this.requestor, object, packageName, transport, this.state.session.username);
1591
+ if (createErr) return err(createErr);
1592
+ const objRef = { name: object.name, extension: object.extension };
1593
+ const [lockHandle, lockErr] = await lockObject(this.requestor, objRef);
1594
+ if (lockErr) return err(lockErr);
1595
+ const [, updateErr] = await updateObject(this.requestor, object, lockHandle, transport);
1596
+ const [, unlockErr] = await unlockObject(this.requestor, objRef, lockHandle);
1597
+ if (updateErr) return err(updateErr);
1598
+ if (unlockErr) return err(unlockErr);
1599
+ return ok(void 0);
1600
+ }
1601
+ async update(object, transport) {
1602
+ if (!this.state.session) return err(new Error("Not logged in"));
1603
+ const objRef = { name: object.name, extension: object.extension };
1604
+ const [lockHandle, lockErr] = await lockObject(this.requestor, objRef);
1605
+ if (lockErr) return err(lockErr);
1606
+ const [, updateErr] = await updateObject(this.requestor, object, lockHandle, transport);
1607
+ const [, unlockErr] = await unlockObject(this.requestor, objRef, lockHandle);
1608
+ if (updateErr) return err(updateErr);
1609
+ if (unlockErr) return err(unlockErr);
1610
+ return ok(void 0);
1611
+ }
1612
+ async upsert(objects, packageName, transport) {
1613
+ if (!this.state.session) return err(new Error("Not logged in"));
1614
+ if (objects.length === 0) return ok([]);
1615
+ const results = [];
1616
+ for (const obj of objects) {
1617
+ if (!obj.name || !obj.extension) continue;
1618
+ const objRef = { name: obj.name, extension: obj.extension };
1619
+ const [existing] = await readObject(this.requestor, objRef);
1620
+ if (!existing) {
1621
+ const [, createErr] = await this.create(obj, packageName, transport);
1622
+ if (createErr) return err(createErr);
1623
+ const result2 = {
1624
+ name: obj.name,
1625
+ extension: obj.extension,
1626
+ status: "created"
1627
+ };
1628
+ if (transport) result2.transport = transport;
1629
+ results.push(result2);
1630
+ continue;
1631
+ }
1632
+ const [, updateErr] = await this.update(obj, transport);
1633
+ if (updateErr) return err(updateErr);
1634
+ const result = {
1635
+ name: obj.name,
1636
+ extension: obj.extension,
1637
+ status: "updated"
1638
+ };
1639
+ if (transport) result.transport = transport;
1640
+ results.push(result);
1641
+ }
1642
+ return ok(results);
1643
+ }
1644
+ async activate(objects) {
1645
+ if (!this.state.session) return err(new Error("Not logged in"));
1646
+ return activateObjects(this.requestor, objects);
1647
+ }
1648
+ async delete(objects, transport) {
1649
+ if (!this.state.session) return err(new Error("Not logged in"));
1650
+ for (const obj of objects) {
1651
+ const [lockHandle, lockErr] = await lockObject(this.requestor, obj);
1652
+ if (lockErr) return err(lockErr);
1653
+ const [, deleteErr] = await deleteObject(this.requestor, obj, lockHandle, transport);
1654
+ if (deleteErr) {
1655
+ await unlockObject(this.requestor, obj, lockHandle);
1656
+ return err(deleteErr);
1657
+ }
1658
+ }
1659
+ return ok(void 0);
1660
+ }
1661
+ // --- Discovery ---
1662
+ async getPackages() {
1663
+ if (!this.state.session) return err(new Error("Not logged in"));
1664
+ return getPackages(this.requestor);
1665
+ }
1666
+ async getTree(query) {
1667
+ if (!this.state.session) return err(new Error("Not logged in"));
1668
+ return getTree(this.requestor, query);
1669
+ }
1670
+ async getTransports(packageName) {
1671
+ if (!this.state.session) return err(new Error("Not logged in"));
1672
+ return getTransports(this.requestor, packageName);
1673
+ }
1674
+ // --- Data Preview ---
1675
+ async previewData(query) {
1676
+ if (!this.state.session) return err(new Error("Not logged in"));
1677
+ return previewData(this.requestor, query);
1678
+ }
1679
+ async getDistinctValues(objectName, column, objectType = "view") {
1680
+ if (!this.state.session) return err(new Error("Not logged in"));
1681
+ return getDistinctValues(this.requestor, objectName, column, objectType);
1682
+ }
1683
+ async countRows(objectName, objectType) {
1684
+ if (!this.state.session) return err(new Error("Not logged in"));
1685
+ return countRows(this.requestor, objectName, objectType);
1686
+ }
1687
+ // --- Search ---
1688
+ async search(query, types) {
1689
+ if (!this.state.session) return err(new Error("Not logged in"));
1690
+ return searchObjects(this.requestor, query, types);
1691
+ }
1692
+ async whereUsed(object) {
1693
+ if (!this.state.session) return err(new Error("Not logged in"));
1694
+ return findWhereUsed(this.requestor, object);
1695
+ }
1696
+ // --- Transport Management ---
1697
+ async createTransport(transportConfig) {
1698
+ if (!this.state.session) return err(new Error("Not logged in"));
1699
+ return createTransport(this.requestor, transportConfig);
1700
+ }
1701
+ // --- Diff Operations ---
1702
+ async gitDiff(objects) {
1703
+ if (!this.state.session) return err(new Error("Not logged in"));
1704
+ if (objects.length === 0) return ok([]);
1705
+ const results = [];
1706
+ for (const obj of objects) {
1707
+ const [result, diffErr] = await gitDiff(this.requestor, obj);
1708
+ if (diffErr) return err(diffErr);
1709
+ results.push(result);
1710
+ }
1711
+ return ok(results);
1712
+ }
1713
+ // --- Configuration ---
1714
+ getObjectConfig() {
1715
+ return Object.values(OBJECT_CONFIG_MAP);
1716
+ }
1717
+ };
1718
+ function createClient(config) {
1719
+ const validation = clientConfigSchema.safeParse(config);
1720
+ if (!validation.success) {
1721
+ const issues = validation.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
1722
+ return err(new Error(`Invalid client configuration: ${issues}`));
1723
+ }
1724
+ return ok(new ADTClientImpl(config));
1725
+ }
1726
+
1727
+ // src/core/config.ts
1728
+ var import_node_fs = require("fs");
1729
+ var import_zod2 = require("zod");
1730
+ var systemConfigSchema = import_zod2.z.record(
1731
+ import_zod2.z.string(),
1732
+ import_zod2.z.object({
1733
+ adt: import_zod2.z.string().url().optional(),
1734
+ odata: import_zod2.z.string().url().optional(),
1735
+ instance_num: import_zod2.z.string().optional()
1736
+ })
1737
+ );
1738
+ // Annotate the CommonJS export names for ESM import in node:
1739
+ 0 && (module.exports = {
1740
+ createClient,
1741
+ err,
1742
+ ok
1743
+ });
1744
+ //# sourceMappingURL=index.js.map