Skip to content

Commit

Permalink
feat: hosted skill create + clone
Browse files Browse the repository at this point in the history
  • Loading branch information
Chih-Ying committed Mar 23, 2020
1 parent 3dca627 commit 25bac7e
Show file tree
Hide file tree
Showing 23 changed files with 2,041 additions and 900 deletions.
48 changes: 48 additions & 0 deletions lib/clients/smapi-client/resources/alexa-hosted.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ const EMPTY_QUERY_PARAMS = {};
const NULL_PAYLOAD = null;

module.exports = (smapiHandle) => {
/**
* To submit a skill creation request for a specified vendorId
* @param {Object} manifest The JSON representation of the skill, and provides Alexa with all of the metadata required
* @param {callback} callback { error, response }
*/
function createHostedSkill(manifest, callback) {
const url = 'skills/';
const runtime = CONSTANTS.HOSTED_SKILL.DEFAULT_RUNTIME[manifest.runtime];
const payload = {
vendorId: manifest.vendorId,
manifest: manifest.manifest,
hosting: {
alexaHosted: {
runtime
}
}
};

smapiHandle(
CONSTANTS.SMAPI.API_NAME.CREATE_HOSTED_SKILL,
CONSTANTS.HTTP_REQUEST.VERB.POST,
CONSTANTS.SMAPI.VERSION.V1,
url,
EMPTY_QUERY_PARAMS,
EMPTY_HEADERS,
payload,
callback
);
}

/**
* To get information about an Alexa-hosted skill
* @param {string} skillId The skill Id
* @param {callback} callback { error, response }
*/
function getAlexaHostedSkillMetadata(skillId, callback) {
const url = `skills/${skillId}/alexaHosted`;

Expand All @@ -20,6 +55,12 @@ module.exports = (smapiHandle) => {
);
}

/**
* To obtain the skill credentials for the specified skill
* @param {string} skillId The skill Id
* @param {string} repoUrl repository url
* @param {callback} callback { error, response }
*/
function getGitCredentials(skillId, repoUrl, callback) {
const url = `skills/${skillId}/alexaHosted/repository/credentials/generate`;

Expand All @@ -42,6 +83,12 @@ module.exports = (smapiHandle) => {
);
}

/**
* To access a permission to an Alexa hosted skill
* @param {string} vendorId The vendor Id
* @param {string} permissionType The permission type
* @param {callback} callback { error, response }
*/
function getHostedSkillPermission(vendorId, permissionType, callback) {
const url = `vendors/${vendorId}/alexaHosted/permissions/${permissionType}`;

Expand All @@ -58,6 +105,7 @@ module.exports = (smapiHandle) => {
}

return {
createHostedSkill,
getAlexaHostedSkillMetadata,
getGitCredentials,
getHostedSkillPermission
Expand Down
224 changes: 65 additions & 159 deletions lib/commands/v2new/helper.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
const path = require('path');
const fs = require('fs-extra');
const path = require('path');

const gitClient = require('@src/clients/git-client');
const httpClient = require('@src/clients/http-client');
const ResourcesConfig = require('@src/model/resources-config');
const Manifest = require('@src/model/manifest');
const SkillInfrastructureController = require('@src/controllers/skill-infrastructure-controller');
const DeployDelegate = require('@src/controllers/skill-infrastructure-controller/deploy-delegate');
const Messenger = require('@src/view/messenger');
const urlUtils = require('@src/utils/url-utils');
const stringUtils = require('@src/utils/string-utils');
const Manifest = require('@src/model/manifest');
const ResourcesConfig = require('@src/model/resources-config');
const CONSTANTS = require('@src/utils/constants');
const stringUtils = require('@src/utils/string-utils');

const ui = require('./ui');

module.exports = {
initializeDeployDelegate,
downloadTemplateFromGit,
loadSkillProjectModel,
updateSkillProjectWithUserSettings,
bootstrapProject,
newWithOfficialTemplate,
newWithCustomTemplate,
downloadTemplateFromGit
bootstrapProject
};

/**
* Validate if ask-resources config and skill.json exist in the skill pacakge template
* To initialize Deploy Engine or not by selected deployment type
* @param {String} deploymentType the deployer type
* @param {String} infrastructurePath the root path for current deploy delegate's files in skill's project
* @param {String} profile ask-cli profile
* @param {Boolean} doDebug
* @param {callback} callback { error, ddType } return the selected deploy delegate type
*/
function initializeDeployDelegate(deploymentType, projectFolderPath, profile, doDebug, callback) {
if (deploymentType === ui.SKIP_DEPLOY_DELEGATE_SELECTION) {
return callback();
}
const infrastructurePath = path.join(projectFolderPath, CONSTANTS.FILE_PATH.SKILL_INFRASTRUCTURE.INFRASTRUCTURE);
bootstrapProject(deploymentType, infrastructurePath, profile, doDebug, (bootstrapErr) => {
if (bootstrapErr) {
return callback(bootstrapErr);
}
const ddType = ResourcesConfig.getInstance().getSkillInfraType(profile);
callback(null, ddType);
});
}

/**
* Download the template from git
* @param {Object} userInput user input initialization setting
* @param {Function} callback (error, projectFolderPath)
*/
function downloadTemplateFromGit(userInput, callback) {
const projectFolderPath = path.join(process.cwd(), userInput.projectFolderName);
gitClient.clone(userInput.templateInfo.templateUrl, CONSTANTS.TEMPLATES.TEMPLATE_BRANCH_NAME, projectFolderPath, (cloneErr) => {
if (cloneErr) {
return callback(cloneErr);
}
callback(null, projectFolderPath);
});
}

/**
* Validate if ask-resources config and skill.json exist in the skill package template
* @param {String} projectPath path for the skill project
* @param {String} profile ask-cli profile
*/
Expand All @@ -49,9 +82,9 @@ function loadSkillProjectModel(projectPath, profile) {
* Filter the downloaded skill project by
* 1.Remove the .git folder to avoid obfuscated git history
* 2.Update skill name in the skill.json
* @param {String} skillName
* @param {String} projectPath
* @param {String} profile
* @param {String} skillName the skill name
* @param {String} projectPath the project file path
* @param {String} profile ask-cli profile
*/
function updateSkillProjectWithUserSettings(skillName, projectPath, profile) {
// update skill name
Expand All @@ -66,155 +99,28 @@ function updateSkillProjectWithUserSettings(skillName, projectPath, profile) {
}

/**
* Ask users' choices for deploy delegate, and then trigger the bootstrap process from the deploy delegate
* Trigger the bootstrap process from the selected deploy delegate
* @param {String} infrastructurePath the root path for current deploy delegate's files in skill's project
* @param {String} profile ask-cli profile
* @param {Boolean} doDebug
* @param {Function} callback (error)
*/
function bootstrapProject(infrastructurePath, profile, doDebug, callback) {
// 1.Ask user for the type of deploy delegate
ui.getDeployDelegateType(DeployDelegate.builtin, (getDDErr, deployDelegateType) => {
if (getDDErr) {
return callback(getDDErr);
}
if (deployDelegateType === ui.SKIP_DEPLOY_DELEGATE_SELECTION) {
return callback();
}
// 2.Initiate ask-resources config for skillInfrastructure field
const ddFolderPath = deployDelegateType.startsWith('@ask-cli/') ? deployDelegateType.replace('@ask-cli/', '') : deployDelegateType;
const workspacePath = path.join(infrastructurePath, stringUtils.filterNonAlphanumeric(ddFolderPath));
fs.ensureDirSync(workspacePath);
ResourcesConfig.getInstance().setSkillInfra(profile, {
type: deployDelegateType,
userConfig: ResourcesConfig.getInstance().getSkillInfraUserConfig(profile),
deployState: {}
});
// 3.Bootstrap skill project with deploy delegate logic
const skillInfraController = new SkillInfrastructureController({ profile, doDebug });
skillInfraController.bootstrapInfrastructures(workspacePath, (bootstrapErr) => {
if (bootstrapErr) {
return callback(bootstrapErr);
}
callback();
});
});
}

/**
* Create new skill templates by downloading from official provisions
* @param {Boolean} doDebug
* @param {Function} callback (error, userInput{ skillName, projectFolderPath })
*/
function newWithOfficialTemplate(doDebug, callback) {
ui.selectSkillCodeLanguage((languageErr, language) => {
if (languageErr) {
return callback(languageErr);
}
const templateIndexUrl = CONSTANTS.TEMPLATES.LANGUAGE_MAP[language].TEMPLATE_INDEX;
_retrieveTemplateIndexMap(templateIndexUrl, doDebug, (httpErr, templateIndexMap) => {
if (httpErr) {
return callback(httpErr);
}
ui.getTargetTemplateName(templateIndexMap, (templateNameErr, templateName) => {
if (templateNameErr) {
return callback(templateNameErr);
}
const templateUrl = templateIndexMap[templateName].url;
module.exports.downloadTemplateFromGit(templateName, templateUrl, (downloadErr, userInput) => {
if (downloadErr) {
return callback(downloadErr);
}
callback(null, userInput);
});
});
});
function bootstrapProject(deployDelegateType, infrastructurePath, profile, doDebug, callback) {
// 1.Initiate ask-resources config for skillInfrastructure field
const ddFolderPath = deployDelegateType.startsWith('@ask-cli/') ? deployDelegateType.replace('@ask-cli/', '') : deployDelegateType;
const workspacePath = path.join(infrastructurePath, stringUtils.filterNonAlphanumeric(ddFolderPath));
fs.ensureDirSync(workspacePath);
ResourcesConfig.getInstance().setSkillInfra(profile, {
type: deployDelegateType,
userConfig: ResourcesConfig.getInstance().getSkillInfraUserConfig(profile),
deployState: {}
});
}

/**
* Create new skill project using the template provided by custom repository url
* @param {String} templateUrl url string for the custom template
* @param {Boolean} doDebug
* @param {Function} callback (error, userInput{ skillName, projectFolderPath })
*/
function newWithCustomTemplate(templateUrl, doDebug, callback) {
if (urlUtils.isUrlWithGitExtension(templateUrl)) {
_confirmDownloadIfNotOfficialTemplate(templateUrl, (confirmErr, confirmResult) => {
if (confirmErr) {
return callback(confirmErr);
}
if (!confirmResult) {
return callback();
}
module.exports.downloadTemplateFromGit(null, templateUrl, (gitErr, userInput) => {
if (gitErr) {
return callback(gitErr);
}
callback(null, userInput);
});
});
} else {
process.nextTick(() => {
callback(`[Error]: The provided template url ${templateUrl} is not a supported type. \
We currently only support ".git" url for user's custom template.`);
});
}
}

/**
* Common method to download the template from git
* @param {String} templateName name of the template if it's offical repository
* @param {String} templateUrl url for the template to download from
* @param {Function} callback (error, userInput{ skillName, projectFolderPath })
*/
function downloadTemplateFromGit(templateName, templateUrl, callback) {
ui.getSkillName(templateUrl, (getSkillNameErr, skillName) => {
if (getSkillNameErr) {
return callback(getSkillNameErr);
// 2.Bootstrap skill project with deploy delegate logic
const skillInfraController = new SkillInfrastructureController({ profile, doDebug });
skillInfraController.bootstrapInfrastructures(workspacePath, (bootstrapErr) => {
if (bootstrapErr) {
return callback(bootstrapErr);
}
const suggestedProjectName = stringUtils.filterNonAlphanumeric(templateName) || stringUtils.filterNonAlphanumeric(skillName);
ui.getProjectFolderName(suggestedProjectName, (getFolderNameErr, folderName) => {
if (getFolderNameErr) {
return callback(getFolderNameErr);
}
const projectFolderPath = path.join(process.cwd(), folderName);
gitClient.clone(templateUrl, CONSTANTS.TEMPLATES.TEMPLATE_BRANCH_NAME, projectFolderPath, (cloneErr) => {
if (cloneErr) {
return callback(cloneErr);
}
callback(null, { skillName, projectFolderPath });
});
});
});
}

function _retrieveTemplateIndexMap(templateIndexUrl, doDebug, callback) {
const params = {
url: templateIndexUrl,
method: CONSTANTS.HTTP_REQUEST.VERB.GET
};
httpClient.request(params, 'getTemplatesMap', doDebug, (error, response) => {
if (error || !response.statusCode || response.statusCode !== 200) {
return callback('[Error]: Failed to retrieve the template list. Please run again with --debug to check more details.');
}
let templateIndexMap = response.body;
if (typeof response.body === 'string') {
templateIndexMap = JSON.parse(response.body);
}
callback(null, templateIndexMap);
});
}

function _confirmDownloadIfNotOfficialTemplate(templateUrl, callback) {
if (urlUtils.isUrlOfficialTemplate(templateUrl)) {
return process.nextTick(() => {
callback(null, true);
});
}
Messenger.getInstance().warn(`CLI is about to download the skill template from unofficial template ${templateUrl}. \
Please make sure you understand the source code to best protect yourself from malicious usage.`);
ui.confirmUsingUnofficialTemplate((confirmErr, confirmResult) => {
callback(confirmErr, confirmErr ? null : confirmResult);
callback();
});
}
Loading

0 comments on commit 25bac7e

Please sign in to comment.