catalyst-relay 0.5.2 → 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.d.mts CHANGED
@@ -182,7 +182,7 @@ type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
182
182
  /**
183
183
  * Machine-readable error codes
184
184
  */
185
- type ErrorCode = 'AUTH_FAILED' | 'SESSION_EXPIRED' | 'SESSION_NOT_FOUND' | 'CSRF_INVALID' | 'OBJECT_LOCKED' | 'OBJECT_NOT_FOUND' | 'TRANSPORT_REQUIRED' | 'ACTIVATION_FAILED' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
185
+ type ErrorCode = 'AUTH_FAILED' | 'SESSION_EXPIRED' | 'SESSION_NOT_FOUND' | 'CSRF_INVALID' | 'OBJECT_LOCKED' | 'OBJECT_NOT_FOUND' | 'TRANSPORT_REQUIRED' | 'ACTIVATION_FAILED' | 'CHECK_FAILED' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
186
186
 
187
187
  /**
188
188
  * Session management type definitions
@@ -308,6 +308,13 @@ interface ActivationMessage {
308
308
  column?: number;
309
309
  }
310
310
 
311
+ interface CheckResult {
312
+ name: string;
313
+ extension: string;
314
+ status: 'success' | 'warning' | 'error';
315
+ messages: ActivationMessage[];
316
+ }
317
+
311
318
  /**
312
319
  * Tree types — Public and internal type definitions
313
320
  */
@@ -341,6 +348,21 @@ interface Package {
341
348
  name: string;
342
349
  description?: string;
343
350
  }
351
+ /**
352
+ * Get list of available packages
353
+ *
354
+ * Uses the ADT search API with DEVC/K object type to search for packages,
355
+ * then enriches results with descriptions from the virtualfolders API.
356
+ *
357
+ * @param client - ADT client
358
+ * @param filter - Package name filter pattern (default: '*' for all packages)
359
+ * Examples: 'Z*' for custom packages, '$TMP' for local, 'ZSNAP*' for specific prefix
360
+ * @returns Array of packages or error
361
+ */
362
+ interface GetPackagesOptions {
363
+ filter?: string;
364
+ includeDescriptions?: boolean;
365
+ }
344
366
 
345
367
  /**
346
368
  * Transports — List transport requests for a package
@@ -543,8 +565,9 @@ interface ADTClient {
543
565
  update(object: ObjectContent, transport?: string): AsyncResult<void>;
544
566
  upsert(objects: ObjectContent[], packageName: string, transport?: string): AsyncResult<UpsertResult[]>;
545
567
  activate(objects: ObjectRef[]): AsyncResult<ActivationResult[]>;
568
+ checkSyntax(objects: ObjectRef[]): AsyncResult<CheckResult[]>;
546
569
  delete(objects: ObjectRef[], transport?: string): AsyncResult<void>;
547
- getPackages(filter?: string): AsyncResult<Package[]>;
570
+ getPackages(options?: GetPackagesOptions): AsyncResult<Package[]>;
548
571
  getTree(query: TreeQuery): AsyncResult<TreeResponse>;
549
572
  getPackageStats(packageName: string): AsyncResult<PackageNode>;
550
573
  getPackageStats(packageNames: string[]): AsyncResult<PackageNode[]>;
package/dist/index.d.ts CHANGED
@@ -182,7 +182,7 @@ type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
182
182
  /**
183
183
  * Machine-readable error codes
184
184
  */
185
- type ErrorCode = 'AUTH_FAILED' | 'SESSION_EXPIRED' | 'SESSION_NOT_FOUND' | 'CSRF_INVALID' | 'OBJECT_LOCKED' | 'OBJECT_NOT_FOUND' | 'TRANSPORT_REQUIRED' | 'ACTIVATION_FAILED' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
185
+ type ErrorCode = 'AUTH_FAILED' | 'SESSION_EXPIRED' | 'SESSION_NOT_FOUND' | 'CSRF_INVALID' | 'OBJECT_LOCKED' | 'OBJECT_NOT_FOUND' | 'TRANSPORT_REQUIRED' | 'ACTIVATION_FAILED' | 'CHECK_FAILED' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
186
186
 
