@saptools/cf-hana 0.3.2 → 0.3.4

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.ts CHANGED
@@ -43,7 +43,7 @@ interface ConnectOptions {
43
43
  readonly autoLimit?: number | false;
44
44
  readonly queryTimeoutMs?: number;
45
45
  readonly connectTimeoutMs?: number;
46
- /** Bypass the cached credentials and fetch them live from Cloud Foundry. */
46
+ /** Deprecated compatibility flag. Binding discovery is already live. */
47
47
  readonly refresh?: boolean;
48
48
  /** SAP BTP email for the live credential fetch (else `SAP_EMAIL`). */
49
49
  readonly email?: string;
package/dist/index.js CHANGED
@@ -625,7 +625,7 @@ async function listColumns(connection, schema, table) {
625
625
  }
626
626
 
627
627
  // src/config.ts
628
- var CLI_VERSION = "0.3.2";
628
+ var CLI_VERSION = "0.3.4";
629
629
  var ENV_PREFIX = "CF_HANA";
630
630
  var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
631
631
  var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
@@ -673,40 +673,94 @@ var REGION_API_MAP = {
673
673
  ap20: "https://api.cf.ap20.hana.ondemand.com",
674
674
  ap21: "https://api.cf.ap21.hana.ondemand.com",
675
675
  ap30: "https://api.cf.ap30.hana.ondemand.com",
676
+ ap31: "https://api.cf.ap31.hana.ondemand.com",
676
677
  br10: "https://api.cf.br10.hana.ondemand.com",
677
678
  br20: "https://api.cf.br20.hana.ondemand.com",
678
679
  br30: "https://api.cf.br30.hana.ondemand.com",
679
680
  ca10: "https://api.cf.ca10.hana.ondemand.com",
680
681
  ca20: "https://api.cf.ca20.hana.ondemand.com",
681
682
  ch20: "https://api.cf.ch20.hana.ondemand.com",
683
+ cn20: "https://api.cf.cn20.platform.sapcloud.cn",
684
+ cn40: "https://api.cf.cn40.platform.sapcloud.cn",
685
+ eu01: "https://api.cf.eu01.hana.ondemand.com",
686
+ eu02: "https://api.cf.eu02.hana.ondemand.com",
682
687
  eu10: "https://api.cf.eu10.hana.ondemand.com",
688
+ "eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
689
+ "eu10-003": "https://api.cf.eu10-003.hana.ondemand.com",
690
+ "eu10-004": "https://api.cf.eu10-004.hana.ondemand.com",
691
+ "eu10-005": "https://api.cf.eu10-005.hana.ondemand.com",
692
+ "eu10-006": "https://api.cf.eu10-006.hana.ondemand.com",
683
693
  eu11: "https://api.cf.eu11.hana.ondemand.com",
684
694
  eu12: "https://api.cf.eu12.hana.ondemand.com",
695
+ eu13: "https://api.cf.eu13.hana.ondemand.com",
685
696
  eu20: "https://api.cf.eu20.hana.ondemand.com",
697
+ "eu20-001": "https://api.cf.eu20-001.hana.ondemand.com",
698
+ "eu20-002": "https://api.cf.eu20-002.hana.ondemand.com",
686
699
  eu21: "https://api.cf.eu21.hana.ondemand.com",
700
+ eu22: "https://api.cf.eu22.hana.ondemand.com",
687
701
  eu30: "https://api.cf.eu30.hana.ondemand.com",
688
- "eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
702
+ eu31: "https://api.cf.eu31.hana.ondemand.com",
703
+ il30: "https://api.cf.il30.hana.ondemand.com",
704
+ in30: "https://api.cf.in30.hana.ondemand.com",
705
+ jp01: "https://api.cf.jp01.hana.ondemand.com",
689
706
  jp10: "https://api.cf.jp10.hana.ondemand.com",
690
707
  jp20: "https://api.cf.jp20.hana.ondemand.com",
691
708
  jp30: "https://api.cf.jp30.hana.ondemand.com",
709
+ jp31: "https://api.cf.jp31.hana.ondemand.com",
710
+ sa30: "https://api.cf.sa30.hana.ondemand.com",
711
+ sa31: "https://api.cf.sa31.hana.ondemand.com",
712
+ uk20: "https://api.cf.uk20.hana.ondemand.com",
713
+ us01: "https://api.cf.us01.hana.ondemand.com",
714
+ us02: "https://api.cf.us02.hana.ondemand.com",
692
715
  us10: "https://api.cf.us10.hana.ondemand.com",
716
+ "us10-001": "https://api.cf.us10-001.hana.ondemand.com",
717
+ "us10-002": "https://api.cf.us10-002.hana.ondemand.com",
718
+ "us10-003": "https://api.cf.us10-003.hana.ondemand.com",
693
719
  us11: "https://api.cf.us11.hana.ondemand.com",
694
720
  us20: "https://api.cf.us20.hana.ondemand.com",
695
721
  us21: "https://api.cf.us21.hana.ondemand.com",
722
+ "us21-001": "https://api.cf.us21-001.hana.ondemand.com",
723
+ us22: "https://api.cf.us22.hana.ondemand.com",
696
724
  us30: "https://api.cf.us30.hana.ondemand.com",
697
- in30: "https://api.cf.in30.hana.ondemand.com"
725
+ us32: "https://api.cf.us32.hana.ondemand.com"
698
726
  };
727
+ function normalizeSapCfApiEndpoint(apiEndpoint) {
728
+ const trimmed = apiEndpoint.trim();
729
+ let parsed;
730
+ try {
731
+ parsed = new URL(trimmed);
732
+ } catch {
733
+ throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
734
+ }
735
+ if (/^https:\/\/[^/]*:\d+(?:[/?#]|$)/i.test(trimmed)) {
736
+ throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
737
+ }
738
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.port !== "" || parsed.pathname.replace(/\/+$/, "") !== "" || parsed.search !== "" || parsed.hash !== "") {
739
+ throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
740
+ }
741
+ const hostname = parsed.hostname.toLowerCase();
742
+ const match = /^api\.cf\.([a-z]{2}\d{2}(?:-\d{3})?)\.(hana\.ondemand\.com|platform\.sapcloud\.cn)$/.exec(hostname);
743
+ if (!match) {
744
+ throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
745
+ }
746
+ return `https://${hostname}`;
747
+ }
699
748
  function getApiEndpointForRegion(regionKey) {
700
- return REGION_API_MAP[regionKey];
749
+ return REGION_API_MAP[regionKey.trim().toLowerCase()];
701
750
  }
702
751
  function getRegionKeyForApi(apiEndpoint) {
703
- const norm = apiEndpoint.trim().replace(/\/+$/, "").toLowerCase();
752
+ let normalized;
753
+ try {
754
+ normalized = normalizeSapCfApiEndpoint(apiEndpoint);
755
+ } catch {
756
+ return void 0;
757
+ }
704
758
  for (const [key, endpoint] of Object.entries(REGION_API_MAP)) {
705
- if (endpoint.toLowerCase() === norm) {
759
+ if (endpoint.toLowerCase() === normalized) {
706
760
  return key;
707
761
  }
708
762
  }
709
- return void 0;
763
+ return /^https:\/\/api\.cf\.([a-z]{2}\d{2}(?:-\d{3})?)\.hana\.ondemand\.com$/.exec(normalized)?.[1];
710
764
  }
711
765
  async function withCfSession(work) {
712
766
  const cfHome = await mkdtemp(join2(tmpdir(), "saptools-cf-hana-"));
@@ -806,9 +860,15 @@ function parseCfTargetOutput(stdout) {
806
860
  if (!api || !org || !space) {
807
861
  return void 0;
808
862
  }
809
- const regionKey = getRegionKeyForApi(api);
863
+ let normalizedApi;
864
+ try {
865
+ normalizedApi = normalizeSapCfApiEndpoint(api);
866
+ } catch {
867
+ return void 0;
868
+ }
869
+ const regionKey = getRegionKeyForApi(normalizedApi);
810
870
  return {
811
- apiEndpoint: api,
871
+ apiEndpoint: normalizedApi,
812
872
  orgName: org,
813
873
  spaceName: space,
814
874
  ...regionKey ? { regionKey } : {}
@@ -834,12 +894,8 @@ function formatCurrentCfAppSelector(target, appName) {
834
894
  if (!name) {
835
895
  throw new Error("App name is required.");
836
896
  }
837
- if (!target.regionKey) {
838
- throw new Error(
839
- `Current CF API endpoint "${target.apiEndpoint}" does not match a known SAP region. Pass a full region/org/space/app selector.`
840
- );
841
- }
842
- return `${target.regionKey}/${target.orgName}/${target.spaceName}/${name}`;
897
+ const region = target.regionKey ?? "current";
898
+ return `${region}/${target.orgName}/${target.spaceName}/${name}`;
843
899
  }
844
900
  function extractVcapSection(stdout) {
845
901
  const start = stdout.indexOf("VCAP_SERVICES:");
@@ -919,6 +975,9 @@ function classifyCfError(stderr = "", stdout = "") {
919
975
  }
920
976
 
921
977
  // src/credentials.ts
978
+ function errorMessageFromUnknown(error) {
979
+ return error instanceof Error ? error.message : "Invalid or untrusted CF API endpoint.";
980
+ }
922
981
  async function resolveAppBindings(rawSelector, options) {
923
982
  const selector = rawSelector.trim();
924
983
  if (!selector) {
@@ -937,7 +996,7 @@ async function resolveAppBindings(rawSelector, options) {
937
996
  const displaySelector = current.regionKey ? formatCurrentCfAppSelector(current, selector) : `current/${current.orgName}/${current.spaceName}/${selector}`;
938
997
  target = {
939
998
  selector: displaySelector,
940
- apiEndpoint: current.regionKey ? getApiEndpointForRegion(current.regionKey) : void 0,
999
+ apiEndpoint: current.apiEndpoint,
941
1000
  orgName: current.orgName,
942
1001
  spaceName: current.spaceName,
943
1002
  appName: selector
@@ -955,10 +1014,16 @@ async function resolveAppBindings(rawSelector, options) {
955
1014
  if (!apiEndpoint) {
956
1015
  throw new CfHanaError(
957
1016
  "CONFIG",
958
- `Unknown region key "${regionKey}". Use a known region or the current CF target.`
1017
+ `Unknown SAP CF region "${regionKey}". Verify the current SAP region list or use the current CF target.`
959
1018
  );
960
1019
  }
961
- target = { selector, apiEndpoint, orgName, spaceName, appName };
1020
+ let normalizedApiEndpoint;
1021
+ try {
1022
+ normalizedApiEndpoint = normalizeSapCfApiEndpoint(apiEndpoint);
1023
+ } catch (error) {
1024
+ throw new CfHanaError("CONFIG", errorMessageFromUnknown(error), { cause: error });
1025
+ }
1026
+ target = { selector, apiEndpoint: normalizedApiEndpoint, orgName, spaceName, appName };
962
1027
  }
963
1028
  let bindings;
964
1029
  const source = "live";
@@ -982,9 +1047,6 @@ Run "cf login" + "cf target", or provide SAP_EMAIL + SAP_PASSWORD.`,
982
1047
  { cause: directError }
983
1048
  );
984
1049
  }
985
- if (!target.apiEndpoint) {
986
- throw new CfHanaError("CONFIG", "Cannot determine API endpoint for fallback auth.");
987
- }
988
1050
  const api = target.apiEndpoint;
989
1051
  bindings = await withCfSession(async (ctx) => {
990
1052
  await cfApi(api, ctx);
@@ -1008,9 +1070,6 @@ Run "cf login" + "cf target", or provide SAP_EMAIL + SAP_PASSWORD.`,
1008
1070
  `SAP_EMAIL and SAP_PASSWORD are required to fetch HANA bindings for explicit selector "${selector}".`
1009
1071
  );
1010
1072
  }
1011
- if (!target.apiEndpoint) {
1012
- throw new CfHanaError("CONFIG", "Invalid explicit selector.");
1013
- }
1014
1073
  const api = target.apiEndpoint;
1015
1074
  bindings = await withCfSession(async (ctx) => {
1016
1075
  await cfApi(api, ctx);