Skip to content

Commit

Permalink
feat: create new command for the react template (#1476)
Browse files Browse the repository at this point in the history
Co-authored-by: asyncapi-bot <[email protected]>
  • Loading branch information
AayushSaini101 and asyncapi-bot authored Jul 12, 2024
1 parent 1c129d0 commit 7d9df54
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 1 deletion.
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 {
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();
});
});
});

0 comments on commit 7d9df54

Please sign in to comment.