Skip to content

Commit 6ba4b47

Browse files
Shurtu-galSouvikns
andauthored
chore(design): implement design system for generate command (#1302)
Co-authored-by: souvik <[email protected]>
1 parent b6702b2 commit 6ba4b47

File tree

7 files changed

+243
-50
lines changed

7 files changed

+243
-50
lines changed

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ spec-examples.zip
2727

2828
coverage
2929

30+
# Analytics
31+
32+
.asyncapi-analytics
33+
3034
# Glee
3135
assets/create-glee-app/templates/default/.glee
3236
assets/create-glee-app/templates/tutorial/.glee
3337
assets/create-glee-app/templates/default/docs
34-
assets/create-glee-app/templates/tutorial/docs
38+
assets/create-glee-app/templates/tutorial/docs

package-lock.json

+41
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@asyncapi/protobuf-schema-parser": "^3.2.11",
2121
"@asyncapi/raml-dt-schema-parser": "^4.0.14",
2222
"@asyncapi/studio": "^0.20.0",
23+
"@clack/prompts": "^0.7.0",
2324
"@oclif/core": "^1.26.2",
2425
"@oclif/errors": "^1.3.6",
2526
"@oclif/plugin-not-found": "^2.3.22",
@@ -38,6 +39,7 @@
3839
"node-fetch": "^2.0.0",
3940
"oclif": "^4.2.0",
4041
"open": "^8.4.0",
42+
"picocolors": "^1.0.0",
4143
"reflect-metadata": "^0.1.13",
4244
"request": "^2.88.2",
4345
"serve-handler": "^6.1.3",

src/commands/generate/fromTemplate.ts

+84-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Flags, CliUx } from '@oclif/core';
1+
import { Flags } from '@oclif/core';
22
import Command from '../../base';
33
// eslint-disable-next-line
44
// @ts-ignore
@@ -13,11 +13,8 @@ import { ValidationError } from '../../errors/validation-error';
1313
import { GeneratorError } from '../../errors/generator-error';
1414
import { Parser } from '@asyncapi/parser';
1515
import type { Example } from '@oclif/core/lib/interfaces';
16-
17-
const red = (text: string) => `\x1b[31m${text}\x1b[0m`;
18-
const magenta = (text: string) => `\x1b[35m${text}\x1b[0m`;
19-
const yellow = (text: string) => `\x1b[33m${text}\x1b[0m`;
20-
const green = (text: string) => `\x1b[32m${text}\x1b[0m`;
16+
import { intro, isCancel, spinner, text } from '@clack/prompts';
17+
import { inverse, yellow, magenta, green, red } from 'picocolors';
2118

