UNPKG

ask-cli-x

Version:

Alexa Skills Kit (ASK) Command Line Interfaces

217 lines (216 loc) 9.6 kB
"use strict"; const { CustomSmapiClientBuilder, ModelIntrospector } = require("ask-smapi-sdk"); const fs = require("fs-extra"); const R = require("ramda"); const queryString = require("querystring"); const { apiToCommanderMap, customizationMap } = require("./customizations/parameters-map"); const AppConfig = require("../../model/app-config"); const AuthorizationController = require("../../controllers/authorization-controller"); const CONSTANTS = require("../../utils/constants"); const DynamicConfig = require("../../utils/dynamic-config"); const jsonView = require("../../view/json-view"); const Messenger = require("../../view/messenger"); const profileHelper = require("../../utils/profile-helper"); const unflatten = require("../../utils/unflatten"); const { getParamNames, standardize, canParseAsJson, kebabCase } = require("../../utils/string-utils"); const BeforeSendProcessor = require("./before-send-processor"); const { BODY_PATH_DELIMITER, ARRAY_SPLIT_DELIMITER, CliCustomizationProcessor } = require("./cli-customization-processor"); const _mapToArgs = (params, paramsObject) => { const res = []; params.forEach((param) => { let value = null; Object.keys(paramsObject).forEach((k) => { if (standardize(k) === standardize(param)) { value = paramsObject[k]; } }); res.push(value); }); return res; }; const _loadValue = (param, value) => { let result = value; if (param.json) { const filePrefix = "file:"; if (value.startsWith(filePrefix)) { const filePath = value.split(filePrefix).pop(); const fileContent = fs.readFileSync(filePath, { encoding: "utf8" }); result = canParseAsJson(fileContent) ? JSON.parse(fileContent) : fileContent; } else { result = canParseAsJson(value) ? JSON.parse(value) : value; } } return result; }; const _mapToParams = (optionsValues, flatParamsMap, commanderToApiCustomizationMap) => { const res = {}; const bodyParam = Array.from(flatParamsMap.values()).find((p) => p.rootName); if (bodyParam) { res[bodyParam.rootName] = {}; } Object.keys(optionsValues).forEach((key) => { const apiName = commanderToApiCustomizationMap.get(key) || key; const param = flatParamsMap.get(standardize(apiName)); if (param) { let value = optionsValues[key]; value = param.isArray ? value.split(ARRAY_SPLIT_DELIMITER) : value; value = param.isNumber ? Number(value) : value; value = param.isBoolean ? Boolean(value) : value; if (param.rootName) { let mergeObject = {}; mergeObject[param.bodyPath] = _loadValue(param, value); mergeObject = unflatten(mergeObject, BODY_PATH_DELIMITER); res[param.rootName] = R.mergeDeepRight(res[param.rootName], mergeObject); } else { res[param.name] = _loadValue(param, value); } } }); return res; }; const _sdkFunctionName = (swaggerApiOperationName) => `call${swaggerApiOperationName.charAt(0).toUpperCase() + swaggerApiOperationName.slice(1)}`; const _matchSwaggerPattern = (url, swaggerPatternUrl) => { const urlParts = url.split("/"); const swaggerParts = swaggerPatternUrl.split("/"); if (urlParts.length !== swaggerParts.length) return false; const params = {}; for (let i = 0; i < swaggerParts.length; i++) { const swaggerPart = swaggerParts[i]; const urlPart = urlParts[i]; if (swaggerPart.startsWith("{")) { const paramName = swaggerPart.slice(0, -1).substring(1); params[paramName] = urlPart; continue; } if (swaggerPart !== urlPart) { return false; } } return { swaggerPatternUrl, params }; }; const _showCheckStatusHint = (locationHeader, profile) => { const { value } = locationHeader; const [baseUrl, queryStringPart] = value.split("?"); const modelIntrospector = new ModelIntrospector(); const paths = Object.keys(modelIntrospector.modelJson.paths); let swaggerMatch; let statusCheckHintCommand; let parameters = {}; for (const path of paths) { swaggerMatch = _matchSwaggerPattern(baseUrl, path); if (swaggerMatch) break; } if (swaggerMatch && modelIntrospector.modelJson.paths[swaggerMatch.swaggerPatternUrl].get) { const operationName = modelIntrospector.modelJson.paths[swaggerMatch.swaggerPatternUrl].get["x-operation-name"]; const processor = new CliCustomizationProcessor(); const baseCliCommand = processor.processOperationName(operationName); const queryStringParameters = queryString.parse(queryStringPart); parameters = { ...swaggerMatch.params, ...queryStringParameters }; const cliParams = Object.keys(parameters) .map((key) => { const customization = customizationMap.get(key); if (customization && customization.skip) return ""; // mapping custom command option if exists const commandOption = kebabCase(apiToCommanderMap.get(key) || key); return `--${commandOption} ${parameters[key]}`; }) .join(" ") .trim(); const profileFlag = profile ? ` --profile ${profile}` : ""; statusCheckHintCommand = `ask smapi ${baseCliCommand} ${cliParams}${profileFlag}`; Messenger.getInstance().warn(`This is an asynchronous operation. Check the progress using the following command: ${statusCheckHintCommand}`); } else { Messenger.getInstance().warn(`This is an asynchronous operation. Check the progress using the following url: ${value}`); } }; /** * Parses response from smapi * @param {Object} response object */ const parseSmapiResponse = (response, profile) => { let result = ""; const { body, headers, statusCode } = response; const contentType = headers.find((h) => h.key === "content-type"); const locationHeader = headers.find((h) => h.key === "location"); // json if no content type or content type is application/json const isJson = !contentType || contentType.value.includes("application/json"); if (statusCode === CONSTANTS.HTTP_REQUEST.STATUS_CODE.ACCEPTED && locationHeader) { _showCheckStatusHint(locationHeader, profile); } if (body && Object.keys(body).length) { result = isJson ? jsonView.toString(body) : body; } else { result = "Command executed successfully!"; } return result; }; /** * Handles smapi command request * @param {string} swaggerApiOperationName Swagger operation name. * @param {Array} swaggerParams Parameters for operation from the Swagger model. * @param {Map} flatParamsMap Flattened parameters. * @param {Map} commanderToApiCustomizationMap Map of commander options to custom options * for api properties. * @param {Boolean} doDebug * @param {Object} cmdObj Commander object with passed values. */ const smapiCommandHandler = (swaggerApiOperationName, flatParamsMap, commanderToApiCustomizationMap, inputCmdObj, modelIntrospector) => { new AppConfig(); const inputOpts = inputCmdObj.opts(); const authorizationController = new AuthorizationController({ auth_client_type: "LWA", doDebug: inputOpts.debug, }); const profile = profileHelper.runtimeProfile(inputOpts.profile); const refreshTokenConfig = { clientId: authorizationController.oauthClient.config.clientId, clientSecret: authorizationController.oauthClient.config.clientConfirmation, refreshToken: AppConfig.getInstance().getToken(profile).refresh_token, }; const authEndpoint = DynamicConfig.lwaTokenHost; const smapiEndpoint = DynamicConfig.smapiBaseUrl; const client = new CustomSmapiClientBuilder() .withAuthEndpoint(authEndpoint) .withApiEndpoint(smapiEndpoint) .withRefreshTokenConfig(refreshTokenConfig) .withCustomUserAgent(DynamicConfig.userAgent) .client(); const paramsObject = _mapToParams(inputOpts, flatParamsMap, commanderToApiCustomizationMap); const beforeSendProcessor = new BeforeSendProcessor(inputCmdObj._name, paramsObject, modelIntrospector, profile); beforeSendProcessor.processAll(); const functionName = _sdkFunctionName(swaggerApiOperationName); const params = getParamNames(client[functionName]); const args = _mapToArgs(params, paramsObject); if (inputOpts.debug) { const payload = {}; params.forEach((k, i) => { payload[k] = args[i]; }); Messenger.getInstance().info(`Operation: ${swaggerApiOperationName}`); Messenger.getInstance().info("Payload:"); Messenger.getInstance().info(`${jsonView.toString(payload)}\n`); } return client[functionName](...args).then((response) => { const { body, headers, statusCode } = response; let result = ""; if (inputOpts.debug) { Messenger.getInstance().info("Response:"); Messenger.getInstance().info(jsonView.toString({ body, headers, statusCode })); } else if (inputOpts.fullResponse) { result = jsonView.toString({ body, headers, statusCode }); } else { result = parseSmapiResponse(response, profile); } return result; }); }; module.exports = { smapiCommandHandler, parseSmapiResponse };