UNPKG

19.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const sdk_1 = require("@cto.ai/sdk");
5const fs = tslib_1.__importStar(require("fs-extra"));
6const path = tslib_1.__importStar(require("path"));
7const base_1 = tslib_1.__importStar(require("../base"));
8const env_1 = require("../constants/env");
9const opConfig_1 = require("../constants/opConfig");
10const CustomErrors_1 = require("../errors/CustomErrors");
11const utils_1 = require("../utils");
12const get_docker_1 = tslib_1.__importDefault(require("../utils/get-docker"));
13const validate_1 = require("../utils/validate");
14const ErrorTemplate_1 = require("../errors/ErrorTemplate");
15class Publish extends base_1.default {
16 constructor() {
17 super(...arguments);
18 this.resolvePath = async (opPath) => {
19 return path.resolve(process.cwd(), opPath);
20 };
21 this.checkDocker = async (opPath) => {
22 const docker = await get_docker_1.default(this, 'publish');
23 if (!docker) {
24 throw new Error('No docker');
25 }
26 return { opPath, docker };
27 };
28 this.getOpsAndWorkFlows = async (inputs) => {
29 const { opPath } = inputs;
30 const manifest = await fs
31 .readFile(path.join(opPath, opConfig_1.OP_FILE), 'utf8')
32 .catch((err) => {
33 this.debug('%O', err);
34 throw new CustomErrors_1.FileNotFoundError(err, opPath, opConfig_1.OP_FILE);
35 });
36 if (!manifest)
37 throw new CustomErrors_1.NoLocalOpsFound();
38 const { ops, version, workflows } = utils_1.parseYaml(manifest);
39 if (!ops && !workflows) {
40 throw new CustomErrors_1.NoLocalOpsOrWorkflowsFound();
41 }
42 return Object.assign(Object.assign({}, inputs), { opCommands: ops, opWorkflows: workflows, version });
43 };
44 this.determineQuestions = async (inputs) => {
45 const { opCommands, opWorkflows } = inputs;
46 let opsAndWorkflows;
47 if (opCommands && opCommands.length && opWorkflows && opWorkflows.length) {
48 ;
49 ({ opsAndWorkflows } = await sdk_1.ux.prompt({
50 type: 'list',
51 name: 'opsAndWorkflows',
52 message: `\n Which would you like to publish ${sdk_1.ux.colors.reset.green('ā†’')}`,
53 choices: [
54 { name: 'Commands', value: opConfig_1.COMMAND },
55 { name: 'Workflows', value: opConfig_1.WORKFLOW },
56 'Both',
57 ],
58 afterMessage: `${sdk_1.ux.colors.reset.green('āœ“')}`,
59 }));
60 }
61 else if (!opCommands || !opCommands.length) {
62 opsAndWorkflows = opConfig_1.WORKFLOW;
63 }
64 else {
65 opsAndWorkflows = opConfig_1.COMMAND;
66 }
67 return Object.assign(Object.assign({}, inputs), { commandsAndWorkflows: opsAndWorkflows });
68 };
69 this.selectOpsAndWorkFlows = async (inputs) => {
70 let { commandsAndWorkflows, opCommands, opWorkflows } = inputs;
71 switch (commandsAndWorkflows) {
72 case opConfig_1.COMMAND:
73 opCommands = await this.selectOps(opCommands);
74 break;
75 case opConfig_1.WORKFLOW:
76 opWorkflows = await this.selectWorkflows(opWorkflows);
77 break;
78 default:
79 opCommands = await this.selectOps(opCommands);
80 opWorkflows = await this.selectWorkflows(opWorkflows);
81 }
82 return Object.assign(Object.assign({}, inputs), { opCommands,
83 opWorkflows,
84 commandsAndWorkflows });
85 };
86 this.selectOps = async (ops) => {
87 if (ops.length <= 1) {
88 return ops;
89 }
90 const answers = await sdk_1.ux.prompt({
91 type: 'checkbox',
92 name: 'ops',
93 message: `\n Which ops would you like to publish ${sdk_1.ux.colors.reset.green('ā†’')}`,
94 choices: ops.map(op => {
95 return {
96 value: op,
97 name: `${op.name} - ${op.description}`,
98 };
99 }),
100 validate: input => input.length > 0,
101 });
102 return answers.ops;
103 };
104 this.selectWorkflows = async (workflows) => {
105 if (workflows.length <= 1) {
106 return workflows;
107 }
108 const answers = await sdk_1.ux.prompt({
109 type: 'checkbox',
110 name: 'workflows',
111 message: `\n Which workflows would you like to publish ${sdk_1.ux.colors.reset.green('ā†’')}`,
112 choices: workflows.map(workflow => {
113 return {
114 value: workflow,
115 name: `${workflow.name} - ${workflow.description}`,
116 };
117 }),
118 validate: input => input.length > 0,
119 });
120 return answers.workflows;
121 };
122 this.findOpsWhereVersionAlreadyExists = async (inputs) => {
123 const { existingVersions: existingCommandVersions, filteredOps: opCommands, } = await this.filterExistingOps(inputs.opCommands);
124 const { existingVersions: existingWorkflowVersions, filteredOps: opWorkflows, } = await this.filterExistingOps(inputs.opWorkflows);
125 return Object.assign(Object.assign({}, inputs), { opCommands,
126 opWorkflows, existingVersions: [
127 ...existingCommandVersions,
128 ...existingWorkflowVersions,
129 ] });
130 };
131 this.filterExistingOps = async (ops) => {
132 let filteredOps = [];
133 let existingVersions = [];
134 for (const op of ops) {
135 try {
136 await this.services.api.find(`/private/teams/${this.team.name}/ops/${op.name}/versions/${op.version}`, {
137 headers: {
138 Authorization: this.accessToken,
139 },
140 });
141 existingVersions = existingVersions.concat(op);
142 }
143 catch (err) {
144 if (err.error[0].code === 404) {
145 filteredOps = filteredOps.concat(op);
146 continue;
147 }
148 throw new CustomErrors_1.APIError(err);
149 }
150 }
151 return { existingVersions, filteredOps };
152 };
153 this.getNewVersion = async (inputs) => {
154 if (inputs.existingVersions.length === 0)
155 return inputs;
156 let manifest = await fs.readFile(path.join(inputs.opPath, opConfig_1.OP_FILE), 'utf8');
157 this.log('\n šŸ¤” It seems like the version of the op that you are trying to publish already taken. \n Add a new version indicator in order to publish');
158 for (let existingOp of inputs.existingVersions) {
159 this.log(`${this.ux.colors.callOutCyan(`Current version for ${existingOp.name}:`)} ${this.ux.colors.white(existingOp.version)}`);
160 const { newVersion } = await this.ux.prompt({
161 type: 'input',
162 name: 'newVersion',
163 message: '\nāœļø Update version:',
164 transformer: input => {
165 return this.ux.colors.white(input);
166 },
167 validate: async (input) => {
168 try {
169 if (input === '')
170 return 'Please enter a version';
171 if (!validate_1.validVersionChars.test(input)) {
172 return 'ā— Sorry, version is required and can only contain letters, digits, underscores, \n periods and dashes and must start and end with a letter or a digit';
173 }
174 await this.services.api.find(`/private/teams/${this.team.name}/ops/${existingOp.name}/versions/${input}`, {
175 headers: {
176 Authorization: this.accessToken,
177 },
178 });
179 return 'That version is already taken';
180 }
181 catch (err) {
182 if (err.error[0].code === 404) {
183 return true;
184 }
185 throw new CustomErrors_1.APIError(err);
186 }
187 },
188 });
189 manifest = manifest.replace(`name: ${existingOp.name}:${existingOp.version}`, `name: ${existingOp.name}:${newVersion}`);
190 existingOp.version = newVersion;
191 if (existingOp.type === opConfig_1.COMMAND_TYPE) {
192 inputs.opCommands = inputs.opCommands.concat(existingOp);
193 const opImageTag = utils_1.getOpImageTag(this.team.name, existingOp.name, existingOp.version, existingOp.isPublic);
194 const image = utils_1.getOpUrl(env_1.OPS_REGISTRY_HOST, opImageTag);
195 await this.services.imageService.build(image, path.resolve(process.cwd(), inputs.opPath), existingOp);
196 }
197 else if (existingOp.type === opConfig_1.WORKFLOW_TYPE) {
198 inputs.opWorkflows = inputs.opWorkflows.concat(existingOp);
199 }
200 }
201 fs.writeFileSync(path.join(inputs.opPath, opConfig_1.OP_FILE), manifest);
202 return Object.assign({}, inputs);
203 };
204 this.getRegistryAuth = async (name, version) => {
205 try {
206 const registryAuth = await this.services.registryAuthService.create(this.accessToken, this.team.name, name, version, false, true);
207 return registryAuth;
208 }
209 catch (err) {
210 throw new CustomErrors_1.CouldNotGetRegistryToken(err);
211 }
212 };
213 this.publishOpsAndWorkflows = async (inputs) => {
214 switch (inputs.commandsAndWorkflows) {
215 case opConfig_1.COMMAND:
216 await this.opsPublishLoop(inputs);
217 break;
218 case opConfig_1.WORKFLOW:
219 await this.workflowsPublishLoop(inputs);
220 break;
221 default:
222 await this.opsPublishLoop(inputs);
223 await this.workflowsPublishLoop(inputs);
224 }
225 };
226 this.opsPublishLoop = async ({ opCommands, version }) => {
227 try {
228 for (const op of opCommands) {
229 if (!validate_1.isValidOpName(op.name)) {
230 throw new CustomErrors_1.InvalidInputCharacter('Op Name');
231 }
232 if (!validate_1.isValidOpVersion(op)) {
233 throw new CustomErrors_1.InvalidOpVersionFormat();
234 }
235 const { publishDescription } = await this.ux.prompt({
236 type: 'input',
237 name: 'publishDescription',
238 message: `\nProvide a changelog of what's new for ${op.name}:${op.version} ${sdk_1.ux.colors.reset.green('ā†’')}\n\n ${sdk_1.ux.colors.white('āœļø Changelog:')}`,
239 afterMessage: sdk_1.ux.colors.reset.green('āœ“'),
240 afterMessageAppend: sdk_1.ux.colors.reset(' added!'),
241 validate: this._validateDescription,
242 });
243 op.publishDescription = publishDescription;
244 const opName = utils_1.getOpImageTag(this.team.name, op.name, op.version, op.isPublic);
245 const localImage = await this.services.imageService.checkLocalImage(`${env_1.OPS_REGISTRY_HOST}/${opName}`);
246 if (!localImage) {
247 throw new CustomErrors_1.DockerPublishNoImageFound(op.name, this.team.name);
248 }
249 if ('run' in op) {
250 op.type = opConfig_1.COMMAND_TYPE;
251 const { data: apiOp, } = await this.services.publishService.publishOpToAPI(op, version, this.team.name, this.accessToken, this.services.api);
252 const registryAuth = await this.getRegistryAuth(op.name, op.version);
253 await this.services.publishService.publishOpToRegistry(apiOp, registryAuth, this.team.name, this.accessToken, this.services.registryAuthService, this.services.api, version);
254 this.sendAnalytics('op', apiOp);
255 }
256 }
257 }
258 catch (err) {
259 if (err instanceof ErrorTemplate_1.ErrorTemplate) {
260 throw err;
261 }
262 throw new CustomErrors_1.APIError(err);
263 4;
264 }
265 };
266 this.workflowsPublishLoop = async ({ opWorkflows, version }) => {
267 try {
268 for (const workflow of opWorkflows) {
269 if (!validate_1.isValidOpName(workflow.name)) {
270 throw new CustomErrors_1.InvalidInputCharacter('Workflow Name');
271 }
272 if (!validate_1.isValidOpVersion(workflow)) {
273 throw new CustomErrors_1.InvalidOpVersionFormat();
274 }
275 const { publishDescription } = await this.ux.prompt({
276 type: 'input',
277 name: 'publishDescription',
278 message: `\nProvide a publish description for ${workflow.name}:${workflow.version} ${sdk_1.ux.colors.reset.green('ā†’')}\n\n ${sdk_1.ux.colors.white('Description:')}`,
279 afterMessage: sdk_1.ux.colors.reset.green('āœ“'),
280 afterMessageAppend: sdk_1.ux.colors.reset(' added!'),
281 validate: this._validateDescription,
282 });
283 workflow.publishDescription = publishDescription;
284 if ('remote' in workflow && workflow.remote) {
285 const newSteps = [];
286 for (const step of workflow.steps) {
287 let newStep = '';
288 if (await this.services.buildStepService.isGlueCode(step)) {
289 const opPath = path.resolve(__dirname, './../templates/workflowsteps/js/');
290 newStep = await this.services.buildStepService.buildAndPublishGlueCode(step, this.team.id, this.team.name, this.accessToken, opPath, this.user, this.services.publishService, this.services.opService, this.services.api, this.services.registryAuthService, this.state.config, workflow.isPublic, version);
291 newSteps.push(newStep);
292 }
293 else {
294 if (!this.services.buildStepService.isOpRun(step)) {
295 this.debug('InvalidStepsFound - Step:', step);
296 throw new CustomErrors_1.InvalidStepsFound(step);
297 }
298 newSteps.push(step);
299 }
300 }
301 workflow.steps = newSteps;
302 }
303 try {
304 const { data: apiWorkflow, } = await this.services.api.create(`/private/teams/${this.team.name}/ops`, Object.assign(Object.assign({}, workflow), { platformVersion: version, type: 'workflow' }), {
305 headers: {
306 Authorization: this.accessToken,
307 },
308 });
309 this.log(`\nšŸ™Œ ${sdk_1.ux.colors.callOutCyan(apiWorkflow.name)} has been published!`);
310 this.log(`šŸ–„ Visit your Op page here: ${sdk_1.ux.url(`${env_1.OPS_API_HOST}registry/${this.team.name}/${apiWorkflow.name}`, `<${env_1.OPS_API_HOST}${this.team.name}/${apiWorkflow.name}>`)}\n`);
311 this.sendAnalytics('workflow', apiWorkflow);
312 }
313 catch (err) {
314 this.debug('%O', err);
315 const InvalidWorkflowStepCodes = [400, 404];
316 if (err &&
317 err.error &&
318 err.error[0] &&
319 InvalidWorkflowStepCodes.includes(err.error[0].code)) {
320 if (err.error[0].message === 'version is taken') {
321 throw new CustomErrors_1.VersionIsTaken();
322 }
323 throw new CustomErrors_1.InvalidWorkflowStep(err);
324 }
325 throw new CustomErrors_1.CouldNotCreateWorkflow(err.message);
326 }
327 }
328 }
329 catch (err) {
330 if (err instanceof ErrorTemplate_1.ErrorTemplate)
331 throw err;
332 throw new CustomErrors_1.APIError(err);
333 }
334 };
335 this.sendAnalytics = (publishType, opOrWorkflow) => {
336 this.services.analytics.track({
337 userId: this.user.email,
338 teamId: this.team.id,
339 cliEvent: 'Ops CLI Publish',
340 event: 'Ops CLI Publish',
341 properties: {
342 name: opOrWorkflow.name,
343 team: this.team.name,
344 namespace: `@${this.team.name}/${opOrWorkflow.name}`,
345 email: this.user.email,
346 username: this.user.username,
347 type: publishType,
348 description: opOrWorkflow.description,
349 image: `${env_1.OPS_REGISTRY_HOST}/${opOrWorkflow.id.toLowerCase()}:${opOrWorkflow.version}`,
350 tag: opOrWorkflow.version,
351 },
352 });
353 };
354 }
355 _validateDescription(input) {
356 if (input === '')
357 return 'You need to provide a publish description of your op before continuing';
358 return true;
359 }
360 async run() {
361 try {
362 await this.isLoggedIn();
363 const { args } = this.parse(Publish);
364 const publishPipeline = utils_1.asyncPipe(this.resolvePath, this.checkDocker, this.getOpsAndWorkFlows, this.determineQuestions, this.selectOpsAndWorkFlows, this.findOpsWhereVersionAlreadyExists, this.getNewVersion, this.publishOpsAndWorkflows);
365 await publishPipeline(args.path);
366 }
367 catch (err) {
368 this.debug('%O', err);
369 this.config.runHook('error', { err, accessToken: this.accessToken });
370 }
371 }
372}
373exports.default = Publish;
374Publish.description = 'Publish an Op to your team.';
375Publish.flags = {
376 help: base_1.flags.help({ char: 'h' }),
377};
378Publish.args = [
379 {
380 name: 'path',
381 description: 'Path to the op you want to publish.',
382 required: true,
383 },
384];