UNPKG

8 kBJavaScriptView Raw
1const path = require('path');
2const R = require('ramda');
3
4const SmapiClient = require('@src/clients/smapi-client');
5const { AbstractCommand } = require('@src/commands/abstract-command');
6const optionModel = require('@src/commands/option-model');
7const HostedSkillController = require('@src/controllers/hosted-skill-controller');
8const CliWarn = require('@src/exceptions/cli-warn');
9const CONSTANTS = require('@src/utils/constants');
10const profileHelper = require('@src/utils/profile-helper');
11const stringUtils = require('@src/utils/string-utils');
12const jsonView = require('@src/view/json-view');
13const Messenger = require('@src/view/messenger');
14
15const helper = require('./helper');
16const ui = require('./ui');
17
18class InitCommand extends AbstractCommand {
19 name() {
20 return 'init';
21 }
22
23 description() {
24 return 'setup a new or existing Alexa skill project';
25 }
26
27 requiredOptions() {
28 return [];
29 }
30
31 optionalOptions() {
32 return ['hosted-skill-id', 'profile', 'debug'];
33 }
34
35 handle(cmd, cb) {
36 let profile;
37 try {
38 profile = profileHelper.runtimeProfile(cmd.profile);
39 } catch (err) {
40 Messenger.getInstance().error(err);
41 return cb(err);
42 }
43
44 const rootPath = process.cwd();
45 if (!cmd.hostedSkillId) {
46 initNonHostedSkill(rootPath, cmd, profile, (nhsErr) => {
47 cb(nhsErr);
48 });
49 } else {
50 initAlexaHostedSkill(rootPath, cmd, profile, (ahsErr) => {
51 cb(ahsErr);
52 });
53 }
54 }
55}
56
57function initAlexaHostedSkill(rootPath, cmd, profile, cb) {
58 const smapiClient = new SmapiClient({ profile, doDebug: cmd.debug });
59 const hostedSkillController = new HostedSkillController({ profile, doDebug: cmd.debug });
60 _getSkillName(smapiClient, cmd.hostedSkillId, (nameErr, skillName) => {
61 if (nameErr) {
62 Messenger.getInstance().error(nameErr);
63 return cb(nameErr);
64 }
65 _confirmProjectFolderName(skillName, (confirmErr, folderName) => {
66 if (confirmErr) {
67 Messenger.getInstance().error(confirmErr);
68 return cb(confirmErr);
69 }
70 const projectPath = path.join(rootPath, folderName);
71 hostedSkillController.clone(cmd.hostedSkillId, skillName, projectPath, (cloneErr) => {
72 if (cloneErr) {
73 Messenger.getInstance().error(cloneErr);
74 return cb(cloneErr);
75 }
76 const templateUrl = CONSTANTS.HOSTED_SKILL.GIT_HOOKS_TEMPLATES.PRE_PUSH.URL;
77 const filePath = path.join(folderName, '.git', 'hooks', 'pre-push');
78 hostedSkillController.downloadGitHooksTemplate(templateUrl, filePath, (hooksErr) => {
79 if (hooksErr) {
80 Messenger.getInstance().error(hooksErr);
81 return cb(hooksErr);
82 }
83 Messenger.getInstance().info(`\n${skillName} successfully initialized.\n`);
84 cb();
85 });
86 });
87 });
88 });
89}
90
91function initNonHostedSkill(rootPath, cmd, profile, cb) {
92 helper.preInitCheck(rootPath, profile, (preCheckErr) => {
93 if (preCheckErr) {
94 if (preCheckErr instanceof CliWarn) {
95 Messenger.getInstance().warn(preCheckErr.message);
96 } else {
97 Messenger.getInstance().error(preCheckErr);
98 }
99 return cb(preCheckErr);
100 }
101 _collectAskResources((inputErr, userInput) => {
102 if (inputErr) {
103 Messenger.getInstance().error(inputErr);
104 return cb(inputErr);
105 }
106 helper.previewAndWriteAskResources(rootPath, userInput, profile, (previewErr) => {
107 if (previewErr) {
108 if (previewErr instanceof CliWarn) {
109 Messenger.getInstance().warn(previewErr.message);
110 } else {
111 Messenger.getInstance().error(previewErr);
112 }
113 return cb(previewErr);
114 }
115 helper.bootstrapSkillInfra(rootPath, profile, cmd.debug, (postErr) => {
116 if (postErr) {
117 Messenger.getInstance().error(postErr);
118 return cb(postErr);
119 }
120 Messenger.getInstance().info('\nSuccess! Run "ask deploy" to deploy your skill.');
121 cb();
122 });
123 });
124 });
125 });
126}
127
128/**
129 * List of QAs to collect users' ask-resources configurations
130 * @param {Function} callback (err, userInput)
131 * userInput { skillId, skillMeta, skillCode, skillInfra } each resource object has the same structure as ask-resources config
132 */
133function _collectAskResources(callback) {
134 helper.getSkillIdUserInput((skillIdErr, skillId) => {
135 if (skillIdErr) {
136 return callback(skillIdErr);
137 }
138 helper.getSkillMetadataUserInput((metaErr, skillMeta) => {
139 if (metaErr) {
140 return callback(metaErr);
141 }
142 helper.getSkillCodeUserInput((codeErr, skillCode) => {
143 if (codeErr) {
144 return callback(codeErr);
145 }
146 if (!skillCode) {
147 // return to skip skillInfra setting if skill code is not provided
148 return callback(null, { skillId, skillMeta });
149 }
150 helper.getSkillInfraUserInput((infraErr, skillInfra) => {
151 if (infraErr) {
152 return callback(infraErr);
153 }
154 callback(null, { skillId, skillMeta, skillCode, skillInfra });
155 });
156 });
157 });
158 });
159}
160
161/**
162 * To get skill name by calling skill's getManifest api
163 * @param {Object} smapiClient SMAPI client to make request
164 * @param {string} skillId The skill ID
165 * @param {callback} callback { error, response }
166 */
167function _getSkillName(smapiClient, skillId, callback) {
168 _getSkillManifest(smapiClient, skillId, (manifestErr, manifest) => {
169 if (manifestErr) {
170 return callback(manifestErr);
171 }
172 const locales = R.view(R.lensPath(['manifest', 'publishingInformation', 'locales']), manifest);
173 if (!locales) {
174 return callback('No skill name found.');
175 }
176 const name = locales['en-US'] ? locales['en-US'].name : Object.keys(locales)[0].name;
177 callback(null, name);
178 });
179}
180
181/**
182 * To call getManifest api and return skill manifest
183 * @param {Object} smapiClient SMAPI client to make request
184 * @param {string} skillId The skill ID
185 * @param {callback} callback { error, response }
186 */
187function _getSkillManifest(smapiClient, skillId, callback) {
188 smapiClient.skill.manifest.getManifest(skillId, CONSTANTS.SKILL.STAGE.DEVELOPMENT, (err, res) => {
189 if (err) {
190 return callback(err);
191 }
192 if (res.statusCode >= 300) {
193 const error = jsonView.toString(res.body);
194 return callback(error);
195 }
196 callback(null, res.body);
197 });
198}
199
200/**
201 * To confirm the project folder name with users,
202 * the default folder name is generated from the skillName
203 * @param {string} skillName The skill name
204 * @param {string} callback callback { error, response }
205 */
206function _confirmProjectFolderName(skillName, callback) {
207 const suggestedProjectName = stringUtils.filterNonAlphanumeric(skillName);
208 ui.getProjectFolderName(suggestedProjectName, (getFolderNameErr, folderName) => {
209 if (getFolderNameErr) {
210 return callback(getFolderNameErr);
211 }
212 callback(null, folderName);
213 });
214}
215
216module.exports = InitCommand;
217module.exports.createCommand = new InitCommand(optionModel).createCommand();