UNPKG

@sap/cli-core

Version:

Command-Line Interface (CLI) Core Module

365 lines (364 loc) 14.6 kB
"use strict"; 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;