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