Skip to content

Commit 129f531

Browse files
derbergasyncapi-botShurtu-gal
authored
feat: enable new generate client command (#1830)
Co-authored-by: Lukasz Gornicki <lpgornicki@gmail.com> Co-authored-by: asyncapi-bot <info@asyncapi.io> Co-authored-by: Shurtu-gal <ashishpadhy1729@gmail.com>
1 parent 2fe72e2 commit 129f531

File tree

23 files changed

+866
-486
lines changed

23 files changed

+866
-486
lines changed

.changeset/1830.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@asyncapi/cli': minor
3+
---
4+
5+
Introduced a new `asyncapi generate client` command to the CLI.
6+
7+
This command allows you to generate client libraries using the new **AsyncAPI Generator** baked-in templates. Read more about [baked-in templates in official documentation](https://www.asyncapi.com/docs/tools/generator/baked-in-templates).
8+
9+
This feature is based on a new concept introduced in [AsyncAPI Generator version 2.8.3](https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.8.3). The number of templates is limited and the solution is still in the experimental phase. It is not recommended to use them in production. Instead, join us in the [Generator project](https://github.com/asyncapi/generator) to help improve templates with your use cases and your AsyncAPI documents.

.eslintrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@
203203
"rules": {
204204
"@typescript-eslint/ban-ts-comment": "off"
205205
}
206+
},
207+
{
208+
"files": ["*.js"],
209+
"rules": {
210+
"@typescript-eslint/no-var-requires": "off"
211+
}
206212
}
207213
]
208214
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ test/fixtures/specification-conv.yml
2323
test/fixtures/specification-conv.yaml
2424
test/fixtures/specification-conv.json
2525
.vscode
26+
src/domains/models/generate/ClientLanguages.ts
2627

2728
/action/
2829
/github-action/output/

package-lock.json

Lines changed: 55 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"express": "^4.17.1",
3737
"fast-levenshtein": "^3.0.0",
3838
"fs-extra": "^11.1.0",
39-
"generator-v2": "npm:@asyncapi/generator@2.4.1",
39+
"generator-v2": "npm:@asyncapi/generator@2.8.3",
4040
"helmet": "^8.1.0",
4141
"https-proxy-agent": "^7.0.6",
4242
"inquirer": "^8.2.0",
@@ -160,7 +160,7 @@
160160
},
161161
"repository": "asyncapi/cli",
162162
"scripts": {
163-
"build": "rimraf lib && node scripts/fetch-asyncapi-example.js && tsc && tsc-alias --project tsconfig.json && oclif manifest && echo \"Build Completed\"",
163+
"build": "rimraf lib && node scripts/fetch-asyncapi-example.js && npm run generate:languages && tsc && tsc-alias --project tsconfig.json && oclif manifest && echo \"Build Completed\"",
164164
"bump:github-action": "cd github-action/lib/ && node bump-action-version.js",
165165
"bump:version": "npx -p @changesets/cli@2.27.7 changeset version && npm run bump:github-action",
166166
"dev": "tsc --watch",
@@ -170,6 +170,7 @@
170170
"generate:assets": "npm run generate:readme:toc && npm run generate:commands",
171171
"generate:commands": "npm run generate:readme:create && npm run generate:readme:commands && node ./scripts/updateUsageDocs.js && rimraf ./scripts/README.md",
172172
"generate:readme:toc": "markdown-toc -i README.md",
173+
"generate:languages": "node scripts/generateTypesForGenerateCommand.js",
173174
"prepare": "lefthook install",
174175
"lint": "eslint --max-warnings 5 --config .eslintrc .",
175176
"lint:fix": "eslint --max-warnings 5 --config .eslintrc . --fix",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const { writeFile } = require('fs/promises');
2+
const path = require('path');
3+
const { listBakedInTemplates } = require('generator-v2');
4+
5+
async function generateClientLanguages() {
6+
const bakedInClients = listBakedInTemplates({ type: 'client' });
7+
8+
const targets = Array.from(new Set(bakedInClients.map(t => t.target))).sort();
9+
10+
const enumEntries = targets
11+
.map(t => ` ${capitalize(t)} = '${t}',`)
12+
.join('\n');
13+
14+
const fileContent = `// Auto-generated. Do not edit manually.
15+
export enum AvailableLanguage {
16+
${enumEntries}
17+
}
18+
19+
export const availableLanguages = [${targets.map(t => `'${t}'`).join(', ')}] as const;
20+
export type AvailableLanguageType = typeof availableLanguages[number];
21+
22+
/**
23+
* Returns the first available language as the default option.
24+
*/
25+
export const getDefaultLanguage = (): AvailableLanguageType => availableLanguages[0];
26+
`;
27+
28+
const outputPath = path.join(__dirname, '../src/domains/models/generate/ClientLanguages.ts');
29+
await writeFile(outputPath, fileContent);
30+
31+
console.log('✅ ClientLanguages.ts generated');
32+
}
33+
34+
function capitalize(str) {
35+
return str.charAt(0).toUpperCase() + str.slice(1);
36+
}
37+
38+
generateClientLanguages().catch(err => {
39+
throw new Error(`Failed to generate ClientLanguages.ts: ${err.message}`);
40+
});
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { Args } from '@oclif/core';
2+
import { BaseGeneratorCommand } from '@cli/internal/base/BaseGeneratorCommand';
3+
// eslint-disable-next-line
4+
// @ts-ignore
5+
import { listBakedInTemplates } from 'generator-v2';
6+
import { intro, note } from '@clack/prompts';
7+
import { inverse, yellow } from 'picocolors';
8+
import { clientsFlags } from '@cli/internal/flags/generate/clients.flags';
9+
import { parseGeneratorFlags } from '@utils/generate/flags';
10+
import { promptForLanguage } from '@utils/generate/prompts';
11+
import { availableLanguages, AvailableLanguageType, getDefaultLanguage } from '@models/generate/ClientLanguages';
12+
import { GeneratorError } from '@errors/generator-error';
13+
14+
export default class Client extends BaseGeneratorCommand {
15+
static description = `Generates clients baked-in AsyncAPI Generator. Available for: ${availableLanguages.join(', ')}. If some language is not supported or you want to improve existing client, join us at https://github.com/asyncapi/generator`;
16+
17+
static examples = [
18+
'asyncapi generate client javascript asyncapi.yaml --param version=1.0.0 singleFile=true --output ./docs --force-write'
19+
];
20+
21+
static readonly flags = {
22+
...clientsFlags(),
23+
...BaseGeneratorCommand.flags
24+
};
25+
26+
static args = {
27+
language: Args.string({ description: `The language you want the client generated for. Available target languages: ${availableLanguages.join(', ')}`, required: true }),
28+
...BaseGeneratorCommand.args
29+
};
30+
31+
async run() {
32+
const { args, flags } = await this.parse(Client); // NOSONAR
33+
const interactive = !flags['no-interactive'];
34+
let asyncapi = args['asyncapi'] ?? '';
35+
let language = args['language'] as AvailableLanguageType;
36+
let output = flags.output as string;
37+
const { proxyPort, proxyHost } = flags;
38+
39+
if (interactive) {
40+
intro(inverse('Client generation with AsyncAPI Generator'));
41+
note(yellow('This feature is in the experimental phase. Please provide feedback at: https://github.com/asyncapi/generator/issues'));
42+
43+
const parsedArgs = await this.parseArgs(args, output);
44+
asyncapi = parsedArgs.asyncapi;
45+
language = parsedArgs.language as AvailableLanguageType;
46+
output = parsedArgs.output;
47+
}
48+
49+
const template = this.getTemplateName(language);
50+
51+
const parsedFlags = parseGeneratorFlags(
52+
flags['disable-hook'],
53+
flags['param'],
54+
flags['map-base-url'],
55+
flags['registry-url'],
56+
flags['registry-auth'],
57+
flags['registry-token']
58+
);
59+
60+
const options = await this.buildGeneratorOptions(flags, parsedFlags);
61+
62+
// Apply proxy configuration using base class method
63+
asyncapi = this.applyProxyConfiguration(asyncapi, proxyHost, proxyPort);
64+
65+
const asyncapiInput = await this.loadAsyncAPIInput(asyncapi);
66+
67+
this.specFile = asyncapiInput;
68+
this.metricsMetadata.language = language;
69+
70+
const watchTemplate = flags['watch'];
71+
const genOption = this.buildGenOption(flags, parsedFlags);
72+
73+
// Use GeneratorService with new generator (v2) for client generation
74+
const specification = await this.loadSpecificationSafely(asyncapi);
75+
const result = await this.generatorService.generateUsingNewGenerator(
76+
specification,
77+
template,
78+
output,
79+
options as any, // GeneratorService expects different options interface
80+
genOption,
81+
);
82+
83+
if (!result.success) {
84+
throw new GeneratorError(new Error(result.error));
85+
}
86+
87+
this.log(result.data?.logs?.join('\n'));
88+
89+
if (watchTemplate) {
90+
await this.handleWatchMode(asyncapi, template, output, options, genOption, interactive);
91+
}
92+
}
93+
94+
private async parseArgs(args: Record<string, any>, output?: string): Promise<{ asyncapi: string; language: string; output: string; }> {
95+
// Use base class method for common args
96+
const commonArgs = await this.parseCommonArgs(args, output);
97+
98+
let language = args['language'] as AvailableLanguageType;
99+
100+
if (!language) {
101+
const defaultLanguage = getDefaultLanguage();
102+
language = await promptForLanguage(defaultLanguage) as AvailableLanguageType;
103+
}
104+
105+
this.handleCancellation(language);
106+
107+
return {
108+
asyncapi: commonArgs.asyncapi,
109+
language,
110+
output: commonArgs.output
111+
};
112+
}
113+
114+
private getTemplateName(language: AvailableLanguageType): string {
115+
const template = listBakedInTemplates({ type: 'client' }).find((template: any) => {
116+
return template.target === language;
117+
})?.name;
118+
119+
if (!template) {
120+
this.log(`❌ Client generation for "${language}" is not yet available.`);
121+
this.log(`✅ Available languages: ${availableLanguages.join(', ')}`);
122+
this.log('🙏 Help us create the missing one. Start discussion at: https://github.com/asyncapi/generator/issues.');
123+
this.exit(1);
124+
}
125+
126+
return template;
127+
}
128+
}

0 commit comments

Comments
 (0)