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 fuzzy_1 = tslib_1.__importDefault(require("fuzzy"));
|
6 | const fs = tslib_1.__importStar(require("fs-extra"));
|
7 | const path = tslib_1.__importStar(require("path"));
|
8 | const base_1 = tslib_1.__importStar(require("../base"));
|
9 | const CustomErrors_1 = require("../errors/CustomErrors");
|
10 | const opConfig_1 = require("../constants/opConfig");
|
11 | const env_1 = require("../constants/env");
|
12 | const utils_1 = require("../utils");
|
13 | const validate_1 = require("../utils/validate");
|
14 | const { multiBlue, multiOrange, green, dim, reset, bold } = sdk_1.ux.colors;
|
15 | class Run extends base_1.default {
|
16 | constructor() {
|
17 | super(...arguments);
|
18 | this.opsAndWorkflows = [];
|
19 | this.customParse = (options, argv) => {
|
20 | const { args, flags } = require('@oclif/parser').parse(argv, Object.assign({}, options, { context: this }));
|
21 | if (!args.nameOrPath && !flags.help) {
|
22 | throw new CustomErrors_1.MissingRequiredArgument('ops run');
|
23 | }
|
24 | if (!args.nameOrPath)
|
25 | this._help();
|
26 | return { args, flags, opParams: argv.slice(1) };
|
27 | };
|
28 | this.checkPathOpsYmlExists = (nameOrPath) => {
|
29 | const pathToOpsYml = path.join(path.resolve(nameOrPath), opConfig_1.OP_FILE);
|
30 | return fs.existsSync(pathToOpsYml);
|
31 | };
|
32 | this.parseYamlFile = async (relativePathToOpsYml) => {
|
33 | const opsYmlExists = this.checkPathOpsYmlExists(relativePathToOpsYml);
|
34 | if (!opsYmlExists) {
|
35 | return null;
|
36 | }
|
37 | const opsYml = await fs.readFile(path.join(path.resolve(relativePathToOpsYml), opConfig_1.OP_FILE), 'utf8');
|
38 | const { ops = [], workflows = [], version = '1' } = (await utils_1.parseYaml(opsYml));
|
39 | return { ops, workflows, version };
|
40 | };
|
41 | this.logResolvedLocalMessage = (inputs) => {
|
42 | const { parsedArgs: { args: { nameOrPath }, }, } = inputs;
|
43 | this.log(`❗️ ${this.ux.colors.callOutCyan(nameOrPath)} ${this.ux.colors.white('resolved to a local path and is running local Op.')} `);
|
44 | return inputs;
|
45 | };
|
46 |
|
47 | this.getOpsAndWorkflowsFromFileSystem = (relativePathToOpsYml) => async (inputs) => {
|
48 | const yamlContents = await this.parseYamlFile(relativePathToOpsYml);
|
49 | if (!yamlContents) {
|
50 | return Object.assign({}, inputs, { opsAndWorkflows: [] });
|
51 | }
|
52 | const { ops, workflows, version } = yamlContents;
|
53 | return Object.assign({}, inputs, { opsAndWorkflows: [...ops, ...workflows], version });
|
54 | };
|
55 | this.addMissingApiFieldsToLocalOps = async (inputs) => {
|
56 | const { opsAndWorkflows, config } = inputs;
|
57 | const updatedOpsAndWorkflows = opsAndWorkflows.map((opOrWorkflow) => {
|
58 | let newOpOrWorkflow = Object.assign({}, opOrWorkflow);
|
59 | newOpOrWorkflow.teamName = config.team.name;
|
60 | newOpOrWorkflow.type =
|
61 | 'steps' in newOpOrWorkflow ? opConfig_1.WORKFLOW_TYPE : opConfig_1.COMMAND_TYPE;
|
62 | return newOpOrWorkflow;
|
63 | });
|
64 | return Object.assign({}, inputs, { opsAndWorkflows: updatedOpsAndWorkflows });
|
65 | };
|
66 | this.filterLocalOps = (inputs) => {
|
67 | const { opsAndWorkflows } = inputs;
|
68 | if (!opsAndWorkflows) {
|
69 | return Object.assign({}, inputs);
|
70 | }
|
71 | const { parsedArgs: { args: { nameOrPath }, }, } = inputs;
|
72 | const keepOnlyMatchingNames = ({ name }) => {
|
73 | return name.indexOf(nameOrPath) >= 0;
|
74 | };
|
75 | return Object.assign({}, inputs, { opsAndWorkflows: opsAndWorkflows.filter(keepOnlyMatchingNames) });
|
76 | };
|
77 | this.formatOpOrWorkflowEmoji = (opOrWorkflow) => {
|
78 | if (!opOrWorkflow.isPublished) {
|
79 | return '🖥 ';
|
80 | }
|
81 | else if (opOrWorkflow.isPublic) {
|
82 | return '🌎 ';
|
83 | }
|
84 | else {
|
85 | return '🔑 ';
|
86 | }
|
87 | };
|
88 | this.formatOpOrWorkflowName = (opOrWorkflow) => {
|
89 | const name = reset.white(opOrWorkflow.name);
|
90 | if ((!opOrWorkflow.isPublished && 'steps' in opOrWorkflow) ||
|
91 | (opOrWorkflow.isPublished && opOrWorkflow.type === opConfig_1.WORKFLOW_TYPE)) {
|
92 | return `${reset(multiOrange('\u2022'))} ${this.formatOpOrWorkflowEmoji(opOrWorkflow)} ${name}`;
|
93 | }
|
94 | else {
|
95 | return `${reset(multiBlue('\u2022'))} ${this.formatOpOrWorkflowEmoji(opOrWorkflow)} ${name}`;
|
96 | }
|
97 | };
|
98 | this.fuzzyFilterParams = () => {
|
99 | const list = this.opsAndWorkflows.map(opOrWorkflow => {
|
100 | const name = this.formatOpOrWorkflowName(opOrWorkflow);
|
101 | return {
|
102 | name: `${name} - ${opOrWorkflow.description}`,
|
103 | value: opOrWorkflow,
|
104 | };
|
105 | });
|
106 | const options = { extract: el => el.name };
|
107 | return { list, options };
|
108 | };
|
109 | this.autocompleteSearch = async (_, input = '') => {
|
110 | try {
|
111 | const { list, options } = this.fuzzyFilterParams();
|
112 | const fuzzyResult = fuzzy_1.default.filter(input, list, options);
|
113 | return fuzzyResult.map(result => result.original);
|
114 | }
|
115 | catch (err) {
|
116 | this.debug('%O', err);
|
117 | throw err;
|
118 | }
|
119 | };
|
120 | this.selectOpOrWorkflowToRun = async (inputs) => {
|
121 | try {
|
122 | const { opsAndWorkflows } = inputs;
|
123 | if (!opsAndWorkflows || !opsAndWorkflows.length)
|
124 | throw new CustomErrors_1.InvalidOpName();
|
125 | if (opsAndWorkflows.length === 1) {
|
126 | return Object.assign({}, inputs, { opOrWorkflow: opsAndWorkflows[0] });
|
127 | }
|
128 | this.opsAndWorkflows = opsAndWorkflows;
|
129 | const { opOrWorkflow } = await sdk_1.ux.prompt({
|
130 | type: 'autocomplete',
|
131 | name: 'opOrWorkflow',
|
132 | pageSize: 5,
|
133 | message: `\nSelect a ${multiBlue('\u2022Command')} or ${multiOrange('\u2022Workflow')} to run ${reset(green('→'))}\n${reset(dim('🌎 = Public 🔑 = Private 🖥 = Local 🔍 Search:'))} `,
|
134 | source: this.autocompleteSearch.bind(this),
|
135 | });
|
136 | return Object.assign({}, inputs, { opOrWorkflow });
|
137 | }
|
138 | catch (err) {
|
139 | this.debug('%O', err);
|
140 | throw err;
|
141 | }
|
142 | };
|
143 | this.printCustomHelp = (op) => {
|
144 | try {
|
145 | if (!op.help) {
|
146 | throw new Error('Custom help message can be defined in the ops.yml\n');
|
147 | }
|
148 | switch (true) {
|
149 | case Boolean(op.description):
|
150 | this.log(`\n${op.description}`);
|
151 | case Boolean(op.help.usage):
|
152 | this.log(`\n${bold('USAGE')}`);
|
153 | this.log(` ${op.help.usage}`);
|
154 | case Boolean(op.help.arguments):
|
155 | this.log(`\n${bold('ARGUMENTS')}`);
|
156 | Object.keys(op.help.arguments).forEach(a => {
|
157 | this.log(` ${a} ${dim(op.help.arguments[a])}`);
|
158 | });
|
159 | case Boolean(op.help.options):
|
160 | this.log(`\n${bold('OPTIONS')}`);
|
161 | Object.keys(op.help.options).forEach(o => {
|
162 | this.log(` -${o.substring(0, 1)}, --${o} ${dim(op.help.options[o])}`);
|
163 | });
|
164 | }
|
165 | }
|
166 | catch (err) {
|
167 | this.debug('%O', err);
|
168 | throw err;
|
169 | }
|
170 | };
|
171 | this.checkForHelpMessage = (inputs) => {
|
172 | try {
|
173 | const { parsedArgs: { flags: { help }, }, opOrWorkflow, } = inputs;
|
174 |
|
175 | if (help && 'run' in opOrWorkflow) {
|
176 | this.printCustomHelp(opOrWorkflow);
|
177 | process.exit();
|
178 | }
|
179 | return inputs;
|
180 | }
|
181 | catch (err) {
|
182 | this.debug('%O', err);
|
183 | throw err;
|
184 | }
|
185 | };
|
186 | this.executeOpOrWorkflowService = async (inputs) => {
|
187 | try {
|
188 | let { opOrWorkflow, config, parsedArgs, parsedArgs: { opParams }, teamName, version, } = inputs;
|
189 | if (opOrWorkflow.type === opConfig_1.WORKFLOW_TYPE) {
|
190 | await this.services.workflowService.run(opOrWorkflow, opParams, config);
|
191 | }
|
192 | else {
|
193 | if (!opOrWorkflow.isPublished) {
|
194 | opOrWorkflow = Object.assign({}, opOrWorkflow, { isPublished: false, teamName: opOrWorkflow.teamName || teamName });
|
195 | }
|
196 | await this.services.opService.run(opOrWorkflow, parsedArgs, config, version);
|
197 | }
|
198 | return Object.assign({}, inputs, { opOrWorkflow });
|
199 | }
|
200 | catch (err) {
|
201 | this.debug('%O', err);
|
202 | throw err;
|
203 | }
|
204 | };
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | this.parseTeamAndOpName = (inputs) => {
|
214 | const { parsedArgs: { args: { nameOrPath }, }, config: { team: { name: configTeamName }, }, } = inputs;
|
215 | const splits = nameOrPath.split('/');
|
216 | if (splits.length === 0 || splits.length > 2)
|
217 | throw new CustomErrors_1.InvalidOpName();
|
218 | if (splits.length === 1) {
|
219 | let [opName] = splits;
|
220 | opName = validate_1.isValidOpName(splits[0]) ? splits[0] : '';
|
221 | return Object.assign({}, inputs, { teamName: configTeamName, opName });
|
222 | }
|
223 | let [teamName, opName] = splits;
|
224 | teamName = teamName.startsWith('@')
|
225 | ? teamName.substring(1, teamName.length)
|
226 | : teamName;
|
227 | teamName = validate_1.isValidTeamName(teamName) ? teamName : '';
|
228 | opName = validate_1.isValidOpName(opName) ? opName : '';
|
229 | return Object.assign({}, inputs, { teamName, opName });
|
230 | };
|
231 | this.getApiOps = async (inputs) => {
|
232 | let { config, teamName, opName, opsAndWorkflows: previousOpsAndWorkflows = [], } = inputs;
|
233 | let apiOp;
|
234 | try {
|
235 | if (!opName)
|
236 | return Object.assign({}, inputs);
|
237 | teamName = teamName ? teamName : config.team.name;
|
238 | ({ data: apiOp } = await this.services.api.find(`teams/${teamName}/ops/${opName}`, {
|
239 | headers: {
|
240 | Authorization: this.accessToken,
|
241 | },
|
242 | }));
|
243 | }
|
244 | catch (err) {
|
245 | this.debug('%O', err);
|
246 | if (err.error[0].code === 4011) {
|
247 | throw new CustomErrors_1.UnauthorizedtoAccessOp(err);
|
248 | }
|
249 | throw new CustomErrors_1.APIError(err);
|
250 | }
|
251 | if (!apiOp) {
|
252 | throw new CustomErrors_1.NoOpsFound(opName);
|
253 | }
|
254 | apiOp.isPublished = true;
|
255 | return Object.assign({}, inputs, { opsAndWorkflows: [...previousOpsAndWorkflows, apiOp] });
|
256 | };
|
257 | this.sendAnalytics = (inputs) => {
|
258 | const { opOrWorkflow: { id, name, description }, parsedArgs: { opParams }, } = inputs;
|
259 | this.services.analytics.track({
|
260 | userId: this.user.email,
|
261 | event: 'Ops CLI Run',
|
262 | properties: {
|
263 | email: this.user.email,
|
264 | username: this.user.username,
|
265 | id,
|
266 | name,
|
267 | description,
|
268 | argments: opParams.length,
|
269 | image: `${env_1.OPS_REGISTRY_HOST}/${name}`,
|
270 | },
|
271 | }, this.accessToken);
|
272 | return inputs;
|
273 | };
|
274 | }
|
275 | async run() {
|
276 | try {
|
277 | await this.isLoggedIn();
|
278 | const { config } = this.state;
|
279 | const parsedArgs = this.customParse(Run, this.argv);
|
280 | const { args: { nameOrPath }, } = parsedArgs;
|
281 | if (this.checkPathOpsYmlExists(nameOrPath)) {
|
282 |
|
283 | const runFsPipeline = utils_1.asyncPipe(this.logResolvedLocalMessage, this.getOpsAndWorkflowsFromFileSystem(nameOrPath), this.addMissingApiFieldsToLocalOps, this.selectOpOrWorkflowToRun, this.checkForHelpMessage, this.sendAnalytics, this.executeOpOrWorkflowService);
|
284 | await runFsPipeline({ parsedArgs, config });
|
285 | }
|
286 | else {
|
287 | |
288 |
|
289 |
|
290 |
|
291 | const runApiPipeline = utils_1.asyncPipe(this.getOpsAndWorkflowsFromFileSystem(process.cwd()), this.addMissingApiFieldsToLocalOps, this.filterLocalOps, this.parseTeamAndOpName, this.getApiOps, this.selectOpOrWorkflowToRun, this.checkForHelpMessage, this.sendAnalytics, this.executeOpOrWorkflowService);
|
292 | await runApiPipeline({ parsedArgs, config });
|
293 | }
|
294 | }
|
295 | catch (err) {
|
296 | this.debug('%O', err);
|
297 | this.config.runHook('error', { err, accessToken: this.accessToken });
|
298 | }
|
299 | }
|
300 | }
|
301 | Run.description = 'Run an op from the registry.';
|
302 | Run.flags = {
|
303 | help: base_1.flags.boolean({
|
304 | char: 'h',
|
305 | description: 'show CLI help',
|
306 | }),
|
307 | build: base_1.flags.boolean({
|
308 | char: 'b',
|
309 | description: 'Builds the op before running. Must provide a path to the op.',
|
310 | default: false,
|
311 | }),
|
312 | };
|
313 |
|
314 | Run.strict = false;
|
315 | Run.args = [
|
316 | {
|
317 | name: 'nameOrPath',
|
318 | description: 'Name or path of the command or workflow you want to run.',
|
319 | },
|
320 | ];
|
321 | exports.default = Run;
|