@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/CHANGELOG.md +13 -0
- package/README.md +41 -31
- package/dist/cli.js +92 -52
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +83 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() ===
|
|
759
|
+
if (endpoint.toLowerCase() === normalized) {
|
|
706
760
|
return key;
|
|
707
761
|
}
|
|
708
762
|
}
|
|
709
|
-
return
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
838
|
-
|
|
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.
|
|
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
|
|
1017
|
+
`Unknown SAP CF region "${regionKey}". Verify the current SAP region list or use the current CF target.`
|
|
959
1018
|
);
|
|
960
1019
|
}
|
|
961
|
-
|
|
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);
|