1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const fs = tslib_1.__importStar(require("fs-extra"));
|
5 | const path = tslib_1.__importStar(require("path"));
|
6 | const yaml = tslib_1.__importStar(require("yaml"));
|
7 | const base_1 = tslib_1.__importStar(require("../base"));
|
8 | const asyncPipe_1 = require("../utils/asyncPipe");
|
9 | const CustomErrors_1 = require("../errors/CustomErrors");
|
10 | const opConfig_1 = require("../constants/opConfig");
|
11 | const utils_1 = require("../utils");
|
12 | const utils_2 = require("../utils");
|
13 | class Init extends base_1.default {
|
14 | constructor() {
|
15 | super(...arguments);
|
16 | this.questions = [];
|
17 | this.srcDir = path.resolve(__dirname, '../templates/');
|
18 | this.destDir = path.resolve(process.cwd());
|
19 | this.initPrompts = {
|
20 | [utils_1.appendSuffix(opConfig_1.COMMAND, 'Name')]: {
|
21 | type: 'input',
|
22 | name: utils_1.appendSuffix(opConfig_1.COMMAND, 'Name'),
|
23 | message: `\n Provide a name for your new command ${this.ux.colors.reset.green('ā')}\n${this.ux.colors.reset(this.ux.colors.secondary('Names must be lowercase'))}\n\nš· ${this.ux.colors.white('Name:')}`,
|
24 | afterMessage: this.ux.colors.reset.green('ā'),
|
25 | afterMessageAppend: this.ux.colors.reset(' added!'),
|
26 | validate: this._validateName,
|
27 | transformer: input => this.ux.colors.cyan(input.toLocaleLowerCase()),
|
28 | filter: input => input.toLowerCase(),
|
29 | },
|
30 | [utils_1.appendSuffix(opConfig_1.COMMAND, 'Description')]: {
|
31 | type: 'input',
|
32 | name: utils_1.appendSuffix(opConfig_1.COMMAND, 'Description'),
|
33 | message: `\nProvide a description ${this.ux.colors.reset.green('ā')} \nāļø ${this.ux.colors.white('Description:')}`,
|
34 | afterMessage: this.ux.colors.reset.green('ā'),
|
35 | afterMessageAppend: this.ux.colors.reset(' added!'),
|
36 | validate: this._validateDescription,
|
37 | },
|
38 | [utils_1.appendSuffix(opConfig_1.COMMAND, 'Version')]: {
|
39 | type: 'input',
|
40 | name: utils_1.appendSuffix(opConfig_1.COMMAND, 'Version'),
|
41 | message: `\nProvide a version ${this.ux.colors.reset.green('ā')} \nāļø ${this.ux.colors.white('Version:')}`,
|
42 | afterMessage: this.ux.colors.reset.green('ā'),
|
43 | afterMessageAppend: this.ux.colors.reset(' added!'),
|
44 | validate: this._validateVersion,
|
45 | default: '0.1.0',
|
46 | },
|
47 | [utils_1.appendSuffix(opConfig_1.WORKFLOW, 'Name')]: {
|
48 | type: 'input',
|
49 | name: utils_1.appendSuffix(opConfig_1.WORKFLOW, 'Name'),
|
50 | message: `\n Provide a name for your new workflow ${this.ux.colors.reset.green('ā')}\n${this.ux.colors.reset(this.ux.colors.secondary('Names must be lowercase'))}\n\nš· ${this.ux.colors.white('Name:')}`,
|
51 | afterMessage: this.ux.colors.reset.green('ā'),
|
52 | afterMessageAppend: this.ux.colors.reset(' added!'),
|
53 | validate: this._validateName,
|
54 | transformer: input => this.ux.colors.cyan(input.toLocaleLowerCase()),
|
55 | filter: input => input.toLowerCase(),
|
56 | },
|
57 | [utils_1.appendSuffix(opConfig_1.WORKFLOW, 'Description')]: {
|
58 | type: 'input',
|
59 | name: utils_1.appendSuffix(opConfig_1.WORKFLOW, 'Description'),
|
60 | message: `\nProvide a description ${this.ux.colors.reset.green('ā')}\n\nāļø ${this.ux.colors.white('Description:')}`,
|
61 | afterMessage: this.ux.colors.reset.green('ā'),
|
62 | afterMessageAppend: this.ux.colors.reset(' added!'),
|
63 | validate: this._validateDescription,
|
64 | },
|
65 | [utils_1.appendSuffix(opConfig_1.WORKFLOW, 'Version')]: {
|
66 | type: 'input',
|
67 | name: utils_1.appendSuffix(opConfig_1.WORKFLOW, 'Version'),
|
68 | message: `\nProvide a version ${this.ux.colors.reset.green('ā')}\n\nāļø ${this.ux.colors.white('Version:')}`,
|
69 | afterMessage: this.ux.colors.reset.green('ā'),
|
70 | afterMessageAppend: this.ux.colors.reset(' added!'),
|
71 | validate: this._validateVersion,
|
72 | default: '0.1.0',
|
73 | },
|
74 | };
|
75 | this.determineTemplate = async (prompts) => {
|
76 | const { templates } = await this.ux.prompt({
|
77 | type: 'checkbox',
|
78 | name: 'templates',
|
79 | message: `What type of op would you like to create ${this.ux.colors.reset.green('ā')}`,
|
80 | choices: [
|
81 | {
|
82 | name: `${utils_1.titleCase(opConfig_1.COMMAND)} - A template for building commands which can be distributed via The Ops Platform.`,
|
83 | value: opConfig_1.COMMAND,
|
84 | },
|
85 | {
|
86 | name: `${utils_1.titleCase(opConfig_1.WORKFLOW)} - A template for combining many commands into a workflow which can be distributed via The Ops Platform.`,
|
87 | value: opConfig_1.WORKFLOW,
|
88 | },
|
89 | ],
|
90 | afterMessage: `${this.ux.colors.reset.green('ā')}`,
|
91 | validate: input => input.length != 0,
|
92 | });
|
93 | return { prompts, templates };
|
94 | };
|
95 | this.determineQuestions = ({ prompts, templates, }) => {
|
96 |
|
97 | const removeIfNotSelectedTemplate = ([key, _val]) => {
|
98 | return key.includes(templates[0]) || key.includes(templates[1]);
|
99 | };
|
100 | const questions = Object.entries(prompts)
|
101 | .filter(removeIfNotSelectedTemplate)
|
102 | .map(([_key, question]) => question);
|
103 | return { questions, templates };
|
104 | };
|
105 | this.askQuestions = async ({ questions, templates, }) => {
|
106 | const answers = await this.ux.prompt(questions);
|
107 | return { answers, templates };
|
108 | };
|
109 | this.determineInitPaths = ({ answers, templates, }) => {
|
110 | const initParams = Object.assign(Object.assign({}, answers), { templates });
|
111 | const { name } = this.getNameAndDescription(initParams);
|
112 | const sharedDir = `${this.srcDir}/shared`;
|
113 | const destDir = `${this.destDir}/${name}`;
|
114 | const initPaths = { sharedDir, destDir };
|
115 | return { initPaths, initParams };
|
116 | };
|
117 | this.copyTemplateFiles = async ({ initPaths, initParams, }) => {
|
118 | try {
|
119 | const { templates } = initParams;
|
120 | const { destDir, sharedDir } = initPaths;
|
121 | await fs.ensureDir(destDir);
|
122 | await fs.copy(sharedDir, destDir);
|
123 | return { initPaths, initParams };
|
124 | }
|
125 | catch (err) {
|
126 | this.debug('%O', err);
|
127 | throw new CustomErrors_1.CopyTemplateFilesError(err);
|
128 | }
|
129 | };
|
130 | this.customizePackageJson = async ({ initPaths, initParams, }) => {
|
131 | try {
|
132 | const { destDir, sharedDir } = initPaths;
|
133 | const { name, description } = this.getNameAndDescription(initParams);
|
134 | const packageObj = JSON.parse(fs.readFileSync(`${sharedDir}/package.json`, 'utf8'));
|
135 | packageObj.name = name;
|
136 | packageObj.description = description;
|
137 | const newPackageString = JSON.stringify(packageObj, null, 2);
|
138 | fs.writeFileSync(`${destDir}/package.json`, newPackageString);
|
139 | return { initPaths, initParams };
|
140 | }
|
141 | catch (err) {
|
142 | this.debug('%O', err);
|
143 | throw new CustomErrors_1.CouldNotInitializeOp(err);
|
144 | }
|
145 | };
|
146 | this.customizeYaml = async ({ initPaths, initParams, }) => {
|
147 | try {
|
148 | const { destDir } = initPaths;
|
149 |
|
150 | const opsYamlDoc = yaml.parseDocument(fs.readFileSync(`${destDir}/ops.yml`, 'utf8'));
|
151 | await this.customizeOpsYaml(initParams, opsYamlDoc);
|
152 | await this.customizeWorkflowYaml(initParams, opsYamlDoc);
|
153 |
|
154 | Object.keys(opConfig_1.HELP_COMMENTS).forEach(rootKey => {
|
155 | this.addHelpCommentsFor(rootKey, opsYamlDoc);
|
156 | });
|
157 |
|
158 | const newOpsString = opsYamlDoc.toString();
|
159 | fs.writeFileSync(`${destDir}/ops.yml`, newOpsString);
|
160 | return { initPaths, initParams };
|
161 | }
|
162 | catch (err) {
|
163 | this.debug('%O', err);
|
164 | throw new CustomErrors_1.CouldNotInitializeOp(err);
|
165 | }
|
166 | };
|
167 |
|
168 |
|
169 |
|
170 | this.addHelpCommentsFor = (key, yamlDoc) => {
|
171 | const docContents = yamlDoc.contents;
|
172 | const docContentsItems = docContents.items;
|
173 | const configItem = docContentsItems.find(item => {
|
174 | if (!item || !item.key)
|
175 | return;
|
176 | const itemKey = item.key;
|
177 | return itemKey.value === key;
|
178 | });
|
179 |
|
180 | if (configItem &&
|
181 | configItem.value &&
|
182 | configItem.value.type === opConfig_1.YAML_TYPE_STRING &&
|
183 | opConfig_1.HELP_COMMENTS[key]) {
|
184 | configItem.comment = ` ${opConfig_1.HELP_COMMENTS[key]}`;
|
185 | }
|
186 |
|
187 | if (configItem &&
|
188 | configItem.value &&
|
189 | configItem.value.type === opConfig_1.YAML_TYPE_SEQUENCE) {
|
190 |
|
191 | yamlDoc.getIn([key, 0]).items.map(configItem => {
|
192 | const comment = opConfig_1.HELP_COMMENTS[key][configItem.key];
|
193 | if (comment)
|
194 | configItem.comment = ` ${opConfig_1.HELP_COMMENTS[key][configItem.key]}`;
|
195 | });
|
196 | }
|
197 | };
|
198 | this.customizeOpsYaml = async (initParams, yamlDoc) => {
|
199 | const { templates, commandName, commandDescription, commandVersion, } = initParams;
|
200 | if (!templates.includes(opConfig_1.COMMAND)) {
|
201 |
|
202 | yamlDoc.delete('commands');
|
203 | return;
|
204 | }
|
205 | yamlDoc
|
206 |
|
207 | .getIn(['commands', 0])
|
208 | .set('name', `${commandName}:${commandVersion}`);
|
209 |
|
210 | yamlDoc.getIn(['commands', 0]).set('description', commandDescription);
|
211 | };
|
212 | this.customizeWorkflowYaml = async (initParams, yamlDoc) => {
|
213 | const { templates, workflowName, workflowDescription, workflowVersion, } = initParams;
|
214 | if (!templates.includes(opConfig_1.WORKFLOW)) {
|
215 |
|
216 | yamlDoc.delete('workflows');
|
217 | return;
|
218 | }
|
219 | yamlDoc
|
220 |
|
221 | .getIn(['workflows', 0])
|
222 | .set('name', `${workflowName}:${workflowVersion}`);
|
223 |
|
224 | yamlDoc.getIn(['workflows', 0]).set('description', workflowDescription);
|
225 | };
|
226 | this.logMessages = async ({ initPaths, initParams, }) => {
|
227 | const { destDir } = initPaths;
|
228 | const { templates } = initParams;
|
229 | const { name } = this.getNameAndDescription(initParams);
|
230 | this.logSuccessMessage(templates);
|
231 | fs.readdirSync(`${destDir}`).forEach((file) => {
|
232 | let callout = '';
|
233 | if (file.indexOf('index.js') > -1) {
|
234 | callout = `${this.ux.colors.green('ā')} ${this.ux.colors.white('Start developing here!')}`;
|
235 | }
|
236 | let msg = this.ux.colors.italic(`${path.relative(this.destDir, process.cwd())}/${name}/${file} ${callout}`);
|
237 | this.log(`š .${msg}`);
|
238 | });
|
239 | if (templates.includes(opConfig_1.COMMAND)) {
|
240 | this.logCommandMessage(initParams);
|
241 | }
|
242 | if (templates.includes(opConfig_1.WORKFLOW)) {
|
243 | this.logWorkflowMessage(initParams);
|
244 | }
|
245 | return { initPaths, initParams };
|
246 | };
|
247 | this.logCommandMessage = (initParams) => {
|
248 | const { commandName } = initParams;
|
249 | this.log(`\nš To test your ${opConfig_1.COMMAND} run: ${this.ux.colors.green('$')} ${this.ux.colors.callOutCyan(`ops run ${commandName}`)}`);
|
250 | };
|
251 | this.logWorkflowMessage = (initParams) => {
|
252 | const { name } = this.getNameAndDescription(initParams);
|
253 | this.log(`\nš To test your ${opConfig_1.WORKFLOW} run: ${this.ux.colors.green('$')} ${this.ux.colors.callOutCyan(`cd ${name} && npm install && ops run .`)}`);
|
254 | };
|
255 | this.logSuccessMessage = (templates) => {
|
256 | const successMessageBoth = `\nš Success! Your ${opConfig_1.COMMAND} and ${opConfig_1.WORKFLOW} template Ops are ready to start coding... \n`;
|
257 | const getSuccessMessage = (opType) => `\nš Success! Your ${opType} template Op is ready to start coding... \n`;
|
258 | if (templates.includes(opConfig_1.COMMAND) && templates.includes(opConfig_1.WORKFLOW)) {
|
259 | return this.log(successMessageBoth);
|
260 | }
|
261 | const opType = templates.includes(opConfig_1.COMMAND) ? opConfig_1.COMMAND : opConfig_1.WORKFLOW;
|
262 | return this.log(getSuccessMessage(opType));
|
263 | };
|
264 | this.sendAnalytics = async ({ initPaths, initParams, }) => {
|
265 | try {
|
266 | const { destDir } = initPaths;
|
267 | const { templates } = initParams;
|
268 | const { name, description } = this.getNameAndDescription(initParams);
|
269 | this.services.analytics.track({
|
270 | userId: this.user.email,
|
271 | teamId: this.team.id,
|
272 | cliEvent: 'Ops CLI Init',
|
273 | event: 'Ops CLI Init',
|
274 | properties: {
|
275 | name,
|
276 | team: this.team.name,
|
277 | namespace: `@${this.team.name}/${name}`,
|
278 | runtime: 'CLI',
|
279 | email: this.user.email,
|
280 | username: this.user.username,
|
281 | path: destDir,
|
282 | description,
|
283 | templates,
|
284 | },
|
285 | }, this.accessToken);
|
286 | return {
|
287 | initPaths,
|
288 | initParams,
|
289 | };
|
290 | }
|
291 | catch (err) {
|
292 | this.debug('%O', err);
|
293 | throw new CustomErrors_1.AnalyticsError(err);
|
294 | }
|
295 | };
|
296 | this.getNameAndDescription = (initParams) => {
|
297 | return {
|
298 | name: initParams.commandName || initParams.workflowName,
|
299 | description: initParams.commandDescription || initParams.workflowDescription,
|
300 | };
|
301 | };
|
302 | }
|
303 | _validateName(input) {
|
304 | if (input === '')
|
305 | return 'You need name your op before you can continue';
|
306 | if (!input.match('^[a-z0-9_-]*$')) {
|
307 | return 'Sorry, please name the Op using only numbers, letters, -, or _';
|
308 | }
|
309 | return true;
|
310 | }
|
311 | _validateDescription(input) {
|
312 | if (input === '')
|
313 | return 'You need to provide a description of your op before continuing';
|
314 | return true;
|
315 | }
|
316 | _validateVersion(input) {
|
317 | if (input === '')
|
318 | return 'You need to provide a version of your op before continuing';
|
319 | if (!input.match(utils_2.validVersionChars)) {
|
320 | return `Sorry, version can only contain letters, digits, underscores, periods and dashes\nand must start and end with a letter or a digit`;
|
321 | }
|
322 | return true;
|
323 | }
|
324 | async run() {
|
325 | this.parse(Init);
|
326 | try {
|
327 | await this.isLoggedIn();
|
328 | const initPipeline = asyncPipe_1.asyncPipe(this.determineTemplate, this.determineQuestions, this.askQuestions, this.determineInitPaths, this.copyTemplateFiles, this.customizePackageJson, this.customizeYaml, this.sendAnalytics, this.logMessages);
|
329 | await initPipeline(this.initPrompts);
|
330 | }
|
331 | catch (err) {
|
332 | this.debug('%O', err);
|
333 | this.config.runHook('error', { err, accessToken: this.accessToken });
|
334 | }
|
335 | }
|
336 | }
|
337 | exports.default = Init;
|
338 | Init.description = 'Easily create a new Op.';
|
339 | Init.flags = {
|
340 | help: base_1.flags.help({ char: 'h' }),
|
341 | };
|