187
187
  /**
188
188
  * Session management type definitions
@@ -308,6 +308,13 @@ interface ActivationMessage {
308
308
  column?: number;
309
309
  }
310
310
 
311
+ interface CheckResult {
312
+ name: string;
313
+ extension: string;
314
+ status: 'success' | 'warning' | 'error';
315
+ messages: ActivationMessage[];
316
+ }
317
+
311
318
  /**
312
319
  * Tree types — Public and internal type definitions
313
320
  */
@@ -341,6 +348,21 @@ interface Package {
341
348
  name: string;
342
349
  description?: string;
343
350
  }
351
+ /**
352
+ * Get list of available packages
353
+ *
354
+ * Uses the ADT search API with DEVC/K object type to search for packages,
355
+ * then enriches results with descriptions from the virtualfolders API.
356
+ *
357
+ * @param client - ADT client
358
+ * @param filter - Package name filter pattern (default: '*' for all packages)
359
+ * Examples: 'Z*' for custom packages, '$TMP' for local, 'ZSNAP*' for specific prefix
360
+ * @returns Array of packages or error
361
+ */
362
+ interface GetPackagesOptions {
363
+ filter?: string;
364
+ includeDescriptions?: boolean;
365
+ }
344
366
 
345
367
  /**
346
368
  * Transports — List transport requests for a package
@@ -543,8 +565,9 @@ interface ADTClient {
543
565
  update(object: ObjectContent, transport?: string): AsyncResult<void>;
544
566
  upsert(objects: ObjectContent[], packageName: string, transport?: string): AsyncResult<UpsertResult[]>;
545
567
  activate(objects: ObjectRef[]): AsyncResult<ActivationResult[]>;
568
+ checkSyntax(objects: ObjectRef[]): AsyncResult<CheckResult[]>;
546
569
  delete(objects: ObjectRef[], transport?: string): AsyncResult<void>;
547
- getPackages(filter?: string): AsyncResult<Package[]>;
570
+ getPackages(options?: GetPackagesOptions): AsyncResult<Package[]>;
548
571
  getTree(query: TreeQuery): AsyncResult<TreeResponse>;
549
572
  getPackageStats(packageName: string): AsyncResult<PackageNode>;
550
573
  getPackageStats(packageNames: string[]): AsyncResult<PackageNode[]>;
package/dist/index.js CHANGED
@@ -1535,8 +1535,201 @@ function extractActivationErrors(objects, xml, _extension) {
1535
1535
  return ok(results);
1536
1536
  }
1537
1537
 
1538
+ // src/core/adt/craud/syntaxCheck.ts
1539
+ async function checkSyntax(client, objects) {
1540
+ if (objects.length === 0) {
1541
+ return ok([]);
1542
+ }
1543
+ const extension = objects[0].extension;
1544
+ const config = getConfigByExtension(extension);
1545
+ if (!config) return err(new Error(`Unsupported extension: ${extension}`));
1546
+ for (const obj of objects) {
1547
+ if (obj.extension !== extension) {
1548
+ return err(new Error("All objects must have the same extension for batch syntax check"));
1549
+ }
1550
+ }
1551
+ const sources = /* @__PURE__ */ new Map();
1552
+ for (const obj of objects) {
1553
+ const [result, readErr] = await readObject(client, obj);
1554
+ if (readErr) return err(new Error(`Failed to read ${obj.name}: ${readErr.message}`));
1555
+ sources.set(obj.name.toLowerCase(), result.content);
1556
+ }
1557
+ const objectRefs = objects.map((obj) => {
1558
+ const uri = `/sap/bc/adt/${config.endpoint}/${obj.name.toLowerCase()}`;
1559
+ const sourceUri = `${uri}/source/main`;
1560
+ const content = sources.get(obj.name.toLowerCase()) ?? "";
1561
+ const encoded = Buffer.from(content).toString("base64");
1562
+ return `<chkrun:checkObject adtcore:uri="${uri}" chkrun:version="active">
1563
+ <chkrun:artifacts>
1564
+ <chkrun:artifact chkrun:contentType="text/plain; charset=utf-8" chkrun:uri="${sourceUri}">
1565
+ <chkrun:content>${encoded}</chkrun:content>
1566
+ </chkrun:artifact>
1567
+ </chkrun:artifacts>
1568
+ </chkrun:checkObject>`;
1569
+ }).join("\n ");
1570
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1571
+ <chkrun:checkObjectList xmlns:chkrun="http://www.sap.com/adt/checkrun"
1572
+ xmlns:adtcore="http://www.sap.com/adt/core">
1573
+ ${objectRefs}
1574
+ </chkrun:checkObjectList>`;
1575
+ const [response, requestErr] = await client.request({
1576
+ method: "POST",
1577
+ path: "/sap/bc/adt/checkruns",
1578
+ params: {
1579
+ "reporters": "abapCheckRun"
1580
+ },
1581
+ headers: {
1582
+ "Content-Type": "application/vnd.sap.adt.checkobjects+xml",
1583
+ "Accept": "application/vnd.sap.adt.checkmessages+xml"
1584
+ },
1585
+ body
1586
+ });
1587
+ if (requestErr) {
1588
+ return err(requestErr);
1589
+ }
1590
+ const text = await response.text();
1591
+ debug(`Syntax check response status: ${response.status}`);
1592
+ debug(`Syntax check response: ${text.substring(0, 500)}`);
1593
+ if (!response.ok) {
1594
+ const errorMsg = extractError(text);
1595
+ return err(new Error(`Syntax check failed: ${errorMsg}`));
1596
+ }
1597
+ const [results, parseErr] = extractCheckMessages(objects, text);
1598
+ if (parseErr) {
1599
+ return err(parseErr);
1600
+ }
1601
+ return ok(results);
1602
+ }
1603
+ function extractCheckMessages(objects, xml) {
1604
+ const [doc, parseErr] = safeParseXml(xml);
1605
+ if (parseErr) {
1606
+ return err(parseErr);
1607
+ }
1608
+ const messageMap = /* @__PURE__ */ new Map();
1609
+ objects.forEach((obj) => messageMap.set(obj.name.toLowerCase(), []));
1610
+ let msgElements = doc.getElementsByTagName("chkrun:checkMessage");
1611
+ if (msgElements.length === 0) {
1612
+ msgElements = doc.getElementsByTagName("checkMessage");
1613
+ }
1614
+ const startRegex = /#start=(\d+),(\d+)/;
1615
+ for (let i = 0; i < msgElements.length; i++) {
1616
+ const msg = msgElements[i];
1617
+ if (!msg) continue;
1618
+ const type = msg.getAttribute("chkrun:type") ?? msg.getAttribute("type");
1619
+ if (!type) continue;
1620
+ const uri = msg.getAttribute("chkrun:uri") ?? msg.getAttribute("uri") ?? "";
1621
+ let line;
1622
+ let column;
1623
+ const match = startRegex.exec(uri);
1624
+ if (match && match[1] && match[2]) {
1625
+ line = parseInt(match[1], 10);
1626
+ column = parseInt(match[2], 10);
1627
+ }
1628
+ const matchingObj = objects.find(
1629
+ (obj) => uri.toLowerCase().includes(obj.name.toLowerCase())
1630
+ );
1631
+ if (!matchingObj) continue;
1632
+ const text = msg.getAttribute("chkrun:shortText") ?? msg.getAttribute("shortText");
1633
+ if (!text) continue;
1634
+ const message = {
1635
+ severity: type === "E" ? "error" : type === "W" ? "warning" : "info",
1636
+ text,
1637
+ ...line !== void 0 && { line },
1638
+ ...column !== void 0 && { column }
1639
+ };
1640
+ const messages = messageMap.get(matchingObj.name.toLowerCase()) || [];
1641
+ messages.push(message);
1642
+ messageMap.set(matchingObj.name.toLowerCase(), messages);
1643
+ }
1644
+ const results = objects.map((obj) => {
1645
+ const messages = messageMap.get(obj.name.toLowerCase()) || [];
1646
+ const hasErrors = messages.some((m) => m.severity === "error");
1647
+ return {
1648
+ name: obj.name,
1649
+ extension: obj.extension,
1650
+ status: hasErrors ? "error" : messages.length > 0 ? "warning" : "success",
1651
+ messages
1652
+ };
1653
+ });
1654
+ return ok(results);
1655
+ }
1656
+
1657
+ // src/core/adt/discovery/tree/packageStats.ts
1658
+ function constructPackageStatsBody(packageNames) {
1659
+ const names = packageNames.length === 1 ? [...packageNames, "SRIS_TEST_DATA_VFS_EMPTY"] : packageNames;
1660
+ const values = names.map((name) => ` <vfs:value>${name}</vfs:value>`).join("\n");
1661
+ return `<?xml version="1.0" encoding="UTF-8"?>
1662
+ <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="*">
1663
+ <vfs:preselection facet="package">
1664
+ ${values}
1665
+ </vfs:preselection>
1666
+ <vfs:facetorder>
1667
+ <vfs:facet>package</vfs:facet>
1668
+ <vfs:facet>group</vfs:facet>
1669
+ <vfs:facet>type</vfs:facet>
1670
+ </vfs:facetorder>
1671
+ </vfs:virtualFoldersRequest>`;
1672
+ }
1673
+ function parsePackageStats(xml) {
1674
+ const [doc, parseErr] = safeParseXml(xml);
1675
+ if (parseErr) return [];
1676
+ const packages = [];
1677
+ const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
1678
+ for (let i = 0; i < virtualFolders.length; i++) {
1679
+ const vf = virtualFolders[i];
1680
+ if (!vf) continue;
1681
+ const facet = vf.getAttribute("facet")?.toUpperCase();
1682
+ if (facet !== "PACKAGE") continue;
1683
+ const name = vf.getAttribute("name");
1684
+ if (!name) continue;
1685
+ const countAttr = vf.getAttribute("counter");
1686
+ const count = countAttr ? parseInt(countAttr, 10) : 0;
1687
+ const description = vf.getAttribute("text");
1688
+ const pkg = {
1689
+ name,
1690
+ numContents: count
1691
+ };
1692
+ if (description) pkg.description = description;
1693
+ packages.push(pkg);
1694
+ }
1695
+ return packages;
1696
+ }
1697
+ async function getPackageStats(client, packageNames) {
1698
+ const isSingle = typeof packageNames === "string";
1699
+ const names = isSingle ? [packageNames] : packageNames;
1700
+ if (names.length === 0) {
1701
+ return ok([]);
1702
+ }
1703
+ const body = constructPackageStatsBody(names);
1704
+ const [response, requestErr] = await client.request({
1705
+ method: "POST",
1706
+ path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
1707
+ headers: {
1708
+ "Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
1709
+ "Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
1710
+ },
1711
+ body
1712
+ });
1713
+ if (requestErr) return err(requestErr);
1714
+ if (!response.ok) {
1715
+ const text = await response.text();
1716
+ const errorMsg = extractError(text);
1717
+ return err(new Error(`Package stats fetch failed: ${errorMsg}`));
1718
+ }
1719
+ const xml = await response.text();
1720
+ const packages = parsePackageStats(xml).filter((pkg) => pkg.name !== "SRIS_TEST_DATA_VFS_EMPTY");
1721
+ if (isSingle) {
1722
+ if (packages.length === 0) {
1723
+ return err(new Error(`Package ${packageNames} not found`));
1724
+ }
1725
+ return ok(packages[0]);
1726
+ }
1727
+ return ok(packages);
1728
+ }
1729
+
1538
1730
  // src/core/adt/discovery/packages.ts
1539
- async function getPackages(client, filter = "*") {
1731
+ async function getPackages(client, options = {}) {
1732
+ const { filter = "*", includeDescriptions = false } = options;
1540
1733
  const params = new URLSearchParams([
1541
1734
  ["operation", "quickSearch"],
1542
1735
  ["query", filter],
@@ -1560,18 +1753,31 @@ async function getPackages(client, filter = "*") {
1560
1753
  if (parseErr) {
1561
1754
  return err(parseErr);
1562
1755
  }
1563
- const packages = [];
1756
+ const packageNames = [];
1564
1757
  const objectRefs = doc.getElementsByTagNameNS("http://www.sap.com/adt/core", "objectReference");
1565
1758
  for (let i = 0; i < objectRefs.length; i++) {
1566
1759
  const obj = objectRefs[i];
1567
1760
  if (!obj) return err(new Error("Invalid object reference in package search results"));
1568
1761
  const name = obj.getAttributeNS("http://www.sap.com/adt/core", "name") || obj.getAttribute("adtcore:name");
1569
- const description = obj.getAttributeNS("http://www.sap.com/adt/core", "description") || obj.getAttribute("adtcore:description");
1570
1762
  if (!name) return err(new Error("Package name missing in object reference"));
1763
+ packageNames.push(name);
1764
+ }
1765
+ if (packageNames.length === 0) return ok([]);
1766
+ if (!includeDescriptions) return ok(packageNames.map((name) => ({ name })));
1767
+ const [stats, statsErr] = await getPackageStats(client, packageNames);
1768
+ if (statsErr) {
1769
+ return ok(packageNames.map((name) => ({ name })));
1770
+ }
1771
+ const descriptionMap = /* @__PURE__ */ new Map();
1772
+ for (const stat2 of stats) {
1773
+ if (stat2.description) descriptionMap.set(stat2.name, stat2.description);
1774
+ }
1775
+ const packages = packageNames.map((name) => {
1571
1776
  const pkg = { name };
1777
+ const description = descriptionMap.get(name);
1572
1778
  if (description) pkg.description = description;
1573
- packages.push(pkg);
1574
- }
1779
+ return pkg;
1780
+ });
1575
1781
  return ok(packages);
1576
1782
  }
1577
1783
 
@@ -1811,79 +2017,6 @@ async function getTree(client, query = {}) {
1811
2017
  return ok(result);
1812
2018
  }
1813
2019
 
1814
- // src/core/adt/discovery/tree/packageStats.ts
1815
- function constructPackageStatsBody(packageNames) {
1816
- const names = packageNames.length === 1 ? [...packageNames, "SRIS_TEST_DATA_VFS_EMPTY"] : packageNames;
1817
- const values = names.map((name) => ` <vfs:value>${name}</vfs:value>`).join("\n");
1818
- return `<?xml version="1.0" encoding="UTF-8"?>
1819
- <vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="*">
1820
- <vfs:preselection facet="package">
1821
- ${values}
1822
- </vfs:preselection>
1823
- <vfs:facetorder>
1824
- <vfs:facet>package</vfs:facet>
1825
- <vfs:facet>group</vfs:facet>
1826
- <vfs:facet>type</vfs:facet>
1827
- </vfs:facetorder>
1828
- </vfs:virtualFoldersRequest>`;
1829
- }
1830
- function parsePackageStats(xml) {
1831
- const [doc, parseErr] = safeParseXml(xml);
1832
- if (parseErr) return [];
1833
- const packages = [];
1834
- const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
1835
- for (let i = 0; i < virtualFolders.length; i++) {
1836
- const vf = virtualFolders[i];
1837
- if (!vf) continue;
1838
- const facet = vf.getAttribute("facet")?.toUpperCase();
1839
- if (facet !== "PACKAGE") continue;
1840
- const name = vf.getAttribute("name");
1841
- if (!name) continue;
1842
- const countAttr = vf.getAttribute("counter");
1843
- const count = countAttr ? parseInt(countAttr, 10) : 0;
1844
- const description = vf.getAttribute("text");
1845
- const pkg = {
1846
- name,
1847
- numContents: count
1848
- };
1849
- if (description) pkg.description = description;
1850
- packages.push(pkg);
1851
- }
1852
- return packages;
1853
- }
1854
- async function getPackageStats(client, packageNames) {
1855
- const isSingle = typeof packageNames === "string";
1856
- const names = isSingle ? [packageNames] : packageNames;
1857
- if (names.length === 0) {
1858
- return ok([]);
1859
- }
1860
- const body = constructPackageStatsBody(names);
1861
- const [response, requestErr] = await client.request({
1862
- method: "POST",
1863
- path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
1864
- headers: {
1865
- "Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
1866
- "Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
1867
- },
1868
- body
1869
- });
1870
- if (requestErr) return err(requestErr);
1871
- if (!response.ok) {
1872
- const text = await response.text();
1873
- const errorMsg = extractError(text);
1874
- return err(new Error(`Package stats fetch failed: ${errorMsg}`));
1875
- }
1876
- const xml = await response.text();
1877
- const packages = parsePackageStats(xml).filter((pkg) => pkg.name !== "SRIS_TEST_DATA_VFS_EMPTY");
1878
- if (isSingle) {
1879
- if (packages.length === 0) {
1880
- return err(new Error(`Package ${packageNames} not found`));
1881
- }
1882
- return ok(packages[0]);
1883
- }
1884
- return ok(packages);
1885
- }
1886
-
1887
2020
  // src/core/adt/transports/transports.ts
1888
2021
  async function getTransports(client, packageName) {
1889
2022
  const contentType = "application/vnd.sap.as+xml; charset=UTF-8; dataname=com.sap.adt.transport.service.checkData";
@@ -2539,10 +2672,16 @@ async function deleteObjects(state, requestor, objects, transport) {
2539
2672
  return ok(void 0);
2540
2673
  }
2541
2674
 
2675
+ // src/client/methods/craud/checkSyntax.ts
2676
+ async function checkSyntax2(state, requestor, objects) {
2677
+ if (!state.session) return err(new Error("Not logged in"));
2678
+ return checkSyntax(requestor, objects);
2679
+ }
2680
+
2542
2681
  // src/client/methods/discovery/getPackages.ts
2543
- async function getPackages2(state, requestor, filter) {
2682
+ async function getPackages2(state, requestor, options) {
2544
2683
  if (!state.session) return err(new Error("Not logged in"));
2545
- return getPackages(requestor, filter);
2684
+ return getPackages(requestor, options);
2546
2685
  }
2547
2686
 
2548
2687
  // src/client/methods/discovery/getTree.ts
@@ -2935,12 +3074,15 @@ var ADTClientImpl = class {
2935
3074
  async activate(objects) {
2936
3075
  return activate(this.state, this.requestor, objects);
2937
3076
  }
3077
+ async checkSyntax(objects) {
3078
+ return checkSyntax2(this.state, this.requestor, objects);
3079
+ }
2938
3080
  async delete(objects, transport) {
2939
3081
  return deleteObjects(this.state, this.requestor, objects, transport);
2940
3082
  }
2941
3083
  // --- Discovery ---
2942
- async getPackages(filter) {
2943
- return getPackages2(this.state, this.requestor, filter);
3084
+ async getPackages(options) {
3085
+ return getPackages2(this.state, this.requestor, options);
2944
3086
  }
2945
3087
  async getTree(query) {
2946
3088
  return getTree2(this.state, this.requestor, query);