catalyst-relay 0.5.14 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -1
- package/dist/index.d.mts +82 -1
- package/dist/index.d.ts +82 -1
- package/dist/index.js +594 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +593 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -502,6 +502,12 @@ var SsoAuth = class {
|
|
|
502
502
|
}
|
|
503
503
|
};
|
|
504
504
|
|
|
505
|
+
// src/core/auth/saml/browser.ts
|
|
506
|
+
import { spawn } from "child_process";
|
|
507
|
+
import { mkdtempSync, rmSync } from "fs";
|
|
508
|
+
import { tmpdir } from "os";
|
|
509
|
+
import { join as join2 } from "path";
|
|
510
|
+
|
|
505
511
|
// src/core/auth/saml/types.ts
|
|
506
512
|
var DEFAULT_FORM_SELECTORS = {
|
|
507
513
|
username: "#j_username",
|
|
@@ -516,9 +522,16 @@ var DEFAULT_PROVIDER_CONFIG = {
|
|
|
516
522
|
// src/core/auth/saml/browser.ts
|
|
517
523
|
var TIMEOUTS = {
|
|
518
524
|
PAGE_LOAD: 6e4,
|
|
519
|
-
FORM_SELECTOR:
|
|
525
|
+
FORM_SELECTOR: 15e3,
|
|
526
|
+
NETWORK_IDLE: 3e4,
|
|
527
|
+
/** Quiet period with no in-flight requests that counts as "idle". */
|
|
528
|
+
IDLE_QUIET: 600
|
|
520
529
|
};
|
|
530
|
+
var isBun = typeof globalThis.Bun !== "undefined";
|
|
521
531
|
async function performBrowserLogin(options) {
|
|
532
|
+
return isBun ? performBrowserLoginViaCdp(options) : performBrowserLoginViaPlaywright(options);
|
|
533
|
+
}
|
|
534
|
+
async function performBrowserLoginViaPlaywright(options) {
|
|
522
535
|
const { baseUrl, credentials, headless = true } = options;
|
|
523
536
|
const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
|
|
524
537
|
let playwright;
|
|
@@ -576,6 +589,222 @@ async function performBrowserLogin(options) {
|
|
|
576
589
|
await browser.close();
|
|
577
590
|
}
|
|
578
591
|
}
|
|
592
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
593
|
+
async function resolveChromiumPath() {
|
|
594
|
+
const override = process.env["CATALYST_CHROMIUM_PATH"];
|
|
595
|
+
if (override) return [override, null];
|
|
596
|
+
try {
|
|
597
|
+
const playwright = await import("playwright");
|
|
598
|
+
const path = playwright.chromium.executablePath();
|
|
599
|
+
if (!path) {
|
|
600
|
+
return [null, new Error("Could not resolve the Chromium executable path from Playwright.")];
|
|
601
|
+
}
|
|
602
|
+
return [path, null];
|
|
603
|
+
} catch {
|
|
604
|
+
return [
|
|
605
|
+
null,
|
|
606
|
+
new Error(
|
|
607
|
+
"Playwright is required for SAML authentication but is not installed. Install it with: npm install playwright"
|
|
608
|
+
)
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function connectCdp(wsUrl) {
|
|
613
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
614
|
+
if (!WebSocketCtor) {
|
|
615
|
+
throw new Error("Native WebSocket is not available in this runtime.");
|
|
616
|
+
}
|
|
617
|
+
const ws = new WebSocketCtor(wsUrl);
|
|
618
|
+
let nextId = 0;
|
|
619
|
+
const pending = /* @__PURE__ */ new Map();
|
|
620
|
+
const eventHandlers = [];
|
|
621
|
+
ws.onmessage = (event) => {
|
|
622
|
+
const msg = JSON.parse(String(event.data));
|
|
623
|
+
if (typeof msg.id === "number" && pending.has(msg.id)) {
|
|
624
|
+
const { resolve, reject } = pending.get(msg.id);
|
|
625
|
+
pending.delete(msg.id);
|
|
626
|
+
if (msg.error) reject(new Error(msg.error.message));
|
|
627
|
+
else resolve(msg.result ?? {});
|
|
628
|
+
} else if (msg.method) {
|
|
629
|
+
for (const handler of eventHandlers) {
|
|
630
|
+
handler({
|
|
631
|
+
method: msg.method,
|
|
632
|
+
params: msg.params ?? {},
|
|
633
|
+
...msg.sessionId !== void 0 ? { sessionId: msg.sessionId } : {}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
await new Promise((resolve, reject) => {
|
|
639
|
+
ws.onopen = () => resolve();
|
|
640
|
+
ws.onerror = () => reject(new Error("Failed to connect to the browser CDP endpoint."));
|
|
641
|
+
});
|
|
642
|
+
return {
|
|
643
|
+
send(method, params = {}, sessionId) {
|
|
644
|
+
return new Promise((resolve, reject) => {
|
|
645
|
+
const id = ++nextId;
|
|
646
|
+
pending.set(id, { resolve, reject });
|
|
647
|
+
ws.send(JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} }));
|
|
648
|
+
});
|
|
649
|
+
},
|
|
650
|
+
on(handler) {
|
|
651
|
+
eventHandlers.push(handler);
|
|
652
|
+
},
|
|
653
|
+
close() {
|
|
654
|
+
ws.close();
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
async function performBrowserLoginViaCdp(options) {
|
|
659
|
+
const { baseUrl, credentials, headless = true } = options;
|
|
660
|
+
const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
|
|
661
|
+
const [exePath, pathError] = await resolveChromiumPath();
|
|
662
|
+
if (pathError) return err(pathError);
|
|
663
|
+
const userDataDir = mkdtempSync(join2(tmpdir(), "catalyst-saml-"));
|
|
664
|
+
const args = [
|
|
665
|
+
...headless ? ["--headless=new"] : [],
|
|
666
|
+
"--remote-debugging-port=0",
|
|
667
|
+
"--no-first-run",
|
|
668
|
+
"--no-default-browser-check",
|
|
669
|
+
`--user-data-dir=${userDataDir}`,
|
|
670
|
+
...config.ignoreHttpsErrors ? ["--ignore-certificate-errors"] : []
|
|
671
|
+
];
|
|
672
|
+
const proc = spawn(exePath, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
673
|
+
let client = null;
|
|
674
|
+
const cleanup = () => {
|
|
675
|
+
try {
|
|
676
|
+
client?.close();
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
try {
|
|
680
|
+
proc.kill();
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
rmSync(userDataDir, { recursive: true, force: true });
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
try {
|
|
689
|
+
const browserWsUrl = await new Promise((resolve, reject) => {
|
|
690
|
+
let buffer = "";
|
|
691
|
+
const timer = setTimeout(
|
|
692
|
+
() => reject(new Error("Failed to launch browser: timed out waiting for the CDP endpoint.")),
|
|
693
|
+
TIMEOUTS.PAGE_LOAD
|
|
694
|
+
);
|
|
695
|
+
proc.stderr?.on("data", (chunk) => {
|
|
696
|
+
buffer += String(chunk);
|
|
697
|
+
const match = buffer.match(/DevTools listening on (ws:\/\/\S+)/);
|
|
698
|
+
if (match) {
|
|
699
|
+
clearTimeout(timer);
|
|
700
|
+
resolve(match[1]);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
proc.on("error", (e) => {
|
|
704
|
+
clearTimeout(timer);
|
|
705
|
+
reject(new Error(`Failed to launch browser: ${e instanceof Error ? e.message : String(e)}`));
|
|
706
|
+
});
|
|
707
|
+
proc.on("exit", (code) => {
|
|
708
|
+
clearTimeout(timer);
|
|
709
|
+
reject(new Error(`Failed to launch browser: process exited early (code ${code}).`));
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
client = await connectCdp(browserWsUrl);
|
|
713
|
+
const { targetId } = await client.send("Target.createTarget", { url: "about:blank" });
|
|
714
|
+
const { sessionId } = await client.send("Target.attachToTarget", {
|
|
715
|
+
targetId,
|
|
716
|
+
flatten: true
|
|
717
|
+
});
|
|
718
|
+
let inflight = 0;
|
|
719
|
+
let lastNetworkChange = Date.now();
|
|
720
|
+
client.on((msg) => {
|
|
721
|
+
if (msg.sessionId !== sessionId) return;
|
|
722
|
+
if (msg.method === "Network.requestWillBeSent") {
|
|
723
|
+
inflight += 1;
|
|
724
|
+
lastNetworkChange = Date.now();
|
|
725
|
+
} else if (msg.method === "Network.loadingFinished" || msg.method === "Network.loadingFailed") {
|
|
726
|
+
inflight = Math.max(0, inflight - 1);
|
|
727
|
+
lastNetworkChange = Date.now();
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
await client.send("Page.enable", {}, sessionId);
|
|
731
|
+
await client.send("Network.enable", {}, sessionId);
|
|
732
|
+
await client.send("Runtime.enable", {}, sessionId);
|
|
733
|
+
const loginUrl = `${baseUrl}/sap/bc/adt/compatibility/graph`;
|
|
734
|
+
const navResult = await client.send("Page.navigate", { url: loginUrl }, sessionId);
|
|
735
|
+
if (navResult.errorText) {
|
|
736
|
+
return err(new Error("Failed to load login page. Please check if the server is online."));
|
|
737
|
+
}
|
|
738
|
+
const usernameSel = config.formSelectors.username;
|
|
739
|
+
const formFound = await waitForSelector(client, sessionId, usernameSel, TIMEOUTS.FORM_SELECTOR);
|
|
740
|
+
if (!formFound) {
|
|
741
|
+
return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
|
|
742
|
+
}
|
|
743
|
+
const fillExpr = `(() => {
|
|
744
|
+
const u = document.querySelector(${JSON.stringify(config.formSelectors.username)});
|
|
745
|
+
const p = document.querySelector(${JSON.stringify(config.formSelectors.password)});
|
|
746
|
+
const s = document.querySelector(${JSON.stringify(config.formSelectors.submit)});
|
|
747
|
+
if (!u || !p || !s) return false;
|
|
748
|
+
const set = (el, val) => {
|
|
749
|
+
el.value = val;
|
|
750
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
751
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
752
|
+
};
|
|
753
|
+
set(u, ${JSON.stringify(credentials.username)});
|
|
754
|
+
set(p, ${JSON.stringify(credentials.password)});
|
|
755
|
+
s.click();
|
|
756
|
+
return true;
|
|
757
|
+
})()`;
|
|
758
|
+
const fillResult = await client.send(
|
|
759
|
+
"Runtime.evaluate",
|
|
760
|
+
{ expression: fillExpr, returnByValue: true },
|
|
761
|
+
sessionId
|
|
762
|
+
);
|
|
763
|
+
if (fillResult.result?.value !== true) {
|
|
764
|
+
return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
|
|
765
|
+
}
|
|
766
|
+
const idleStart = Date.now();
|
|
767
|
+
while (Date.now() - idleStart < TIMEOUTS.NETWORK_IDLE) {
|
|
768
|
+
if (inflight === 0 && Date.now() - lastNetworkChange > TIMEOUTS.IDLE_QUIET) break;
|
|
769
|
+
await sleep(100);
|
|
770
|
+
}
|
|
771
|
+
const { cookies } = await client.send("Network.getAllCookies", {}, sessionId);
|
|
772
|
+
return ok(cookies.map(toPlaywrightCookie));
|
|
773
|
+
} catch (e) {
|
|
774
|
+
return err(
|
|
775
|
+
new Error(`SAML browser login failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
776
|
+
);
|
|
777
|
+
} finally {
|
|
778
|
+
cleanup();
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
async function waitForSelector(client, sessionId, selector, timeoutMs) {
|
|
782
|
+
const start = Date.now();
|
|
783
|
+
const expression = `!!document.querySelector(${JSON.stringify(selector)})`;
|
|
784
|
+
while (Date.now() - start < timeoutMs) {
|
|
785
|
+
const res = await client.send(
|
|
786
|
+
"Runtime.evaluate",
|
|
787
|
+
{ expression, returnByValue: true },
|
|
788
|
+
sessionId
|
|
789
|
+
);
|
|
790
|
+
if (res.result?.value === true) return true;
|
|
791
|
+
await sleep(200);
|
|
792
|
+
}
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
function toPlaywrightCookie(c) {
|
|
796
|
+
const sameSite = c.sameSite === "Strict" || c.sameSite === "Lax" || c.sameSite === "None" ? c.sameSite : "Lax";
|
|
797
|
+
return {
|
|
798
|
+
name: c.name,
|
|
799
|
+
value: c.value,
|
|
800
|
+
domain: c.domain,
|
|
801
|
+
path: c.path,
|
|
802
|
+
expires: typeof c.expires === "number" ? c.expires : -1,
|
|
803
|
+
httpOnly: Boolean(c.httpOnly),
|
|
804
|
+
secure: Boolean(c.secure),
|
|
805
|
+
sameSite
|
|
806
|
+
};
|
|
807
|
+
}
|
|
579
808
|
|
|
580
809
|
// src/core/auth/saml/cookies.ts
|
|
581
810
|
function toAuthCookies(playwrightCookies) {
|
|
@@ -780,6 +1009,16 @@ function extractError(xml) {
|
|
|
780
1009
|
const message = messageElement?.textContent;
|
|
781
1010
|
return message || "No message found";
|
|
782
1011
|
}
|
|
1012
|
+
function extractTagText(xml, tagName) {
|
|
1013
|
+
if (!xml) return null;
|
|
1014
|
+
const [doc, parseErr] = safeParseXml(xml);
|
|
1015
|
+
if (parseErr) return null;
|
|
1016
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
1017
|
+
if (elements.length === 0) return null;
|
|
1018
|
+
const text = elements[0]?.textContent;
|
|
1019
|
+
if (!text || text.trim().length === 0) return null;
|
|
1020
|
+
return text.trim();
|
|
1021
|
+
}
|
|
783
1022
|
function escapeXml(str) {
|
|
784
1023
|
if (!str) {
|
|
785
1024
|
return "";
|
|
@@ -1200,6 +1439,24 @@ var OBJECT_CONFIG_MAP = {
|
|
|
1200
1439
|
type: "PROG/I",
|
|
1201
1440
|
label: "ABAP Include" /* INCLUDE */,
|
|
1202
1441
|
extension: "asinc"
|
|
1442
|
+
},
|
|
1443
|
+
"srvd": {
|
|
1444
|
+
endpoint: "ddic/srvd/sources",
|
|
1445
|
+
nameSpace: 'xmlns:srvd="http://www.sap.com/adt/ddic/srvdsources"',
|
|
1446
|
+
rootName: "srvd:srvdSource",
|
|
1447
|
+
type: "SRVD/SRV",
|
|
1448
|
+
label: "Service Definition" /* SERVICE_DEFINITION */,
|
|
1449
|
+
extension: "srvd",
|
|
1450
|
+
rootAttributes: { "srvd:srvdSourceType": "S" }
|
|
1451
|
+
},
|
|
1452
|
+
"asbdef": {
|
|
1453
|
+
endpoint: "bo/behaviordefinitions",
|
|
1454
|
+
nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
|
|
1455
|
+
rootName: "blue:blueSource",
|
|
1456
|
+
type: "BDEF/BDO",
|
|
1457
|
+
label: "Behavior Definition" /* BEHAVIOR_DEFINITION */,
|
|
1458
|
+
extension: "asbdef",
|
|
1459
|
+
requiresImplementationType: true
|
|
1203
1460
|
}
|
|
1204
1461
|
};
|
|
1205
1462
|
function getConfigByExtension(extension) {
|
|
@@ -1512,11 +1769,44 @@ async function multiDeleteObjects(client, objects, transport) {
|
|
|
1512
1769
|
return ok(results);
|
|
1513
1770
|
}
|
|
1514
1771
|
|
|
1772
|
+
// src/types/requests.ts
|
|
1773
|
+
import { z as z2 } from "zod";
|
|
1774
|
+
var BehaviorImplementationType = /* @__PURE__ */ ((BehaviorImplementationType2) => {
|
|
1775
|
+
BehaviorImplementationType2["Managed"] = "Managed";
|
|
1776
|
+
return BehaviorImplementationType2;
|
|
1777
|
+
})(BehaviorImplementationType || {});
|
|
1778
|
+
var objectRefSchema = z2.object({
|
|
1779
|
+
name: z2.string().min(1),
|
|
1780
|
+
extension: z2.string().min(1)
|
|
1781
|
+
});
|
|
1782
|
+
var objectContentSchema = objectRefSchema.extend({
|
|
1783
|
+
content: z2.string(),
|
|
1784
|
+
description: z2.string().optional(),
|
|
1785
|
+
implementationType: z2.nativeEnum(BehaviorImplementationType).optional()
|
|
1786
|
+
});
|
|
1787
|
+
var treeQuerySchema = z2.object({
|
|
1788
|
+
package: z2.string().min(1).optional(),
|
|
1789
|
+
path: z2.string().optional(),
|
|
1790
|
+
owner: z2.string().min(1).optional()
|
|
1791
|
+
});
|
|
1792
|
+
var previewQuerySchema = z2.object({
|
|
1793
|
+
objectName: z2.string().min(1),
|
|
1794
|
+
objectType: z2.enum(["table", "view"]),
|
|
1795
|
+
sqlQuery: z2.string().min(1),
|
|
1796
|
+
limit: z2.number().positive().max(5e4).optional()
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1515
1799
|
// src/core/adt/craud/create.ts
|
|
1516
1800
|
async function createObject(client, object, packageName, transport, username) {
|
|
1517
1801
|
const [config, configErr] = requireConfig(object.extension);
|
|
1518
1802
|
if (configErr) return err(configErr);
|
|
1519
1803
|
const description = object.description ?? "";
|
|
1804
|
+
const extraAttrs = config.rootAttributes ? Object.entries(config.rootAttributes).map(([key, value]) => `
|
|
1805
|
+
${key}="${escapeXml(value)}"`).join("") : "";
|
|
1806
|
+
const implementationType = object.implementationType ?? "Managed" /* Managed */;
|
|
1807
|
+
const adtTemplate = config.requiresImplementationType ? ` <adtcore:adtTemplate>
|
|
1808
|
+
<adtcore:adtProperty adtcore:key="implementation_type">${implementationType}</adtcore:adtProperty>
|
|
1809
|
+
</adtcore:adtTemplate>` : "";
|
|
1520
1810
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1521
1811
|
<${config.rootName} ${config.nameSpace}
|
|
1522
1812
|
xmlns:adtcore="http://www.sap.com/adt/core"
|
|
@@ -1524,8 +1814,8 @@ async function createObject(client, object, packageName, transport, username) {
|
|
|
1524
1814
|
adtcore:language="EN"
|
|
1525
1815
|
adtcore:name="${object.name.toUpperCase()}"
|
|
1526
1816
|
adtcore:type="${config.type}"
|
|
1527
|
-
adtcore:responsible="${username.toUpperCase()}">
|
|
1528
|
-
|
|
1817
|
+
adtcore:responsible="${username.toUpperCase()}"${extraAttrs}>
|
|
1818
|
+
${adtTemplate}
|
|
1529
1819
|
<adtcore:packageRef adtcore:name="${packageName}"/>
|
|
1530
1820
|
|
|
1531
1821
|
</${config.rootName}>`;
|
|
@@ -1577,6 +1867,33 @@ async function updateObject(client, object, lockHandle, transport) {
|
|
|
1577
1867
|
return ok(void 0);
|
|
1578
1868
|
}
|
|
1579
1869
|
|
|
1870
|
+
// src/core/adt/craud/classInclude.ts
|
|
1871
|
+
async function updateClassInclude(client, className, includeType, source, lockHandle, transport) {
|
|
1872
|
+
const [config, configErr] = requireConfig("aclass");
|
|
1873
|
+
if (configErr) return err(configErr);
|
|
1874
|
+
const params = {
|
|
1875
|
+
"lockHandle": lockHandle
|
|
1876
|
+
};
|
|
1877
|
+
if (transport) {
|
|
1878
|
+
params["corrNr"] = transport;
|
|
1879
|
+
}
|
|
1880
|
+
debug(`Update class include ${className}/${includeType}: length=${source.length}`);
|
|
1881
|
+
const [response, requestErr] = await client.request({
|
|
1882
|
+
method: "PUT",
|
|
1883
|
+
path: `/sap/bc/adt/${config.endpoint}/${className.toLowerCase()}/includes/${includeType}`,
|
|
1884
|
+
params,
|
|
1885
|
+
headers: { "Content-Type": "*/*" },
|
|
1886
|
+
body: source
|
|
1887
|
+
});
|
|
1888
|
+
const [, checkErr] = await checkResponse(
|
|
1889
|
+
response,
|
|
1890
|
+
requestErr,
|
|
1891
|
+
`Failed to write ${includeType} include of class ${className}`
|
|
1892
|
+
);
|
|
1893
|
+
if (checkErr) return err(checkErr);
|
|
1894
|
+
return ok(void 0);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1580
1897
|
// src/core/adt/craud/activation.ts
|
|
1581
1898
|
var MAX_POLL_ATTEMPTS = 30;
|
|
1582
1899
|
var POLL_RETRY_DELAY_MS = 1e3;
|
|
@@ -1586,14 +1903,26 @@ async function activateObjects(client, objects) {
|
|
|
1586
1903
|
if (objects.length === 0) {
|
|
1587
1904
|
return ok([]);
|
|
1588
1905
|
}
|
|
1906
|
+
const references = [];
|
|
1589
1907
|
for (const obj of objects) {
|
|
1590
1908
|
const config = getConfigByExtension(obj.extension);
|
|
1591
1909
|
if (!config) return err(new Error(`Unsupported extension: ${obj.extension}`));
|
|
1910
|
+
references.push({
|
|
1911
|
+
uri: `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`,
|
|
1912
|
+
type: config.type,
|
|
1913
|
+
name: obj.name,
|
|
1914
|
+
extension: obj.extension
|
|
1915
|
+
});
|
|
1592
1916
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1917
|
+
return activateByReferences(client, references);
|
|
1918
|
+
}
|
|
1919
|
+
async function activateByReferences(client, references) {
|
|
1920
|
+
if (references.length === 0) {
|
|
1921
|
+
return ok([]);
|
|
1922
|
+
}
|
|
1923
|
+
const objectRefs = references.map(
|
|
1924
|
+
(ref) => `<adtcore:objectReference adtcore:uri="${ref.uri}" adtcore:type="${ref.type}" adtcore:name="${ref.name}"/>`
|
|
1925
|
+
).join("\n ");
|
|
1597
1926
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1598
1927
|
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
1599
1928
|
${objectRefs}
|
|
@@ -1657,7 +1986,7 @@ async function activateObjects(client, objects) {
|
|
|
1657
1986
|
if (!resultsRes.ok) {
|
|
1658
1987
|
return err(new Error(`Failed to fetch activation results: ${extractError(resultsText)}`));
|
|
1659
1988
|
}
|
|
1660
|
-
const [results, parseErr] = extractActivationErrors(
|
|
1989
|
+
const [results, parseErr] = extractActivationErrors(references, resultsText);
|
|
1661
1990
|
if (parseErr) return err(parseErr);
|
|
1662
1991
|
return ok(results);
|
|
1663
1992
|
}
|
|
@@ -3018,6 +3347,203 @@ async function gitDiff(client, object) {
|
|
|
3018
3347
|
});
|
|
3019
3348
|
}
|
|
3020
3349
|
|
|
3350
|
+
// src/core/adt/businessservices/validate.ts
|
|
3351
|
+
var SEVERITY_OK = "OK";
|
|
3352
|
+
async function validateServiceBinding(client, options) {
|
|
3353
|
+
const bindingType = options.bindingType ?? "ODATA";
|
|
3354
|
+
const bindingVersion = options.bindingVersion ?? "V4";
|
|
3355
|
+
const [response, requestErr] = await client.request({
|
|
3356
|
+
method: "POST",
|
|
3357
|
+
path: "/sap/bc/adt/businessservices/bindings/validation",
|
|
3358
|
+
params: {
|
|
3359
|
+
objname: options.bindingName.toUpperCase(),
|
|
3360
|
+
description: options.description ?? "",
|
|
3361
|
+
serviceBindingVersion: `${bindingType}\\${bindingVersion}`,
|
|
3362
|
+
serviceDefinition: options.serviceDefinition.toUpperCase(),
|
|
3363
|
+
package: options.packageName.toUpperCase()
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
const [text, checkErr] = await checkResponse(
|
|
3367
|
+
response,
|
|
3368
|
+
requestErr,
|
|
3369
|
+
`Failed to validate service binding ${options.bindingName}`
|
|
3370
|
+
);
|
|
3371
|
+
if (checkErr) return err(checkErr);
|
|
3372
|
+
const severity = extractTagText(text, "SEVERITY");
|
|
3373
|
+
if (severity && severity !== SEVERITY_OK) {
|
|
3374
|
+
const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
|
|
3375
|
+
return err(new Error(`Service binding validation failed (${severity}): ${shortText}`));
|
|
3376
|
+
}
|
|
3377
|
+
return ok(void 0);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// src/core/adt/businessservices/create.ts
|
|
3381
|
+
var SERVICE_BINDING_TYPE_CODE = "SRVB/SVB";
|
|
3382
|
+
var BINDING_CATEGORY = "1";
|
|
3383
|
+
var CONTENT_VERSION = "0001";
|
|
3384
|
+
async function createServiceBindingObject(client, options, username) {
|
|
3385
|
+
const bindingType = options.bindingType ?? "ODATA";
|
|
3386
|
+
const bindingVersion = options.bindingVersion ?? "V4";
|
|
3387
|
+
const description = options.description ?? "";
|
|
3388
|
+
const bindingName = options.bindingName.toUpperCase();
|
|
3389
|
+
const serviceDefinition = options.serviceDefinition.toUpperCase();
|
|
3390
|
+
const packageName = options.packageName.toUpperCase();
|
|
3391
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3392
|
+
<srvb:serviceBinding xmlns:srvb="http://www.sap.com/adt/ddic/ServiceBindings" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${escapeXml(description)}" adtcore:language="EN" adtcore:name="${bindingName}" adtcore:type="${SERVICE_BINDING_TYPE_CODE}" adtcore:masterLanguage="EN" adtcore:responsible="${username.toUpperCase()}">
|
|
3393
|
+
<adtcore:packageRef adtcore:name="${packageName}"/>
|
|
3394
|
+
<srvb:services srvb:name="${serviceDefinition}">
|
|
3395
|
+
<srvb:content srvb:version="${CONTENT_VERSION}">
|
|
3396
|
+
<srvb:serviceDefinition adtcore:name="${serviceDefinition}"/>
|
|
3397
|
+
</srvb:content>
|
|
3398
|
+
</srvb:services>
|
|
3399
|
+
<srvb:binding srvb:category="${BINDING_CATEGORY}" srvb:type="${bindingType}" srvb:version="${bindingVersion}">
|
|
3400
|
+
<srvb:implementation adtcore:name=""/>
|
|
3401
|
+
</srvb:binding>
|
|
3402
|
+
</srvb:serviceBinding>`;
|
|
3403
|
+
const params = {};
|
|
3404
|
+
if (options.transport) {
|
|
3405
|
+
params["corrNr"] = options.transport;
|
|
3406
|
+
}
|
|
3407
|
+
const [response, requestErr] = await client.request({
|
|
3408
|
+
method: "POST",
|
|
3409
|
+
path: "/sap/bc/adt/businessservices/bindings",
|
|
3410
|
+
params,
|
|
3411
|
+
headers: { "Content-Type": "application/*" },
|
|
3412
|
+
body
|
|
3413
|
+
});
|
|
3414
|
+
const [, checkErr] = await checkResponse(
|
|
3415
|
+
response,
|
|
3416
|
+
requestErr,
|
|
3417
|
+
`Failed to create service binding ${options.bindingName}`
|
|
3418
|
+
);
|
|
3419
|
+
if (checkErr) return err(checkErr);
|
|
3420
|
+
return ok(void 0);
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// src/core/adt/businessservices/activate.ts
|
|
3424
|
+
var SERVICE_BINDING_TYPE_CODE2 = "SRVB/SVB";
|
|
3425
|
+
async function activateServiceBinding(client, bindingName) {
|
|
3426
|
+
return activateByReferences(client, [{
|
|
3427
|
+
uri: `/sap/bc/adt/businessservices/bindings/${bindingName.toLowerCase()}`,
|
|
3428
|
+
type: SERVICE_BINDING_TYPE_CODE2,
|
|
3429
|
+
name: bindingName.toUpperCase(),
|
|
3430
|
+
extension: ""
|
|
3431
|
+
}]);
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
// src/core/adt/businessservices/helpers.ts
|
|
3435
|
+
var BINDINGS_BASE_PATH = "/sap/bc/adt/businessservices/bindings";
|
|
3436
|
+
var LOCK_ACCEPT_HEADER = "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result";
|
|
3437
|
+
var PUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/publishjobs";
|
|
3438
|
+
var UNPUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/unpublishjobs";
|
|
3439
|
+
var SEVERITY_OK2 = "OK";
|
|
3440
|
+
function bindingPath(bindingName) {
|
|
3441
|
+
return `${BINDINGS_BASE_PATH}/${bindingName.toLowerCase()}`;
|
|
3442
|
+
}
|
|
3443
|
+
async function lockServiceBinding(client, bindingName) {
|
|
3444
|
+
const [response, requestErr] = await client.request({
|
|
3445
|
+
method: "POST",
|
|
3446
|
+
path: bindingPath(bindingName),
|
|
3447
|
+
params: {
|
|
3448
|
+
"_action": "LOCK",
|
|
3449
|
+
"accessMode": "MODIFY"
|
|
3450
|
+
},
|
|
3451
|
+
headers: { "Accept": LOCK_ACCEPT_HEADER }
|
|
3452
|
+
});
|
|
3453
|
+
const [text, checkErr] = await checkResponse(
|
|
3454
|
+
response,
|
|
3455
|
+
requestErr,
|
|
3456
|
+
`Failed to lock service binding ${bindingName}`
|
|
3457
|
+
);
|
|
3458
|
+
if (checkErr) return err(checkErr);
|
|
3459
|
+
const [lockHandle, extractErr] = extractLockHandle(text);
|
|
3460
|
+
if (extractErr) return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
|
|
3461
|
+
return ok(lockHandle);
|
|
3462
|
+
}
|
|
3463
|
+
async function unlockServiceBinding(client, bindingName, lockHandle) {
|
|
3464
|
+
const [response, requestErr] = await client.request({
|
|
3465
|
+
method: "POST",
|
|
3466
|
+
path: bindingPath(bindingName),
|
|
3467
|
+
params: {
|
|
3468
|
+
"_action": "UNLOCK",
|
|
3469
|
+
"lockHandle": lockHandle
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
const [, checkErr] = await checkResponse(
|
|
3473
|
+
response,
|
|
3474
|
+
requestErr,
|
|
3475
|
+
`Failed to unlock service binding ${bindingName}`
|
|
3476
|
+
);
|
|
3477
|
+
if (checkErr) return err(checkErr);
|
|
3478
|
+
return ok(void 0);
|
|
3479
|
+
}
|
|
3480
|
+
async function submitServiceBindingJob(client, bindingName, action) {
|
|
3481
|
+
const path = action === "publish" ? PUBLISH_JOBS_PATH : UNPUBLISH_JOBS_PATH;
|
|
3482
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3483
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
3484
|
+
<adtcore:objectReference adtcore:type="SCGR" adtcore:name="${bindingName.toUpperCase()}"/>
|
|
3485
|
+
</adtcore:objectReferences>`;
|
|
3486
|
+
const [response, requestErr] = await client.request({
|
|
3487
|
+
method: "POST",
|
|
3488
|
+
path,
|
|
3489
|
+
headers: { "Content-Type": "application/xml" },
|
|
3490
|
+
body
|
|
3491
|
+
});
|
|
3492
|
+
const [text, checkErr] = await checkResponse(
|
|
3493
|
+
response,
|
|
3494
|
+
requestErr,
|
|
3495
|
+
`Failed to ${action} service binding ${bindingName}`
|
|
3496
|
+
);
|
|
3497
|
+
if (checkErr) return err(checkErr);
|
|
3498
|
+
const severity = extractTagText(text, "SEVERITY");
|
|
3499
|
+
if (severity && severity !== SEVERITY_OK2) {
|
|
3500
|
+
const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
|
|
3501
|
+
return err(new Error(`Service binding ${action} failed (${severity}): ${shortText}`));
|
|
3502
|
+
}
|
|
3503
|
+
return ok(extractTagText(text, "SHORT_TEXT") ?? "");
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// src/core/adt/businessservices/publish.ts
|
|
3507
|
+
async function publishServiceBinding(client, bindingName) {
|
|
3508
|
+
return runBindingJob(client, bindingName, "publish");
|
|
3509
|
+
}
|
|
3510
|
+
async function runBindingJob(client, bindingName, action) {
|
|
3511
|
+
const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
|
|
3512
|
+
if (lockErr) return err(lockErr);
|
|
3513
|
+
debug(`Service binding lock acquired for ${action}: handle=${lockHandle}`);
|
|
3514
|
+
const [message, jobErr] = await submitServiceBindingJob(client, bindingName, action);
|
|
3515
|
+
const [, unlockErr] = await unlockServiceBinding(client, bindingName, lockHandle);
|
|
3516
|
+
if (jobErr) return err(jobErr);
|
|
3517
|
+
if (unlockErr) return err(unlockErr);
|
|
3518
|
+
return ok(message);
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
// src/core/adt/businessservices/delete.ts
|
|
3522
|
+
async function deleteServiceBinding(client, bindingName, transport) {
|
|
3523
|
+
const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
|
|
3524
|
+
if (lockErr) return err(lockErr);
|
|
3525
|
+
const [, unpublishErr] = await submitServiceBindingJob(client, bindingName, "unpublish");
|
|
3526
|
+
if (unpublishErr) debug(`Unpublish before delete skipped: ${unpublishErr.message}`);
|
|
3527
|
+
const params = { lockHandle };
|
|
3528
|
+
if (transport) params["corrNr"] = transport;
|
|
3529
|
+
const [response, requestErr] = await client.request({
|
|
3530
|
+
method: "DELETE",
|
|
3531
|
+
path: bindingPath(bindingName),
|
|
3532
|
+
params,
|
|
3533
|
+
headers: { "Accept": "text/plain" }
|
|
3534
|
+
});
|
|
3535
|
+
const [, checkErr] = await checkResponse(
|
|
3536
|
+
response,
|
|
3537
|
+
requestErr,
|
|
3538
|
+
`Failed to delete service binding ${bindingName}`
|
|
3539
|
+
);
|
|
3540
|
+
if (checkErr) {
|
|
3541
|
+
await unlockServiceBinding(client, bindingName, lockHandle);
|
|
3542
|
+
return err(checkErr);
|
|
3543
|
+
}
|
|
3544
|
+
return ok(void 0);
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3021
3547
|
// src/client/methods/craud/read.ts
|
|
3022
3548
|
async function read(state, requestor, objects) {
|
|
3023
3549
|
if (!state.session) return err(new Error("Not logged in"));
|
|
@@ -3058,6 +3584,19 @@ async function update(state, requestor, object, transport) {
|
|
|
3058
3584
|
return ok(void 0);
|
|
3059
3585
|
}
|
|
3060
3586
|
|
|
3587
|
+
// src/client/methods/craud/specialcases/classes/writeClassInclude.ts
|
|
3588
|
+
async function writeClassInclude(state, requestor, className, includeType, source, transport) {
|
|
3589
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3590
|
+
const objRef = { name: className, extension: "aclass" };
|
|
3591
|
+
const [lockHandle, lockErr] = await lockObject(requestor, objRef);
|
|
3592
|
+
if (lockErr) return err(lockErr);
|
|
3593
|
+
const [, updateErr] = await updateClassInclude(requestor, className, includeType, source, lockHandle, transport);
|
|
3594
|
+
const [, unlockErr] = await unlockObject(requestor, objRef, lockHandle);
|
|
3595
|
+
if (updateErr) return err(updateErr);
|
|
3596
|
+
if (unlockErr) return err(unlockErr);
|
|
3597
|
+
return ok(void 0);
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3061
3600
|
// src/client/methods/craud/upsert.ts
|
|
3062
3601
|
async function upsertSingle(state, requestor, object, packageName, transport) {
|
|
3063
3602
|
if (!state.session) return err(new Error("Not logged in"));
|
|
@@ -3234,6 +3773,41 @@ function getObjectConfig() {
|
|
|
3234
3773
|
return Object.values(OBJECT_CONFIG_MAP);
|
|
3235
3774
|
}
|
|
3236
3775
|
|
|
3776
|
+
// src/client/methods/craud/specialcases/businessservices/createServiceBinding.ts
|
|
3777
|
+
async function createServiceBinding(state, requestor, options) {
|
|
3778
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3779
|
+
const [, validateErr] = await validateServiceBinding(requestor, options);
|
|
3780
|
+
if (validateErr) return err(validateErr);
|
|
3781
|
+
const [, createErr] = await createServiceBindingObject(requestor, options, state.session.username);
|
|
3782
|
+
if (createErr) return err(createErr);
|
|
3783
|
+
const [activation, activateErr] = await activateServiceBinding(requestor, options.bindingName);
|
|
3784
|
+
if (activateErr) return err(activateErr);
|
|
3785
|
+
if (activation.some((result2) => result2.status === "error")) {
|
|
3786
|
+
return err(new Error(`Service binding ${options.bindingName} failed activation`));
|
|
3787
|
+
}
|
|
3788
|
+
const result = {
|
|
3789
|
+
name: options.bindingName.toUpperCase(),
|
|
3790
|
+
serviceDefinition: options.serviceDefinition.toUpperCase(),
|
|
3791
|
+
created: true,
|
|
3792
|
+
activation,
|
|
3793
|
+
published: false
|
|
3794
|
+
};
|
|
3795
|
+
if (options.publish === false) {
|
|
3796
|
+
return ok(result);
|
|
3797
|
+
}
|
|
3798
|
+
const [publishMessage, publishErr] = await publishServiceBinding(requestor, options.bindingName);
|
|
3799
|
+
if (publishErr) return err(publishErr);
|
|
3800
|
+
result.published = true;
|
|
3801
|
+
if (publishMessage) result.publishMessage = publishMessage;
|
|
3802
|
+
return ok(result);
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
// src/client/methods/craud/specialcases/businessservices/deleteServiceBinding.ts
|
|
3806
|
+
async function deleteServiceBinding2(state, requestor, bindingName, transport) {
|
|
3807
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3808
|
+
return deleteServiceBinding(requestor, bindingName, transport);
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3237
3811
|
// src/client/methods/internal/cookies.ts
|
|
3238
3812
|
function storeCookies(cookies, response) {
|
|
3239
3813
|
const setCookieHeader = response.headers.get("set-cookie");
|
|
@@ -3546,6 +4120,9 @@ var ADTClientImpl = class {
|
|
|
3546
4120
|
async update(object, transport) {
|
|
3547
4121
|
return update(this.state, this.requestor, object, transport);
|
|
3548
4122
|
}
|
|
4123
|
+
async writeClassInclude(className, includeType, source, transport) {
|
|
4124
|
+
return writeClassInclude(this.state, this.requestor, className, includeType, source, transport);
|
|
4125
|
+
}
|
|
3549
4126
|
async upsert(objects, packageName, transport) {
|
|
3550
4127
|
return upsert(this.state, this.requestor, objects, packageName, transport);
|
|
3551
4128
|
}
|
|
@@ -3611,6 +4188,13 @@ var ADTClientImpl = class {
|
|
|
3611
4188
|
async gitDiff(objects) {
|
|
3612
4189
|
return gitDiff2(this.state, this.requestor, objects);
|
|
3613
4190
|
}
|
|
4191
|
+
// --- Business Services ---
|
|
4192
|
+
async createServiceBinding(options) {
|
|
4193
|
+
return createServiceBinding(this.state, this.requestor, options);
|
|
4194
|
+
}
|
|
4195
|
+
async deleteServiceBinding(bindingName, transport) {
|
|
4196
|
+
return deleteServiceBinding2(this.state, this.requestor, bindingName, transport);
|
|
4197
|
+
}
|
|
3614
4198
|
// --- Configuration ---
|
|
3615
4199
|
getObjectConfig() {
|
|
3616
4200
|
return getObjectConfig();
|
|
@@ -3627,6 +4211,7 @@ function createClient(config) {
|
|
|
3627
4211
|
return ok(new ADTClientImpl(config));
|
|
3628
4212
|
}
|
|
3629
4213
|
export {
|
|
4214
|
+
BehaviorImplementationType,
|
|
3630
4215
|
ExternalReferencesError,
|
|
3631
4216
|
activateLogging,
|
|
3632
4217
|
buildSQLQuery,
|