catalyst-relay 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -839,6 +839,12 @@ function extractCsrfToken(headers) {
839
839
 
840
840
  // src/core/utils/logging.ts
841
841
  var isActive = false;
842
+ function activateLogging() {
843
+ isActive = true;
844
+ }
845
+ function deactivateLogging() {
846
+ isActive = false;
847
+ }
842
848
  function debug(message) {
843
849
  if (isActive) {
844
850
  console.log(`[DEBUG] ${message}`);
@@ -1061,16 +1067,22 @@ function exportSessionState(ctx, ssoCerts) {
1061
1067
  // src/client/methods/session/importSessionState.ts
1062
1068
  var DEFAULT_REFRESH_INTERVAL2 = 30 * 60 * 1e3;
1063
1069
  async function importSessionState(ctx, state, setSsoCerts) {
1070
+ debug(`importSessionState: starting import`);
1064
1071
  if (state.session.expiresAt <= Date.now()) {
1072
+ debug(`importSessionState: session expired`);
1065
1073
  return err(new Error("Session has expired"));
1066
1074
  }
1075
+ debug(`importSessionState: session expiry OK (expires at ${state.session.expiresAt})`);
1067
1076
  ctx.state.session = state.session;
1068
1077
  ctx.state.csrfToken = state.csrfToken;
1078
+ debug(`importSessionState: restored session and CSRF token`);
1069
1079
  ctx.state.cookies.clear();
1070
1080
  for (const cookie of state.cookies) {
1071
1081
  ctx.state.cookies.set(cookie.name, cookie.value);
1072
1082
  }
1083
+ debug(`importSessionState: restored ${state.cookies.length} cookies`);
1073
1084
  if (state.authType === "sso" && state.ssoCertPaths) {
1085
+ debug(`importSessionState: loading SSO certs from ${state.ssoCertPaths.fullChainPath}`);
1074
1086
  try {
1075
1087
  const fs = await import("fs/promises");
1076
1088
  const [fullChain, key] = await Promise.all([
@@ -1078,32 +1090,52 @@ async function importSessionState(ctx, state, setSsoCerts) {
1078
1090
  fs.readFile(state.ssoCertPaths.keyPath, "utf-8")
1079
1091
  ]);
1080
1092
  setSsoCerts({ cert: fullChain, key });
1093
+ debug(`importSessionState: SSO certs loaded successfully`);
1081
1094
  } catch (certErr) {
1095
+ debug(`importSessionState: SSO cert load failed: ${certErr}`);
1082
1096
  return err(new Error(`Failed to load SSO certificates: ${certErr instanceof Error ? certErr.message : String(certErr)}`));
1083
1097
  }
1084
1098
  }
1099
+ debug(`importSessionState: fetching fresh CSRF token...`);
1085
1100
  const [response, reqErr] = await ctx.request({
1086
1101
  method: "GET",
1087
- path: "/sap/bc/adt/compatibility/graph"
1102
+ path: "/sap/bc/adt/compatibility/graph",
1103
+ headers: {
1104
+ [CSRF_TOKEN_HEADER]: FETCH_CSRF_TOKEN
1105
+ }
1088
1106
  });
1107
+ debug(`importSessionState: CSRF fetch completed`);
1089
1108
  if (reqErr) {
1109
+ debug(`importSessionState: CSRF fetch error: ${reqErr.message}`);
1090
1110
  ctx.state.session = null;
1091
1111
  ctx.state.csrfToken = null;
1092
1112
  ctx.state.cookies.clear();
1093
1113
  return err(new Error(`Session validation failed: ${reqErr.message}`));
1094
1114
  }
1095
1115
  if (!response.ok) {
1116
+ debug(`importSessionState: CSRF fetch failed with status ${response.status}`);
1096
1117
  ctx.state.session = null;
1097
1118
  ctx.state.csrfToken = null;
1098
1119
  ctx.state.cookies.clear();
1099
1120
  return err(new Error(`Session validation failed with status ${response.status}`));
1100
1121
  }
1122
+ const newToken = extractCsrfToken(response.headers);
1123
+ if (!newToken) {
1124
+ debug(`importSessionState: no CSRF token in response headers`);
1125
+ ctx.state.session = null;
1126
+ ctx.state.csrfToken = null;
1127
+ ctx.state.cookies.clear();
1128
+ return err(new Error("Session validation failed: no CSRF token returned"));
1129
+ }
1130
+ ctx.state.csrfToken = newToken;
1131
+ debug(`importSessionState: new CSRF token obtained: ${newToken.substring(0, 20)}...`);
1101
1132
  const autoRefresh = ctx.state.config.autoRefresh ?? { enabled: true };
1102
1133
  if (autoRefresh.enabled) {
1103
1134
  const interval = autoRefresh.intervalMs ?? DEFAULT_REFRESH_INTERVAL2;
1104
1135
  ctx.startAutoRefresh(interval);
1105
1136
  debug(`Auto-refresh started with ${interval}ms interval (after import)`);
1106
1137
  }
1138
+ debug(`importSessionState: import complete`);
1107
1139
  return ok(true);
1108
1140
  }
1109
1141
 
@@ -1145,6 +1177,14 @@ var OBJECT_CONFIG_MAP = {
1145
1177
  dpEndpoint: "ddic",
1146
1178
  dpParam: "ddicEntityName"
1147
1179
  },
1180
+ "astablds": {
1181
+ endpoint: "ddic/structures",
1182
+ nameSpace: 'xmlns:blue="http://www.sap.com/wbobj/blue"',
1183
+ rootName: "blue:blueSource",
1184
+ type: "STRU/D",
1185
+ label: "Structure" /* STRUCTURE */,
1186
+ extension: "astablds"
1187
+ },
1148
1188
  "asprog": {
1149
1189
  endpoint: "programs/programs",
1150
1190
  nameSpace: 'xmlns:program="http://www.sap.com/adt/programs/programs"',
@@ -1461,8 +1501,201 @@ function extractActivationErrors(objects, xml, _extension) {
1461
1501
  return ok(results);
1462
1502
  }
1463
1503
 
1504
+ // src/core/adt/craud/syntaxCheck.ts
1505
+ async function checkSyntax(client, objects) {
1506
+ if (objects.length === 0) {
1507
+ return ok([]);
1508
+ }
1509
+ const extension = objects[0].extension;
1510
+ const config = getConfigByExtension(extension);
1511
+ if (!config) return err(new Error(`Unsupported extension: ${extension}`));
1512
+ for (const obj of objects) {
1513
+ if (obj.extension !== extension) {
1514
+ return err(new Error("All objects must have the same extension for batch syntax check"));
1515
+ }
1516
+ }
1517
+ const sources = /* @__PURE__ */ new Map();
1518
+ for (const obj of objects) {
1519
+ const [result, readErr] = await readObject(client, obj);
1520
+ if (readErr) return err(new Error(`Failed to read ${obj.name}: ${readErr.message}`));
1521
+ sources.set(obj.name.toLowerCase(), result.content);
1522
+ }
1523
+ const objectRefs = objects.map((obj) => {
1524
+ const uri = `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`;
1525
+ const sourceUri = `${uri}/source/main`;
1526
+ const content = sources.get(obj.name.toLowerCase()) ?? "";
1527
+ const encoded = Buffer.from(content).toString("base64");
1528
+ return `<chkrun:checkObject adtcore:uri="${uri}" chkrun:version="active">
1529
+ <chkrun:artifacts>
1530
+ <chkrun:artifact chkrun:contentType="text/plain; charset=utf-8" chkrun:uri="${sourceUri}">
1531
+ <chkrun:content>${encoded}</chkrun:content>
1532
+ </chkrun:artifact>
1533
+ </chkrun:artifacts>
1534
+ </chkrun:checkObject>`;
1535
+ }).join("\n ");
1536
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1537
+ <chkrun:checkObjectList xmlns:chkrun="http://www.sap.com/adt/checkrun"
1538
+ xmlns:adtcore="http://www.sap.com/adt/core">
1539
+ ${objectRefs}
1540
+ </chkrun:checkObjectList>`;
1541
+ const [response, requestErr] = await client.request({
1542
+ method: "POST",
1543
+ path: "/sap/bc/adt/checkruns",
1544
+ params: {
1545
+ "reporters": "abapCheckRun"
1546
+ },
1547
+ headers: {
1548
+ "Content-Type": "application/vnd.sap.adt.checkobjects+xml",
1549
+ "Accept": "application/vnd.sap.adt.checkmessages+xml"
1550
+ },
1551
+ body
1552
+ });
1553
+ if (requestErr) {
1554
+ return err(requestErr);
1555
+ }
1556
+ const text = await response.text();
1557
+ debug(`Syntax check response status: ${response.status}`);
1558
+ debug(`Syntax check response: ${text.substring(0, 500)}`);
1559
+ if (!response.ok) {
1560
+ const errorMsg = extractError(text);
1561
+ return err(new Error(`Syntax check failed: ${errorMsg}`));
1562
+ }
1563
+ const [results, parseErr] = extractCheckMessages(objects, text);
1564
+ if (parseErr) {
1565
+ return err(parseErr);
1566
+ }
1567
+ return ok(results);
1568
+ }
1569
+ function extractCheckMessages(objects, xml) {
1570
+ const [doc, parseErr] = safeParseXml(xml);
1571
+ if (parseErr) {
1572
+ return err(parseErr);
1573
+ }
1574
+ const messageMap = /* @__PURE__ */ new Map();
1575
+ objects.forEach((obj) => messageMap.set(obj.name.toLowerCase(), []));
1576
+ let msgElements = doc.getElementsByTagName("chkrun:checkMessage");
1577
+ if (msgElements.length === 0) {
1578
+ msgElements = doc.getElementsByTagName("checkMessage");
1579
+ }
1580
+ const startRegex = /#start=(\d+),(\d+)/;
1581
+ for (let i = 0; i < msgElements.length; i++) {
1582
+ const msg = msgElements[i];
1583
+ if (!msg) continue;
1584
+ const type = msg.getAttribute("chkrun:type") ?? msg.getAttribute("type");
1585
+ if (!type) continue;
1586
+ const uri = msg.getAttribute("chkrun:uri") ?? msg.getAttribute("uri") ?? "";
1587
+ let line;
1588
+ let column;
1589
+ const match = startRegex.exec(uri);
1590
+ if (match && match[1] && match[2]) {
1591
+ line = parseInt(match[1], 10);
1592
+ column = parseInt(match[2], 10);
1593
+ }
1594
+ const matchingObj = objects.find(
1595
+ (obj) => uri.toLowerCase().includes(obj.name.toLowerCase())
1596
+ );
1597
+ if (!matchingObj) continue;
1598
+ const text = msg.getAttribute("chkrun:shortText") ?? msg.getAttribute("shortText");
1599
+ if (!text) continue;
1600
+ const message = {
1601
+ severity: type === "E" ? "error" : type === "W" ? "warning" : "info",
1602
+ text,
1603
+ ...line !== void 0 && { line },
1604
+ ...column !== void 0 && { column }
1605
+ };
1606
+ const messages = messageMap.get(matchingObj.name.toLowerCase()) || [];
1607
+ messages.push(message);
1608
+ messageMap.set(matchingObj.name.toLowerCase(), messages);
1609
+ }
1610
+ const results = objects.map((obj) => {
1611
+ const messages = messageMap.get(obj.name.toLowerCase()) || [];
1612
+ const hasErrors = messages.some((m) => m.severity === "error");
1613
+ return {
1614
+ name: obj.name,
1615
+ extension: obj.extension,
1616
+ status: hasErrors ? "error" : messages.length > 0 ? "warning" : "success",
1617
+ messages
1618
+ };
1619
+ });
1620
+ return ok(results);
1621
+ }
1622
+
1623
+ // src/core/adt/discovery/tree/packageStats.ts
1624
+ function constructPackageStatsBody(packageNames) {
1625
+ const names = packageNames.length === 1 ? [...packageNames, "SRIS_TEST_DATA_VFS_EMPTY"] : packageNames;
1626
+ const values = names.map((name) => ` <vfs:value>${name}</vfs:value>`).join("\n");
1627
+ return `<?xml version="1.0" encoding="UTF-8"?>
1628
+ <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="*">
1629
+ <vfs:preselection facet="package">
1630
+ ${values}
1631
+ </vfs:preselection>
1632
+ <vfs:facetorder>
1633
+ <vfs:facet>package</vfs:facet>
1634
+ <vfs:facet>group</vfs:facet>
1635
+ <vfs:facet>type</vfs:facet>
1636
+ </vfs:facetorder>
1637
+ </vfs:virtualFoldersRequest>`;
1638
+ }
1639
+ function parsePackageStats(xml) {
1640
+ const [doc, parseErr] = safeParseXml(xml);
1641
+ if (parseErr) return [];
1642
+ const packages = [];
1643
+ const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
1644
+ for (let i = 0; i < virtualFolders.length; i++) {
1645
+ const vf = virtualFolders[i];
1646
+ if (!vf) continue;
1647
+ const facet = vf.getAttribute("facet")?.toUpperCase();
1648
+ if (facet !== "PACKAGE") continue;
1649
+ const name = vf.getAttribute("name");
1650
+ if (!name) continue;
1651
+ const countAttr = vf.getAttribute("counter");
1652
+ const count = countAttr ? parseInt(countAttr, 10) : 0;
1653
+ const description = vf.getAttribute("text");
1654
+ const pkg = {
1655
+ name,
1656
+ numContents: count
1657
+ };
1658
+ if (description) pkg.description = description;
1659
+ packages.push(pkg);
1660
+ }
1661
+ return packages;
1662
+ }
1663
+ async function getPackageStats(client, packageNames) {
1664
+ const isSingle = typeof packageNames === "string";
1665
+ const names = isSingle ? [packageNames] : packageNames;
1666
+ if (names.length === 0) {
1667
+ return ok([]);
1668
+ }
1669
+ const body = constructPackageStatsBody(names);
1670
+ const [response, requestErr] = await client.request({
1671
+ method: "POST",
1672
+ path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
1673
+ headers: {
1674
+ "Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
1675
+ "Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
1676
+ },
1677
+ body
1678
+ });
1679
+ if (requestErr) return err(requestErr);
1680
+ if (!response.ok) {
1681
+ const text = await response.text();
1682
+ const errorMsg = extractError(text);
1683
+ return err(new Error(`Package stats fetch failed: ${errorMsg}`));
1684
+ }
1685
+ const xml = await response.text();
1686
+ const packages = parsePackageStats(xml).filter((pkg) => pkg.name !== "SRIS_TEST_DATA_VFS_EMPTY");
1687
+ if (isSingle) {
1688
+ if (packages.length === 0) {
1689
+ return err(new Error(`Package ${packageNames} not found`));
1690
+ }
1691
+ return ok(packages[0]);
1692
+ }
1693
+ return ok(packages);
1694
+ }
1695
+
1464
1696
  // src/core/adt/discovery/packages.ts
1465
- async function getPackages(client, filter = "*") {
1697
+ async function getPackages(client, options = {}) {
1698
+ const { filter = "*", includeDescriptions = false } = options;
1466
1699
  const params = new URLSearchParams([
1467
1700
  ["operation", "quickSearch"],
1468
1701
  ["query", filter],
@@ -1486,18 +1719,31 @@ async function getPackages(client, filter = "*") {
1486
1719
  if (parseErr) {
1487
1720
  return err(parseErr);
1488
1721
  }
1489
- const packages = [];
1722
+ const packageNames = [];
1490
1723
  const objectRefs = doc.getElementsByTagNameNS("http://www.sap.com/adt/core", "objectReference");
1491
1724
  for (let i = 0; i < objectRefs.length; i++) {
1492
1725
  const obj = objectRefs[i];
1493
1726
  if (!obj) return err(new Error("Invalid object reference in package search results"));
1494
1727
  const name = obj.getAttributeNS("http://www.sap.com/adt/core", "name") || obj.getAttribute("adtcore:name");
1495
- const description = obj.getAttributeNS("http://www.sap.com/adt/core", "description") || obj.getAttribute("adtcore:description");
1496
1728
  if (!name) return err(new Error("Package name missing in object reference"));
1729
+ packageNames.push(name);
1730
+ }
1731
+ if (packageNames.length === 0) return ok([]);
1732
+ if (!includeDescriptions) return ok(packageNames.map((name) => ({ name })));
1733
+ const [stats, statsErr] = await getPackageStats(client, packageNames);
1734
+ if (statsErr) {
1735
+ return ok(packageNames.map((name) => ({ name })));
1736
+ }
1737
+ const descriptionMap = /* @__PURE__ */ new Map();
1738
+ for (const stat2 of stats) {
1739
+ if (stat2.description) descriptionMap.set(stat2.name, stat2.description);
1740
+ }
1741
+ const packages = packageNames.map((name) => {
1497
1742
  const pkg = { name };
1743
+ const description = descriptionMap.get(name);
1498
1744
  if (description) pkg.description = description;
1499
- packages.push(pkg);
1500
- }
1745
+ return pkg;
1746
+ });
1501
1747
  return ok(packages);
1502
1748
  }
1503
1749
 
@@ -1737,79 +1983,6 @@ async function getTree(client, query = {}) {
1737
1983
  return ok(result);
1738
1984
  }
1739
1985
 
1740
- // src/core/adt/discovery/tree/packageStats.ts
1741
- function constructPackageStatsBody(packageNames) {
1742
- const names = packageNames.length === 1 ? [...packageNames, "SRIS_TEST_DATA_VFS_EMPTY"] : packageNames;
1743
- const values = names.map((name) => ` <vfs:value>${name}</vfs:value>`).join("\n");
1744
- return `<?xml version="1.0" encoding="UTF-8"?>
1745
- <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="*">
1746
- <vfs:preselection facet="package">
1747
- ${values}
1748
- </vfs:preselection>
1749
- <vfs:facetorder>
1750
- <vfs:facet>package</vfs:facet>
1751
- <vfs:facet>group</vfs:facet>
1752
- <vfs:facet>type</vfs:facet>
1753
- </vfs:facetorder>
1754
- </vfs:virtualFoldersRequest>`;
1755
- }
1756
- function parsePackageStats(xml) {
1757
- const [doc, parseErr] = safeParseXml(xml);
1758
- if (parseErr) return [];
1759
- const packages = [];
1760
- const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
1761
- for (let i = 0; i < virtualFolders.length; i++) {
1762
- const vf = virtualFolders[i];
1763
- if (!vf) continue;
1764
- const facet = vf.getAttribute("facet")?.toUpperCase();
1765
- if (facet !== "PACKAGE") continue;
1766
- const name = vf.getAttribute("name");
1767
- if (!name) continue;
1768
- const countAttr = vf.getAttribute("counter");
1769
- const count = countAttr ? parseInt(countAttr, 10) : 0;
1770
- const description = vf.getAttribute("text");
1771
- const pkg = {
1772
- name,
1773
- numContents: count
1774
- };
1775
- if (description) pkg.description = description;
1776
- packages.push(pkg);
1777
- }
1778
- return packages;
1779
- }
1780
- async function getPackageStats(client, packageNames) {
1781
- const isSingle = typeof packageNames === "string";
1782
- const names = isSingle ? [packageNames] : packageNames;
1783
- if (names.length === 0) {
1784
- return ok([]);
1785
- }
1786
- const body = constructPackageStatsBody(names);
1787
- const [response, requestErr] = await client.request({
1788
- method: "POST",
1789
- path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
1790
- headers: {
1791
- "Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
1792
- "Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
1793
- },
1794
- body
1795
- });
1796
- if (requestErr) return err(requestErr);
1797
- if (!response.ok) {
1798
- const text = await response.text();
1799
- const errorMsg = extractError(text);
1800
- return err(new Error(`Package stats fetch failed: ${errorMsg}`));
1801
- }
1802
- const xml = await response.text();
1803
- const packages = parsePackageStats(xml).filter((pkg) => pkg.name !== "SRIS_TEST_DATA_VFS_EMPTY");
1804
- if (isSingle) {
1805
- if (packages.length === 0) {
1806
- return err(new Error(`Package ${packageNames} not found`));
1807
- }
1808
- return ok(packages[0]);
1809
- }
1810
- return ok(packages);
1811
- }
1812
-
1813
1986
  // src/core/adt/transports/transports.ts
1814
1987
  async function getTransports(client, packageName) {
1815
1988
  const contentType = "application/vnd.sap.as+xml; charset=UTF-8; dataname=com.sap.adt.transport.service.checkData";
@@ -2465,10 +2638,16 @@ async function deleteObjects(state, requestor, objects, transport) {
2465
2638
  return ok(void 0);
2466
2639
  }
2467
2640
 
2641
+ // src/client/methods/craud/checkSyntax.ts
2642
+ async function checkSyntax2(state, requestor, objects) {
2643
+ if (!state.session) return err(new Error("Not logged in"));
2644
+ return checkSyntax(requestor, objects);
2645
+ }
2646
+
2468
2647
  // src/client/methods/discovery/getPackages.ts
2469
- async function getPackages2(state, requestor, filter) {
2648
+ async function getPackages2(state, requestor, options) {
2470
2649
  if (!state.session) return err(new Error("Not logged in"));
2471
- return getPackages(requestor, filter);
2650
+ return getPackages(requestor, options);
2472
2651
  }
2473
2652
 
2474
2653
  // src/client/methods/discovery/getTree.ts
@@ -2573,6 +2752,7 @@ function createAutoRefresh(getSession, refreshSession3) {
2573
2752
  debug(`Auto-refresh failed: ${refreshErr.message}`);
2574
2753
  }
2575
2754
  }, intervalMs);
2755
+ timer.unref();
2576
2756
  },
2577
2757
  stop() {
2578
2758
  if (timer) {
@@ -2860,12 +3040,15 @@ var ADTClientImpl = class {
2860
3040
  async activate(objects) {
2861
3041
  return activate(this.state, this.requestor, objects);
2862
3042
  }
3043
+ async checkSyntax(objects) {
3044
+ return checkSyntax2(this.state, this.requestor, objects);
3045
+ }
2863
3046
  async delete(objects, transport) {
2864
3047
  return deleteObjects(this.state, this.requestor, objects, transport);
2865
3048
  }
2866
3049
  // --- Discovery ---
2867
- async getPackages(filter) {
2868
- return getPackages2(this.state, this.requestor, filter);
3050
+ async getPackages(options) {
3051
+ return getPackages2(this.state, this.requestor, options);
2869
3052
  }
2870
3053
  async getTree(query) {
2871
3054
  return getTree2(this.state, this.requestor, query);
@@ -2917,8 +3100,10 @@ function createClient(config) {
2917
3100
  return ok(new ADTClientImpl(config));
2918
3101
  }
2919
3102
  export {
3103
+ activateLogging,
2920
3104
  buildSQLQuery,
2921
3105
  createClient,
3106
+ deactivateLogging,
2922
3107
  err,
2923
3108
  ok
2924
3109
  };