2219
interface IMapBaseUrlToFlag {
2320
url: string,
@@ -68,6 +65,11 @@ export default class Template extends Command {
6865
description: 'Disable a specific hook type or hooks from a given hook type',
6966
multiple: true
7067
}),
68+
'no-interactive': Flags.boolean({
69+
description: 'Disable interactive mode and run with the provided flags.',
70+
required: false,
71+
default: false,
72+
}),
7173
install: Flags.boolean({
7274
char: 'i',
7375
default: false,
@@ -103,18 +105,27 @@ export default class Template extends Command {
103105
};
104106

105107
static args = [
106-
{ name: 'asyncapi', description: '- Local path, url or context-name pointing to AsyncAPI file', required: true },
107-
{ name: 'template', description: '- Name of the generator template like for example @asyncapi/html-template or https://github.com/asyncapi/html-template', required: true }
108+
{ name: 'asyncapi', description: '- Local path, url or context-name pointing to AsyncAPI file' },
109+
{ name: 'template', description: '- Name of the generator template like for example @asyncapi/html-template or https://github.com/asyncapi/html-template' },
108110
];
109111

110112
parser = new Parser();
111113

112114
async run() {
113115
const { args, flags } = await this.parse(Template); // NOSONAR
116+
const interactive = !flags['no-interactive'];
117+
118+
let { asyncapi, template } = args;
119+
let output = flags.output as string;
120+
if (interactive) {
121+
intro(inverse('AsyncAPI Generator'));
122+
123+
const parsedArgs = await this.parseArgs(args, output);
124+
asyncapi = parsedArgs.asyncapi;
125+
template = parsedArgs.template;
126+
output = parsedArgs.output;
127+
}
114128

115-
const asyncapi = args['asyncapi'];
116-
const template = args['template'];
117-
const output = flags.output || process.cwd();
118129
const parsedFlags = this.parseFlags(flags['disable-hook'], flags['param'], flags['map-base-url']);
119130
const options = {
120131
forceWrite: flags['force-write'],
@@ -142,13 +153,66 @@ export default class Template extends Command {
142153
this.error(`${template} template does not support AsyncAPI v3 documents, please checkout ${v3IssueLink}`);
143154
}
144155
}
145-
await this.generate(asyncapi, template, output, options, genOption);
156+
await this.generate(asyncapi, template, output, options, genOption, interactive);
146157
if (watchTemplate) {
147-
const watcherHandler = this.watcherHandler(asyncapi, template, output, options, genOption);
158+
const watcherHandler = this.watcherHandler(asyncapi, template, output, options, genOption, interactive);
148159
await this.runWatchMode(asyncapi, template, output, watcherHandler);
149160
}
150161
}
151162

163+
private async parseArgs(args: Record<string, any>, output?: string): Promise<{ asyncapi: string; template: string; output: string; }> {
164+
let asyncapi = args['asyncapi'];
165+
let template = args['template'];
166+
const cancellationMessage = 'Operation cancelled';
167+
168+
if (!asyncapi) {
169+
asyncapi = await text({
170+
message: 'Please provide the path to the AsyncAPI document',
171+
placeholder: 'asyncapi.yaml',
172+
defaultValue: 'asyncapi.yaml',
173+
validate(value: string) {
174+
if (!value) {
175+
return 'The path to the AsyncAPI document is required';
176+
} else if (!fs.existsSync(value)) {
177+
return 'The file does not exist';
178+
}
179+
}
180+
});
181+
}
182+
183+
if (isCancel(asyncapi)) {
184+
this.error(cancellationMessage, { exit: 1 });
185+
}
186+
187+
if (!template) {
188+
template = await text({
189+
message: 'Please provide the name of the generator template',
190+
placeholder: '@asyncapi/html-template',
191+
defaultValue: '@asyncapi/html-template',
192+
});
193+
}
194+
195+
if (!output) {
196+
output = await text({
197+
message: 'Please provide the output directory',
198+
placeholder: './docs',
199+
validate(value: string) {
200+
if (!value) {
201+
return 'The output directory is required';
202+
} else if (typeof value !== 'string') {
203+
return 'The output directory must be a string';
204+
}
205+
}
206+
}) as string;
207+
}
208+
209+
if (isCancel(output) || isCancel(template)) {
210+
this.error(cancellationMessage, { exit: 1 });
211+
}
212+
213+
return { asyncapi, template, output };
214+
}
215+
152216
private parseFlags(disableHooks?: string[], params?: string[], mapBaseUrl?: string): ParsedFlags {
153217
return {
154218
params: this.paramParser(params),
@@ -204,7 +268,7 @@ export default class Template extends Command {
204268
return mapBaseURLToFolder;
205269
}
206270

207-
private async generate(asyncapi: string | undefined, template: string, output: string, options: any, genOption: any) {
271+
private async generate(asyncapi: string | undefined, template: string, output: string, options: any, genOption: any, interactive = true) {
208272
let specification: Specification;
209273
try {
210274
specification = await load(asyncapi);
@@ -218,16 +282,15 @@ export default class Template extends Command {
218282
);
219283
}
220284
const generator = new AsyncAPIGenerator(template, output || path.resolve(os.tmpdir(), 'asyncapi-generator'), options);
221-
222-
CliUx.ux.action.start('Generation in progress. Keep calm and wait a bit');
285+
const s = interactive ? spinner() : { start: () => null, stop: (string: string) => console.log(string) };
286+
s.start('Generation in progress. Keep calm and wait a bit');
223287
try {
224288
await generator.generateFromString(specification.text(), genOption);
225-
CliUx.ux.action.stop();
226289
} catch (err: any) {
227-
CliUx.ux.action.stop('done\n');
290+
s.stop('Generation failed');
228291
throw new GeneratorError(err);
229292
}
230-
console.log(`${yellow('Check out your shiny new generated files at ') + magenta(output) + yellow('.')}\n`);
293+
s.stop(`${yellow('Check out your shiny new generated files at ') + magenta(output) + yellow('.')}\n`);
231294
}
232295

233296
private async runWatchMode(asyncapi: string | undefined, template: string, output: string, watchHandler: ReturnType<typeof this.watcherHandler>) {
@@ -270,7 +333,7 @@ export default class Template extends Command {
270333
});
271334
}
272335

273-
private watcherHandler(asyncapi: string, template: string, output: string, options: Record<string, any>, genOption: any): (changedFiles: Record<string, any>) => Promise<void> {
336+
private watcherHandler(asyncapi: string, template: string, output: string, options: Record<string, any>, genOption: any, interactive: boolean): (changedFiles: Record<string, any>) => Promise<void> {
274337
return async (changedFiles: Record<string, any>): Promise<void> => {
275338
console.clear();
276339
console.log('[WATCHER] Change detected');
@@ -292,7 +355,7 @@ export default class Template extends Command {
292355
this.log(`\t${magenta(value.path)} was ${eventText}`);
293356
}
294357
try {
295-
await this.generate(asyncapi, template, output, options, genOption);
358+
await this.generate(asyncapi, template, output, options, genOption, interactive);
296359
} catch (err: any) {
297360
throw new GeneratorError(err);
298361
}

0 commit comments

Comments
 (0)