{"id":300128,"date":"2020-03-14T09:00:18","date_gmt":"2020-03-14T09:00:18","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=300128"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=300128","title":{"rendered":"\u042d\u0442\u043e \u043c\u043e\u0433 \u0431\u044b\u0442\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 JavaScript-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/487648\/\">\n<p>\u041f\u0440\u043e\u0448\u043b\u044b\u043c \u043b\u0435\u0442\u043e\u043c, \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/lanit\/blog\/462855\/\">\u0441\u0442\u0430\u0442\u044c\u0438<\/a> \u0434\u043b\u044f \u0425\u0430\u0431\u0440\u0430, \u044f \u043d\u0435 \u043f\u043e\u043b\u0435\u043d\u0438\u043b\u0441\u044f \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u0434\u043b\u044f \u0431\u044d\u043a\u044d\u043d\u0434-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 Node.js \u0432 npm-\u043f\u0430\u043a\u0435\u0442, \u0441\u0434\u0435\u043b\u0430\u0432 \u0438\u0437 \u043d\u0435\u0433\u043e cli-\u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u0441\u0442\u0430\u0440\u0442\u0430.<\/p>\n<p>  <\/p>\n<p>\u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u043d\u0430\u0434\u0435\u0436\u0434 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u044d\u0442\u0438\u043c \u043f\u0430\u043a\u0435\u0442\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0442\u043e-\u0442\u043e, \u043a\u0440\u043e\u043c\u0435 \u043c\u0435\u043d\u044f, \u043d\u0435 \u0431\u044b\u043b\u043e \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e. \u041e\u0434\u043d\u0430\u043a\u043e, \u043a\u043e\u0433\u0434\u0430 \u044f \u0440\u0435\u0448\u0438\u043b \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d, \u0432\u043d\u0435\u0434\u0440\u0438\u0432 \u0432 \u043d\u0435\u0433\u043e \u043d\u0443\u0436\u043d\u044b\u0435 \u043c\u043d\u0435 \u0444\u0438\u0447\u0438, \u044f \u043e\u0431\u0440\u0430\u0442\u0438\u043b \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0443 npm-\u043f\u0430\u043a\u0435\u0442\u0430 \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0435\u0441\u044f\u0442\u043a\u043e\u0432 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0439 \u0432 \u043d\u0435\u0434\u0435\u043b\u044e, \u0430 \u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 \u0433\u0438\u0442\u0445\u0430\u0431\u0435 12 \u0437\u0432\u0451\u0437\u0434. \u041f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e \u0434\u043e\u0431\u0440\u043e\u0442\u0435 \u0445\u043e\u0440\u043e\u0448\u0438\u043c\u0438 \u043b\u044e\u0434\u044c\u043c\u0438, \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043c\u0435\u043d\u044f, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442. \u0412\u0441\u0435\u0433\u043e 12 \u0437\u0432\u0451\u0437\u0434, \u043d\u043e \u043c\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u0445\u0432\u0430\u0442\u0438\u043b\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0448\u0438\u0442\u044c, \u0447\u0442\u043e karcass \u044f \u0431\u0443\u0434\u0443 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043e\u043d \u043d\u0443\u0436\u0435\u043d \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u043d\u0435.<\/p>\n<p>  <\/p>\n<p>\u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043b\u0435\u0433\u043a\u043e\u0432\u0435\u0441\u043d\u044b\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0434\u043b\u044f \u0431\u044d\u043a\u044d\u043d\u0434-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u0435\u0431\u044f \u0443\u0431\u0435\u0434\u0438\u0442\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d. \u0418 \u0447\u0442\u043e karcass \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0442\u0430\u0442\u044c \u043d\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c, \u0430 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438\u0437 \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/8z\/z7\/w-\/8zz7w-6mc0gxiotwj-bbz3uuete.jpeg\" alt=\"image\"><\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043b\u043e\u0433\u0438\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u044b cli-\u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u0431\u044b\u043b\u0430 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043d\u043e\u0439.<\/p>\n<p>  <\/p>\n<ol>\n<li>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.<\/li>\n<li>karcass \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0441\u0432\u043e\u0435\u0439 \u0436\u0435 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 template \u0432 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u0443\u044e \u043f\u043e\u0434 \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e.<\/li>\n<li>\u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0444\u0430\u0439\u043b\u0430 (\u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0442\u0435\u043a\u0441\u0442, \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0438) \u0438\u043b\u0438 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 (\u0444\u0430\u0439\u043b \u043d\u0435 \u043f\u043e\u043f\u0430\u0434\u0451\u0442 \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e).<\/li>\n<li>\u041f\u043e\u0441\u043b\u0435 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0430, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 <code>npm install<\/code>.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/rk\/uz\/8f\/rkuz8fkc3bpzr4dksqgerrganjo.gif\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u043e\u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438 \u0431\u044b\u043b\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u0443\u0441\u043b\u043e\u0432\u0438\u0439 \u0438 \u0440\u0435\u043f\u043b\u044d\u0439\u0441\u043e\u0432. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, <a href=\"https:\/\/github.com\/karcass-ts\/karcass\/blob\/v0.0.8\/src\/Create\/Service\/MorphyService.ts\" rel=\"nofollow\">\u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0441\u0430\u043c\u0438<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0421\u0430\u043c \u0448\u0430\u0431\u043b\u043e\u043d \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u043b \u0438\u0437 \u0441\u0435\u0431\u044f \u0441\u043f\u043b\u043e\u0448\u043d\u043e\u0439 \u0430\u043d\u0442\u0438\u043f\u0430\u0442\u0442\u0435\u0440\u043d, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0432\u0448\u0438\u0439 \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u044b: \u043a\u043b\u0430\u0441\u0441 Application \u044f\u0432\u043b\u044f\u043b\u0441\u044f \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u043c, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0432 \u043b\u044e\u0431\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435, \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0438\u043b\u0438 \u043a\u043e\u043d\u0441\u043e\u043b\u044c\u043d\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u0435. \u0427\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e \u043c\u043e\u0433\u043b\u043e \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a \u0447\u0435\u043c\u0443 \u0443\u0433\u043e\u0434\u043d\u043e \u0431\u0435\u0437 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Application.ts<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import Express from 'express' import { AbstractConsoleCommand } from '.\/Base\/Console\/AbstractConsoleCommand' import { DbService } from '.\/Database\/Service\/DbService' import { HelpCommand } from '.\/Base\/Console\/HelpCommand' import { LoggerService } from '.\/Logger\/Service\/LoggerService' import { IssueService } from '.\/Project\/Service\/IssueService' import { GitlabService } from '.\/Gitlab\/Service\/GitlabService' import { LocalCacheService } from '.\/Base\/Service\/LocalCacheService' import { ProjectService } from '.\/Project\/Service\/ProjectService' import { GroupService } from '.\/Project\/Service\/GroupService' import { UserService } from '.\/User\/Service\/UserService' import { UpdateProjectsCommand } from '.\/Gitlab\/Console\/UpdateProjectsCommand' import { CreateMigrationCommand } from '.\/Database\/Console\/CreateMigrationCommand' import { MigrateCommand } from '.\/Database\/Console\/MigrateCommand' import { MigrateUndoCommand } from '.\/Database\/Console\/MigrateUndoCommand' import IssueController from '.\/Project\/Controller\/IssueController' import fs from 'fs'  export class Application {     public http!: Express.Express      \/\/ Services     public localCacheService!: LocalCacheService     public loggerService!: LoggerService     public dbService!: DbService     public gitlabService!: GitlabService     public issueService!: IssueService     public projectService!: ProjectService     public groupService!: GroupService     public userService!: UserService      \/\/ Commands     public helpCommand!: HelpCommand     public createMigrationCommand!: CreateMigrationCommand     public migrateCommand!: MigrateCommand     public migrateUndoCommand!: MigrateUndoCommand     public updateProjectsCommand!: UpdateProjectsCommand      \/\/ Controllers     public issueController!: IssueController      public constructor(public readonly config: IConfig) {         if (config.columns.length &lt; 2) {             throw new Error('There are too few columns :-(')         }     }      public async run() {         this.initializeServices()         if (process.argv[2]) {             this.initializeCommands()             for (const command of Object.values(this)                 .filter((c: any) =&gt; c instanceof AbstractConsoleCommand) as AbstractConsoleCommand[]             ) {                 if (command.name === process.argv[2]) {                     await command.execute()                     process.exit()                 }             }             await this.helpCommand.execute()             process.exit()         } else {             this.runWebServer()         }     }      protected runWebServer() {         this.initCron()         this.http = Express()         this.http.use('\/', Express.static('vue\/dist'))         this.http.use((req, res, next) =&gt; {             if (req.url.indexOf('\/api') === -1) {                 res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')                 res.header('Expires', '-1')                 res.header('Pragma', 'no-cache')                 return res.send(fs.readFileSync('vue\/dist\/index.html').toString())             }             next()         })         this.http.use(Express.urlencoded())         this.http.use(Express.json())         this.http.listen(this.config.listen, () =&gt; console.log(`Listening on port ${this.config.listen}`))          this.initializeControllers()     }      protected initCron() {         if (this.config.gitlab.updateInterval) {             setInterval(async () =&gt; {                 if (!this.updateProjectsCommand) {                     this.updateProjectsCommand = new UpdateProjectsCommand(this)                 }                 await this.updateProjectsCommand.execute()             }, this.config.gitlab.updateInterval * 1000)         }     }      protected initializeServices() {         this.localCacheService = new LocalCacheService(this)         this.gitlabService = new GitlabService(this)         this.loggerService = new LoggerService(this)         this.dbService = new DbService(this)         this.issueService = new IssueService(this)         this.projectService = new ProjectService(this)         this.groupService = new GroupService(this)         this.userService = new UserService(this)     }      protected initializeCommands() {         this.helpCommand = new HelpCommand(this)         this.createMigrationCommand = new CreateMigrationCommand(this)         this.migrateCommand = new MigrateCommand(this)         this.migrateUndoCommand = new MigrateUndoCommand(this)         this.updateProjectsCommand = new UpdateProjectsCommand(this)     }      protected initializeControllers() {         this.issueController = new IssueController(this)     }  }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">ProjectService.ts<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import { AbstractService } from '..\/..\/Base\/Service\/AbstractService' import { Project } from '..\/Entity\/Project'  export class ProjectService extends AbstractService {      public get projectRepository() {         return this.app.dbService.connection.getRepository(Project)     }      public async updateProjects(allTime = false) {         await this.app.groupService.updateGroups()         for (const data of await this.app.gitlabService.getProjects()) {             let project = await this.getProject(data.id)              if (!project) {                 project = this.projectRepository.create({ id: data.id })             }             project.name = data.name             project.url = data.web_url             project.updatedTimestamp = Math.round(new Date(data.last_activity_at).getTime() \/ 1000)             project.groupId = data.namespace &amp;&amp; data.namespace.kind === 'group' ? data.namespace.id : null             await this.projectRepository.save(project)             await this.app.issueService.updateProjectIssues(project, allTime)         }     }      public async getProject(id: number): Promise&lt;Project|undefined&gt; {         return id ? this.app.localCacheService.get(`project.${id}`, () =&gt; this.projectRepository.findOne(id)) : undefined     }  }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0420\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f, \u0432 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0448\u0430\u0431\u043b\u043e\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0430\u043b\u0441\u044f \u0431\u044b \u0443\u0436\u0435 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043c\u0435\u043d\u044f, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 \u0431\u044b\u043b\u043e \u0431\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435\u043f\u0440\u0438\u043b\u0438\u0447\u043d\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043f\u043e\u0441\u0432\u044f\u0442\u0438\u043b \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u043e\u0433\u043e, \u043d\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e <a href=\"https:\/\/github.com\/karcass-ts\/container\" rel=\"nofollow\">DI-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430<\/a>, \u0430 \u043d\u0430 \u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0434\u0435\u043b\u0430\u043b <a href=\"https:\/\/github.com\/karcass-ts\/cli\" rel=\"nofollow\">\u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 cli<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 <code>Application.ts<\/code> \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u0438\u043b\u0438 \u00ab\u043d\u0430 \u043c\u0435\u0441\u0442\u0435\u00bb. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0441\u0430\u043c \u0438\u043b\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0435\u0433\u043e \u0438\u0437 \u043a\u043e\u043b\u043b\u0431\u044d\u043a\u0430.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Application.ts<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import CreateExpress, { Express } from 'express'; import { TwingEnvironment, TwingLoaderFilesystem } from 'twing'; import { Container } from '@karcass\/container'; import { Cli } from '@karcass\/cli'; import { Connection, createConnection } from 'typeorm'; import { CreateMigrationCommand, MigrateCommand, MigrateUndoCommand } from '@karcass\/migration-commands'; import { createLogger } from '.\/routines\/createLogger'; import { Logger } from 'winston'; import { FrontPageController } from '.\/SampleBundle\/Controller\/FrontPageController'; import { Message } from '.\/SampleBundle\/Entity\/Message'; import { MessagesService } from '.\/SampleBundle\/Service\/MessagesService';  export class Application {     private container = new Container();     private console = new Cli();     private controllers: object[] = [];     private http!: Express;      public constructor(public readonly config: IConfig) { }      public async run() {         await this.initializeServices();          if (process.argv[2]) {             this.initializeCommands();             await this.console.run();         } else {             this.runWebServer();         }     }      protected runWebServer() {         this.http = CreateExpress();         this.http.use('\/public', CreateExpress.static('public'));         this.http.use(CreateExpress.urlencoded());         this.http.listen(this.config.listen, () =&gt; console.log(`Listening on port ${this.config.listen}`));          this.container.add&lt;Express&gt;('express', () =&gt; this.http);         this.container.add(TwingEnvironment, () =&gt; new TwingEnvironment(new TwingLoaderFilesystem('src')));          this.initializeControllers();     }      protected async initializeServices() {         await this.container.addInplace&lt;Logger&gt;('logger', () =&gt; createLogger(this.config.logdir));         const typeorm = await this.container.addInplace(Connection, () =&gt; createConnection({             type: 'sqlite',             database: 'db\/sample.sqlite',             entities: ['build\/**\/Entity\/*.js'],             migrations: ['build\/**\/Migrations\/*.js'],             logging: ['error', 'warn', 'migration'],         }));         this.container.add('Repository&lt;Message&gt;', () =&gt; typeorm.getRepository(Message));         this.container.add(MessagesService);     }      protected initializeCommands() {         this.console.add(CreateMigrationCommand, () =&gt; new CreateMigrationCommand());         this.console.add(MigrateCommand, async () =&gt; new MigrateCommand(await this.container.get(Connection)));         this.console.add(MigrateUndoCommand, async () =&gt; new MigrateUndoCommand(await this.container.get(Connection)));     }      protected async initializeControllers() {         this.controllers.push(             await this.container.inject(FrontPageController),         );     }  }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 TypeScript \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u0430:<\/p>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">FrontPageController.ts<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\">import { Express } from 'express'; import { Dependency } from '@karcass\/container'; import { TwingEnvironment } from 'twing'; import { AbstractController, QueryData } from '.\/AbstractController'; import { MessagesService } from '..\/Service\/MessagesService';  export class FrontPageController extends AbstractController {      public constructor(         @Dependency('express') protected express: Express,         @Dependency(TwingEnvironment) protected twing: TwingEnvironment,         @Dependency(MessagesService) protected messagesService: MessagesService,     ) {         super(express);          this.onQuery('\/', 'get', this.frontPageAction);         this.onQuery('\/', 'post', this.sendMessageAction);     }      public async sendMessageAction(data: QueryData) {         await this.messagesService.addMessage(data.params.text);         data.res.redirect('\/');     }      public async frontPageAction() {         if (await this.messagesService.isEmpty()) {             await this.messagesService.createSampleMessages();         }         return this.twing.render('SampleBundle\/Views\/front.twig', {             messages: await this.messagesService.getMessages(),         });     }  }<\/code><\/pre>\n<\/div>\n<\/div>\n<p>  <\/p>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 JavaScript, \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u00ab\u0432\u0440\u0443\u0447\u043d\u0443\u044e\u00bb:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">protected async initializeControllers() {     this.controllers.push(         new FrontPageController(             await this.container.get('express'),             await this.container.get(TwingEnvironment),             await this.container.get(MessagesService),         ),     ); }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0440\u0430\u043d\u044c\u0448\u0435 \u0448\u0430\u0431\u043b\u043e\u043d \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u043b\u0441\u044f \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>template<\/code> \u0441\u0430\u043c\u043e\u0433\u043e karcass, \u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u044f \u0440\u0435\u0448\u0438\u043b \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0448\u0430\u0431\u043b\u043e\u043d \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442: \u0442\u0430\u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438 \u043e\u0442\u043b\u0430\u0434\u043a\u0430 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0449\u0435. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0449\u0438\u043a\u043e\u043c \u0438 \u0441\u0430\u043c\u0438\u043c \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u043c. <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043d\u0435\u0434\u0435\u043b\u044c \u0432\u0435\u0447\u0435\u0440\u043d\u0438\u0445 \u0438\u0437\u044b\u0441\u043a\u0430\u043d\u0438\u0439 \u044f \u043f\u0440\u0438\u0448\u0451\u043b \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0443 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438: \u0432 \u043a\u043e\u0440\u043d\u0435 \u043b\u044e\u0431\u043e\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0444\u0430\u0439\u043b <code>TemplateReducer.ts<\/code> \u0438\u043b\u0438 <code>TemplateReducer.js<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441 <code>TemplateReducer<\/code>, \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0449\u0438\u0439 \u0442\u0430\u043a\u043e\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">interface TemplateReducerInterface {     getConfigParameters(): Promise&lt;ConfigParametersResult&gt;     getConfig(): Record&lt;string, any&gt;     setConfig(config: Record&lt;string, any&gt;): void     getDirectoriesForRemove(): Promise&lt;string[]&gt;     getFilesForRemove(): Promise&lt;string[]&gt;     getDependenciesForRemove(): Promise&lt;string[]&gt;     getFilesContentReplacers(): Promise&lt;ReplaceFileContentItem[]&gt;     finish(): Promise&lt;void&gt;     getTestConfigSet(): Promise&lt;Record&lt;string, any&gt;[]&gt; }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u043c\u0435\u043d\u044f, \u043d\u0430\u043a\u043e\u043d\u0435\u0446, \u043e\u0441\u0435\u043d\u0438\u043b\u043e, \u0447\u0442\u043e \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u043d\u0435 \u043d\u0430\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u043a\u0430\u043a\u043e\u0433\u043e-\u0442\u043e \u0441\u043a\u0435\u043b\u0435\u0442\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e karcass \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0442\u0430\u0442\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u043d\u044b\u043c \u043d\u0435\u043d\u0443\u0436\u043d\u044b\u043c \u043d\u0438\u043a\u043e\u043c\u0443 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c, \u043d\u043e \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u0438\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u043c\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 JavaScript\/TypeScript. \u042d\u0442\u043e \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d\u044b \u0431\u044d\u043a\u044d\u043d\u0434-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439. \u041a\u0442\u043e-\u0442\u043e \u0441\u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0441\u0432\u043e\u0435\u0439 \u0432\u044b\u0441\u0442\u0440\u0430\u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 webpack. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0448\u0430\u0431\u043b\u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043e\u0439 <code>create-react-app<\/code> <s>\u0441 \u0431\u043b\u0435\u043a\u0434\u0436&#8230;<\/s> \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438 \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438, \u043a\u0430\u043a \u0443 <code>vue create<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041d\u0435 \u0431\u0443\u0434\u0443 \u043d\u0443\u0434\u0438\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 <code>TemplateReducerInterface<\/code>, \u0442\u0435\u043c \u0431\u043e\u043b\u0435\u0435, \u0447\u0442\u043e \u043f\u0440\u0438\u043c\u0435\u0440 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043e\u0436\u043d\u043e <a href=\"https:\/\/github.com\/karcass-ts\/default-template\/blob\/master\/TemplateReducer.ts\" rel=\"nofollow\">\u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c<\/a> \u043d\u0430 \u0433\u0438\u0442\u0445\u0430\u0431\u0435. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0435\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0434\u043b\u044f karcass:<\/p>\n<p>  <\/p>\n<p>\u0412 \u043f\u0443\u0441\u0442\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e <code>hello<\/code> \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043c \u0444\u0430\u0439\u043b <code>index.js<\/code> \u043d\u0435\u0437\u0430\u0442\u0435\u0439\u043b\u0438\u0432\u043e\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">console.log('Hello, [replacethisname]!')<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u044f\u0434\u043e\u043c \u0441 \u043d\u0438\u043c \u043a\u043b\u0430\u0434\u0451\u043c \u0444\u0430\u0439\u043b <code>TemplateReducer.js<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u0442 karcass&#8217;\u0443 \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const reducer = require('@karcass\/template-reducer') const Type = reducer.ConfigParameterType  class TemplateReducer extends reducer.AbstractTemplateReducer {     getConfigParameters() {         return [             { name: 'name', description: 'Please enter your name', type: Type.string },         ]     }     async getFilesContentReplacers() {         return [             { filename: 'index.js', replacer: (content) =&gt; {                 return content.replace('[replacethisname]', this.config.name)             } },         ]     }     async finish() {         console.log(`Application installed, to launch it execute\\n  cd ${this.directoryName} &amp;&amp; node index.js`)it.`)     } } module.exports = { TemplateReducer }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0443 \u043b\u044e\u0431\u043e\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0435\u0441\u0442\u044c, \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c, \u043e\u0434\u043d\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u2014 <code>@karcass\/template-reducer<\/code>, \u043d\u0443\u0436\u043d\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u0435\u0451 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c, \u0441\u043e\u0437\u0434\u0430\u0432 \u043f\u0435\u0440\u0435\u0434 \u044d\u0442\u0438\u043c <code>package.json<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">npm init &amp;&amp; npm install @karcass\/template-reducer<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0435\u0441\u043f\u043e\u043a\u043e\u0438\u0442\u044c\u0441\u044f \u043e \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0440\u0430\u0442\u044c \u044d\u0442\u0443 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 <code>getDependenciesForRemove<\/code>, \u0435\u0451 karcass \u0443\u0431\u0435\u0440\u0451\u0442 \u0441\u0430\u043c.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0430\u043a karcass \u0441\u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0437 \u043d\u0430\u0448\u0435\u0433\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430. \u0414\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u043d\u0435 \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0448\u0430\u0431\u043b\u043e\u043d\u0430, \u0438\u043d\u0430\u0447\u0435 \u043d\u0435\u0438\u0437\u0431\u0435\u0436\u043d\u0430 \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u044f \u043f\u0440\u0438 \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0441\u0430\u043c\u0443 \u0432 \u0441\u0435\u0431\u044f.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/qz\/qh\/vr\/qzqhvrhktoayi9xaym0lvssbx94.gif\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u0412\u0441\u0451 \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0431\u0435\u0441\u0441\u043c\u044b\u0441\u043b\u0435\u043d\u043d\u043e, \u0435\u0441\u043b\u0438 \u0431\u044b \u043a\u0440\u043e\u043c\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438\u0437 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 karcass \u043d\u0435 \u043c\u043e\u0433 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0437 \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432. \u041f\u043e\u043a\u0430 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e github.com, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">npx karcass create helloworld https:\/\/github.com\/karcass-ts\/hello-world<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0435\u043d\u0430\u0432\u044f\u0437\u0447\u0438\u0432\u043e \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0443 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0438 \u043c\u043e\u0439 \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u043e-\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0434\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">npx karcass create ooohhhh-ok-show-it<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435? \u0423 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u0440\u0435\u0441\u0435\u0442\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0432 <code>TemplateReducer<\/code> \u043d\u0430\u0448\u0435\u0433\u043e helloworld-\u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043c\u0435\u0442\u043e\u0434:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">    getTestConfigSet() {         return [             { name: 'testname1' },             { name: 'testname2' },         ]     }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0430\u043c\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">npx karcass test www\/karcass\/hello<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043e\u0447\u0435\u0440\u0451\u0434\u043d\u0430\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0441 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0435\u0439. \u041f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043d\u043e, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043b\u0443\u0447\u0448\u0435 \u044f \u043d\u0435 \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043b, \u043f\u043e \u043a\u0440\u0430\u0439\u043d\u0435\u0439 \u043c\u0435\u0440\u0435, \u043f\u043e\u043a\u0430:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/jw\/k2\/ho\/jwk2hobgtfuf9i-gokqmgcr_5ko.gif\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u042f \u0440\u0435\u0448\u0438\u043b \u043d\u0435 \u0434\u0435\u043b\u0430\u0442\u044c \u043b\u043e\u043d\u0433\u0440\u0438\u0434 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c \u0432\u0441\u0435\u0445 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0435\u0439 karcass, \u0430 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u043c\u0435\u0442\u043a\u0443 \u0441 \u0446\u0435\u043b\u044c\u044e \u043f\u0440\u0435\u0437\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0431\u043e\u0440\u0430 \u0444\u0438\u0434\u0431\u0435\u043a\u0430: \u043b\u044e\u0431\u043e\u0439 \u043e\u0442\u0437\u044b\u0432 \u0431\u0443\u0434\u0435\u0442 \u043c\u043d\u0435 \u043f\u043e\u043b\u0435\u0437\u0435\u043d, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u0443\u0434\u0430 \u0434\u0432\u0438\u0433\u0430\u0442\u044c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u0441\u0442\u043e\u0438\u0442 \u043b\u0438. \u041f\u043e\u043a\u0430 \u0436\u0435 \u0432 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/karcass-ts\/karcass\" rel=\"nofollow\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 karcass \u043d\u0430 github<\/a>;<\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/487648\/\"> https:\/\/habr.com\/ru\/post\/487648\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/487648\/\">\n<p>\u041f\u0440\u043e\u0448\u043b\u044b\u043c \u043b\u0435\u0442\u043e\u043c, \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/lanit\/blog\/462855\/\">\u0441\u0442\u0430\u0442\u044c\u0438<\/a> \u0434\u043b\u044f \u0425\u0430\u0431\u0440\u0430, \u044f \u043d\u0435 \u043f\u043e\u043b\u0435\u043d\u0438\u043b\u0441\u044f \u0443\u043f\u0430\u043a\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u0434\u043b\u044f \u0431\u044d\u043a\u044d\u043d\u0434-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 Node.js \u0432 npm-\u043f\u0430\u043a\u0435\u0442, \u0441\u0434\u0435\u043b\u0430\u0432 \u0438\u0437 \u043d\u0435\u0433\u043e cli-\u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u0441\u0442\u0430\u0440\u0442\u0430.<\/p>\n<p>  <\/p>\n<p>\u041d\u0438\u043a\u0430\u043a\u0438\u0445 \u043d\u0430\u0434\u0435\u0436\u0434 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u044d\u0442\u0438\u043c \u043f\u0430\u043a\u0435\u0442\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0442\u043e-\u0442\u043e, \u043a\u0440\u043e\u043c\u0435 \u043c\u0435\u043d\u044f, \u043d\u0435 \u0431\u044b\u043b\u043e \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e. \u041e\u0434\u043d\u0430\u043a\u043e, \u043a\u043e\u0433\u0434\u0430 \u044f \u0440\u0435\u0448\u0438\u043b \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d, \u0432\u043d\u0435\u0434\u0440\u0438\u0432 \u0432 \u043d\u0435\u0433\u043e \u043d\u0443\u0436\u043d\u044b\u0435 \u043c\u043d\u0435 \u0444\u0438\u0447\u0438, \u044f \u043e\u0431\u0440\u0430\u0442\u0438\u043b \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0443 npm-\u043f\u0430\u043a\u0435\u0442\u0430 \u0435\u0441\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0435\u0441\u044f\u0442\u043a\u043e\u0432 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0439 \u0432 \u043d\u0435\u0434\u0435\u043b\u044e, \u0430 \u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 \u0433\u0438\u0442\u0445\u0430\u0431\u0435 12 \u0437\u0432\u0451\u0437\u0434. \u041f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e \u0434\u043e\u0431\u0440\u043e\u0442\u0435 \u0445\u043e\u0440\u043e\u0448\u0438\u043c\u0438 \u043b\u044e\u0434\u044c\u043c\u0438, \u043d\u0430\u0432\u0435\u0440\u043d\u044f\u043a\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043c\u0435\u043d\u044f, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442. \u0412\u0441\u0435\u0433\u043e 12 \u0437\u0432\u0451\u0437\u0434, \u043d\u043e \u043c\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u0445\u0432\u0430\u0442\u0438\u043b\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0448\u0438\u0442\u044c, \u0447\u0442\u043e karcass \u044f \u0431\u0443\u0434\u0443 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a, \u043a\u0430\u043a \u0431\u0443\u0434\u0442\u043e \u043e\u043d \u043d\u0443\u0436\u0435\u043d \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u043d\u0435.<\/p>\n<p>  <\/p>\n<p>\u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u044f \u0440\u0435\u0448\u0438\u043b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043b\u0435\u0433\u043a\u043e\u0432\u0435\u0441\u043d\u044b\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0434\u043b\u044f \u0431\u044d\u043a\u044d\u043d\u0434-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u0435\u0431\u044f \u0443\u0431\u0435\u0434\u0438\u0442\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d. \u0418 \u0447\u0442\u043e karcass \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0442\u0430\u0442\u044c \u043d\u0435 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c, \u0430 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438\u0437 \u0448\u0430\u0431\u043b\u043e\u043d\u043e\u0432.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/8z\/z7\/w-\/8zz7w-6mc0gxiotwj-bbz3uuete.jpeg\" alt=\"image\"><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-300128","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/300128","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=300128"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/300128\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=300128"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=300128"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=300128"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}