Links
https://github.com/EndyKaufman/kaufman-bot - source code of bot
https://telegram.me/KaufmanBot - current bot in telegram
https://github.com/kaufman-bot/schematics-example - project generated with @kaufman-bot/schematics
Description of work
In the current post, I'm copying the NestJS schemas for building apps and libraries, and after a lot of modifications, I'll make the schemas for building kaufman-bot apps and libraries.
Create new libraries
Create new library for schematics
npm run nx -- g lib schematics
endy@endy-virtual-machine:~/Projects/current/kaufman-bot$ npm run nx -- g lib schematics
> kaufman-bot@2.2.2 nx
> nx "g" "lib" "schematics"
CREATE libs/schematics/README.md
CREATE libs/schematics/.babelrc
CREATE libs/schematics/src/index.ts
CREATE libs/schematics/tsconfig.json
CREATE libs/schematics/tsconfig.lib.json
UPDATE tsconfig.base.json
CREATE libs/schematics/project.json
UPDATE workspace.json
CREATE libs/schematics/.eslintrc.json
CREATE libs/schematics/jest.config.js
CREATE libs/schematics/tsconfig.spec.json
CREATE libs/schematics/src/lib/schematics.module.ts
Append logic for schematics
Remove not need files
rm -rf libs/schematics/src/lib
Clone nrwl nx repository
git clone git@github.com:nrwl/nx.git
endy@endy-virtual-machine:~/Projects/current/kaufman-bot$ git clone git@github.com:nrwl/nx.git
Cloning into 'nx'...
remote: Enumerating objects: 94099, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 94099 (delta 0), reused 2 (delta 0), pack-reused 94089
Receiving objects: 100% (94099/94099), 69.44 MiB | 4.43 MiB/s, done.
Resolving deltas: 100% (64903/64903), done.
Copy-past logic
Copy nest schematics to kaufman-bot libs
cp -Rf ./nx/packages/nest/** ./libs/schematics
Remove not need files
rm -rf libs/schematics/src/generators/class libs/schematics/src/generators/controller libs/schematics/src/generators/convert-tslint-to-eslint libs/schematics/src/generators/decorator libs/schematics/src/generators/filter libs/schematics/src/generators/gateway libs/schematics/src/generators/guard libs/schematics/src/generators/interceptor libs/schematics/src/generators/interface libs/schematics/src/generators/middleware libs/schematics/src/generators/module libs/schematics/src/generators/pipe libs/schematics/src/generators/provider libs/schematics/src/generators/resolver libs/schematics/src/generators/resource libs/schematics/src/generators/service libs/schematics/src/lib libs/schematics/src/migrations libs/schematics/README.md libs/schematics/migrations.spec.ts libs/schematics/migrations.json libs/schematics/index.ts nx libs/schematics/index.ts libs/schematics/src/generators/library/library.spec.ts libs/schematics/src/generators/library/snapshots libs/schematics/src/index.ts libs/schematics/src/generators/init/init.spec.ts libs/schematics/src/generators/utils/run-nest-schematic.spec.ts libs/schematics/src/generators/application/application.spec.ts libs/schematics/src/generators/application/files/app/app.controller.spec.ts_tmpl_ libs/schematics/src/generators/application/files/app/app.controller.ts_tmpl_ libs/schematics/src/generators/application/files/app/app.service.spec.ts_tmpl_ libs/schematics/src/generators/application/files/app/app.service.ts_tmpl_ libs/schematics/src/generators/library/snapshots/library.spec.ts.snap libs/schematics/src/generators/library/library.spec.ts libs/schematics/src/generators/utils/run-nest-schematic.spec.ts libs/schematics/src/index.ts libs/schematics/src/generators/library/files/controller/src/lib/fileName.controller.spec.ts_tmpl_ libs/schematics/src/generators/library/files/controller/src/lib/fileName.controller.ts_tmpl_ libs/schematics/src/generators/library/files/service/src/lib/fileName.service.spec.ts_tmpl_
Update generators list
libs/schematics/generators.json
{
  "name": "nx/nest",
  "version": "0.1",
  "extends": ["@nrwl/workspace"],
  "schematics": {
    "application": {
      "factory": "./src/generators/application/application#applicationSchematic",
      "schema": "./src/generators/application/schema.json",
      "aliases": ["app"],
      "x-type": "application",
      "description": "Create a KaufmanBot application."
    },
    "init": {
      "factory": "./src/generators/init/init#initSchematic",
      "schema": "./src/generators/init/schema.json",
      "description": "Initialize the `@kaufman-bot/schematics` plugin.",
      "aliases": ["ng-add"],
      "hidden": true
    },
    "library": {
      "factory": "./src/generators/library/library#librarySchematic",
      "schema": "./src/generators/library/schema.json",
      "aliases": ["lib"],
      "x-type": "library",
      "description": "Create a new KaufmanBot library."
    }
  },
  "generators": {
    "application": {
      "factory": "./src/generators/application/application",
      "schema": "./src/generators/application/schema.json",
      "aliases": ["app"],
      "x-type": "application",
      "description": "Create a KaufmanBot application."
    },
    "init": {
      "factory": "./src/generators/init/init",
      "schema": "./src/generators/init/schema.json",
      "description": "Initialize the `@kaufman-bot/schematics` plugin.",
      "aliases": ["ng-add"],
      "hidden": true
    },
    "library": {
      "factory": "./src/generators/library/library",
      "schema": "./src/generators/library/schema.json",
      "aliases": ["lib"],
      "x-type": "library",
      "description": "Create a new KaufmanBot library."
    }
  }
}
Replace all names
npx -y replace-in-files-cli --string="build/packages/nest" --replacement="dist/libs/schematics" './libs/schematics/**'
npx -y replace-in-files-cli --string="packages/nest" --replacement="libs/schematics" './libs/schematics/**'
Update template file of main
libs/schematics/src/generators/application/files/main.ts__tmpl__
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('source-map-support').install();
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import env from 'env-var';
import { getBotToken } from 'nestjs-telegraf';
import { AppModule } from './app/app.module';
const logger = new Logger('Application');
//do something when app is closing
process.on('exit', exitHandler.bind(null, { cleanup: true }));
//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, { exit: true }));
// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, { exit: true }));
process.on('SIGUSR2', exitHandler.bind(null, { exit: true }));
//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, { exit: true }));
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const TELEGRAM_BOT_WEB_HOOKS_PATH = env
    .get('TELEGRAM_BOT_WEB_HOOKS_PATH')
    .asString();
  if (TELEGRAM_BOT_WEB_HOOKS_PATH) {
    const bot = app.get(getBotToken());
    app.use(bot.webhookCallback(TELEGRAM_BOT_WEB_HOOKS_PATH));
  }
  const port = env.get('PORT').default(3333).asPortNumber();
  await app.listen(port);
  logger.log(`🚀 Application is running on: http://localhost:${port}`);
}
try {
  bootstrap().catch((err) => {
    logger.error(err, err.stack);
  });
} catch (err) {
  logger.error(err, err.stack);
}
function exitHandler(options, exitCode) {
  if (options.cleanup) {
    logger.log('exit: clean');
  }
  if (exitCode || exitCode === 0) {
    if (exitCode !== 0) {
      logger.error(exitCode, exitCode.stack);
      logger.log(`exit: code - ${exitCode}`);
    } else {
      logger.log(`exit: code - ${exitCode}`);
    }
  }
  if (options.exit) {
    process.exit();
  }
}
Update template of service
libs/schematics/src/generators/application/files/app/app.service.ts__tmpl__
import { BotInGroupsProcessorService } from '@kaufman-bot/bot-in-groups-server';
import { BotCommandsService } from '@kaufman-bot/core-server';
import { Injectable, Logger } from '@nestjs/common';
import { On, Start, Update, Use } from 'nestjs-telegraf';
import { Context } from 'telegraf';
@Update()
@Injectable()
export class AppService {
  private readonly logger = new Logger(AppService.name);
  constructor(
    private readonly botCommandsService: BotCommandsService,
    private readonly botInGroupsProcessorService: BotInGroupsProcessorService
  ) {}
  @Start()
  async startCommand(ctx: Context) {
    await this.botCommandsService.start(ctx);
  }
  @Use()
  async use(ctx) {
    try {
      await this.botInGroupsProcessorService.process(ctx);
    } catch (err) {
      this.logger.error(err, err.stack);
    }
  }
  @On('sticker')
  async onSticker(ctx) {
    try {
      await this.botCommandsService.process(ctx);
    } catch (err) {
      this.logger.error(err, err.stack);
    }
  }
  @On('text')
  async onMessage(ctx) {
    try {
      await this.botCommandsService.process(ctx);
    } catch (err) {
      this.logger.error(err, err.stack);
    }
  }
}
Update template of module
libs/schematics/src/generators/application/files/app/app.module.ts__tmpl__
import { BotInGroupsModule } from '@kaufman-bot/bot-in-groups-server';
import { BotCommandsModule } from '@kaufman-bot/core-server';
import { DebugMessagesModule } from '@kaufman-bot/debug-messages-server';
import { FactsGeneratorModule } from '@kaufman-bot/facts-generator-server';
import { LanguageSwitherModule } from '@kaufman-bot/language-swither-server';
import { ShortCommandsModule } from '@kaufman-bot/short-commands-server';
import { Module } from '@nestjs/common';
import env from 'env-var';
import { TelegrafModule } from 'nestjs-telegraf';
import {
  getDefaultTranslatesModuleOptions,
  TranslatesModule,
} from 'nestjs-translates';
import { join } from 'path';
import { AppService } from './app.service';
const TELEGRAM_BOT_WEB_HOOKS_DOMAIN = env
  .get('TELEGRAM_BOT_WEB_HOOKS_DOMAIN')
  .asString();
const TELEGRAM_BOT_WEB_HOOKS_PATH = env
  .get('TELEGRAM_BOT_WEB_HOOKS_PATH')
  .asString();
const BOT_NAMES = env.get('BOT_NAMES').required().asArray();
@Module({
  imports: [
    TelegrafModule.forRoot({
      token: env.get('TELEGRAM_BOT_TOKEN').required().asString(),
      launchOptions: {
        dropPendingUpdates: true,
        ...(TELEGRAM_BOT_WEB_HOOKS_DOMAIN && TELEGRAM_BOT_WEB_HOOKS_PATH
          ? {
              webhook: {
                domain: TELEGRAM_BOT_WEB_HOOKS_DOMAIN,
                hookPath: TELEGRAM_BOT_WEB_HOOKS_PATH,
              },
            }
          : {}),
      },
    }),
    TranslatesModule.forRoot(
      getDefaultTranslatesModuleOptions({
        localePaths: [
          join(__dirname, 'assets', 'i18n'),
          join(__dirname, 'assets', 'i18n', 'getText'),
          join(__dirname, 'assets', 'i18n', 'class-validator-messages'),
        ],
        vendorLocalePaths: [join(__dirname, 'assets', 'i18n')],
        locales: ['en'],
      })
    ),
    DebugMessagesModule.forRoot(),
    BotCommandsModule.forRoot({
      admins: env.get('TELEGRAM_BOT_ADMINS').default('').asArray(','),
      commit: env.get('DEPLOY_COMMIT').default('').asString(),
      date: env.get('DEPLOY_DATE').default('').asString(),
      version: env.get('DEPLOY_VERSION').default('').asString(),
    }),
    ShortCommandsModule.forRoot({
      commands: {
        en: {
          '*fact*|history': 'get facts',
          '*what you can do*|faq': 'help',
          'disable debug': 'debug off',
          'enable debug': 'debug on',
        },
      },
    }),
    BotInGroupsModule.forRoot({
      botNames: {
        en: BOT_NAMES,
      },
      botMeetingInformation: {
        en: [`Hello! I'm ${BOT_NAMES[0]} 😉`, 'Hello!', 'Hello 🖖'],
      },
    }),
    LanguageSwitherModule.forRoot(),
    FactsGeneratorModule.forRoot(),
  ],
  providers: [AppService],
})
export class AppModule {}
Update library service template
libs/schematics/src/generators/library/files/service/src/lib/__fileName__.service.ts__tmpl__
import {
  BotCommandsEnum,
  BotCommandsProvider,
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Inject, Injectable, Logger } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesService } from 'nestjs-translates';
export const <%= constantName %>_CONFIG = '<%= constantName %>_CONFIG';
export interface <%= className %>Config {
  title: string;
  name: string;
  descriptions: string;
  usage: string[];
  spyWords: string[];
  category: string;
}
@Injectable()
export class <%= className %>Service implements BotCommandsProvider {
  private readonly logger = new Logger(<%= className %>Service.name);
  constructor(
    @Inject(<%= constantName %>_CONFIG)
    private readonly <%= propertyName %>Config: <%= className %>Config,
    private readonly translatesService: TranslatesService,
    private readonly commandToolsService: BotCommandsToolsService,
    private readonly botCommandsToolsService: BotCommandsToolsService
  ) {}
  async onHelp<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    return await this.onMessage({
      ...msg,
      text: `${this.<%= propertyName %>Config.name} ${BotCommandsEnum.help}`,
    });
  }
  async onMessage<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    const spyWord = this.<%= propertyName %>Config.spyWords.find((spyWord) =>
      this.commandToolsService.checkCommands(msg.text, [spyWord], locale)
    );
    if (spyWord) {
      if (
        this.commandToolsService.checkCommands(
          msg.text,
          [BotCommandsEnum.help],
          locale
        )
      ) {
        return {
          type: 'markdown',
          message: msg,
          markdown: this.commandToolsService.generateHelpMessage(msg, {
            locale,
            name: this.<%= propertyName %>Config.title,
            descriptions: this.<%= propertyName %>Config.descriptions,
            usage: this.<%= propertyName %>Config.usage,
            category: this.<%= propertyName %>Config.category,
          }),
        };
      }
      const processedMsg = await this.process(msg, locale);
      if (typeof processedMsg === 'string') {
        return {
          type: 'text',
          message: msg,
          text: processedMsg,
        };
      }
      if (processedMsg) {
        return { type: 'message', message: processedMsg };
      }
      this.logger.warn(`Unhandled commands for text: "${msg.text}"`);
      this.logger.debug(msg);
    }
    return null;
  }
  private async process<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg, locale: string) {
    if (
      this.commandToolsService.checkCommands(
        msg.text,
        [getText('ping')],
        locale
      )
    ) {
      return this.translatesService.translate(
          getText('pong'),
          locale
      );
    }
    return null;
  }
}
Update library module template
libs/schematics/src/generators/library/files/common/src/lib/__fileName__.module.ts__tmpl__
import {
  BotCommandsCategory,
  BotCommandsModule,
  BOT_COMMANDS_PROVIDER,
} from '@kaufman-bot/core-server';
import { DynamicModule, Module } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesModule } from 'nestjs-translates';
import {
  <%= className %>Service,
  <%= className %>Config,
  <%= constantName %>_CONFIG,
} from './<%= fileName %>.service';
@Module({
    imports: [TranslatesModule, BotCommandsModule],
    exports: [TranslatesModule, BotCommandsModule],
})
export class <%= className %>Module {
  static forRoot(): DynamicModule {
    return {
      module: <%= className %>Module,
      providers: [
        {
            provide: <%= constantName %>_CONFIG,
            useValue: <<%= className %>Config>{
                title: getText('<%= TitleName %> commands'),
                name: '<%= propertyName %>',
                usage: [
                    getText('<%= propertyName %> ping'),
                    getText('<%= propertyName %> help'),
                ],
                descriptions: getText(
                    'Commands for <%= titleName %>'
                ),
                spyWords: [getText('<%= propertyName %>')],
                category: BotCommandsCategory.user,
            },
        },
        {
            provide: BOT_COMMANDS_PROVIDER,
            useClass: <%= className %>Service,
        },
      ],
      exports: [<%= constantName %>_CONFIG],
    };
  }
}
Add custom schematics helpers
libs/schematics/src/generators/init/lib/add-custom.ts
import type { Tree } from '@nrwl/devkit';
import { updateJson } from '@nrwl/devkit';
export function updateTsConfig(tree: Tree) {
  if (tree.exists('tsconfig.base.json')) {
    updateJson(tree, 'tsconfig.base.json', (json) => {
      json['compilerOptions'] = {
        ...json['compilerOptions'],
        allowSyntheticDefaultImports: true,
        strictNullChecks: true,
        noImplicitOverride: true,
        strictPropertyInitialization: true,
        noImplicitReturns: true,
        noFallthroughCasesInSwitch: true,
        esModuleInterop: true,
        noImplicitAny: false,
      };
      return json;
    });
  }
}
export function addScript(tree: Tree, projectName: string) {
  updateJson(tree, 'package.json', (json) => {
    json['scripts'] = {
      ...json['scripts'],
      rucken: 'rucken',
      nx: 'nx',
    };
    if (!json['scripts'][`serve:${projectName}-local`]) {
      json['scripts'][
        `serve:${projectName}-local`
      ] = `export $(xargs < ./.env.local) > /dev/null 2>&1 && npm run nx -- serve ${projectName}`;
    }
    return json;
  });
}
export function addGitIgnoreEntry(host: Tree) {
  if (host.exists('.gitignore')) {
    let content = host.read('.gitignore', 'utf-8');
    if (!content?.includes('*.env.*')) {
      content = `${content}\n*.env.*\n`;
    }
    host.write('.gitignore', content);
  } else {
    host.write('.gitignore', `*.env.*\n`);
  }
}
export function addEnvFilesEntry(host: Tree, botName: string) {
  append('.env.local');
  append('.env-example.local');
  function append(filename: string) {
    let content = '';
    if (host.exists(filename)) {
      content = host.read(filename, 'utf-8') || '';
    }
    const contentRows = content.split('\n');
    const newRows: string[] = [];
    const rows = [
      `TELEGRAM_BOT_TOKEN=`,
      `TELEGRAM_BOT_WEB_HOOKS_DOMAIN=`,
      `TELEGRAM_BOT_WEB_HOOKS_PATH=`,
      `TELEGRAM_BOT_ADMINS=`,
      `BOT_NAMES=${botName}`,
    ];
    for (
      let contentRowindex = 0;
      contentRowindex < contentRows.length;
      contentRowindex++
    ) {
      const contentRow = contentRows[contentRowindex];
      for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
        const row = rows[rowIndex];
        if ((contentRow || '').split('=')[0] !== (row || '').split('=')[0]) {
          newRows.push(row);
        }
      }
    }
    host.write(
      filename,
      [
        ...(contentRows.length === 1 && !contentRows[0] ? [] : contentRows),
        ...newRows,
      ].join('\n')
    );
  }
}
Update index file of init generator
libs/schematics/src/generators/init/lib/index.ts
export * from './add-custom';
export * from './add-dependencies';
export * from './normalize-options';
Update versions file
libs/schematics/src/utils/versions.ts
export const nxVersion = '*';
export const nestJsVersion7 = '^7.0.0';
export const nestJsVersion8 = '^8.0.0';
export const nestJsSchematicsVersion = '^8.0.0';
export const rxjsVersion6 = '~6.6.3';
export const rxjsVersion7 = '^7.0.0';
export const reflectMetadataVersion = '^0.1.13';
export const kaufmanBotVersion = '^2.2.2';
Update dependencies helper
libs/schematics/src/generators/init/lib/add-dependencies.ts
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { addDependenciesToPackageJson, readJson } from '@nrwl/devkit';
import { satisfies } from 'semver';
import {
  kaufmanBotVersion,
  nestJsSchematicsVersion,
  nestJsVersion7,
  nestJsVersion8,
  nxVersion,
  reflectMetadataVersion,
  rxjsVersion6,
  rxjsVersion7,
} from '../../../utils/versions';
export function addDependencies(tree: Tree): GeneratorCallback {
  // Old nest 7 and rxjs 6 by default
  let NEST_VERSION = nestJsVersion7;
  let RXJS = rxjsVersion6;
  const packageJson = readJson(tree, 'package.json');
  if (packageJson.dependencies['@angular/core']) {
    let rxjs = packageJson.dependencies['rxjs'];
    if (rxjs.startsWith('~') || rxjs.startsWith('^')) {
      rxjs = rxjs.substring(1);
    }
    if (satisfies(rxjs, rxjsVersion7)) {
      NEST_VERSION = nestJsVersion8;
      RXJS = packageJson.dependencies['rxjs'];
    }
  } else {
    NEST_VERSION = nestJsVersion8;
    RXJS = rxjsVersion7;
  }
  return addDependenciesToPackageJson(
    tree,
    {
      '@nestjs/common': NEST_VERSION,
      '@nestjs/core': NEST_VERSION,
      '@nestjs/platform-express': NEST_VERSION,
      'reflect-metadata': reflectMetadataVersion,
      '@kaufman-bot/bot-in-groups-server': kaufmanBotVersion,
      '@kaufman-bot/core-server': kaufmanBotVersion,
      '@kaufman-bot/debug-messages-server': kaufmanBotVersion,
      '@kaufman-bot/language-swither-server': kaufmanBotVersion,
      '@kaufman-bot/short-commands-server': kaufmanBotVersion,
      '@kaufman-bot/html-scraper-server': kaufmanBotVersion,
      '@kaufman-bot/facts-generator-server': kaufmanBotVersion,
      '@ngneat/transloco': '^4.0.0',
      '@ngneat/transloco-locale': '^4.0.0',
      'class-validator-multi-lang': '^0.130.201',
      'class-transformer': '^0.5.1',
      'class-transformer-global-storage': '^0.4.1-1',
      'env-var': '^7.1.1',
      'nestjs-telegraf': '^2.4.0',
      'nestjs-translates': '^1.0.3',
      rxjs: RXJS,
      tslib: '^2.0.0',
    },
    {
      '@nestjs/schematics': nestJsSchematicsVersion,
      '@nestjs/testing': NEST_VERSION,
      '@nrwl/nest': nxVersion,
      '@ngneat/transloco-keys-manager': '^3.4.1',
      "source-map-support": "^0.5.21",
      rucken: '^3.5.3',
    }
  );
}
Update application generator helper
libs/schematics/src/generators/application/application.ts
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { convertNxGenerator, formatFiles } from '@nrwl/devkit';
import { applicationGenerator as nodeApplicationGenerator } from '@nrwl/node';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { initGenerator } from '../init/init';
import { addScript } from '../init/lib';
import {
  createFiles,
  normalizeOptions,
  toNodeApplicationGeneratorOptions,
  updateTsConfig,
} from './lib';
import type { ApplicationGeneratorOptions } from './schema';
export async function applicationGenerator(
  tree: Tree,
  rawOptions: ApplicationGeneratorOptions
): Promise<GeneratorCallback> {
  const options = normalizeOptions(tree, rawOptions);
  addScript(tree, rawOptions.name);
  const initTask = await initGenerator(tree, {
    botName: options.botName,
    unitTestRunner: options.unitTestRunner,
    skipFormat: true,
  });
  const nodeApplicationTask = await nodeApplicationGenerator(tree, {
    ...toNodeApplicationGeneratorOptions(options),
  });
  createFiles(tree, options);
  updateTsConfig(tree, options);
  if (!options.skipFormat) {
    await formatFiles(tree);
  }
  return runTasksInSerial(initTask, nodeApplicationTask);
}
export default applicationGenerator;
export const applicationSchematic = convertNxGenerator(applicationGenerator);
Update init helper
libs/schematics/src/generators/init/init.ts
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { convertNxGenerator, formatFiles } from '@nrwl/devkit';
import { initGenerator as nodeInitGenerator } from '@nrwl/node';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { setDefaultCollection } from '@nrwl/workspace/src/utilities/set-default-collection';
import {
  addDependencies,
  addEnvFilesEntry,
  addGitIgnoreEntry,
  normalizeOptions,
  updateTsConfig,
} from './lib';
import type { InitGeneratorOptions } from './schema';
export async function initGenerator(
  tree: Tree,
  rawOptions: InitGeneratorOptions
): Promise<GeneratorCallback> {
  const options = normalizeOptions(rawOptions);
  setDefaultCollection(tree, '@kaufman-bot/schematics');
  updateTsConfig(tree);
  addGitIgnoreEntry(tree);
  addEnvFilesEntry(tree, options.botName);
  const nodeInitTask = await nodeInitGenerator(tree, options);
  const installPackagesTask = addDependencies(tree);
  if (!options.skipFormat) {
    await formatFiles(tree);
  }
  return runTasksInSerial(nodeInitTask, installPackagesTask);
}
export default initGenerator;
export const initSchematic = convertNxGenerator(initGenerator);
Create public.ts
libs/schematics/src/public.ts
export { applicationGenerator } from './generators/application/application';
export { initGenerator } from './generators/init/init';
export { libraryGenerator } from './generators/library/library';
Update package.json
libs/schematics/package.json
{
  "name": "@kaufman-bot/schematics",
  "description": "The Nx Plugin for Nest that contains executors and generators for allowing your workspace to create KaufmanBot APIs.",
  "license": "MIT",
  "author": "EndyKaufman <admin@site15.ru>",
  "keywords": [
    "Monorepo",
    "Node",
    "Nest",
    "CLI",
    "kaufman-bot",
    "nx",
    "schematics",
    "nests",
    "telegram"
  ],
  "bugs": {
    "url": "https://github.com/EndyKaufman/kaufman-bot/issues"
  },
  "homepage": "https://github.com/EndyKaufman/kaufman-bot",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/EndyKaufman/kaufman-bot.git"
  },
  "maintainers": [
    {
      "name": "EndyKaufman",
      "email": "admin@site15.ru"
    }
  ],
  "readme": "README.md",
  "main": "./index.js",
  "typings": "./index.d.ts",
  "schematics": "./generators.json",
  "dependencies": {
    "@nrwl/devkit": "13.8.1",
    "@nrwl/linter": "13.8.1",
    "@nrwl/node": "13.8.1",
    "@nrwl/js": "^13.10.2",
    "@nrwl/jest": "13.8.1",
    "@nestjs/schematics": "^8.0.0"
  },
  "version": "2.2.2",
  "i18n": [
    {
      "scope": "schematics",
      "path": "src/i18n",
      "strategy": "join"
    }
  ]
}
Update project.json file
libs/schematics/project.json
{
  "root": "libs/schematics",
  "sourceRoot": "libs/schematics",
  "projectType": "library",
  "targets": {
    "test": {
      "executor": "@nrwl/jest:jest",
      "options": {
        "jestConfig": "libs/schematics/jest.config.js",
        "passWithNoTests": true
      },
      "outputs": ["coverage/libs/schematics"]
    },
    "build": {
      "executor": "@nrwl/js:tsc",
      "options": {
        "outputPath": "dist/libs/schematics",
        "tsConfig": "libs/schematics/tsconfig.lib.json",
        "main": "libs/schematics/index.ts",
        "updateBuildableProjectDepsInPackageJson": false,
        "assets": [
          {
            "input": "libs/schematics",
            "glob": "**/files/**",
            "output": "/"
          },
          {
            "input": "libs/schematics",
            "glob": "**/files/**/.gitkeep",
            "output": "/"
          },
          {
            "input": "libs/schematics",
            "glob": "**/*.json",
            "ignore": ["**/tsconfig*.json", "project.json"],
            "output": "/"
          },
          {
            "input": "libs/schematics",
            "glob": "**/*.js",
            "ignore": ["**/jest.config.js"],
            "output": "/"
          },
          {
            "input": "libs/schematics",
            "glob": "**/*.d.ts",
            "output": "/"
          },
          {
            "input": "",
            "glob": "LICENSE",
            "output": "/"
          },
          {
            "input": "libs/schematics",
            "glob": "**/*.md",
            "output": "/"
          }
        ]
      },
      "outputs": ["{options.outputPath}"]
    },
    "lint": {
      "executor": "@nrwl/linter:eslint",
      "options": {
        "lintFilePatterns": [
          "libs/schematics/**/*.ts",
          "libs/schematics/**/*.spec.ts",
          "libs/schematics/**/*_spec.ts",
          "libs/schematics/**/*.spec.tsx",
          "libs/schematics/**/*.spec.js",
          "libs/schematics/**/*.spec.jsx",
          "libs/schematics/**/*.d.ts"
        ]
      },
      "outputs": ["{options.outputFile}"]
    }
  }
}
Install need dependencies in root project
npm i --save-dev @nrwl/js @nrwl/devkit
Add custom config for rucken tools for exclude folders from generated index file
rucken.json
{
  "makeTsList": {
    "indexFileName": "index",
    "excludes": [
      "*node_modules*",
      "*public_api.ts*",
      "*.spec*",
      "environment*",
      "*test*",
      "*e2e*",
      "*.stories.ts",
      "*.d.ts",
      "*files*",
      "*generators*",
      "*utils*"
    ]
  }
}
Update options of init generator
libs/schematics/src/generators/init/lib/normalize-options.ts
import type { InitGeneratorOptions } from '../schema';
export function normalizeOptions(
  options: InitGeneratorOptions
): InitGeneratorOptions & Pick<Required<InitGeneratorOptions>, 'botName'> {
  return {
    ...options,
    unitTestRunner: options.unitTestRunner ?? 'jest',
    botName: options.botName ?? 'Bot',
  };
}
Update schema types of init generator
libs/schematics/src/generators/init/schema.d.ts
import { UnitTestRunner } from '../utils';
export interface InitGeneratorOptions {
  botName?: string;
  skipFormat?: boolean;
  unitTestRunner?: UnitTestRunner;
}
Update schema json on init generator
libs/schematics/src/generators/init/schema.json
{
  "$schema": "http://json-schema.org/schema",
  "$id": "NxNestInitGenerator",
  "title": "Init Nest Plugin",
  "description": "Init Nest Plugin.",
  "cli": "nx",
  "type": "object",
  "properties": {
    "botName": {
      "description": "Bot name.",
      "type": "string",
      "default": "Bot"
    },
    "unitTestRunner": {
      "description": "Adds the specified unit test runner.",
      "type": "string",
      "enum": ["jest", "none"],
      "default": "jest"
    },
    "skipFormat": {
      "description": "Skip formatting files.",
      "type": "boolean",
      "default": false
    }
  },
  "additionalProperties": false,
  "required": []
}
Update application options
libs/schematics/src/generators/application/lib/normalize-options.ts
import type { Tree } from '@nrwl/devkit';
import { getWorkspaceLayout, joinPathFragments, names } from '@nrwl/devkit';
import { Linter } from '@nrwl/linter';
import type { Schema as NodeApplicationGeneratorOptions } from '@nrwl/node/src/generators/application/schema';
import type { ApplicationGeneratorOptions, NormalizedOptions } from '../schema';
export function normalizeOptions(
  tree: Tree,
  options: ApplicationGeneratorOptions
): NormalizedOptions {
  const appDirectory = options.directory
    ? `${names(options.directory).fileName}/${names(options.name).fileName}`
    : names(options.name).fileName;
  const appProjectRoot = joinPathFragments(
    getWorkspaceLayout(tree).appsDir,
    appDirectory
  );
  return {
    ...options,
    appProjectRoot,
    linter: options.linter ?? Linter.EsLint,
    unitTestRunner: options.unitTestRunner ?? 'jest',
    botName: options.botName,
  };
}
export function toNodeApplicationGeneratorOptions(
  options: NormalizedOptions
): NodeApplicationGeneratorOptions {
  return {
    name: options.name,
    directory: options.directory,
    frontendProject: options.frontendProject,
    linter: options.linter,
    skipFormat: true,
    skipPackageJson: options.skipPackageJson,
    standaloneConfig: options.standaloneConfig,
    tags: options.tags,
    unitTestRunner: options.unitTestRunner,
    setParserOptionsProject: options.setParserOptionsProject,
  };
}
Update application schema type
libs/schematics/src/generators/application/schema.d.ts
import { Linter } from '@nrwl/linter';
import { UnitTestRunner } from '../../utils/test-runners';
export interface ApplicationGeneratorOptions {
  name: string;
  directory?: string;
  frontendProject?: string;
  linter?: Exclude<Linter, Linter.TsLint>;
  skipFormat?: boolean;
  skipPackageJson?: boolean;
  standaloneConfig?: boolean;
  tags?: string;
  unitTestRunner?: UnitTestRunner;
  setParserOptionsProject?: boolean;
  botName?: string;
}
interface NormalizedOptions extends ApplicationGeneratorOptions {
  appProjectRoot: Path;
}
Update application schema json
libs/schematics/src/generators/application/schema.json
{
  "$schema": "http://json-schema.org/schema",
  "$id": "NxNestKaufmanBotApplicationGenerator",
  "title": "Nx Nest KaufmanBot Application Options Schema",
  "description": "Nx Nest KaufmanBot Application Options Schema.",
  "cli": "nx",
  "type": "object",
  "properties": {
    "name": {
      "description": "The name of the application.",
      "type": "string",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "What name would you like to use for the node application?"
    },
    "botName": {
      "description": "Bot name.",
      "type": "string",
      "default": "Bot"
    },
    "directory": {
      "description": "The directory of the new application.",
      "type": "string"
    },
    "skipFormat": {
      "description": "Skip formatting files.",
      "type": "boolean",
      "default": false
    },
    "skipPackageJson": {
      "description": "Do not add dependencies to `package.json`.",
      "type": "boolean",
      "default": false
    },
    "linter": {
      "description": "The tool to use for running lint checks.",
      "type": "string",
      "enum": ["eslint", "none"],
      "default": "eslint"
    },
    "unitTestRunner": {
      "description": "Test runner to use for unit tests.",
      "type": "string",
      "enum": ["jest", "none"],
      "default": "jest"
    },
    "tags": {
      "description": "Add tags to the application (used for linting).",
      "type": "string"
    },
    "frontendProject": {
      "description": "Frontend project that needs to access this application. This sets up proxy configuration.",
      "type": "string"
    },
    "standaloneConfig": {
      "description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
      "type": "boolean"
    },
    "setParserOptionsProject": {
      "type": "boolean",
      "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
      "default": false
    }
  },
  "additionalProperties": false,
  "required": ["name"]
}
Update export to barrel
libs/schematics/src/generators/library/lib/add-exports-to-barrel.ts
import type { Tree } from '@nrwl/devkit';
import {
  addGlobal,
  removeChange,
} from '@nrwl/workspace/src/utilities/ast-utils';
import * as ts from 'typescript';
import type { NormalizedOptions } from '../schema';
export function addExportsToBarrelFile(
  tree: Tree,
  options: NormalizedOptions
): void {
  const indexPath = `${options.projectRoot}/src/index.ts`;
  const indexContent = tree.read(indexPath, 'utf-8');
  let sourceFile = ts.createSourceFile(
    indexPath,
    indexContent || '',
    ts.ScriptTarget.Latest,
    true
  );
  sourceFile = removeChange(
    tree,
    sourceFile,
    indexPath,
    0,
    `export * from './lib/${options.fileName}';`
  );
  sourceFile = addGlobal(
    tree,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    sourceFile,
    indexPath,
    `export * from './lib/${options.fileName}.module';`
  );
}
Update create files
libs/schematics/src/generators/library/lib/create-files.ts
import type { Tree } from '@nrwl/devkit';
import {
  generateFiles,
  joinPathFragments,
  names,
  offsetFromRoot,
} from '@nrwl/devkit';
import type { NormalizedOptions } from '../schema';
function capitalizeFirstLetter(text: string | undefined, locale: string) {
  const [first, ...rest] = (text || '').trim();
  return (first || '').toLocaleUpperCase(locale) + rest.join('');
}
export function createFiles(tree: Tree, options: NormalizedOptions): void {
  const substitutions = {
    ...options,
    ...names(options.projectName),
    titleName: names(options.projectName).fileName.split('-').join(' '),
    TitleName: capitalizeFirstLetter(
      names(options.projectName).fileName.split('-').join(' '),
      'en'
    ),
    tmpl: '',
    offsetFromRoot: offsetFromRoot(options.projectRoot),
  };
  generateFiles(
    tree,
    joinPathFragments(__dirname, '..', 'files', 'common'),
    options.projectRoot,
    substitutions
  );
  generateFiles(
    tree,
    joinPathFragments(__dirname, '..', 'files', 'service'),
    options.projectRoot,
    substitutions
  );
}
Update options
libs/schematics/src/generators/library/lib/normalize-options.ts
import type { Tree } from '@nrwl/devkit';
import {
  generateFiles,
  joinPathFragments,
  names,
  offsetFromRoot,
} from '@nrwl/devkit';
import type { NormalizedOptions } from '../schema';
function capitalizeFirstLetter(text: string | undefined, locale: string) {
  const [first, ...rest] = (text || '').trim();
  return (first || '').toLocaleUpperCase(locale) + rest.join('');
}
export function createFiles(tree: Tree, options: NormalizedOptions): void {
  const substitutions = {
    ...options,
    ...names(options.projectName),
    titleName: names(options.projectName).fileName.split('-').join(' '),
    TitleName: capitalizeFirstLetter(
      names(options.projectName).fileName.split('-').join(' '),
      'en'
    ),
    tmpl: '',
    offsetFromRoot: offsetFromRoot(options.projectRoot),
  };
  generateFiles(
    tree,
    joinPathFragments(__dirname, '..', 'files', 'common'),
    options.projectRoot,
    substitutions
  );
  generateFiles(
    tree,
    joinPathFragments(__dirname, '..', 'files', 'service'),
    options.projectRoot,
    substitutions
  );
}
Update library schema types
libs/schematics/src/generators/library/schema.d.ts
import { Linter } from '@nrwl/linter';
import { UnitTestRunner } from '../utils';
export interface LibraryGeneratorOptions {
  name: string;
  buildable?: boolean;
  directory?: string;
  importPath?: string;
  linter?: Exclude<Linter, Linter.TsLint>;
  publishable?: boolean;
  skipFormat?: boolean;
  skipTsConfig?: boolean;
  strict?: boolean;
  tags?: string;
  target?:
    | 'es5'
    | 'es6'
    | 'esnext'
    | 'es2015'
    | 'es2016'
    | 'es2017'
    | 'es2018'
    | 'es2019'
    | 'es2020';
  testEnvironment?: 'jsdom' | 'node';
  unitTestRunner?: UnitTestRunner;
  standaloneConfig?: boolean;
  setParserOptionsProject?: boolean;
}
export interface NormalizedOptions extends LibraryGeneratorOptions {
  fileName: string;
  parsedTags: string[];
  prefix: string;
  projectDirectory: string;
  projectName: string;
  projectRoot: Path;
}
Update all index files and translate
npm run generate
Check logic of work with @kaufman-bot/schematics
Create empty nx project
npx -y create-nx-workspace@13.8.1 --name=kaufman-bot-generated --preset=empty --interactive=false --nx-cloud=false
endy@endy-virtual-machine:~/Projects/current$ npx -y create-nx-workspace@13.8.1 --name=kaufman-bot-generated --preset=empty --interactive=false --nx-cloud=false
 >  NX   Nx is creating your v13.8.1 workspace.
   To make sure the command works reliably in all environments, and that the preset is applied correctly,
   Nx will run "npm install" several times. Please wait.
