Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create new command for the react template #1476

Merged
merged 12 commits into from
Jul 12, 2024
34 changes: 34 additions & 0 deletions assets/create-template/templates/default/asyncapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
asyncapi: 3.0.0
info:
title: Temperature Service
version: 1.0.0
description: This service is in charge of processing all the events related to temperature.

servers:
dev:
url: test.mosquitto.org
protocol: mqtt

channels:
temperature/changed:
description: Updates the bedroom temperature in the database when the temperature drops or goes up.
publish:
operationId: temperatureChange
message:
description: Message that is being sent when the temperature in the bedroom changes.
contentType: application/json
payload:
type: object
additionalProperties: false
properties:
temperatureId:
type: string

components:
schemas:
temperatureId:
type: object
additionalProperties: false
properties:
temperatureId:
type: string
10 changes: 10 additions & 0 deletions assets/create-template/templates/default/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "myTemplate",
"generator": {
"renderer": "react",
"supportedProtocols": []
},
"dependencies": {
"@asyncapi/generator-react-sdk": "^1.0.20"
}
}
4 changes: 4 additions & 0 deletions assets/create-template/templates/default/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### First install all the dependencies for template using below command:
npm install
### Run the template using for a specific asyncapi document
asyncapi generate fromTemplate <templateName> ../asyncapi-template
11 changes: 11 additions & 0 deletions assets/create-template/templates/default/template/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { File, Text } from '@asyncapi/generator-react-sdk';

