@sap/cli-core
Version:
Command-Line Interface (CLI) Core Module
365 lines (364 loc) • 14.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleDeprecationNotice = exports.handleParameters = exports.handleResponses = exports.handleForceOption = exports.handleRequestBody = exports.buildOptionFromType = exports.addOptionToCommand = exports.getSchema = exports.getDescriptionForCommand = exports.handleReadPathHandler = exports.getSegments = exports.writeParameter = exports.isPathParameter = exports.updatePath = void 0;
const lodash_1 = __importStar(require("lodash"));
const constants_1 = require("../../constants");
const logger_1 = require("../../logger");
const utils_1 = require("../../utils/utils");
const config_1 = require("../../config");
const handler_1 = require("../handler");
const ResultHandlerFactory_1 = require("../../result/ResultHandlerFactory");
const getLogger = () => (0, logger_1.get)("commands.openAPI.utils");
const updatePath = (doc, path) => {
const version = (0, utils_1.parseVersion)(doc.info.version);
if (version.major < 2023) {
return `/dwaas-core${path}`;
}
return path;
};
exports.updatePath = updatePath;
const isPathParameter = (segment) => /^{.*}$/.test(segment);
exports.isPathParameter = isPathParameter;
function getParameterName(parameter) {
return `--${(0, lodash_1.kebabCase)(parameter.slice(1, -1))}`;
}
const writeParameter = (parameter, value) => `${getParameterName(parameter)} ${value}`;
exports.writeParameter = writeParameter;
const getSegments = (value) => {
const segments = value.split("/");
return segments.filter((segment) => {
const s = segment.trim();
return s !== "" && s !== "marketplace";
});
};
exports.getSegments = getSegments;
const handleParameter = (params, pathSegment, locationSegment) => {
if ((0, exports.isPathParameter)(pathSegment)) {
return params
? `${params} ${(0, exports.writeParameter)(pathSegment, locationSegment)}`
: (0, exports.writeParameter)(pathSegment, locationSegment);
}
return params;
};
const getPathItem = (doc, operation, path) => {
const pathItem = doc.paths[path];
if (pathItem.get) {
return pathItem;
}
throw new Error(`path ${operation["x-read-path"]} does not support GET operation`);
};
function handleOptions(options, option, value) {
if ((0, exports.isPathParameter)(option)) {
return { ...options, [getParameterName(option)]: value };
}
return options;
}
const outputReadCommand = (doc, operation, response, path) => {
const locationSegments = (0, exports.getSegments)(response.headers.location);
const pathSegments = (0, exports.getSegments)(path);
let params = "";
let options = {};
for (let i = 0; i < pathSegments.length; i++) {
params = handleParameter(params, pathSegments[i], locationSegments[i]);
options = handleOptions(options, pathSegments[i], locationSegments[i]);
}
const pathItem = getPathItem(doc, operation, path);
ResultHandlerFactory_1.ResultHandlerFactory.get().setResult({
command: pathItem.get.operationId,
options,
});
const { output } = getLogger();
output(`Use ${(0, config_1.get)()[constants_1.CLI_NAME]} ${pathItem.get.operationId} ${params} to retrieve the entity you just created`);
};
const readPathHandlerFunction = (doc, operation, path) => async (response) => {
const { debug } = getLogger();
if (!response.headers.location) {
debug("response contains no location header");
}
else {
outputReadCommand(doc, operation, response, path);
}
};
const dummyFunction = async () => {
// This is intentional
};
const handleReadPathHandler = (doc, operation) => {
const path = operation["x-read-path"];
if (path) {
return readPathHandlerFunction(doc, operation, path);
}
return dummyFunction;
};
exports.handleReadPathHandler = handleReadPathHandler;
const getDescriptionForCommand = (command, operation) => command.description || operation.description || operation.summary || "";
exports.getDescriptionForCommand = getDescriptionForCommand;
const getReferenceFromDocument = (reference, document) => {
const { trace } = getLogger();
const segments = reference.$ref.split("/");
if (segments.length < 4 || // #(1)/components(2)/(parameters|schemas|responses|...)(3)/<name>(4)
segments[0] !== "#" ||
segments[1] !== "components") {
throw new Error(`invalid reference ${reference.$ref}`);
}
const path = segments
.slice(1)
.reduce((prev, curr) => (prev !== "" ? `${prev}.${curr}` : curr), "");
trace("reading reference %s, path %s", reference.$ref, path);
return lodash_1.default.get(document, path);
};
const getSchema = (obj, doc) => {
if (obj.$ref) {
return getReferenceFromDocument(obj, doc);
}
return obj;
};
exports.getSchema = getSchema;
const getKeysFromSchema = (schema, doc, key = "", required = false) => {
const { trace } = getLogger();
trace(`retrieving keys for key ${key} from schema ${JSON.stringify(schema)}`);
if (schema.$ref) {
// eslint-disable-next-line no-param-reassign
schema = (0, exports.getSchema)(schema, doc);
}
if (schema.type === "object" && schema.properties) {
let keys = [];
Object.keys(schema.properties).forEach((property) => {
keys = keys.concat(getKeysFromSchema(schema.properties[property], doc, key ? `${key}.${property}` : property, schema.required?.includes(property)));
});
return keys;
}
return [{ key, schema, required }];
};
const flattenType = (type, doc) => {
if (type.oneOf) {
let result = [];
type.oneOf.forEach((t) => {
result = result.concat(flattenType(t, doc));
});
return result;
}
if (type.$ref) {
const schema = (0, exports.getSchema)(type, doc);
return flattenType({
...schema,
allowEmptyValue: type.allowEmptyValue,
required: type.required,
}, doc);
}
if (type.type === "object") {
throw new Error("type: Object not supported");
}
return [type];
};
const addOptionToCommand = (option, options) => {
const { trace } = getLogger();
options.push(option);
trace("added option %s", JSON.stringify(option));
};
exports.addOptionToCommand = addOptionToCommand;
const checkTypes = (types) => {
for (const t of types) {
if (types.length > 1 && t.enum?.length === 1) {
throw new Error("constants not allowed for multiple types");
}
}
};
const initParams = (types) => {
const params = { booleanAvailable: false, choices: [] };
for (const t of types) {
if (t.type === "boolean") {
params.booleanAvailable = true;
}
else {
t.enum?.forEach((e) => params.choices.push(e.toString()));
}
params.default = t.default;
}
return params;
};
const handleOptionName = (name, params, description) => {
let optionName = lodash_1.default.kebabCase(name);
let def;
let newDescription = description;
if (params.default !== undefined) {
if (params.booleanAvailable && params.default === true) {
// see npmjs.com/package/commander#other-option-types-negatable-boolean-and-booleanvalue
optionName = `no-${optionName}`;
if (description) {
newDescription = `do not ${description}`;
}
}
else {
def = params.default.toString();
}
}
return {
optionName,
def,
description: newDescription,
};
};
const buildOption = (commandName, longName, types, params, description, def) => {
const option = {
longName,
required: !types[0].allowEmptyValue && types[0].required,
description: description || "",
default: def === "false" ? undefined : def,
args: params.booleanAvailable && types.length === 1
? undefined
: [
{
name: longName,
optional: params.booleanAvailable && types.length > 1,
},
],
choices: params.choices.length > 0 ? params.choices : undefined,
};
if (option.required) {
const desc = option.description ? ` (${option.description})` : "";
option.prompts = {
message: `Provide a value for option ${option.longName}${desc}:`,
type: params.choices.length > 0 ? "select" : "text",
};
}
return option;
};
const handleOption = (commandName, name, params, types, options, description) => {
const { optionName, def, description: newDescription, } = handleOptionName(name, params, description);
const option = buildOption(commandName, optionName, types, params, newDescription, def);
(0, exports.addOptionToCommand)(option, options);
};
const requiresUserInput = (name) => name !== constants_1.X_CSRF_TOKEN;
const buildOptionFromType = (commandName, doc, parameterIn, name, type, parameterMappings, options, description) => {
try {
const types = flattenType(type, doc);
checkTypes(types);
if (types[0].enum?.length === 1) {
parameterMappings.push({
in: parameterIn,
name,
source: { type: "value", value: types[0].enum[0] },
});
}
else {
const params = initParams(types);
if (requiresUserInput(name)) {
handleOption(commandName, name, params, types, options, description);
}
parameterMappings.push({
in: parameterIn,
name,
source: {
type: "option",
name: (0, lodash_1.kebabCase)(name),
dataType: params.booleanAvailable ? "boolean" : "string", // we don't care whether it's a string or number, it simply must not be boolean in this case
},
});
}
}
catch (err) {
if (!type.required) {
const { trace } = getLogger();
trace(`option ${name} silently ignored since not required`, err.stack);
}
else {
throw err;
}
}
};
exports.buildOptionFromType = buildOptionFromType;
const handleRequestBody = (operation, handler, doc, parameterMappings, command) => {
if (operation["x-requestbody-fileonly"]) {
handler.push((0, handler_1.createInputHandler)());
}
else if (operation.requestBody) {
const schema = (0, exports.getSchema)(Object.values(operation.requestBody.content)[0].schema, doc);
const keys = getKeysFromSchema(schema, doc);
keys.forEach((key) => {
if (key.schema.oneOf) {
throw new Error("invalid request body parameter resolution, oneOf not supported");
}
(0, exports.buildOptionFromType)(command.command, doc, "body", key.key, {
...key.schema,
required: key.required,
}, parameterMappings, command.options, key.schema.description);
});
}
};
exports.handleRequestBody = handleRequestBody;
const handleForceOption = (operation, handler) => {
if (operation["x-user-to-confirm"]) {
handler.push((0, handler_1.createForceHandler)(operation["x-user-to-confirm"]));
}
};
exports.handleForceOption = handleForceOption;
const handleResponses = (operation, command) => {
if (operation.responses?.[200]) {
(0, exports.addOptionToCommand)(constants_1.OPTION_OUTPUT, command.options);
}
(0, exports.addOptionToCommand)(constants_1.OPTION_NO_PRETTY, command.options);
};
exports.handleResponses = handleResponses;
const handleParameters = (operation, doc, parameterMappings, command, topLevelParameters = []) => {
const { error } = getLogger();
(operation.parameters || []).concat(topLevelParameters).forEach((p) => {
try {
const parameter = (0, exports.getSchema)(p, doc);
(0, exports.buildOptionFromType)(command.command, doc, parameter.in, parameter.name, {
...parameter.schema,
allowEmptyValue: parameter.allowEmptyValue,
required: parameter.required,
}, parameterMappings, command.options, parameter.description);
}
catch (err) {
error(`cannot add option ${p} for operation ${operation.operationId}`, err.stack);
}
});
};
exports.handleParameters = handleParameters;
const handleDeprecationNotice = (operation, command) => {
if (operation.deprecated && command.type === "command") {
// eslint-disable-next-line no-param-reassign
command.deprecationInfo = {
deprecated: true,
deprecatedWithWave: operation["x-deprecated-with-wave"],
decommissionedAfterWave: operation["x-decommissioned-after-wave"],
newCommand: operation["x-deprecation-new-command"],
sapHelpUrl: operation["x-deprecation-sap-help-url"],
};
}
};
exports.handleDeprecationNotice = handleDeprecationNotice;