catalyst-relay 0.5.14 → 0.6.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/README.md +29 -1
- package/dist/index.d.mts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +551 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +550 -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}>`;
|
|
@@ -1586,14 +1876,26 @@ async function activateObjects(client, objects) {
|
|
|
1586
1876
|
if (objects.length === 0) {
|
|
1587
1877
|
return ok([]);
|
|
1588
1878
|
}
|
|
1879
|
+
const references = [];
|
|
1589
1880
|
for (const obj of objects) {
|
|
1590
1881
|
const config = getConfigByExtension(obj.extension);
|
|
1591
1882
|
if (!config) return err(new Error(`Unsupported extension: ${obj.extension}`));
|
|
1883
|
+
references.push({
|
|
1884
|
+
uri: `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`,
|
|
1885
|
+
type: config.type,
|
|
1886
|
+
name: obj.name,
|
|
1887
|
+
extension: obj.extension
|
|
1888
|
+
});
|
|
1592
1889
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1890
|
+
return activateByReferences(client, references);
|
|
1891
|
+
}
|
|
1892
|
+
async function activateByReferences(client, references) {
|
|
1893
|
+
if (references.length === 0) {
|
|
1894
|
+
return ok([]);
|
|
1895
|
+
}
|
|
1896
|
+
const objectRefs = references.map(
|
|
1897
|
+
(ref) => `<adtcore:objectReference adtcore:uri="${ref.uri}" adtcore:type="${ref.type}" adtcore:name="${ref.name}"/>`
|
|
1898
|
+
).join("\n ");
|
|
1597
1899
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1598
1900
|
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
1599
1901
|
${objectRefs}
|
|
@@ -1657,7 +1959,7 @@ async function activateObjects(client, objects) {
|
|
|
1657
1959
|
if (!resultsRes.ok) {
|
|
1658
1960
|
return err(new Error(`Failed to fetch activation results: ${extractError(resultsText)}`));
|
|
1659
1961
|
}
|
|
1660
|
-
const [results, parseErr] = extractActivationErrors(
|
|
1962
|
+
const [results, parseErr] = extractActivationErrors(references, resultsText);
|
|
1661
1963
|
if (parseErr) return err(parseErr);
|
|
1662
1964
|
return ok(results);
|
|
1663
1965
|
}
|
|
@@ -3018,6 +3320,203 @@ async function gitDiff(client, object) {
|
|
|
3018
3320
|
});
|
|
3019
3321
|
}
|
|
3020
3322
|
|
|
3323
|
+
// src/core/adt/businessservices/validate.ts
|
|
3324
|
+
var SEVERITY_OK = "OK";
|
|
3325
|
+
async function validateServiceBinding(client, options) {
|
|
3326
|
+
const bindingType = options.bindingType ?? "ODATA";
|
|
3327
|
+
const bindingVersion = options.bindingVersion ?? "V4";
|
|
3328
|
+
const [response, requestErr] = await client.request({
|
|
3329
|
+
method: "POST",
|
|
3330
|
+
path: "/sap/bc/adt/businessservices/bindings/validation",
|
|
3331
|
+
params: {
|
|
3332
|
+
objname: options.bindingName.toUpperCase(),
|
|
3333
|
+
description: options.description ?? "",
|
|
3334
|
+
serviceBindingVersion: `${bindingType}\\${bindingVersion}`,
|
|
3335
|
+
serviceDefinition: options.serviceDefinition.toUpperCase(),
|
|
3336
|
+
package: options.packageName.toUpperCase()
|
|
3337
|
+
}
|
|
3338
|
+
});
|
|
3339
|
+
const [text, checkErr] = await checkResponse(
|
|
3340
|
+
response,
|
|
3341
|
+
requestErr,
|
|
3342
|
+
`Failed to validate service binding ${options.bindingName}`
|
|
3343
|
+
);
|
|
3344
|
+
if (checkErr) return err(checkErr);
|
|
3345
|
+
const severity = extractTagText(text, "SEVERITY");
|
|
3346
|
+
if (severity && severity !== SEVERITY_OK) {
|
|
3347
|
+
const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
|
|
3348
|
+
return err(new Error(`Service binding validation failed (${severity}): ${shortText}`));
|
|
3349
|
+
}
|
|
3350
|
+
return ok(void 0);
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// src/core/adt/businessservices/create.ts
|
|
3354
|
+
var SERVICE_BINDING_TYPE_CODE = "SRVB/SVB";
|
|
3355
|
+
var BINDING_CATEGORY = "1";
|
|
3356
|
+
var CONTENT_VERSION = "0001";
|
|
3357
|
+
async function createServiceBindingObject(client, options, username) {
|
|
3358
|
+
const bindingType = options.bindingType ?? "ODATA";
|
|
3359
|
+
const bindingVersion = options.bindingVersion ?? "V4";
|
|
3360
|
+
const description = options.description ?? "";
|
|
3361
|
+
const bindingName = options.bindingName.toUpperCase();
|
|
3362
|
+
const serviceDefinition = options.serviceDefinition.toUpperCase();
|
|
3363
|
+
const packageName = options.packageName.toUpperCase();
|
|
3364
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3365
|
+
<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()}">
|
|
3366
|
+
<adtcore:packageRef adtcore:name="${packageName}"/>
|
|
3367
|
+
<srvb:services srvb:name="${serviceDefinition}">
|
|
3368
|
+
<srvb:content srvb:version="${CONTENT_VERSION}">
|
|
3369
|
+
<srvb:serviceDefinition adtcore:name="${serviceDefinition}"/>
|
|
3370
|
+
</srvb:content>
|
|
3371
|
+
</srvb:services>
|
|
3372
|
+
<srvb:binding srvb:category="${BINDING_CATEGORY}" srvb:type="${bindingType}" srvb:version="${bindingVersion}">
|
|
3373
|
+
<srvb:implementation adtcore:name=""/>
|
|
3374
|
+
</srvb:binding>
|
|
3375
|
+
</srvb:serviceBinding>`;
|
|
3376
|
+
const params = {};
|
|
3377
|
+
if (options.transport) {
|
|
3378
|
+
params["corrNr"] = options.transport;
|
|
3379
|
+
}
|
|
3380
|
+
const [response, requestErr] = await client.request({
|
|
3381
|
+
method: "POST",
|
|
3382
|
+
path: "/sap/bc/adt/businessservices/bindings",
|
|
3383
|
+
params,
|
|
3384
|
+
headers: { "Content-Type": "application/*" },
|
|
3385
|
+
body
|
|
3386
|
+
});
|
|
3387
|
+
const [, checkErr] = await checkResponse(
|
|
3388
|
+
response,
|
|
3389
|
+
requestErr,
|
|
3390
|
+
`Failed to create service binding ${options.bindingName}`
|
|
3391
|
+
);
|
|
3392
|
+
if (checkErr) return err(checkErr);
|
|
3393
|
+
return ok(void 0);
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
// src/core/adt/businessservices/activate.ts
|
|
3397
|
+
var SERVICE_BINDING_TYPE_CODE2 = "SRVB/SVB";
|
|
3398
|
+
async function activateServiceBinding(client, bindingName) {
|
|
3399
|
+
return activateByReferences(client, [{
|
|
3400
|
+
uri: `/sap/bc/adt/businessservices/bindings/${bindingName.toLowerCase()}`,
|
|
3401
|
+
type: SERVICE_BINDING_TYPE_CODE2,
|
|
3402
|
+
name: bindingName.toUpperCase(),
|
|
3403
|
+
extension: ""
|
|
3404
|
+
}]);
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// src/core/adt/businessservices/helpers.ts
|
|
3408
|
+
var BINDINGS_BASE_PATH = "/sap/bc/adt/businessservices/bindings";
|
|
3409
|
+
var LOCK_ACCEPT_HEADER = "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result";
|
|
3410
|
+
var PUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/publishjobs";
|
|
3411
|
+
var UNPUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/unpublishjobs";
|
|
3412
|
+
var SEVERITY_OK2 = "OK";
|
|
3413
|
+
function bindingPath(bindingName) {
|
|
3414
|
+
return `${BINDINGS_BASE_PATH}/${bindingName.toLowerCase()}`;
|
|
3415
|
+
}
|
|
3416
|
+
async function lockServiceBinding(client, bindingName) {
|
|
3417
|
+
const [response, requestErr] = await client.request({
|
|
3418
|
+
method: "POST",
|
|
3419
|
+
path: bindingPath(bindingName),
|
|
3420
|
+
params: {
|
|
3421
|
+
"_action": "LOCK",
|
|
3422
|
+
"accessMode": "MODIFY"
|
|
3423
|
+
},
|
|
3424
|
+
headers: { "Accept": LOCK_ACCEPT_HEADER }
|
|
3425
|
+
});
|
|
3426
|
+
const [text, checkErr] = await checkResponse(
|
|
3427
|
+
response,
|
|
3428
|
+
requestErr,
|
|
3429
|
+
`Failed to lock service binding ${bindingName}`
|
|
3430
|
+
);
|
|
3431
|
+
if (checkErr) return err(checkErr);
|
|
3432
|
+
const [lockHandle, extractErr] = extractLockHandle(text);
|
|
3433
|
+
if (extractErr) return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
|
|
3434
|
+
return ok(lockHandle);
|
|
3435
|
+
}
|
|
3436
|
+
async function unlockServiceBinding(client, bindingName, lockHandle) {
|
|
3437
|
+
const [response, requestErr] = await client.request({
|
|
3438
|
+
method: "POST",
|
|
3439
|
+
path: bindingPath(bindingName),
|
|
3440
|
+
params: {
|
|
3441
|
+
"_action": "UNLOCK",
|
|
3442
|
+
"lockHandle": lockHandle
|
|
3443
|
+
}
|
|
3444
|
+
});
|
|
3445
|
+
const [, checkErr] = await checkResponse(
|
|
3446
|
+
response,
|
|
3447
|
+
requestErr,
|
|
3448
|
+
`Failed to unlock service binding ${bindingName}`
|
|
3449
|
+
);
|
|
3450
|
+
if (checkErr) return err(checkErr);
|
|
3451
|
+
return ok(void 0);
|
|
3452
|
+
}
|
|
3453
|
+
async function submitServiceBindingJob(client, bindingName, action) {
|
|
3454
|
+
const path = action === "publish" ? PUBLISH_JOBS_PATH : UNPUBLISH_JOBS_PATH;
|
|
3455
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3456
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
3457
|
+
<adtcore:objectReference adtcore:type="SCGR" adtcore:name="${bindingName.toUpperCase()}"/>
|
|
3458
|
+
</adtcore:objectReferences>`;
|
|
3459
|
+
const [response, requestErr] = await client.request({
|
|
3460
|
+
method: "POST",
|
|
3461
|
+
path,
|
|
3462
|
+
headers: { "Content-Type": "application/xml" },
|
|
3463
|
+
body
|
|
3464
|
+
});
|
|
3465
|
+
const [text, checkErr] = await checkResponse(
|
|
3466
|
+
response,
|
|
3467
|
+
requestErr,
|
|
3468
|
+
`Failed to ${action} service binding ${bindingName}`
|
|
3469
|
+
);
|
|
3470
|
+
if (checkErr) return err(checkErr);
|
|
3471
|
+
const severity = extractTagText(text, "SEVERITY");
|
|
3472
|
+
if (severity && severity !== SEVERITY_OK2) {
|
|
3473
|
+
const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
|
|
3474
|
+
return err(new Error(`Service binding ${action} failed (${severity}): ${shortText}`));
|
|
3475
|
+
}
|
|
3476
|
+
return ok(extractTagText(text, "SHORT_TEXT") ?? "");
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
// src/core/adt/businessservices/publish.ts
|
|
3480
|
+
async function publishServiceBinding(client, bindingName) {
|
|
3481
|
+
return runBindingJob(client, bindingName, "publish");
|
|
3482
|
+
}
|
|
3483
|
+
async function runBindingJob(client, bindingName, action) {
|
|
3484
|
+
const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
|
|
3485
|
+
if (lockErr) return err(lockErr);
|
|
3486
|
+
debug(`Service binding lock acquired for ${action}: handle=${lockHandle}`);
|
|
3487
|
+
const [message, jobErr] = await submitServiceBindingJob(client, bindingName, action);
|
|
3488
|
+
const [, unlockErr] = await unlockServiceBinding(client, bindingName, lockHandle);
|
|
3489
|
+
if (jobErr) return err(jobErr);
|
|
3490
|
+
if (unlockErr) return err(unlockErr);
|
|
3491
|
+
return ok(message);
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
// src/core/adt/businessservices/delete.ts
|
|
3495
|
+
async function deleteServiceBinding(client, bindingName, transport) {
|
|
3496
|
+
const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
|
|
3497
|
+
if (lockErr) return err(lockErr);
|
|
3498
|
+
const [, unpublishErr] = await submitServiceBindingJob(client, bindingName, "unpublish");
|
|
3499
|
+
if (unpublishErr) debug(`Unpublish before delete skipped: ${unpublishErr.message}`);
|
|
3500
|
+
const params = { lockHandle };
|
|
3501
|
+
if (transport) params["corrNr"] = transport;
|
|
3502
|
+
const [response, requestErr] = await client.request({
|
|
3503
|
+
method: "DELETE",
|
|
3504
|
+
path: bindingPath(bindingName),
|
|
3505
|
+
params,
|
|
3506
|
+
headers: { "Accept": "text/plain" }
|
|
3507
|
+
});
|
|
3508
|
+
const [, checkErr] = await checkResponse(
|
|
3509
|
+
response,
|
|
3510
|
+
requestErr,
|
|
3511
|
+
`Failed to delete service binding ${bindingName}`
|
|
3512
|
+
);
|
|
3513
|
+
if (checkErr) {
|
|
3514
|
+
await unlockServiceBinding(client, bindingName, lockHandle);
|
|
3515
|
+
return err(checkErr);
|
|
3516
|
+
}
|
|
3517
|
+
return ok(void 0);
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3021
3520
|
// src/client/methods/craud/read.ts
|
|
3022
3521
|
async function read(state, requestor, objects) {
|
|
3023
3522
|
if (!state.session) return err(new Error("Not logged in"));
|
|
@@ -3234,6 +3733,41 @@ function getObjectConfig() {
|
|
|
3234
3733
|
return Object.values(OBJECT_CONFIG_MAP);
|
|
3235
3734
|
}
|
|
3236
3735
|
|
|
3736
|
+
// src/client/methods/businessservices/createServiceBinding.ts
|
|
3737
|
+
async function createServiceBinding(state, requestor, options) {
|
|
3738
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3739
|
+
const [, validateErr] = await validateServiceBinding(requestor, options);
|
|
3740
|
+
if (validateErr) return err(validateErr);
|
|
3741
|
+
const [, createErr] = await createServiceBindingObject(requestor, options, state.session.username);
|
|
3742
|
+
if (createErr) return err(createErr);
|
|
3743
|
+
const [activation, activateErr] = await activateServiceBinding(requestor, options.bindingName);
|
|
3744
|
+
if (activateErr) return err(activateErr);
|
|
3745
|
+
if (activation.some((result2) => result2.status === "error")) {
|
|
3746
|
+
return err(new Error(`Service binding ${options.bindingName} failed activation`));
|
|
3747
|
+
}
|
|
3748
|
+
const result = {
|
|
3749
|
+
name: options.bindingName.toUpperCase(),
|
|
3750
|
+
serviceDefinition: options.serviceDefinition.toUpperCase(),
|
|
3751
|
+
created: true,
|
|
3752
|
+
activation,
|
|
3753
|
+
published: false
|
|
3754
|
+
};
|
|
3755
|
+
if (options.publish === false) {
|
|
3756
|
+
return ok(result);
|
|
3757
|
+
}
|
|
3758
|
+
const [publishMessage, publishErr] = await publishServiceBinding(requestor, options.bindingName);
|
|
3759
|
+
if (publishErr) return err(publishErr);
|
|
3760
|
+
result.published = true;
|
|
3761
|
+
if (publishMessage) result.publishMessage = publishMessage;
|
|
3762
|
+
return ok(result);
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// src/client/methods/businessservices/deleteServiceBinding.ts
|
|
3766
|
+
async function deleteServiceBinding2(state, requestor, bindingName, transport) {
|
|
3767
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3768
|
+
return deleteServiceBinding(requestor, bindingName, transport);
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3237
3771
|
// src/client/methods/internal/cookies.ts
|
|
3238
3772
|
function storeCookies(cookies, response) {
|
|
3239
3773
|
const setCookieHeader = response.headers.get("set-cookie");
|
|
@@ -3611,6 +4145,13 @@ var ADTClientImpl = class {
|
|
|
3611
4145
|
async gitDiff(objects) {
|
|
3612
4146
|
return gitDiff2(this.state, this.requestor, objects);
|
|
3613
4147
|
}
|
|
4148
|
+
// --- Business Services ---
|
|
4149
|
+
async createServiceBinding(options) {
|
|
4150
|
+
return createServiceBinding(this.state, this.requestor, options);
|
|
4151
|
+
}
|
|
4152
|
+
async deleteServiceBinding(bindingName, transport) {
|
|
4153
|
+
return deleteServiceBinding2(this.state, this.requestor, bindingName, transport);
|
|
4154
|
+
}
|
|
3614
4155
|
// --- Configuration ---
|
|
3615
4156
|
getObjectConfig() {
|
|
3616
4157
|
return getObjectConfig();
|
|
@@ -3627,6 +4168,7 @@ function createClient(config) {
|
|
|
3627
4168
|
return ok(new ADTClientImpl(config));
|
|
3628
4169
|
}
|
|
3629
4170
|
export {
|
|
4171
|
+
BehaviorImplementationType,
|
|
3630
4172
|
ExternalReferencesError,
|
|
3631
4173
|
activateLogging,
|
|
3632
4174
|
buildSQLQuery,
|