// Pass the others parameters to get the specificatin of the asyncapi document
export default function ({ asyncapi }) {
return (
<File name="asyncapi.md">
<Text>My application's markdown file.</Text>
<Text>App name: **{asyncapi.info().title()}**</Text>
</File>
);
}
19 changes: 19 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ USAGE
* [`asyncapi new`](#asyncapi-new)
* [`asyncapi new file`](#asyncapi-new-file)
* [`asyncapi new glee`](#asyncapi-new-glee)
* [`asyncapi new template`](#asyncapi-new-glee)
* [`asyncapi optimize [SPEC-FILE]`](#asyncapi-optimize-spec-file)
* [`asyncapi start`](#asyncapi-start)
* [`asyncapi start studio`](#asyncapi-start-studio)
Expand Down Expand Up @@ -634,6 +635,24 @@ DESCRIPTION

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

## `asyncapi new template`

Creates a new template

```
USAGE
$ asyncapi new glee [-h] [-n <value>] [-t <value>] [-renderer <value>]

FLAGS
-h, --help Show CLI help.
-n, --name=<value> [default: project] Name of the Project
-t, --template=<value> [default: default] Name of the Template
-r --renderer=<value> [default: react] Name of the renderer engine

DESCRIPTION
Creates a new template project
```

## `asyncapi optimize [SPEC-FILE]`

optimize asyncapi specification file
Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,4 @@
"createhookinit": "oclif generate hook inithook --event=init"
},
"types": "lib/index.d.ts"
}
}
117 changes: 117 additions & 0 deletions src/commands/new/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { promises as fPromises } from 'fs';
import Command from '../../core/base';
import { resolve, join } from 'path';
import { load } from '../../core/models/SpecificationFile';
import fs from 'fs-extra';
import { templateFlags } from '../../core/flags/new/template.flags';
import { cyan, gray } from 'picocolors';
import jsonfile from 'jsonfile';
import path from 'path';

export const successMessage = (projectName: string) =>
`🎉 Your template is succesfully created
⏩ Next steps: follow the instructions ${cyan('below')} to manage your project:

cd ${projectName}\t\t ${gray('# Navigate to the project directory')}
npm install\t\t ${gray('# Install the project dependencies')}
asyncapi generate fromTemplate <templateName> ../${projectName} \t\t ${gray('# Execute the template from anasyncapi document')}

You can also open the project in your favourite editor and start tweaking it.
`;

const errorMessages = {
alreadyExists: (projectName: string) =>
`Unable to create the project because the directory "${cyan(projectName)}" already exists at "${process.cwd()}/${projectName}".
To specify a different name for the new project, please run the command below with a unique project name:

${gray('asyncapi new template --name ') + gray(projectName) + gray('-1')}`,
};

export default class template extends Command {
static description = 'Creates a new template';
protected commandName = 'template';
static readonly successMessage = successMessage;
static readonly errorMessages = errorMessages;
static flags = templateFlags();

async run() {
const { flags } = await this.parse(template); // NOSONAR

const {
name: projectName,
template: templateName,
renderer: rendererName
} = flags;

const PROJECT_DIRECTORY = join(process.cwd(), projectName);

if (rendererName!=='nunjucks' && rendererName!=='react') {
this.error('Invalid flag check the flag name of renderer');
}

const templateDirectory = resolve(
__dirname,
'../../../assets/create-template/templates/',
templateName
);

{
try {
await fPromises.mkdir(PROJECT_DIRECTORY);
} catch (err: any) {
switch (err.code) {
case 'EEXIST':
this.error(errorMessages.alreadyExists(projectName));
break;
case 'EACCES':
this.error(
`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()}").`
);
break;
case 'EPERM':
this.error(
`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.`
);
break;
default:
this.error(
`Unable to create the project. Please check the following message for further info about the error:\n\n${err}`
);
}
}

try {
await copyAndModify(templateDirectory, PROJECT_DIRECTORY,rendererName, projectName);
this.log(successMessage(projectName));
} catch (err) {
this.error(
`Unable to create the project. Please check the following message for further info about the error:\n\n${err}`
);
}
this.specFile = await load(`${templateDirectory}/asyncapi.yaml`);
this.metricsMetadata.template = flags.template;
}
}
}

async function copyAndModify(templateDirectory:string, PROJECT_DIRECTORY:string, rendererName:string, projectName:string) {
const packageJsonPath = path.join(templateDirectory, 'package.json');
try {
await fs.copy(templateDirectory, PROJECT_DIRECTORY, {
filter: (src) => {
return !src.endsWith('package.json');
}
});
const packageData = await jsonfile.readFile(packageJsonPath);
if ((packageData.generator && 'renderer' in packageData.generator)) {
packageData.generator.renderer = rendererName;
}
if (packageData.name) {
packageData.name = projectName;
}

await fs.writeJSON(`${PROJECT_DIRECTORY}/package.json`, packageData, { spaces: 2 });
} catch (err) {
console.error('Error:', err);
}
}
32 changes: 32 additions & 0 deletions src/core/flags/new/template.flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Flags } from '@oclif/core';

export const templateFlags = () => {
return {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to add also renderer flag as some people may use nunjucks, see: https://www.asyncapi.com/docs/tools/generator/template-development

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done @Amzani

help: Flags.help({ char: 'h' }),
name: Flags.string({
char: 'n',
description: 'Name of the Project',
default: 'project',
}),
template: Flags.string({
char: 't',
description: 'Name of the Template',
default: 'default',
}),
file: Flags.string({
char: 'f',
description:
'The path to the AsyncAPI file for generating a template.',
}),
'force-write': Flags.boolean({
default: false,
description:
'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)',
}),
renderer: Flags.string({
char: 'r',
default: 'react',
description: 'Creating a template for particular engine, Its value can be either react or nunjucks.'
})
};
};
66 changes: 66 additions & 0 deletions test/integration/new/template.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { test } from '@oclif/test';
import TestHelper from '../../helpers';
import { expect } from '@oclif/test';
import { cyan, gray } from 'picocolors';
const testHelper = new TestHelper();
const successMessage = (projectName: string) =>
'🎉 Your template is succesfully created';

const errorMessages = {
alreadyExists: (projectName: string) =>
'Unable to create the project',
};
describe('new template', () => {
before(() => {
try {
testHelper.deleteDummyProjectDirectory();
} catch (e: any) {
if (e.code !== 'ENOENT') {
throw e;
}
}
});

describe('creation of new project is successful', () => {
afterEach(() => {
testHelper.deleteDummyProjectDirectory();
});

test
.stderr()
.stdout()
.command(['new:template', '-n=test-project'])
.it('runs new glee command with name flag', async (ctx,done) => {
expect(ctx.stderr).to.equal('');
expect(ctx.stdout).to.contains(successMessage('test-project'));
done();
});
});

describe('when new project name already exists', () => {
beforeEach(() => {
try {
testHelper.createDummyProjectDirectory();
} catch (e: any) {
if (e.code !== 'EEXIST') {
throw e;
}
}
});

afterEach(() => {
testHelper.deleteDummyProjectDirectory();
});

test
.stderr()
.stdout()
.command(['new:template', '-n=test-project'])
.it('should throw error if name of the new project already exists', async (ctx,done) => {
expect(ctx.stderr).to.contains(`Error: ${errorMessages.alreadyExists('test-project')}`);
expect(ctx.stdout).to.equal('');
done();
});
});
});

Loading