Skip to content

Commit 7d9df54

Browse files
feat: create new command for the react template (#1476)
Co-authored-by: asyncapi-bot <bot+chan@asyncapi.io>
1 parent 1c129d0 commit 7d9df54

File tree

10 files changed

+306
-1
lines changed

10 files changed

+306
-1
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
asyncapi: 3.0.0
2+
info:
3+
title: Temperature Service
4+
version: 1.0.0
5+
description: This service is in charge of processing all the events related to temperature.
6+
7+
servers:
8+
dev:
9+
url: test.mosquitto.org
10+
protocol: mqtt
11+
12+
channels:
13+
temperature/changed:
14+
description: Updates the bedroom temperature in the database when the temperature drops or goes up.
15+
publish:
16+
operationId: temperatureChange
17+
message:
18+
description: Message that is being sent when the temperature in the bedroom changes.
19+
contentType: application/json
20+
payload:
21+
type: object
22+
additionalProperties: false
23+
properties:
24+
temperatureId:
25+
type: string
26+
27+
components:
28+
schemas:
29+
temperatureId:
30+
type: object
31+
additionalProperties: false
32+
properties:
33+
temperatureId:
34+
type: string
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "myTemplate",
3+
"generator": {
4+
"renderer": "react",
5+
"supportedProtocols": []
6+
},
7+
"dependencies": {
8+
"@asyncapi/generator-react-sdk": "^1.0.20"
9+
}
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
### First install all the dependencies for template using below command:
2+
npm install
3+
### Run the template using for a specific asyncapi document
4+
asyncapi generate fromTemplate <templateName> ../asyncapi-template
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { File, Text } from '@asyncapi/generator-react-sdk';
2+
3+
// Pass the others parameters to get the specificatin of the asyncapi document
4+
export default function ({ asyncapi }) {
5+
return (
6+
<File name="asyncapi.md">
7+
<Text>My application's markdown file.</Text>
8+
<Text>App name: **{asyncapi.info().title()}**</Text>
9+
</File>
10+
);
11+
}

docs/usage.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ USAGE
5858
* [`asyncapi new`](#asyncapi-new)
5959
* [`asyncapi new file`](#asyncapi-new-file)
6060
* [`asyncapi new glee`](#asyncapi-new-glee)
61+
* [`asyncapi new template`](#asyncapi-new-glee)
6162
* [`asyncapi optimize [SPEC-FILE]`](#asyncapi-optimize-spec-file)
6263
* [`asyncapi start`](#asyncapi-start)
6364
* [`asyncapi start studio`](#asyncapi-start-studio)
@@ -634,6 +635,24 @@ DESCRIPTION
634635

635636
_See code: [src/commands/new/glee.ts](https://github.com/asyncapi/cli/blob/v2.0.3/src/commands/new/glee.ts)_
636637

638+
## `asyncapi new template`
639+
640+
Creates a new template
641+
642+
```
643+
USAGE
644+
$ asyncapi new glee [-h] [-n <value>] [-t <value>] [-renderer <value>]
645+
646+
FLAGS
647+
-h, --help Show CLI help.
648+
-n, --name=<value> [default: project] Name of the Project
649+
-t, --template=<value> [default: default] Name of the Template
650+
-r --renderer=<value> [default: react] Name of the renderer engine
651+
652+
DESCRIPTION
653+
Creates a new template project
654+
```
655+
637656
## `asyncapi optimize [SPEC-FILE]`
638657

639658
optimize asyncapi specification file

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,4 @@
175175
"createhookinit": "oclif generate hook inithook --event=init"
176176
},
177177
"types": "lib/index.d.ts"
178-
}
178+
}

src/commands/new/template.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { promises as fPromises } from 'fs';
2+
import Command from '../../core/base';
3+
import { resolve, join } from 'path';
4+
import { load } from '../../core/models/SpecificationFile';
5+
import fs from 'fs-extra';
6+
import { templateFlags } from '../../core/flags/new/template.flags';
7+
import { cyan, gray } from 'picocolors';
8+
import jsonfile from 'jsonfile';
9+
import path from 'path';
10+
11+
export const successMessage = (projectName: string) =>
12+
`🎉 Your template is succesfully created
13+
⏩ Next steps: follow the instructions ${cyan('below')} to manage your project:
14+
15+
cd ${projectName}\t\t ${gray('# Navigate to the project directory')}
16+
npm install\t\t ${gray('# Install the project dependencies')}
17+
asyncapi generate fromTemplate <templateName> ../${projectName} \t\t ${gray('# Execute the template from anasyncapi document')}
18+
19+
You can also open the project in your favourite editor and start tweaking it.
20+
`;
21+
22+
const errorMessages = {
23+
alreadyExists: (projectName: string) =>
24+
`Unable to create the project because the directory "${cyan(projectName)}" already exists at "${process.cwd()}/${projectName}".
25+
To specify a different name for the new project, please run the command below with a unique project name:
26+
27+
${gray('asyncapi new template --name ') + gray(projectName) + gray('-1')}`,
28+
};
29+
30+
export default class template extends Command {
31+
static description = 'Creates a new template';
32+
protected commandName = 'template';
33+
static readonly successMessage = successMessage;
34+
static readonly errorMessages = errorMessages;
35+
static flags = templateFlags();
36+
37+
async run() {
38+
const { flags } = await this.parse(template); // NOSONAR
39+
40+
const {
41+
name: projectName,
42+
template: templateName,
43+
renderer: rendererName
44+
} = flags;
45+
46+
const PROJECT_DIRECTORY = join(process.cwd(), projectName);
47+
48+
if (rendererName!=='nunjucks' && rendererName!=='react') {
49+
this.error('Invalid flag check the flag name of renderer');
50+
}
51+
52+
const templateDirectory = resolve(
53+
__dirname,
54+
'../../../assets/create-template/templates/',
55+
templateName
56+
);
57+
58+
{
59+
try {
60+
await fPromises.mkdir(PROJECT_DIRECTORY);
61+
} catch (err: any) {
62+
switch (err.code) {
63+
case 'EEXIST':
64+
this.error(errorMessages.alreadyExists(projectName));
65+
break;
66+
case 'EACCES':
67+
this.error(
68+
`Unable to create the project. We tried to access the "${PROJECT_DIRECTORY}" directory but it was not possible due to file access permissions. Please check the write permissions of your current working directory ("${process.cwd()}").`
69+
);
70+
break;
71+
case 'EPERM':
72+
this.error(
73+
`Unable to create the project. We tried to create the "${PROJECT_DIRECTORY}" directory but the operation requires elevated privileges. Please check the privileges for your current user.`
74+
);
75+
break;
76+
default:
77+
this.error(
78+
`Unable to create the project. Please check the following message for further info about the error:\n\n${err}`
79+
);
80+
}
81+
}
82+
83+
try {
84+
await copyAndModify(templateDirectory, PROJECT_DIRECTORY,rendererName, projectName);
85+
this.log(successMessage(projectName));
86+
} catch (err) {
87+
this.error(
88+
`Unable to create the project. Please check the following message for further info about the error:\n\n${err}`
89+
);
90+
}
91+
this.specFile = await load(`${templateDirectory}/asyncapi.yaml`);
92+
this.metricsMetadata.template = flags.template;
93+
}
94+
}
95+
}
96+
97+
async function copyAndModify(templateDirectory:string, PROJECT_DIRECTORY:string, rendererName:string, projectName:string) {
98+
const packageJsonPath = path.join(templateDirectory, 'package.json');
99+
try {
100+
await fs.copy(templateDirectory, PROJECT_DIRECTORY, {
101+
filter: (src) => {
102+
return !src.endsWith('package.json');
103+
}
104+
});
105+
const packageData = await jsonfile.readFile(packageJsonPath);
106+
if ((packageData.generator && 'renderer' in packageData.generator)) {
107+
packageData.generator.renderer = rendererName;
108+
}
109+
if (packageData.name) {
110+
packageData.name = projectName;
111+
}
112+
113+
await fs.writeJSON(`${PROJECT_DIRECTORY}/package.json`, packageData, { spaces: 2 });
114+
} catch (err) {
115+
console.error('Error:', err);
116+
}
117+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Flags } from '@oclif/core';
2+
3+
export const templateFlags = () => {
4+
return {
5+
help: Flags.help({ char: 'h' }),
6+
name: Flags.string({
7+
char: 'n',
8+
description: 'Name of the Project',
9+
default: 'project',
10+
}),
11+
template: Flags.string({
12+
char: 't',
13+
description: 'Name of the Template',
14+
default: 'default',
15+
}),
16+
file: Flags.string({
17+
char: 'f',
18+
description:
19+
'The path to the AsyncAPI file for generating a template.',
20+
}),
21+
'force-write': Flags.boolean({
22+
default: false,
23+
description:
24+
'Force writing of the generated files to given directory even if it is a git repo with unstaged files or not empty dir (defaults to false)',
25+
}),
26+
renderer: Flags.string({
27+
char: 'r',
28+
default: 'react',
29+
description: 'Creating a template for particular engine, Its value can be either react or nunjucks.'
30+
})
31+
};
32+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { test } from '@oclif/test';
2+
import TestHelper from '../../helpers';
3+
import { expect } from '@oclif/test';
4+
import { cyan, gray } from 'picocolors';
5+
const testHelper = new TestHelper();
6+
const successMessage = (projectName: string) =>
7+
'🎉 Your template is succesfully created';
8+
9+
const errorMessages = {
10+
alreadyExists: (projectName: string) =>
11+
'Unable to create the project',
12+
};
13+
describe('new template', () => {
14+
before(() => {
15+
try {
16+
testHelper.deleteDummyProjectDirectory();
17+
} catch (e: any) {
18+
if (e.code !== 'ENOENT') {
19+
throw e;
20+
}
21+
}
22+
});
23+
24+
describe('creation of new project is successful', () => {
25+
afterEach(() => {
26+
testHelper.deleteDummyProjectDirectory();
27+
});
28+
29+
test
30+
.stderr()
31+
.stdout()
32+
.command(['new:template', '-n=test-project'])
33+
.it('runs new glee command with name flag', async (ctx,done) => {
34+
expect(ctx.stderr).to.equal('');
35+
expect(ctx.stdout).to.contains(successMessage('test-project'));
36+
done();
37+
});
38+
});
39+
40+
describe('when new project name already exists', () => {
41+
beforeEach(() => {
42+
try {
43+
testHelper.createDummyProjectDirectory();
44+
} catch (e: any) {
45+
if (e.code !== 'EEXIST') {
46+
throw e;
47+
}
48+
}
49+
});
50+
51+
afterEach(() => {
52+
testHelper.deleteDummyProjectDirectory();
53+
});
54+
55+
test
56+
.stderr()
57+
.stdout()
58+
.command(['new:template', '-n=test-project'])
59+
.it('should throw error if name of the new project already exists', async (ctx,done) => {
60+
expect(ctx.stderr).to.contains(`Error: ${errorMessages.alreadyExists('test-project')}`);
61+
expect(ctx.stdout).to.equal('');
62+
done();
63+
});
64+
});
65+
});
66+

0 commit comments

Comments
 (0)