Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 51 additions & 15 deletions src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as uuid from 'uuid';

import {
AppCreator,
BoilerplateCreator,
FolderDetails,
VariousUtils,
} from '../misc';
Expand All @@ -18,17 +19,32 @@ export default class Create extends Command {

public static flags = {
help: flags.help({ char: 'h' }),
name: flags.string({char: 'n', description: 'Name of the app'}),
description: flags.string({char: 'd', description: 'Description of the app'}),
author: flags.string({char: 'a', description: 'Author\'s name'}),
homepage: flags.string({char: 'H', description: 'Author\'s or app\'s home page'}),
support: flags.string({char: 's', description: 'URL or email address to get support for the app'}),
name: flags.string({ char: 'n', description: 'Name of the app' }),
description: flags.string({
char: 'd',
description: 'Description of the app',
}),
author: flags.string({ char: 'a', description: "Author's name" }),
homepage: flags.string({
char: 'H',
description: "Author's or app's home page",
}),
boilerplate: flags.string({
char: 'b',
description:
'Boilerplate templates (slash-command, api-endpoint, settings)',
multiple: true,
options: ['slash-command', 'api-endpoint', 'settings'],
}),
support: flags.string({
char: 's',
description: 'URL or email address to get support for the app',
}),
};

public async run() {
if (!semver.satisfies(process.version, '>=4.2.0')) {
this.error('NodeJS version needs to be at least 4.2.0 or higher.');
return;
}

const info: IAppInfo = {
Expand All @@ -39,23 +55,35 @@ export default class Create extends Command {
author: {},
} as IAppInfo;

this.log('Let\'s get started creating your app.');
this.log("Let's get started creating your app.");
this.log('We need some information first:');
this.log('');

const { flags } = this.parse(Create);
info.name = flags.name ? flags.name : await cli.prompt(chalk.bold(' App Name'));
info.name = flags.name
? flags.name
: await cli.prompt(chalk.bold(' App Name'));
info.nameSlug = VariousUtils.slugify(info.name);
info.classFile = `${ pascalCase(info.name) }App.ts`;
info.classFile = `${pascalCase(info.name)}App.ts`;

info.description = flags.description ? flags.description : await cli.prompt(chalk.bold(' App Description'));
info.author.name = flags.author ? flags.author : await cli.prompt(chalk.bold(' Author\'s Name'));
info.author.homepage = flags.homepage ? flags.homepage : await cli.prompt(chalk.bold(' Author\'s Home Page'));
info.author.support = flags.support ? flags.support : await cli.prompt(chalk.bold(' Author\'s Support Page'));
info.description = flags.description
? flags.description
: await cli.prompt(chalk.bold(' App Description'));
info.author.name = flags.author
? flags.author
: await cli.prompt(chalk.bold(" Author's Name"));
info.author.homepage = flags.homepage
? flags.homepage
: await cli.prompt(chalk.bold(" Author's Home Page"));
info.author.support = flags.support
? flags.support
: await cli.prompt(chalk.bold(" Author's Support Page"));

const folder = path.join(process.cwd(), info.nameSlug);

cli.action.start(`Creating a Rocket.Chat App in ${ chalk.green(folder) }`);
cli.action.start(
`Creating a Rocket.Chat App in ${chalk.green(folder)}`,
);

const fd = new FolderDetails(this);
fd.setAppInfo(info);
Expand All @@ -68,9 +96,17 @@ export default class Create extends Command {
await fd.readInfoFile();
} catch (e) {
this.error(e && e.message ? e.message : e);
return;
}

cli.action.stop(chalk.cyan('done!'));

const boilerplateFlags = flags.boilerplate || [];
if (boilerplateFlags.length > 0) {
this.log('');
this.log('Generating boilerplate files:');
const boilerplateCreator = new BoilerplateCreator(fd, this);
await boilerplateCreator.generate(boilerplateFlags);
this.log(chalk.cyan('Boilerplate done!'));
}
}
}
121 changes: 121 additions & 0 deletions src/misc/BoilerplateCreator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Command } from '@oclif/command';
import * as fs from 'fs';
import * as path from 'path';
import { FolderDetails } from './folderDetails';

const TEMPLATES: Record<
string,
{ file: string; content: (appName: string) => string }
> = {
'slash-command': {
file: 'SlashCommand.ts',
content: (
appName,
) => `import { ISlashCommand, SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands';
import { IModify, IRead } from '@rocket.chat/apps-engine/definition/accessors';
import { ${appName} } from '../${appName}';

export class MyCommand implements ISlashCommand {
public command = 'mycommand';
public i18nParamsExample = 'MyCommand_Params';
public i18nDescription = 'MyCommand_Description';
public providesPreview = false;

constructor(private readonly app: ${appName}) {}

public async executor(
context: SlashCommandContext,
read: IRead,
modify: IModify,
): Promise<void> {
const sender = context.getSender();
const room = context.getRoom();
const msg = modify.getCreator().startMessage()
.setRoom(room)
.setText(\`Hello, \${sender.name}!\`);
await modify.getCreator().finish(msg);
}
}
`,
},
'api-endpoint': {
file: 'Endpoint.ts',
content: (
_appName,
) => `import { ApiEndpoint } from '@rocket.chat/apps-engine/definition/api';
import { IApiEndpointInfo, IApiRequest, IApiResponse } from '@rocket.chat/apps-engine/definition/api';

export class MyEndpoint extends ApiEndpoint {
public path = 'my-endpoint';

public async get(request: IApiRequest, endpoint: IApiEndpointInfo): Promise<IApiResponse> {
return this.success({ message: 'Hello!' });
}

public async post(request: IApiRequest, endpoint: IApiEndpointInfo): Promise<IApiResponse> {
const { body } = request;
if (!body?.data) {
return this.notFound('Missing field: data');
}
return this.success({ received: body.data });
}
}
`,
},
'settings': {
file: 'Settings.ts',
content: (
_appName,
) => `import { ISetting, SettingType } from '@rocket.chat/apps-engine/definition/settings';

export enum AppSetting {
ApiToken = 'api_token',
WelcomeMessage = 'welcome_message',
}

export const settings: Array<ISetting> = [
{
id: AppSetting.ApiToken,
type: SettingType.STRING,
packageValue: '',
required: true,
public: false,
i18nLabel: 'ApiToken_Label',
i18nDescription: 'ApiToken_Description',
},
{
id: AppSetting.WelcomeMessage,
type: SettingType.STRING,
packageValue: 'Welcome!',
required: false,
public: true,
i18nLabel: 'WelcomeMessage_Label',
i18nDescription: 'WelcomeMessage_Description',
},
];
`,
},
};

export class BoilerplateCreator {
constructor(
private readonly fd: FolderDetails,
private readonly command: Command,
) {}

public async generate(templates: Array<string>): Promise<void> {
const appClassName = this.fd.info.classFile.replace('.ts', '');
for (const template of templates) {
const tpl = TEMPLATES[template];
if (!tpl) {
this.command.warn(
`Unknown boilerplate: "${template}", skipping.`,
);
continue;
}
const destPath = path.join(this.fd.folder, tpl.file);
fs.writeFileSync(destPath, tpl.content(appClassName), 'utf8');
this.command.log(` ✔ Generated ${tpl.file}`);
}
}
}
Loading