✔ Installing dependencies with npm
✔ Nx has successfully created the workspace.
 ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
 >  NX   First time using Nx? Check out this interactive Nx tutorial.
   https://nx.dev/getting-started/nx-core
Go to created project
cd kaufman-bot-generated
Add all need schematics
npm install -D @nrwl/nest@13.8.1 @kaufman-bot/schematics@2.4.0
endy@endy-virtual-machine:~/Projects/current/kaufman-bot-generated$ npm install -D @nrwl/nest@13.8.1 @kaufman-bot/schematics@2.4.0
added 162 packages, and audited 567 packages in 12s
54 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities
Create kaufman-bot application
npx -y nx@13.8.1 g @kaufman-bot/schematics:app adam-bot --bot-name adam
endy@endy-virtual-machine:~/Projects/current/kaufman-bot-generated$ npx -y nx@13.8.1 g @kaufman-bot/schematics:app adam-bot --bot-name adam
UPDATE package.json
UPDATE nx.json
UPDATE tsconfig.base.json
UPDATE .gitignore
CREATE .env.local
CREATE .env-example.local
CREATE jest.config.js
CREATE jest.preset.js
UPDATE .vscode/extensions.json
CREATE apps/adam-bot/src/app/.gitkeep
CREATE apps/adam-bot/src/assets/.gitkeep
CREATE apps/adam-bot/src/environments/environment.prod.ts
CREATE apps/adam-bot/src/environments/environment.ts
CREATE apps/adam-bot/src/main.ts
CREATE apps/adam-bot/tsconfig.app.json
CREATE apps/adam-bot/tsconfig.json
CREATE apps/adam-bot/project.json
UPDATE workspace.json
CREATE .eslintrc.json
CREATE apps/adam-bot/.eslintrc.json
CREATE apps/adam-bot/jest.config.js
CREATE apps/adam-bot/tsconfig.spec.json
CREATE apps/adam-bot/src/app/app.module.ts
CREATE apps/adam-bot/src/app/app.service.ts
added 343 packages, removed 1 package, changed 1 package, and audited 909 packages in 37s
102 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities
up to date, audited 909 packages in 2s
102 packages are looking for funding
  run `npm fund` for details
