catalyst-relay 0.2.4 → 0.3.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/dist/index.d.mts CHANGED
@@ -35,8 +35,12 @@ interface SamlProviderConfig {
35
35
  */
36
36
  interface SamlAuthConfig {
37
37
  type: 'saml';
38
+ /** SAML username (often an email address) - used for browser login */
38
39
  username: string;
40
+ /** SAML password */
39
41
  password: string;
42
+ /** SAP system username - used for object creation (adtcore:responsible) */
43
+ sapUser: string;
40
44
  /** Optional custom provider configuration for non-standard login forms */
41
45
  providerConfig?: SamlProviderConfig;
42
46
  }
@@ -288,7 +292,6 @@ interface Transport {
288
292
  id: string;
289
293
  description: string;
290
294
  owner: string;
291
- status: 'modifiable' | 'released';
292
295
  }
293
296
 
294
297
  /**
@@ -477,7 +480,7 @@ interface ADTClient {
477
480
  upsert(objects: ObjectContent[], packageName: string, transport?: string): AsyncResult<UpsertResult[]>;
478
481
  activate(objects: ObjectRef[]): AsyncResult<ActivationResult[]>;
479
482
  delete(objects: ObjectRef[], transport?: string): AsyncResult<void>;
480
- getPackages(): AsyncResult<Package[]>;
483
+ getPackages(filter?: string): AsyncResult<Package[]>;
481
484
  getTree(query: TreeQuery): AsyncResult<TreeNode[]>;
482
485
  getTransports(packageName: string): AsyncResult<Transport[]>;
483
486
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
package/dist/index.d.ts CHANGED
@@ -35,8 +35,12 @@ interface SamlProviderConfig {
35
35
  */
36
36
  interface SamlAuthConfig {
37
37
  type: 'saml';
38
+ /** SAML username (often an email address) - used for browser login */
38
39
  username: string;
40
+ /** SAML password */
39
41
  password: string;
42
+ /** SAP system username - used for object creation (adtcore:responsible) */
43
+ sapUser: string;
40
44
  /** Optional custom provider configuration for non-standard login forms */
41
45
  providerConfig?: SamlProviderConfig;
42
46
  }
@@ -288,7 +292,6 @@ interface Transport {
288
292
  id: string;
289
293
  description: string;
290
294
  owner: string;
291
- status: 'modifiable' | 'released';
292
295
  }
293
296
 
294
297
  /**
@@ -477,7 +480,7 @@ interface ADTClient {
477
480
  upsert(objects: ObjectContent[], packageName: string, transport?: string): AsyncResult<UpsertResult[]>;
478
481
  activate(objects: ObjectRef[]): AsyncResult<ActivationResult[]>;
479
482
  delete(objects: ObjectRef[], transport?: string): AsyncResult<void>;
480
- getPackages(): AsyncResult<Package[]>;
483
+ getPackages(filter?: string): AsyncResult<Package[]>;
481
484
  getTree(query: TreeQuery): AsyncResult<TreeNode[]>;
482
485
  getTransports(packageName: string): AsyncResult<Transport[]>;
483
486
  previewData(query: PreviewSQL): AsyncResult<DataFrame>;
package/dist/index.js CHANGED
@@ -174,6 +174,11 @@ function debugError(message, cause) {
174
174
  }
175
175
  }
176
176
 
177
+ // src/core/utils/content.ts
178
+ function normalizeContent(content) {
179
+ return content.replace(/\s+/g, " ").trim();
180
+ }
181
+
177
182
  // src/types/config.ts
178
183
  var import_zod = require("zod");
179
184
  var samlFormSelectorsSchema = import_zod.z.object({
@@ -198,6 +203,7 @@ var clientConfigSchema = import_zod.z.object({
198
203
  type: import_zod.z.literal("saml"),
199
204
  username: import_zod.z.string().min(1),
200
205
  password: import_zod.z.string().min(1),
206
+ sapUser: import_zod.z.string().min(1),
201
207
  providerConfig: samlProviderConfigSchema.optional()
202
208
  }),
203
209
  import_zod.z.object({
@@ -271,8 +277,9 @@ function getSessionTimeout(authType) {
271
277
  function extractUsername(auth) {
272
278
  switch (auth.type) {
273
279
  case "basic":
274
- case "saml":
275
280
  return auth.username;
281
+ case "saml":
282
+ return auth.sapUser;
276
283
  case "sso":
277
284
  return process.env["USERNAME"] ?? process.env["USER"] ?? "SSO_USER";
278
285
  default: {
@@ -677,6 +684,46 @@ function extractActivationErrors(objects, xml, _extension) {
677
684
  return ok(results);
678
685
  }
679
686
 
687
+ // src/core/adt/discovery/packages.ts
688
+ async function getPackages(client, filter = "*") {
689
+ const params = new URLSearchParams([
690
+ ["operation", "quickSearch"],
691
+ ["query", filter],
692
+ ["maxResults", "10001"],
693
+ ["objectType", "DEVC/K"]
694
+ ]);
695
+ const [response, requestErr] = await client.request({
696
+ method: "GET",
697
+ path: `/sap/bc/adt/repository/informationsystem/search?${params.toString()}`
698
+ });
699
+ if (requestErr) {
700
+ return err(requestErr);
701
+ }
702
+ if (!response.ok) {
703
+ const text2 = await response.text();
704
+ const errorMsg = extractError(text2);
705
+ return err(new Error(`Package search failed: ${errorMsg}`));
706
+ }
707
+ const text = await response.text();
708
+ const [doc, parseErr] = safeParseXml(text);
709
+ if (parseErr) {
710
+ return err(parseErr);
711
+ }
712
+ const packages = [];
713
+ const objectRefs = doc.getElementsByTagNameNS("http://www.sap.com/adt/core", "objectReference");
714
+ for (let i = 0; i < objectRefs.length; i++) {
715
+ const obj = objectRefs[i];
716
+ if (!obj) return err(new Error("Invalid object reference in package search results"));
717
+ const name = obj.getAttributeNS("http://www.sap.com/adt/core", "name") || obj.getAttribute("adtcore:name");
718
+ const description = obj.getAttributeNS("http://www.sap.com/adt/core", "description") || obj.getAttribute("adtcore:description");
719
+ if (!name) return err(new Error("Package name missing in object reference"));
720
+ const pkg = { name };
721
+ if (description) pkg.description = description;
722
+ packages.push(pkg);
723
+ }
724
+ return ok(packages);
725
+ }
726
+
680
727
  // src/core/adt/discovery/tree.ts
681
728
  async function getTree(client, query) {
682
729
  const internalQuery = {};
@@ -794,32 +841,23 @@ function parseTreeResponse(xml) {
794
841
  return ok({ nodes, packages });
795
842
  }
796
843
 
797
- // src/core/adt/discovery/packages.ts
798
- async function getPackages(client) {
799
- const [treeResult, treeErr] = await getTreeInternal(client, {}, "*");
800
- if (treeErr) {
801
- return err(treeErr);
802
- }
803
- return ok(treeResult.packages);
804
- }
805
-
806
844
  // src/core/adt/transports/transports.ts
807
845
  async function getTransports(client, packageName) {
808
846
  const contentType = "application/vnd.sap.as+xml; charset=UTF-8; dataname=com.sap.adt.transport.service.checkData";
809
847
  const body = `<?xml version="1.0" encoding="UTF-8"?>
810
- <asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
811
- <asx:values>
812
- <DATA>
813
- <PGMID></PGMID>
814
- <OBJECT></OBJECT>
815
- <OBJECTNAME></OBJECTNAME>
816
- <DEVCLASS>${packageName}</DEVCLASS>
817
- <SUPER_PACKAGE></SUPER_PACKAGE>
818
- <OPERATION>I</OPERATION>
819
- <URI>/sap/bc/adt/ddic/ddl/sources/transport_check</URI>
820
- </DATA>
821
- </asx:values>
822
- </asx:abap>`;
848
+ <asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
849
+ <asx:values>
850
+ <DATA>
851
+ <PGMID></PGMID>
852
+ <OBJECT></OBJECT>
853
+ <OBJECTNAME></OBJECTNAME>
854
+ <DEVCLASS>${packageName}</DEVCLASS>
855
+ <SUPER_PACKAGE></SUPER_PACKAGE>
856
+ <OPERATION>I</OPERATION>
857
+ <URI>/sap/bc/adt/ddic/ddl/sources/zsnap_test4transports</URI>
858
+ </DATA>
859
+ </asx:values>
860
+ </asx:abap>`;
823
861
  const [response, requestErr] = await client.request({
824
862
  method: "POST",
825
863
  path: "/sap/bc/adt/cts/transportchecks",
@@ -829,44 +867,33 @@ async function getTransports(client, packageName) {
829
867
  },
830
868
  body
831
869
  });
832
- if (requestErr) {
833
- return err(requestErr);
834
- }
870
+ if (requestErr) return err(requestErr);
835
871
  if (!response.ok) {
836
872
  const text2 = await response.text();
837
873
  const errorMsg = extractError(text2);
838
- return err(new Error(`Failed to fetch transports for ${packageName}: ${errorMsg}`));
874
+ return err(new Error(`Failed to fetch transports for package ${packageName}: ${errorMsg}`));
839
875
  }
840
876
  const text = await response.text();
841
877
  const [transports, parseErr] = extractTransports(text);
842
- if (parseErr) {
843
- return err(parseErr);
844
- }
878
+ if (parseErr) return err(parseErr);
845
879
  return ok(transports);
846
880
  }
847
881
  function extractTransports(xml) {
848
882
  const [doc, parseErr] = safeParseXml(xml);
849
- if (parseErr) {
850
- return err(parseErr);
851
- }
883
+ if (parseErr) return err(parseErr);
852
884
  const transports = [];
853
885
  const reqHeaders = doc.getElementsByTagName("REQ_HEADER");
854
886
  for (let i = 0; i < reqHeaders.length; i++) {
855
887
  const header = reqHeaders[i];
856
888
  if (!header) continue;
857
- const trkorrElement = header.getElementsByTagName("TRKORR")[0];
858
- const userElement = header.getElementsByTagName("AS4USER")[0];
859
- const textElement = header.getElementsByTagName("AS4TEXT")[0];
860
- if (!trkorrElement || !userElement || !textElement) continue;
861
- const id = trkorrElement.textContent;
862
- const owner = userElement.textContent;
863
- const description = textElement.textContent;
864
- if (!id || !owner || !description) continue;
889
+ const id = header.getElementsByTagName("TRKORR")[0]?.textContent;
890
+ const owner = header.getElementsByTagName("AS4USER")[0]?.textContent;
891
+ const description = header.getElementsByTagName("AS4TEXT")[0]?.textContent;
892
+ if (!id) continue;
865
893
  transports.push({
866
894
  id,
867
- owner,
868
- description,
869
- status: "modifiable"
895
+ description: description || "",
896
+ owner: owner || ""
870
897
  });
871
898
  }
872
899
  return ok(transports);
@@ -1827,11 +1854,22 @@ var SamlAuth = class {
1827
1854
  if (!config.username || !config.password) {
1828
1855
  throw new Error("SamlAuth requires both username and password");
1829
1856
  }
1857
+ if (!config.sapUser) {
1858
+ throw new Error("SamlAuth requires sapUser (SAP system username for object creation)");
1859
+ }
1830
1860
  if (!config.baseUrl) {
1831
1861
  throw new Error("SamlAuth requires baseUrl");
1832
1862
  }
1833
1863
  this.config = config;
1834
1864
  }
1865
+ /**
1866
+ * Get SAP system username
1867
+ *
1868
+ * Used for object creation (adtcore:responsible) instead of the SAML email.
1869
+ */
1870
+ getSapUser() {
1871
+ return this.config.sapUser;
1872
+ }
1835
1873
  /**
1836
1874
  * Get auth headers for SAML
1837
1875
  *
@@ -1895,6 +1933,7 @@ function createAuthStrategy(options) {
1895
1933
  return new SamlAuth({
1896
1934
  username: config.username,
1897
1935
  password: config.password,
1936
+ sapUser: config.sapUser,
1898
1937
  baseUrl,
1899
1938
  ...config.providerConfig && { providerConfig: config.providerConfig }
1900
1939
  });
@@ -2020,6 +2059,9 @@ var ADTClientImpl = class {
2020
2059
  if (this.agent) {
2021
2060
  fetchOptions.dispatcher = this.agent;
2022
2061
  }
2062
+ if (config.insecure) {
2063
+ fetchOptions.tls = { rejectUnauthorized: false };
2064
+ }
2023
2065
  if (body) {
2024
2066
  fetchOptions.body = body;
2025
2067
  }
@@ -2084,6 +2126,13 @@ var ADTClientImpl = class {
2084
2126
  return err(loginErr);
2085
2127
  }
2086
2128
  }
2129
+ if (authStrategy.type === "saml" && authStrategy.getCookies) {
2130
+ const cookies = authStrategy.getCookies();
2131
+ for (const cookie of cookies) {
2132
+ this.state.cookies.set(cookie.name, cookie.value);
2133
+ }
2134
+ debug(`Transferred ${cookies.length} SAML cookies to client`);
2135
+ }
2087
2136
  if (authStrategy.type === "sso" && authStrategy.getCertificates) {
2088
2137
  const certs = authStrategy.getCertificates();
2089
2138
  if (certs) {
@@ -2160,6 +2209,18 @@ var ADTClientImpl = class {
2160
2209
  results.push(result2);
2161
2210
  continue;
2162
2211
  }
2212
+ const serverContent = normalizeContent(existing.content);
2213
+ const localContent = normalizeContent(obj.content);
2214
+ if (serverContent === localContent) {
2215
+ const result2 = {
2216
+ name: obj.name,
2217
+ extension: obj.extension,
2218
+ status: "unchanged"
2219
+ };
2220
+ if (transport) result2.transport = transport;
2221
+ results.push(result2);
2222
+ continue;
2223
+ }
2163
2224
  const [, updateErr] = await this.update(obj, transport);
2164
2225
  if (updateErr) return err(updateErr);
2165
2226
  const result = {
@@ -2190,9 +2251,9 @@ var ADTClientImpl = class {
2190
2251
  return ok(void 0);
2191
2252
  }
2192
2253
  // --- Discovery ---
2193
- async getPackages() {
2254
+ async getPackages(filter) {
2194
2255
  if (!this.state.session) return err(new Error("Not logged in"));
2195
- return getPackages(this.requestor);
2256
+ return getPackages(this.requestor, filter);
2196
2257
  }
2197
2258
  async getTree(query) {
2198
2259
  if (!this.state.session) return err(new Error("Not logged in"));