@sap/cli-core
Version:
Command-Line Interface (CLI) Core Module
215 lines (214 loc) • 8.08 kB
JavaScript
import fs from "fs-extra";
import { set } from "lodash-es";
import { EOL } from "os";
import { URL } from "url";
import { get as getConfig, set as setConfig } from "../../../config/index.js";
import { AuthenticationMethod, OPTION_OUTPUT, SEGMENTS_TO_REMOVE_FOR_PASSCODE_AUTH, X_CSRF_TOKEN, X_DSP_API_DEPRECATED_PROPERTIES, X_OUTPUT_FILE_NAME, } from "../../../constants.js";
import { get as getLogger } from "../../../logger/index.js";
import { ResultHandlerFactory } from "../../../result/ResultHandlerFactory.js";
import { getAuthenticationMethod, getTargetHost } from "../utils.js";
const CURLY_OPEN_ENCODED = encodeURIComponent("{");
const CURLY_CLOSED_ENCODED = encodeURIComponent("}");
export const checkConfiguration = (config) => {
if (!config.authorization) {
throw new Error("no authorization header");
}
if (!config.host) {
throw new Error("no host");
}
if (!config.publicfqdn) {
throw new Error("no publicfqdn");
}
};
export const removeLeadingPathSegmentForPasscode = (path) => {
const authenticationMethod = getAuthenticationMethod();
if (authenticationMethod === AuthenticationMethod.passcode) {
const segments = path.split("/");
if (SEGMENTS_TO_REMOVE_FOR_PASSCODE_AUTH.includes(segments[1])) {
segments.splice(1, 1);
return segments.join("/");
}
}
return path;
};
const getUrl = (path) => {
if (path.startsWith("http")) {
return new URL(path);
}
const pathInt = removeLeadingPathSegmentForPasscode(path);
const targetHost = getTargetHost();
return new URL(`${targetHost}${pathInt}`);
};
export const handleMappingIn = (mapping, url, value, headers, bodyWrapper) => {
if (mapping.in === "query") {
url.searchParams.append(mapping.name, value);
}
else if (mapping.in === "header") {
// eslint-disable-next-line no-param-reassign
headers[mapping.name] = value;
}
else if (mapping.in === "path") {
// eslint-disable-next-line no-param-reassign
url.pathname = url.pathname.replace(new RegExp(`${CURLY_OPEN_ENCODED}${mapping.name}${CURLY_CLOSED_ENCODED}`, "g"), encodeURIComponent(value));
}
else {
if (!bodyWrapper.body) {
// eslint-disable-next-line no-param-reassign
bodyWrapper.body = {};
}
set(bodyWrapper.body, mapping.name, value);
}
};
export const getValueFromMappping = (mapping, config) => {
if (mapping.source.type === "value") {
return mapping.source.value;
}
if (
// mapping.source.type === "option"
config.options[mapping.source.name] === undefined &&
mapping.source.dataType === "boolean") {
return false;
}
// mapping.source.type === "option"
return config.options[mapping.source.name];
};
export const handleParameterMapping = (config, url, headers, bodyWrapper) => (mapping) => {
const value = getValueFromMappping(mapping, config);
if (value === undefined || value === null) {
return;
}
handleMappingIn(mapping, url, value, headers, bodyWrapper);
};
export const buildParameters = (path, parameterMappings) => {
const config = getConfig();
const bodyWrapper = {
body: undefined,
};
let headers = {};
if (config.data?.type === "file") {
// stackoverflow.com/a/59177066
headers = config.data.content.getHeaders();
bodyWrapper.body = config.data.content;
}
else if (config.data?.type === "json") {
headers = { "Content-Type": "application/json" };
bodyWrapper.body = config.data.content;
}
const url = getUrl(path);
parameterMappings?.forEach(handleParameterMapping(config, url, headers, bodyWrapper));
return {
url,
headers,
body: bodyWrapper.body,
};
};
/**
* If --output is present: Return value of --output if defined, otherwise take value from outputPath.
* If --output is not present, print response to console
* @param outputPath Optional path defined by server
* @returns Path to output file
*/
export function getOutputFileName(outputPath = "") {
const config = getConfig();
if (typeof config.options[OPTION_OUTPUT.longName] === "boolean" &&
config.options[OPTION_OUTPUT.longName]) {
return outputPath;
}
return config.options[OPTION_OUTPUT.longName];
}
export function checkResponseDataForInvalidLoginData(response) {
if (
// jira.tools.sap/browse/DW101-73607
typeof response === "string" &&
response.includes("Please contact your system administrator and ensure you have an active account on this system.")) {
const logger = getLogger("checkResponseDataForInvalidLoginData");
logger.output("WARNING: You are using an access token which is not known to the tenant. Please check your login credentials.");
logger.debug(response);
return true;
}
return false;
}
export const handleResponseData = async (data, outputPath) => {
checkResponseDataForInvalidLoginData(data);
const config = getConfig();
if (!config.doNotStoreResult) {
ResultHandlerFactory.get().setResult(data);
}
if (config.doNotProcessResponseData) {
return;
}
const { output } = getLogger("handler.fetch");
const formatted = config.options.pretty
? JSON.stringify(data, null, 2)
: JSON.stringify(data);
const outputFileName = getOutputFileName(outputPath);
if (outputFileName) {
await fs.writeFile(outputFileName, formatted);
}
else {
output(formatted);
}
};
export function handleResponseHeaders(headers) {
const { error, output } = getLogger("handler.fetch.handleResponseHeaders");
if (headers[X_DSP_API_DEPRECATED_PROPERTIES]) {
try {
const deprecatedProperties = JSON.parse(headers[X_DSP_API_DEPRECATED_PROPERTIES]);
const deprecatedPropertiesStr = deprecatedProperties
.map((dp) => {
let msg = dp.name;
if (dp.deprecatedWithWave) {
msg += `. Deprecated since version ${dp.deprecatedWithWave}`;
}
if (dp.decommissionedAfterWave) {
msg += `. Decommissioned after version ${dp.decommissionedAfterWave}`;
}
if (dp.sapHelpUrl) {
msg += `. See the SAP Help documentation at ${dp.sapHelpUrl} for more information`;
}
if (dp.customMessage) {
msg += `. ${dp.customMessage}`;
}
return msg;
})
.reduce((prev, curr) => `${prev}${EOL}\t${curr}`, "");
output(`WARNING: the following properties are deprecated:${deprecatedPropertiesStr}`);
}
catch (err) {
error(`failed to parse deprecated properties via header ${X_DSP_API_DEPRECATED_PROPERTIES}`, err);
}
}
}
export const handleResponse = async (data, headers) => {
if (headers?.[X_CSRF_TOKEN]) {
setConfig({ [X_CSRF_TOKEN]: headers[X_CSRF_TOKEN] });
}
else {
if (data) {
await handleResponseData(data, headers?.[X_OUTPUT_FILE_NAME]);
}
if (headers) {
handleResponseHeaders(headers);
}
}
};
export const configRequiresBody = (method) => ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
export const buildHttpConfig = async (method, path, parameterMappings) => {
const config = getConfig();
const { url, body, headers } = buildParameters(path, parameterMappings);
const httpConfig = {
url: url.toString(),
method,
data: configRequiresBody(method) ? body : undefined,
headers: {
...config.authorization,
...headers,
...config.headers,
publicfqdn: config.publicfqdn,
},
};
if (getAuthenticationMethod() === AuthenticationMethod.oauth) {
delete httpConfig.headers.publicfqdn;
}
return httpConfig;
};