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.js
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BehaviorImplementationType: () => BehaviorImplementationType,
|
|
33
34
|
ExternalReferencesError: () => ExternalReferencesError,
|
|
34
35
|
activateLogging: () => activateLogging,
|
|
35
36
|
buildSQLQuery: () => buildSQLQuery,
|
|
@@ -537,6 +538,12 @@ var SsoAuth = class {
|
|
|
537
538
|
}
|
|
538
539
|
};
|
|
539
540
|
|
|
541
|
+
// src/core/auth/saml/browser.ts
|
|
542
|
+
var import_node_child_process = require("child_process");
|
|
543
|
+
var import_node_fs = require("fs");
|
|
544
|
+
var import_node_os = require("os");
|
|
545
|
+
var import_node_path2 = require("path");
|
|
546
|
+
|
|
540
547
|
// src/core/auth/saml/types.ts
|
|
541
548
|
var DEFAULT_FORM_SELECTORS = {
|
|
542
549
|
username: "#j_username",
|
|
@@ -551,9 +558,16 @@ var DEFAULT_PROVIDER_CONFIG = {
|
|
|
551
558
|
// src/core/auth/saml/browser.ts
|
|
552
559
|
var TIMEOUTS = {
|
|
553
560
|
PAGE_LOAD: 6e4,
|
|
554
|
-
FORM_SELECTOR:
|
|
561
|
+
FORM_SELECTOR: 15e3,
|
|
562
|
+
NETWORK_IDLE: 3e4,
|
|
563
|
+
/** Quiet period with no in-flight requests that counts as "idle". */
|
|
564
|
+
IDLE_QUIET: 600
|
|
555
565
|
};
|
|
566
|
+
var isBun = typeof globalThis.Bun !== "undefined";
|
|
556
567
|
async function performBrowserLogin(options) {
|
|
568
|
+
return isBun ? performBrowserLoginViaCdp(options) : performBrowserLoginViaPlaywright(options);
|
|
569
|
+
}
|
|
570
|
+
async function performBrowserLoginViaPlaywright(options) {
|
|
557
571
|
const { baseUrl, credentials, headless = true } = options;
|
|
558
572
|
const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
|
|
559
573
|
let playwright;
|
|
@@ -611,6 +625,222 @@ async function performBrowserLogin(options) {
|
|
|
611
625
|
await browser.close();
|
|
612
626
|
}
|
|
613
627
|
}
|
|
628
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
629
|
+
async function resolveChromiumPath() {
|
|
630
|
+
const override = process.env["CATALYST_CHROMIUM_PATH"];
|
|
631
|
+
if (override) return [override, null];
|
|
632
|
+
try {
|
|
633
|
+
const playwright = await import("playwright");
|
|
634
|
+
const path = playwright.chromium.executablePath();
|
|
635
|
+
if (!path) {
|
|
636
|
+
return [null, new Error("Could not resolve the Chromium executable path from Playwright.")];
|
|
637
|
+
}
|
|
638
|
+
return [path, null];
|
|
639
|
+
} catch {
|
|
640
|
+
return [
|
|
641
|
+
null,
|
|
642
|
+
new Error(
|
|
643
|
+
"Playwright is required for SAML authentication but is not installed. Install it with: npm install playwright"
|
|
644
|
+
)
|
|
645
|
+
];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async function connectCdp(wsUrl) {
|
|
649
|
+
const WebSocketCtor = globalThis.WebSocket;
|
|
650
|
+
if (!WebSocketCtor) {
|
|
651
|
+
throw new Error("Native WebSocket is not available in this runtime.");
|
|
652
|
+
}
|
|
653
|
+
const ws = new WebSocketCtor(wsUrl);
|
|
654
|
+
let nextId = 0;
|
|
655
|
+
const pending = /* @__PURE__ */ new Map();
|
|
656
|
+
const eventHandlers = [];
|
|
657
|
+
ws.onmessage = (event) => {
|
|
658
|
+
const msg = JSON.parse(String(event.data));
|
|
659
|
+
if (typeof msg.id === "number" && pending.has(msg.id)) {
|
|
660
|
+
const { resolve, reject } = pending.get(msg.id);
|
|
661
|
+
pending.delete(msg.id);
|
|
662
|
+
if (msg.error) reject(new Error(msg.error.message));
|
|
663
|
+
else resolve(msg.result ?? {});
|
|
664
|
+
} else if (msg.method) {
|
|
665
|
+
for (const handler of eventHandlers) {
|
|
666
|
+
handler({
|
|
667
|
+
method: msg.method,
|
|
668
|
+
params: msg.params ?? {},
|
|
669
|
+
...msg.sessionId !== void 0 ? { sessionId: msg.sessionId } : {}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
await new Promise((resolve, reject) => {
|
|
675
|
+
ws.onopen = () => resolve();
|
|
676
|
+
ws.onerror = () => reject(new Error("Failed to connect to the browser CDP endpoint."));
|
|
677
|
+
});
|
|
678
|
+
return {
|
|
679
|
+
send(method, params = {}, sessionId) {
|
|
680
|
+
return new Promise((resolve, reject) => {
|
|
681
|
+
const id = ++nextId;
|
|
682
|
+
pending.set(id, { resolve, reject });
|
|
683
|
+
ws.send(JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} }));
|
|
684
|
+
});
|
|
685
|
+
},
|
|
686
|
+
on(handler) {
|
|
687
|
+
eventHandlers.push(handler);
|
|
688
|
+
},
|
|
689
|
+
close() {
|
|
690
|
+
ws.close();
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
async function performBrowserLoginViaCdp(options) {
|
|
695
|
+
const { baseUrl, credentials, headless = true } = options;
|
|
696
|
+
const config = options.providerConfig ?? DEFAULT_PROVIDER_CONFIG;
|
|
697
|
+
const [exePath, pathError] = await resolveChromiumPath();
|
|
698
|
+
if (pathError) return err(pathError);
|
|
699
|
+
const userDataDir = (0, import_node_fs.mkdtempSync)((0, import_node_path2.join)((0, import_node_os.tmpdir)(), "catalyst-saml-"));
|
|
700
|
+
const args = [
|
|
701
|
+
...headless ? ["--headless=new"] : [],
|
|
702
|
+
"--remote-debugging-port=0",
|
|
703
|
+
"--no-first-run",
|
|
704
|
+
"--no-default-browser-check",
|
|
705
|
+
`--user-data-dir=${userDataDir}`,
|
|
706
|
+
...config.ignoreHttpsErrors ? ["--ignore-certificate-errors"] : []
|
|
707
|
+
];
|
|
708
|
+
const proc = (0, import_node_child_process.spawn)(exePath, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
709
|
+
let client = null;
|
|
710
|
+
const cleanup = () => {
|
|
711
|
+
try {
|
|
712
|
+
client?.close();
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
proc.kill();
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
(0, import_node_fs.rmSync)(userDataDir, { recursive: true, force: true });
|
|
721
|
+
} catch {
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
try {
|
|
725
|
+
const browserWsUrl = await new Promise((resolve, reject) => {
|
|
726
|
+
let buffer = "";
|
|
727
|
+
const timer = setTimeout(
|
|
728
|
+
() => reject(new Error("Failed to launch browser: timed out waiting for the CDP endpoint.")),
|
|
729
|
+
TIMEOUTS.PAGE_LOAD
|
|
730
|
+
);
|
|
731
|
+
proc.stderr?.on("data", (chunk) => {
|
|
732
|
+
buffer += String(chunk);
|
|
733
|
+
const match = buffer.match(/DevTools listening on (ws:\/\/\S+)/);
|
|
734
|
+
if (match) {
|
|
735
|
+
clearTimeout(timer);
|
|
736
|
+
resolve(match[1]);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
proc.on("error", (e) => {
|
|
740
|
+
clearTimeout(timer);
|
|
741
|
+
reject(new Error(`Failed to launch browser: ${e instanceof Error ? e.message : String(e)}`));
|
|
742
|
+
});
|
|
743
|
+
proc.on("exit", (code) => {
|
|
744
|
+
clearTimeout(timer);
|
|
745
|
+
reject(new Error(`Failed to launch browser: process exited early (code ${code}).`));
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
client = await connectCdp(browserWsUrl);
|
|
749
|
+
const { targetId } = await client.send("Target.createTarget", { url: "about:blank" });
|
|
750
|
+
const { sessionId } = await client.send("Target.attachToTarget", {
|
|
751
|
+
targetId,
|
|
752
|
+
flatten: true
|
|
753
|
+
});
|
|
754
|
+
let inflight = 0;
|
|
755
|
+
let lastNetworkChange = Date.now();
|
|
756
|
+
client.on((msg) => {
|
|
757
|
+
if (msg.sessionId !== sessionId) return;
|
|
758
|
+
if (msg.method === "Network.requestWillBeSent") {
|
|
759
|
+
inflight += 1;
|
|
760
|
+
lastNetworkChange = Date.now();
|
|
761
|
+
} else if (msg.method === "Network.loadingFinished" || msg.method === "Network.loadingFailed") {
|
|
762
|
+
inflight = Math.max(0, inflight - 1);
|
|
763
|
+
lastNetworkChange = Date.now();
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
await client.send("Page.enable", {}, sessionId);
|
|
767
|
+
await client.send("Network.enable", {}, sessionId);
|
|
768
|
+
await client.send("Runtime.enable", {}, sessionId);
|
|
769
|
+
const loginUrl = `${baseUrl}/sap/bc/adt/compatibility/graph`;
|
|
770
|
+
const navResult = await client.send("Page.navigate", { url: loginUrl }, sessionId);
|
|
771
|
+
if (navResult.errorText) {
|
|
772
|
+
return err(new Error("Failed to load login page. Please check if the server is online."));
|
|
773
|
+
}
|
|
774
|
+
const usernameSel = config.formSelectors.username;
|
|
775
|
+
const formFound = await waitForSelector(client, sessionId, usernameSel, TIMEOUTS.FORM_SELECTOR);
|
|
776
|
+
if (!formFound) {
|
|
777
|
+
return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
|
|
778
|
+
}
|
|
779
|
+
const fillExpr = `(() => {
|
|
780
|
+
const u = document.querySelector(${JSON.stringify(config.formSelectors.username)});
|
|
781
|
+
const p = document.querySelector(${JSON.stringify(config.formSelectors.password)});
|
|
782
|
+
const s = document.querySelector(${JSON.stringify(config.formSelectors.submit)});
|
|
783
|
+
if (!u || !p || !s) return false;
|
|
784
|
+
const set = (el, val) => {
|
|
785
|
+
el.value = val;
|
|
786
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
787
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
788
|
+
};
|
|
789
|
+
set(u, ${JSON.stringify(credentials.username)});
|
|
790
|
+
set(p, ${JSON.stringify(credentials.password)});
|
|
791
|
+
s.click();
|
|
792
|
+
return true;
|
|
793
|
+
})()`;
|
|
794
|
+
const fillResult = await client.send(
|
|
795
|
+
"Runtime.evaluate",
|
|
796
|
+
{ expression: fillExpr, returnByValue: true },
|
|
797
|
+
sessionId
|
|
798
|
+
);
|
|
799
|
+
if (fillResult.result?.value !== true) {
|
|
800
|
+
return err(new Error("Login form not found. The page may have changed or loaded incorrectly."));
|
|
801
|
+
}
|
|
802
|
+
const idleStart = Date.now();
|
|
803
|
+
while (Date.now() - idleStart < TIMEOUTS.NETWORK_IDLE) {
|
|
804
|
+
if (inflight === 0 && Date.now() - lastNetworkChange > TIMEOUTS.IDLE_QUIET) break;
|
|
805
|
+
await sleep(100);
|
|
806
|
+
}
|
|
807
|
+
const { cookies } = await client.send("Network.getAllCookies", {}, sessionId);
|
|
808
|
+
return ok(cookies.map(toPlaywrightCookie));
|
|
809
|
+
} catch (e) {
|
|
810
|
+
return err(
|
|
811
|
+
new Error(`SAML browser login failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
812
|
+
);
|
|
813
|
+
} finally {
|
|
814
|
+
cleanup();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
async function waitForSelector(client, sessionId, selector, timeoutMs) {
|
|
818
|
+
const start = Date.now();
|
|
819
|
+
const expression = `!!document.querySelector(${JSON.stringify(selector)})`;
|
|
820
|
+
while (Date.now() - start < timeoutMs) {
|
|
821
|
+
const res = await client.send(
|
|
822
|
+
"Runtime.evaluate",
|
|
823
|
+
{ expression, returnByValue: true },
|
|
824
|
+
sessionId
|
|
825
|
+
);
|
|
826
|
+
if (res.result?.value === true) return true;
|
|
827
|
+
await sleep(200);
|
|
828
|
+
}
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
function toPlaywrightCookie(c) {
|
|
832
|
+
const sameSite = c.sameSite === "Strict" || c.sameSite === "Lax" || c.sameSite === "None" ? c.sameSite : "Lax";
|
|
833
|
+
return {
|
|
834
|
+
name: c.name,
|
|
835
|
+
value: c.value,
|
|
836
|
+
domain: c.domain,
|
|
837
|
+
path: c.path,
|
|
838
|
+
expires: typeof c.expires === "number" ? c.expires : -1,
|
|
839
|
+
httpOnly: Boolean(c.httpOnly),
|
|
840
|
+
secure: Boolean(c.secure),
|
|
841
|
+
sameSite
|
|
842
|
+
};
|
|
843
|
+
}
|
|
614
844
|
|
|
615
845
|
// src/core/auth/saml/cookies.ts
|
|
616
846
|
function toAuthCookies(playwrightCookies) {
|
|
@@ -815,6 +1045,16 @@ function extractError(xml) {
|
|
|
815
1045
|
const message = messageElement?.textContent;
|
|
816
1046
|
return message || "No message found";
|
|
817
1047
|
}
|
|
1048
|
+
function extractTagText(xml, tagName) {
|
|
1049
|
+
if (!xml) return null;
|
|
1050
|
+
const [doc, parseErr] = safeParseXml(xml);
|
|
1051
|
+
if (parseErr) return null;
|
|
1052
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
1053
|
+
if (elements.length === 0) return null;
|
|
1054
|
+
const text = elements[0]?.textContent;
|
|
1055
|
+
if (!text || text.trim().length === 0) return null;
|
|
1056
|
+
return text.trim();
|
|
1057
|
+
}
|
|
818
1058
|
function escapeXml(str) {
|
|
819
1059
|
if (!str) {
|
|
820
1060
|
return "";
|
|
@@ -1235,6 +1475,24 @@ var OBJECT_CONFIG_MAP = {
|
|
|
1235
1475
|
type: "PROG/I",
|
|
1236
1476
|
label: "ABAP Include" /* INCLUDE */,
|
|
1237
1477
|
extension: "asinc"
|
|
1478
|
+
},
|
|
1479
|
+
"srvd": {
|
|
1480
|
+
endpoint: "ddic/srvd/sources",
|
|
1481
|
+
nameSpace: 'xmlns:srvd="http://www.sap.com/adt/ddic/srvdsources"',
|
|
1482
|
+
rootName: "srvd:srvdSource",
|
|
1483
|
+
type: "SRVD/SRV",
|
|
1484
|
+
label: "Service Definition" /* SERVICE_DEFINITION */,
|
|
1485
|
+
extension: "srvd",
|
|
1486
|
+
rootAttributes: { "srvd:srvdSourceType": "S" }
|
|
1487
|
+
},
|
|
1488
|
+
"asbdef": {
|
|
1489
|
+
endpoint: "bo/behaviordefinitions",
|
|
1490
|
+
nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
|
|
1491
|
+
rootName: "blue:blueSource",
|
|
1492
|
+
type: "BDEF/BDO",
|
|
1493
|
+
label: "Behavior Definition" /* BEHAVIOR_DEFINITION */,
|
|
1494
|
+
extension: "asbdef",
|
|
1495
|
+
requiresImplementationType: true
|
|
1238
1496
|
}
|
|
1239
1497
|
};
|
|
1240
1498
|
function getConfigByExtension(extension) {
|
|
@@ -1547,11 +1805,44 @@ async function multiDeleteObjects(client, objects, transport) {
|
|
|
1547
1805
|
return ok(results);
|
|
1548
1806
|
}
|
|
1549
1807
|
|
|
1808
|
+
// src/types/requests.ts
|
|
1809
|
+
var import_zod2 = require("zod");
|
|
1810
|
+
var BehaviorImplementationType = /* @__PURE__ */ ((BehaviorImplementationType2) => {
|
|
1811
|
+
BehaviorImplementationType2["Managed"] = "Managed";
|
|
1812
|
+
return BehaviorImplementationType2;
|
|
1813
|
+
})(BehaviorImplementationType || {});
|
|
1814
|
+
var objectRefSchema = import_zod2.z.object({
|
|
1815
|
+
name: import_zod2.z.string().min(1),
|
|
1816
|
+
extension: import_zod2.z.string().min(1)
|
|
1817
|
+
});
|
|
1818
|
+
var objectContentSchema = objectRefSchema.extend({
|
|
1819
|
+
content: import_zod2.z.string(),
|
|
1820
|
+
description: import_zod2.z.string().optional(),
|
|
1821
|
+
implementationType: import_zod2.z.nativeEnum(BehaviorImplementationType).optional()
|
|
1822
|
+
});
|
|
1823
|
+
var treeQuerySchema = import_zod2.z.object({
|
|
1824
|
+
package: import_zod2.z.string().min(1).optional(),
|
|
1825
|
+
path: import_zod2.z.string().optional(),
|
|
1826
|
+
owner: import_zod2.z.string().min(1).optional()
|
|
1827
|
+
});
|
|
1828
|
+
var previewQuerySchema = import_zod2.z.object({
|
|
1829
|
+
objectName: import_zod2.z.string().min(1),
|
|
1830
|
+
objectType: import_zod2.z.enum(["table", "view"]),
|
|
1831
|
+
sqlQuery: import_zod2.z.string().min(1),
|
|
1832
|
+
limit: import_zod2.z.number().positive().max(5e4).optional()
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1550
1835
|
// src/core/adt/craud/create.ts
|
|
1551
1836
|
async function createObject(client, object, packageName, transport, username) {
|
|
1552
1837
|
const [config, configErr] = requireConfig(object.extension);
|
|
1553
1838
|
if (configErr) return err(configErr);
|
|
1554
1839
|
const description = object.description ?? "";
|
|
1840
|
+
const extraAttrs = config.rootAttributes ? Object.entries(config.rootAttributes).map(([key, value]) => `
|
|
1841
|
+
${key}="${escapeXml(value)}"`).join("") : "";
|
|
1842
|
+
const implementationType = object.implementationType ?? "Managed" /* Managed */;
|
|
1843
|
+
const adtTemplate = config.requiresImplementationType ? ` <adtcore:adtTemplate>
|
|
1844
|
+
<adtcore:adtProperty adtcore:key="implementation_type">${implementationType}</adtcore:adtProperty>
|
|
1845
|
+
</adtcore:adtTemplate>` : "";
|
|
1555
1846
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1556
1847
|
<${config.rootName} ${config.nameSpace}
|
|
1557
1848
|
xmlns:adtcore="http://www.sap.com/adt/core"
|
|
@@ -1559,8 +1850,8 @@ async function createObject(client, object, packageName, transport, username) {
|
|
|
1559
1850
|
adtcore:language="EN"
|
|
1560
1851
|
adtcore:name="${object.name.toUpperCase()}"
|
|
1561
1852
|
adtcore:type="${config.type}"
|
|
1562
|
-
adtcore:responsible="${username.toUpperCase()}">
|
|
1563
|
-
|
|
1853
|
+
adtcore:responsible="${username.toUpperCase()}"${extraAttrs}>
|
|
1854
|
+
${adtTemplate}
|
|
1564
1855
|
<adtcore:packageRef adtcore:name="${packageName}"/>
|
|
1565
1856
|
|
|
1566
1857
|
</${config.rootName}>`;
|
|
@@ -1621,14 +1912,26 @@ async function activateObjects(client, objects) {
|
|
|
1621
1912
|
if (objects.length === 0) {
|
|
1622
1913
|
return ok([]);
|
|
1623
1914
|
}
|
|
1915
|
+
const references = [];
|
|
1624
1916
|
for (const obj of objects) {
|
|
1625
1917
|
const config = getConfigByExtension(obj.extension);
|
|
1626
1918
|
if (!config) return err(new Error(`Unsupported extension: ${obj.extension}`));
|
|
1919
|
+
references.push({
|
|
1920
|
+
uri: `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`,
|
|
1921
|
+
type: config.type,
|
|
1922
|
+
name: obj.name,
|
|
1923
|
+
extension: obj.extension
|
|
1924
|
+
});
|
|
1627
1925
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1926
|
+
return activateByReferences(client, references);
|
|
1927
|
+
}
|
|
1928
|
+
async function activateByReferences(client, references) {
|
|
1929
|
+
if (references.length === 0) {
|
|
1930
|
+
return ok([]);
|
|
1931
|
+
}
|
|
1932
|
+
const objectRefs = references.map(
|
|
1933
|
+
(ref) => `<adtcore:objectReference adtcore:uri="${ref.uri}" adtcore:type="${ref.type}" adtcore:name="${ref.name}"/>`
|
|
1934
|
+
).join("\n ");
|
|
1632
1935
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1633
1936
|
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
1634
1937
|
${objectRefs}
|
|
@@ -1692,7 +1995,7 @@ async function activateObjects(client, objects) {
|
|
|
1692
1995
|
if (!resultsRes.ok) {
|
|
1693
1996
|
return err(new Error(`Failed to fetch activation results: ${extractError(resultsText)}`));
|
|
1694
1997
|
}
|
|
1695
|
-
const [results, parseErr] = extractActivationErrors(
|
|
1998
|
+
const [results, parseErr] = extractActivationErrors(references, resultsText);
|
|
1696
1999
|
if (parseErr) return err(parseErr);
|
|
1697
2000
|
return ok(results);
|
|
1698
2001
|
}
|
|
@@ -3053,6 +3356,203 @@ async function gitDiff(client, object) {
|
|
|
3053
3356
|
});
|
|
3054
3357
|
}
|
|
3055
3358
|
|
|
3359
|
+
// src/core/adt/businessservices/validate.ts
|
|
3360
|
+
var SEVERITY_OK = "OK";
|
|
3361
|
+
async function validateServiceBinding(client, options) {
|
|
3362
|
+
const bindingType = options.bindingType ?? "ODATA";
|
|
3363
|
+
const bindingVersion = options.bindingVersion ?? "V4";
|
|
3364
|
+
const [response, requestErr] = await client.request({
|
|
3365
|
+
method: "POST",
|
|
3366
|
+
path: "/sap/bc/adt/businessservices/bindings/validation",
|
|
3367
|
+
params: {
|
|
3368
|
+
objname: options.bindingName.toUpperCase(),
|
|
3369
|
+
description: options.description ?? "",
|
|
3370
|
+
serviceBindingVersion: `${bindingType}\\${bindingVersion}`,
|
|
3371
|
+
serviceDefinition: options.serviceDefinition.toUpperCase(),
|
|
3372
|
+
package: options.packageName.toUpperCase()
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
const [text, checkErr] = await checkResponse(
|
|
3376
|
+
response,
|
|
3377
|
+
requestErr,
|
|
3378
|
+
`Failed to validate service binding ${options.bindingName}`
|
|
3379
|
+
);
|
|
3380
|
+
if (checkErr) return err(checkErr);
|
|
3381
|
+
const severity = extractTagText(text, "SEVERITY");
|
|
3382
|
+
if (severity && severity !== SEVERITY_OK) {
|
|
3383
|
+
const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
|
|
3384
|
+
return err(new Error(`Service binding validation failed (${severity}): ${shortText}`));
|
|
3385
|
+
}
|
|
3386
|
+
return ok(void 0);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// src/core/adt/businessservices/create.ts
|
|
3390
|
+
var SERVICE_BINDING_TYPE_CODE = "SRVB/SVB";
|
|
3391
|
+
var BINDING_CATEGORY = "1";
|
|
3392
|
+
var CONTENT_VERSION = "0001";
|
|
3393
|
+
async function createServiceBindingObject(client, options, username) {
|
|
3394
|
+
const bindingType = options.bindingType ?? "ODATA";
|
|
3395
|
+
const bindingVersion = options.bindingVersion ?? "V4";
|
|
3396
|
+
const description = options.description ?? "";
|
|
3397
|
+
const bindingName = options.bindingName.toUpperCase();
|
|
3398
|
+
const serviceDefinition = options.serviceDefinition.toUpperCase();
|
|
3399
|
+
const packageName = options.packageName.toUpperCase();
|
|
3400
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3401
|
+
<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()}">
|
|
3402
|
+
<adtcore:packageRef adtcore:name="${packageName}"/>
|
|
3403
|
+
<srvb:services srvb:name="${serviceDefinition}">
|
|
3404
|
+
<srvb:content srvb:version="${CONTENT_VERSION}">
|
|
3405
|
+
<srvb:serviceDefinition adtcore:name="${serviceDefinition}"/>
|
|
3406
|
+
</srvb:content>
|
|
3407
|
+
</srvb:services>
|
|
3408
|
+
<srvb:binding srvb:category="${BINDING_CATEGORY}" srvb:type="${bindingType}" srvb:version="${bindingVersion}">
|
|
3409
|
+
<srvb:implementation adtcore:name=""/>
|
|
3410
|
+
</srvb:binding>
|
|
3411
|
+
</srvb:serviceBinding>`;
|
|
3412
|
+
const params = {};
|
|
3413
|
+
if (options.transport) {
|
|
3414
|
+
params["corrNr"] = options.transport;
|
|
3415
|
+
}
|
|
3416
|
+
const [response, requestErr] = await client.request({
|
|
3417
|
+
method: "POST",
|
|
3418
|
+
path: "/sap/bc/adt/businessservices/bindings",
|
|
3419
|
+
params,
|
|
3420
|
+
headers: { "Content-Type": "application/*" },
|
|
3421
|
+
body
|
|
3422
|
+
});
|
|
3423
|
+
const [, checkErr] = await checkResponse(
|
|
3424
|
+
response,
|
|
3425
|
+
requestErr,
|
|
3426
|
+
`Failed to create service binding ${options.bindingName}`
|
|
3427
|
+
);
|
|
3428
|
+
if (checkErr) return err(checkErr);
|
|
3429
|
+
return ok(void 0);
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
// src/core/adt/businessservices/activate.ts
|
|
3433
|
+
var SERVICE_BINDING_TYPE_CODE2 = "SRVB/SVB";
|
|
3434
|
+
async function activateServiceBinding(client, bindingName) {
|
|
3435
|
+
return activateByReferences(client, [{
|
|
3436
|
+
uri: `/sap/bc/adt/businessservices/bindings/${bindingName.toLowerCase()}`,
|
|
3437
|
+
type: SERVICE_BINDING_TYPE_CODE2,
|
|
3438
|
+
name: bindingName.toUpperCase(),
|
|
3439
|
+
extension: ""
|
|
3440
|
+
}]);
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
// src/core/adt/businessservices/helpers.ts
|
|
3444
|
+
var BINDINGS_BASE_PATH = "/sap/bc/adt/businessservices/bindings";
|
|
3445
|
+
var LOCK_ACCEPT_HEADER = "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result";
|
|
3446
|
+
var PUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/publishjobs";
|
|
3447
|
+
var UNPUBLISH_JOBS_PATH = "/sap/bc/adt/businessservices/odatav4/unpublishjobs";
|
|
3448
|
+
var SEVERITY_OK2 = "OK";
|
|
3449
|
+
function bindingPath(bindingName) {
|
|
3450
|
+
return `${BINDINGS_BASE_PATH}/${bindingName.toLowerCase()}`;
|
|
3451
|
+
}
|
|
3452
|
+
async function lockServiceBinding(client, bindingName) {
|
|
3453
|
+
const [response, requestErr] = await client.request({
|
|
3454
|
+
method: "POST",
|
|
3455
|
+
path: bindingPath(bindingName),
|
|
3456
|
+
params: {
|
|
3457
|
+
"_action": "LOCK",
|
|
3458
|
+
"accessMode": "MODIFY"
|
|
3459
|
+
},
|
|
3460
|
+
headers: { "Accept": LOCK_ACCEPT_HEADER }
|
|
3461
|
+
});
|
|
3462
|
+
const [text, checkErr] = await checkResponse(
|
|
3463
|
+
response,
|
|
3464
|
+
requestErr,
|
|
3465
|
+
`Failed to lock service binding ${bindingName}`
|
|
3466
|
+
);
|
|
3467
|
+
if (checkErr) return err(checkErr);
|
|
3468
|
+
const [lockHandle, extractErr] = extractLockHandle(text);
|
|
3469
|
+
if (extractErr) return err(new Error(`Failed to extract lock handle: ${extractErr.message}`));
|
|
3470
|
+
return ok(lockHandle);
|
|
3471
|
+
}
|
|
3472
|
+
async function unlockServiceBinding(client, bindingName, lockHandle) {
|
|
3473
|
+
const [response, requestErr] = await client.request({
|
|
3474
|
+
method: "POST",
|
|
3475
|
+
path: bindingPath(bindingName),
|
|
3476
|
+
params: {
|
|
3477
|
+
"_action": "UNLOCK",
|
|
3478
|
+
"lockHandle": lockHandle
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
const [, checkErr] = await checkResponse(
|
|
3482
|
+
response,
|
|
3483
|
+
requestErr,
|
|
3484
|
+
`Failed to unlock service binding ${bindingName}`
|
|
3485
|
+
);
|
|
3486
|
+
if (checkErr) return err(checkErr);
|
|
3487
|
+
return ok(void 0);
|
|
3488
|
+
}
|
|
3489
|
+
async function submitServiceBindingJob(client, bindingName, action) {
|
|
3490
|
+
const path = action === "publish" ? PUBLISH_JOBS_PATH : UNPUBLISH_JOBS_PATH;
|
|
3491
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3492
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
3493
|
+
<adtcore:objectReference adtcore:type="SCGR" adtcore:name="${bindingName.toUpperCase()}"/>
|
|
3494
|
+
</adtcore:objectReferences>`;
|
|
3495
|
+
const [response, requestErr] = await client.request({
|
|
3496
|
+
method: "POST",
|
|
3497
|
+
path,
|
|
3498
|
+
headers: { "Content-Type": "application/xml" },
|
|
3499
|
+
body
|
|
3500
|
+
});
|
|
3501
|
+
const [text, checkErr] = await checkResponse(
|
|
3502
|
+
response,
|
|
3503
|
+
requestErr,
|
|
3504
|
+
`Failed to ${action} service binding ${bindingName}`
|
|
3505
|
+
);
|
|
3506
|
+
if (checkErr) return err(checkErr);
|
|
3507
|
+
const severity = extractTagText(text, "SEVERITY");
|
|
3508
|
+
if (severity && severity !== SEVERITY_OK2) {
|
|
3509
|
+
const shortText = extractTagText(text, "SHORT_TEXT") ?? "";
|
|
3510
|
+
return err(new Error(`Service binding ${action} failed (${severity}): ${shortText}`));
|
|
3511
|
+
}
|
|
3512
|
+
return ok(extractTagText(text, "SHORT_TEXT") ?? "");
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
// src/core/adt/businessservices/publish.ts
|
|
3516
|
+
async function publishServiceBinding(client, bindingName) {
|
|
3517
|
+
return runBindingJob(client, bindingName, "publish");
|
|
3518
|
+
}
|
|
3519
|
+
async function runBindingJob(client, bindingName, action) {
|
|
3520
|
+
const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
|
|
3521
|
+
if (lockErr) return err(lockErr);
|
|
3522
|
+
debug(`Service binding lock acquired for ${action}: handle=${lockHandle}`);
|
|
3523
|
+
const [message, jobErr] = await submitServiceBindingJob(client, bindingName, action);
|
|
3524
|
+
const [, unlockErr] = await unlockServiceBinding(client, bindingName, lockHandle);
|
|
3525
|
+
if (jobErr) return err(jobErr);
|
|
3526
|
+
if (unlockErr) return err(unlockErr);
|
|
3527
|
+
return ok(message);
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
// src/core/adt/businessservices/delete.ts
|
|
3531
|
+
async function deleteServiceBinding(client, bindingName, transport) {
|
|
3532
|
+
const [lockHandle, lockErr] = await lockServiceBinding(client, bindingName);
|
|
3533
|
+
if (lockErr) return err(lockErr);
|
|
3534
|
+
const [, unpublishErr] = await submitServiceBindingJob(client, bindingName, "unpublish");
|
|
3535
|
+
if (unpublishErr) debug(`Unpublish before delete skipped: ${unpublishErr.message}`);
|
|
3536
|
+
const params = { lockHandle };
|
|
3537
|
+
if (transport) params["corrNr"] = transport;
|
|
3538
|
+
const [response, requestErr] = await client.request({
|
|
3539
|
+
method: "DELETE",
|
|
3540
|
+
path: bindingPath(bindingName),
|
|
3541
|
+
params,
|
|
3542
|
+
headers: { "Accept": "text/plain" }
|
|
3543
|
+
});
|
|
3544
|
+
const [, checkErr] = await checkResponse(
|
|
3545
|
+
response,
|
|
3546
|
+
requestErr,
|
|
3547
|
+
`Failed to delete service binding ${bindingName}`
|
|
3548
|
+
);
|
|
3549
|
+
if (checkErr) {
|
|
3550
|
+
await unlockServiceBinding(client, bindingName, lockHandle);
|
|
3551
|
+
return err(checkErr);
|
|
3552
|
+
}
|
|
3553
|
+
return ok(void 0);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3056
3556
|
// src/client/methods/craud/read.ts
|
|
3057
3557
|
async function read(state, requestor, objects) {
|
|
3058
3558
|
if (!state.session) return err(new Error("Not logged in"));
|
|
@@ -3269,6 +3769,41 @@ function getObjectConfig() {
|
|
|
3269
3769
|
return Object.values(OBJECT_CONFIG_MAP);
|
|
3270
3770
|
}
|
|
3271
3771
|
|
|
3772
|
+
// src/client/methods/businessservices/createServiceBinding.ts
|
|
3773
|
+
async function createServiceBinding(state, requestor, options) {
|
|
3774
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3775
|
+
const [, validateErr] = await validateServiceBinding(requestor, options);
|
|
3776
|
+
if (validateErr) return err(validateErr);
|
|
3777
|
+
const [, createErr] = await createServiceBindingObject(requestor, options, state.session.username);
|
|
3778
|
+
if (createErr) return err(createErr);
|
|
3779
|
+
const [activation, activateErr] = await activateServiceBinding(requestor, options.bindingName);
|
|
3780
|
+
if (activateErr) return err(activateErr);
|
|
3781
|
+
if (activation.some((result2) => result2.status === "error")) {
|
|
3782
|
+
return err(new Error(`Service binding ${options.bindingName} failed activation`));
|
|
3783
|
+
}
|
|
3784
|
+
const result = {
|
|
3785
|
+
name: options.bindingName.toUpperCase(),
|
|
3786
|
+
serviceDefinition: options.serviceDefinition.toUpperCase(),
|
|
3787
|
+
created: true,
|
|
3788
|
+
activation,
|
|
3789
|
+
published: false
|
|
3790
|
+
};
|
|
3791
|
+
if (options.publish === false) {
|
|
3792
|
+
return ok(result);
|
|
3793
|
+
}
|
|
3794
|
+
const [publishMessage, publishErr] = await publishServiceBinding(requestor, options.bindingName);
|
|
3795
|
+
if (publishErr) return err(publishErr);
|
|
3796
|
+
result.published = true;
|
|
3797
|
+
if (publishMessage) result.publishMessage = publishMessage;
|
|
3798
|
+
return ok(result);
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
// src/client/methods/businessservices/deleteServiceBinding.ts
|
|
3802
|
+
async function deleteServiceBinding2(state, requestor, bindingName, transport) {
|
|
3803
|
+
if (!state.session) return err(new Error("Not logged in"));
|
|
3804
|
+
return deleteServiceBinding(requestor, bindingName, transport);
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3272
3807
|
// src/client/methods/internal/cookies.ts
|
|
3273
3808
|
function storeCookies(cookies, response) {
|
|
3274
3809
|
const setCookieHeader = response.headers.get("set-cookie");
|
|
@@ -3646,6 +4181,13 @@ var ADTClientImpl = class {
|
|
|
3646
4181
|
async gitDiff(objects) {
|
|
3647
4182
|
return gitDiff2(this.state, this.requestor, objects);
|
|
3648
4183
|
}
|
|
4184
|
+
// --- Business Services ---
|
|
4185
|
+
async createServiceBinding(options) {
|
|
4186
|
+
return createServiceBinding(this.state, this.requestor, options);
|
|
4187
|
+
}
|
|
4188
|
+
async deleteServiceBinding(bindingName, transport) {
|
|
4189
|
+
return deleteServiceBinding2(this.state, this.requestor, bindingName, transport);
|
|
4190
|
+
}
|
|
3649
4191
|
// --- Configuration ---
|
|
3650
4192
|
getObjectConfig() {
|
|
3651
4193
|
return getObjectConfig();
|
|
@@ -3663,6 +4205,7 @@ function createClient(config) {
|
|
|
3663
4205
|
}
|
|
3664
4206
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3665
4207
|
0 && (module.exports = {
|
|
4208
|
+
BehaviorImplementationType,
|
|
3666
4209
|
ExternalReferencesError,
|
|
3667
4210
|
activateLogging,
|
|
3668
4211
|
buildSQLQuery,
|