ask-cli
Version:
Alexa Skills Kit (ASK) Command Line Interfaces
151 lines (150 loc) • 7.04 kB
JavaScript
;
const fs = require("fs-extra");
const path = require("path");
const R = require("ramda");
const async = require("async");
const httpClient = require("../../../clients/http-client");
const SkillMetadataController = require("../../../controllers/skill-metadata-controller");
const CliError = require("../../../exceptions/cli-error");
const ResourcesConfig = require("../../../model/resources-config");
const Manifest = require("../../../model/manifest");
const stringUtils = require("../../../utils/string-utils");
const CONSTANTS = require("../../../utils/constants");
module.exports = {
initiateModels,
addLocales,
};
/**
* Start with loading the project Models global instances.
* @param {String} profile
*/
function initiateModels(profile) {
new ResourcesConfig(path.join(process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG));
const skillPackageSrc = ResourcesConfig.getInstance().getSkillMetaSrc(profile);
if (!stringUtils.isNonBlankString(skillPackageSrc)) {
throw new CliError("Skill package src is not found in ask-resources.json.");
}
if (!fs.existsSync(skillPackageSrc)) {
throw new CliError(`The skillMetadata src file ${skillPackageSrc} does not exist.`);
}
const manifestPath = path.join(skillPackageSrc, CONSTANTS.FILE_PATH.SKILL_PACKAGE.MANIFEST);
new Manifest(manifestPath);
}
/**
* Add locales by deciding where to get the source of interaction models,
* and commit the copy paste from the source to the target location.
*
* @param {Array} selectedLocales
* @param {String} profile
* @param {Boolean} doDebug
* @param {Function} callback
*/
function addLocales(selectedLocales, profile, doDebug, callback) {
// analyze all the selected locales, whether copy from local existing files or get from remote templates
_getNewLocaleModelUri(selectedLocales, profile, doDebug, (resolveErr, iModelSourceByLocales) => {
if (resolveErr) {
return callback(resolveErr);
}
const iModelFolderPath = path.join(process.cwd(), ResourcesConfig.getInstance().getSkillMetaSrc(profile), CONSTANTS.FILE_PATH.SKILL_PACKAGE.INTERACTION_MODEL, "custom");
const remoteTemplateSet = new Set([...iModelSourceByLocales.values()].reduce((acc, cur) => {
if (!cur.canCopy) {
acc.push(cur.uri);
}
return acc;
}, []));
// retrieve remote templates when no existing JSON shares the same language
_retrieveTemplatesByLanguage(remoteTemplateSet, doDebug, (templateErr, templateByLanguage) => {
if (templateErr) {
return callback(templateErr);
}
// copy iModel JSONs that contains same language
iModelSourceByLocales.forEach((v, k) => {
if (v.canCopy) {
const sourceLocale = path.basename(v.uri, path.extname(v.uri));
const targetFilePath = path.join(iModelFolderPath, `${k}.json`);
fs.copySync(v.uri, targetFilePath); // do not fail if the target locale exists already
const sourceManifestLocale = Manifest.getInstance().getPublishingLocale(sourceLocale);
Manifest.getInstance().setPublishingLocale(k, sourceManifestLocale);
}
else {
const targetFilePath = path.join(iModelFolderPath, `${k}.json`);
fs.writeFileSync(targetFilePath, templateByLanguage.get(v.uri), { encoding: "utf-8" });
Manifest.getInstance().setPublishingLocale(k, { name: "please change" });
}
});
Manifest.getInstance().write();
callback(undefined, iModelSourceByLocales);
});
});
}
/**
* Based on user's selected locales, and the local project's iModel files, decide where to get the
* iModel file to add in user's local project. Exclude reusableLocales themselves from the result.
*
* @param {Array} selectedLocales
* @param {String} profile
* @param {Boolean} doDebug
* @returns {Function} callback Map { locale: { uri, canCopy} } where the uri is filePath or remoteURI
*/
function _getNewLocaleModelUri(selectedLocales, profile, doDebug, callback) {
const skillMetaController = new SkillMetadataController({ profile, doDebug });
const localIModelByLocale = skillMetaController.getInteractionModelLocales();
const result = new Map();
// get from map of templates
httpClient.request({
url: CONSTANTS.TEMPLATES.INTERACTION_MODEL_MAP,
method: CONSTANTS.HTTP_REQUEST.VERB.GET,
}, "GET_INTERACTION_MODEL_MAP", doDebug, (error, templateMapResponse) => {
if (error) {
return callback(`Failed to retrieve the interaction model map template list.\nError: ${JSON.stringify(error.errorMessage || error, null, 2)}`);
}
const templateIndexMap = templateMapResponse.body;
// assign for each locale
for (const locale of selectedLocales) {
if (R.keys(localIModelByLocale).includes(locale)) {
continue;
}
const languageExtractor = (s) => R.slice(0, R.lastIndexOf("-", s), s);
const language = languageExtractor(locale);
const reusableLocale = R.find((k) => languageExtractor(k) === language)(R.keys(localIModelByLocale));
if (reusableLocale) {
result.set(locale, { uri: localIModelByLocale[reusableLocale], canCopy: true });
}
else {
result.set(locale, { uri: templateIndexMap.INTERACTION_MODEL_BY_LANGUAGE[language], canCopy: false });
}
}
callback(undefined, result);
});
}
/**
* Retrieve the template content from the url link.
* @param {Set} templateSet Set of template URLs to get the content for
* @param {Boolean} doDebug
* @param {Function} callback Map { templateUrl: bodyContent }
*/
function _retrieveTemplatesByLanguage(templateSet, doDebug, callback) {
const result = new Map();
if (templateSet.size === 0) {
return process.nextTick(() => {
callback(undefined, result);
});
}
async.forEach(templateSet.values(), (templateUrl, loopCallback) => {
httpClient.request({
url: templateUrl,
method: CONSTANTS.HTTP_REQUEST.VERB.GET,
}, "GET_INTERACTION_MODEL_TEMPLATE", doDebug, (error, response) => {
if (error) {
if (error.statusCode > 300) {
return loopCallback(`Failed to retrieve the template list, please see the details from the error response.\n${JSON.stringify(error.errorMessage || error, null, 2)}`);
}
return loopCallback(`Failed to retrieve the template list.\n${error.errorMessage || error}`);
}
result.set(templateUrl, response.body);
loopCallback();
});
}, (error) => {
callback(error, error ? undefined : result);
});
}