found 0 vulnerabilities
Create telegram bot in @botfather
Append token to env file
.env.local
TELEGRAM_BOT_TOKEN=5384981645:AAEKAfqNpZmoN1w5eQL2QxJtvY5h3O-71Zs
TELEGRAM_BOT_WEB_HOOKS_DOMAIN=
TELEGRAM_BOT_WEB_HOOKS_PATH=
TELEGRAM_BOT_ADMINS=
BOT_NAMES=adam
Check from telegram
npm run serve:adam-bot-local
endy@endy-virtual-machine:~/Projects/current/kaufman-bot-generated$ npm run serve:adam-bot-local
> kaufman-bot-generated@0.0.0 serve:adam-bot-local
> export $(xargs < ./.env.local) > /dev/null 2>&1 && npm run nx -- serve adam-bot
> kaufman-bot-generated@0.0.0 nx
> nx "serve" "adam-bot"
> nx run adam-bot:serve
chunk (runtime: main) main.js (main) 10.1 KiB [entry] [rendered]
webpack compiled successfully (3e915c7195348378)
Debugger listening on ws://localhost:9229/045c9820-61d9-42b1-a3b5-57dc00299eea
For help, see: https://nodejs.org/en/docs/inspector
Issues checking in progress...
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [NestFactory] Starting Nest application...
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] TelegrafModule dependencies initialized +49ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] DebugMessagesModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] DebugMessagesModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] TranslatesModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] CustomInjectorModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] LanguageSwitherModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] LanguageSwitherModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] FactsGeneratorModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] DiscoveryModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] CustomInjectorCoreModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] TranslatesModuleCore dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] TranslatesModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] BotCommandsModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] BotCommandsModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] ShortCommandsModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] ScraperModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] ScraperModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] CustomInjectorModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] CustomInjectorModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] ShortCommandsModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] BotInGroupsModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] CustomInjectorModule dependencies initialized +0ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] CustomInjectorModule dependencies initialized +1ms
[Nest] 1363135  - 04/22/2022, 1:32:02 PM     LOG [InstanceLoader] AppModule dependencies initialized +0ms
No issues found.
[Nest] 1363135  - 04/22/2022, 1:32:05 PM     LOG [InstanceLoader] TelegrafCoreModule dependencies initialized +2985ms
[Nest] 1363135  - 04/22/2022, 1:32:05 PM     LOG [TranslatesBootstrapService] onModuleInit
[Nest] 1363135  - 04/22/2022, 1:32:05 PM     LOG [TranslatesStorage] Add 1 translates for locale: en
[Nest] 1363135  - 04/22/2022, 1:32:05 PM     LOG [NestApplication] Nest application successfully started +2ms
[Nest] 1363135  - 04/22/2022, 1:32:05 PM     LOG [Application] 🚀 Application is running on: http://localhost:3333
Create new command
npm run nx -- g @kaufman-bot/schematics:lib super
endy@endy-virtual-machine:~/Projects/current/kaufman-bot-generated$ npm run nx -- g @kaufman-bot/schematics:lib super
> kaufman-bot-generated@0.0.0 nx
> nx "g" "@kaufman-bot/schematics:lib" "super"
CREATE libs/super/README.md
CREATE libs/super/src/index.ts
CREATE libs/super/tsconfig.json
CREATE libs/super/tsconfig.lib.json
CREATE libs/super/project.json
UPDATE workspace.json
UPDATE tsconfig.base.json
CREATE libs/super/.eslintrc.json
CREATE libs/super/jest.config.js
CREATE libs/super/tsconfig.spec.json
CREATE libs/super/src/lib/super.module.ts
CREATE libs/super/src/lib/super.service.ts
Update app module
apps/adam-bot/src/app/app.module.ts
import { SuperModule } from '@kaufman-bot-generated/super';
...
@Module({
  imports: [
    ...
    SuperModule.forRoot(),
  ],
  providers: [AppService],
})
export class AppModule {}
Restart application and check work in telegram
Generated commands service
libs/super/src/lib/super.service.ts
import {
  BotCommandsEnum,
  BotCommandsProvider,
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
} from '@kaufman-bot/core-server';
import { Inject, Injectable, Logger } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesService } from 'nestjs-translates';
export const SUPER_CONFIG = 'SUPER_CONFIG';
export interface SuperConfig {
  title: string;
  name: string;
  descriptions: string;
  usage: string[];
  spyWords: string[];
  category: string;
}
@Injectable()
export class SuperService implements BotCommandsProvider {
  private readonly logger = new Logger(SuperService.name);
  constructor(
    @Inject(SUPER_CONFIG)
    private readonly superConfig: SuperConfig,
    private readonly translatesService: TranslatesService,
    private readonly commandToolsService: BotCommandsToolsService,
    private readonly botCommandsToolsService: BotCommandsToolsService
  ) {}
  async onHelp<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    return await this.onMessage({
      ...msg,
      text: `${this.superConfig.name} ${BotCommandsEnum.help}`,
    });
  }
  async onMessage<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    const locale = this.botCommandsToolsService.getLocale(msg, 'en');
    const spyWord = this.superConfig.spyWords.find((spyWord) =>
      this.commandToolsService.checkCommands(msg.text, [spyWord], locale)
    );
    if (spyWord) {
      if (
        this.commandToolsService.checkCommands(
          msg.text,
          [BotCommandsEnum.help],
          locale
        )
      ) {
        return {
          type: 'markdown',
          message: msg,
          markdown: this.commandToolsService.generateHelpMessage(msg, {
            locale,
            name: this.superConfig.title,
            descriptions: this.superConfig.descriptions,
            usage: this.superConfig.usage,
            category: this.superConfig.category,
          }),
        };
      }
      const processedMsg = await this.process(msg, locale);
      if (typeof processedMsg === 'string') {
        return {
          type: 'text',
          message: msg,
          text: processedMsg,
        };
      }
      if (processedMsg) {
        return { type: 'message', message: processedMsg };
      }
      this.logger.warn(`Unhandled commands for text: "${msg.text}"`);
      this.logger.debug(msg);
    }
    return null;
  }
  private async process<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg, locale: string) {
    if (
      this.commandToolsService.checkCommands(
        msg.text,
        [getText('ping')],
        locale
      )
    ) {
      return this.translatesService.translate(getText('pong'), locale);
    }
    return null;
  }
}
Generated commands module
libs/super/src/lib/super.module.ts
import {
  BotCommandsCategory,
  BotCommandsModule,
  BOT_COMMANDS_PROVIDER,
} from '@kaufman-bot/core-server';
import { DynamicModule, Module } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesModule } from 'nestjs-translates';
import { SuperService, SuperConfig, SUPER_CONFIG } from './super.service';
@Module({
  imports: [TranslatesModule, BotCommandsModule],
  exports: [TranslatesModule, BotCommandsModule],
})
export class SuperModule {
  static forRoot(): DynamicModule {
    return {
      module: SuperModule,
      providers: [
        {
          provide: SUPER_CONFIG,
          useValue: <SuperConfig>{
            title: getText('Super commands'),
            name: 'super',
            usage: [getText('super ping'), getText('super help')],
            descriptions: getText('Commands for super'),
            spyWords: [getText('super')],
            category: BotCommandsCategory.user,
          },
        },
        {
          provide: BOT_COMMANDS_PROVIDER,
          useClass: SuperService,
        },
      ],
      exports: [SUPER_CONFIG],
    };
  }
}
Generated files your may look in https://github.com/kaufman-bot/schematics-example
In next post I append menu for quick run commands for bot...
 








 
    
Top comments (0)