ask-cli-x
Version:
Alexa Skills Kit (ASK) Command Line Interfaces
163 lines (162 loc) • 7.74 kB
JavaScript
;
const path = require("path");
const CONSTANTS = require("../../utils/constants");
const BUILT_IN_DEPLOY_DELEGATES = {
[CONSTANTS.DEPLOYER_TYPE.CFN.NAME]: {
location: "cfn-deployer",
description: CONSTANTS.DEPLOYER_TYPE.CFN.DESCRIPTION,
templates: {
branch: "ask-cli-x",
language_map: {
NodeJS: "https://ask-cli-static-content.s3-us-west-2.amazonaws.com/skill-templates/NodeJS/cfn-deployer-templates.json",
Python: "https://ask-cli-static-content.s3-us-west-2.amazonaws.com/skill-templates/Python/cfn-deployer-templates.json",
Java: "https://ask-cli-static-content.s3-us-west-2.amazonaws.com/skill-templates/Java/cfn-deployer-templates.json",
},
},
},
[CONSTANTS.DEPLOYER_TYPE.LAMBDA.NAME]: {
location: "lambda-deployer",
description: CONSTANTS.DEPLOYER_TYPE.LAMBDA.DESCRIPTION,
templates: {
branch: "ask-cli-x",
language_map: {
NodeJS: "https://ask-cli-static-content.s3-us-west-2.amazonaws.com/skill-templates/NodeJS/lambda-deployer-templates.json",
Python: "https://ask-cli-static-content.s3-us-west-2.amazonaws.com/skill-templates/Python/lambda-deployer-templates.json",
Java: "https://ask-cli-static-content.s3-us-west-2.amazonaws.com/skill-templates/Java/lambda-deployer-templates.json",
},
},
},
};
/**
* Class for 3p DeployDelegate which implements the contract between ask-cli and its deploy delegate.
*
* Each deploy delegate contains the following methods to implement as its interface:
* - bootstrap initiate ask-cli's ask-resources config with custom skill infrastructure logic
* input: options.codeProperty { runtime, handler }
* options.workspacePath workspace dedicated to the deploy delegate, usually it's under "{skillProj}/infrastructures/{type}"
* options.userConfig the initial userConfig in the infrastructure setting
* return: error any error during the bootstrap
* result.userConfig updated userConfig after bootstrap
*
* - invoke trigger the actual deploy logic inside the deploy delegate which will pass back the deploy state if the process succeeds
* input: options.profile ask-cli profile
* options.alexaRegion alexa region, i.e. default, NA, EU, FE
* options.skillId
* options.skillName
* options.code the code config from ask-resources for a certain alexa region
* options.userConfig the userConfig from ask-resources config
* options.deployState the deployState from ask-resources config
* return: error any error during the invoke
* result.endpoint the endpoint field following the definition of skill.json, usually contains the url for the endpoint
* result.deployState the result of deploy to be tracked in ask-resources config
*/
class DeployDelegate {
/**
* Constructor for wrapping the deploy delegate instance to enforce its contract with ask-cli.
* @param {String} type the deploy delegate type name
* @param {Object} instance the instance for the deploy delegate
*/
constructor(type, instance) {
this.type = type;
this.instance = instance;
this._validateDeployDelegateMethods();
}
/**
* Wrapper of deployDelegate's bootstrap method
* @param {Function} callback
*/
bootstrap(options, callback) {
if (!this.instance) {
return callback("[Fatal]: Please instantiate the DeployDelegate class before using.");
}
this.instance.bootstrap(options, callback);
}
/**
* Wrapper of deployDelegate's invoke method
* @param {Object} reporter upstream CLI status reporter
* @param {Object} options Invoke options { profile, alexaRegion, skillId, skillName, code, userConfig, deployState }
* @param {Function} callback
*/
invoke(reporter, options, callback) {
if (!this.instance) {
return callback("[Fatal]: Please instantiate the DeployDelegate class before using.");
}
this.instance.invoke(reporter, options, callback);
}
/**
* Validate if the response from DeployDelegate is following the contract between ask-cli and DeployDelegate.
* @param {Object} deployResult the result from deployDelegate's invoke.
*/
validateDeployDelegateResponse(deployResult) {
if (!deployResult) {
throw new Error("[Error]: Deploy result should not be empty.");
}
let error = null;
for (const region of Object.keys(deployResult)) {
if (!deployResult[region].endpoint) {
error = '[Error]: Invalid response from deploy delegate. "endpoint" field must exist in the response.';
break;
}
if (!deployResult[region].endpoint.uri) {
error = '[Error]: Invalid response from deploy delegate. "uri" field must exist in the "endpoint" field in the response.';
break;
}
if (!deployResult[region].deployState) {
error = '[Error]: Invalid response from deploy delegate. "deployState" field must exist in the response.';
break;
}
}
if (error) {
throw new Error(error);
}
}
/**
* Validate if the instance of DeployDelegate has required method.
*/
_validateDeployDelegateMethods() {
if (!this.instance) {
throw new Error("[Error]: Invalid deploy delegate. Failed to load the target module.");
}
if (!this.instance.bootstrap || typeof this.instance.bootstrap !== "function") {
throw new Error('[Error]: Invalid deploy delegate. The class of the deploy delegate must contain "bootstrap" method.');
}
if (!this.instance.invoke || typeof this.instance.invoke !== "function") {
throw new Error('[Error]: Invalid deploy delegate. The class of the deploy delegate must contain "invoke" method.');
}
}
}
/**
* Factory method to load DeployDelegate class in the managed way
* Supported built-ins:
* @ask-cli/cfn-deployer
*
* @param {String} type deploy delegate type
* @param {Function} callback (error, deployDelegateInstance)
*/
function loadDeployDelegate(type, callback) {
if (typeof type === "string" && BUILT_IN_DEPLOY_DELEGATES[type]) {
let dd;
const builtinLocation = BUILT_IN_DEPLOY_DELEGATES[type].location;
try {
const modulePath = path.join(__dirname, "..", "..", "builtins", "deploy-delegates", builtinLocation);
dd = new DeployDelegate(type, require(modulePath)); // eslint-disable-line global-require
}
catch (loadErr) {
return process.nextTick(() => {
callback(`[Error]: Built-in skill infrastructure type "${type}" failed to load.\n${loadErr}`);
});
}
callback(null, dd);
}
else if (type === "custom") {
// TODO support custom script type deploy delegate
}
else {
process.nextTick(() => {
callback(`[Error]: Skill infrastructure type "${type}" is not recognized by ask-cli.`);
});
}
}
module.exports = DeployDelegate;
module.exports.load = loadDeployDelegate;
module.exports.builtin = BUILT_IN_DEPLOY_DELEGATES;