@nest-arch/tui 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +31 -0
  2. package/dist/cli.js +2 -1
  3. package/dist/{constants-DX5wiqrq.js → constants-CUTbfu9C.js} +1 -1
  4. package/dist/index.js +6 -417
  5. package/dist/nonInteractive-C1KZ0b_6.js +5 -0
  6. package/dist/nonInteractiveOptions-CIaoLAWg.js +24 -0
  7. package/dist/projectGeneratorService-CeWJK-HA.js +420 -0
  8. package/dist/projectName-CcE90t2V.js +1 -0
  9. package/dist/templates/base/.env.example.hbs +23 -0
  10. package/dist/templates/base/prisma.config.ts.hbs +6 -6
  11. package/dist/templates/base/src/app.controller.spec.ts.hbs +6 -0
  12. package/dist/templates/base/src/app.module.ts.hbs +1 -1
  13. package/dist/templates/base/src/database/adapters/interfaces/db-adapter.interface.ts.hbs +5 -5
  14. package/dist/templates/base/src/database/adapters/mongo/raw-mongo.adapter.ts.hbs +2 -2
  15. package/dist/templates/base/src/database/adapters/mongo/typeorm-mongo.adapter.ts.hbs +27 -0
  16. package/dist/templates/base/src/database/db.module.ts.hbs +22 -16
  17. package/dist/templates/base/src/database/db.service.ts.hbs +7 -2
  18. package/dist/templates/base/src/database/entities/todo.entity.ts.hbs +15 -3
  19. package/dist/templates/base/src/database/orm/drizzle/schema.ts.hbs +38 -14
  20. package/dist/templates/base/src/database/orm/typeorm/data-source.ts.hbs +12 -6
  21. package/dist/templates/base/src/libs/middlewares/rate-limit.trpc.middleware.ts.hbs +2 -1
  22. package/dist/templates/base/test/app.e2e-spec.ts.hbs +14 -10
  23. package/dist/templates/extras/docker/Dockerfile.hbs +2 -2
  24. package/dist/templates/extras/health/src/health/health.controller.ts.hbs +5 -0
  25. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +4 -4
  26. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +135 -11
  27. package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +24 -1
  28. package/dist/templates/single-project/architecture/nest-microservice/proto/app.proto.hbs +13 -0
  29. package/dist/templates/single-project/architecture/nest-microservice/src/app.controller.ts.hbs +24 -0
  30. package/dist/templates/single-project/architecture/nest-microservice/src/app.module.ts.hbs +33 -0
  31. package/dist/templates/single-project/architecture/nest-microservice/src/app.service.ts.hbs +16 -4
  32. package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +25 -6
  33. package/dist/templates/single-project/architecture/nest-microservice/test/microservice.e2e-spec.ts.hbs +7 -122
  34. package/dist/templates/single-project/shared/config/env.ts.hbs +14 -14
  35. package/package.json +2 -1
@@ -1,25 +1,123 @@
1
+ {{#if (and hasMongodb useTypeorm)}}
2
+ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
3
+ import { ObjectId } from "mongodb";
4
+ import type { Document, WithId } from "mongodb";
5
+
6
+ import { DbService } from "../../database/db.service.js";
7
+ import { CreateTodoDto } from "./dto/create-todo.dto.js";
8
+ import { UpdateTodoDto } from "./dto/update-todo.dto.js";
9
+
10
+ interface TodoDocument extends Document {
11
+ completed: boolean;
12
+ createdAt: Date;
13
+ description?: string;
14
+ title: string;
15
+ updatedAt: Date;
16
+ }
17
+
18
+ const serialize = ({ _id, ...todo }: WithId<TodoDocument>) => ({
19
+ ...todo,
20
+ id: _id.toHexString(),
21
+ });
22
+
23
+ function objectId(id: string): ObjectId {
24
+ if (!/^[a-f0-9]{24}$/iu.test(id)) {
25
+ throw new BadRequestException("Invalid todo identifier");
26
+ }
27
+ return new ObjectId(id);
28
+ }
29
+
30
+ @Injectable()
31
+ export class TodoService {
32
+ constructor(private readonly db: DbService) {}
33
+
34
+ private get todos() {
35
+ return this.db.mongo.collection<TodoDocument>("todos");
36
+ }
37
+
38
+ async findAll() {
39
+ const todos = await this.todos.find().sort({ _id: 1 }).limit(100).toArray();
40
+ return todos.map(serialize);
41
+ }
42
+
43
+ async create(input: CreateTodoDto) {
44
+ const now = new Date();
45
+ const todo = {
46
+ completed: input.completed ?? false,
47
+ createdAt: now,
48
+ ...(input.description === undefined ? {} : { description: input.description }),
49
+ title: input.title,
50
+ updatedAt: now,
51
+ };
52
+ const result = await this.todos.insertOne(todo);
53
+ return { ...todo, id: result.insertedId.toHexString() };
54
+ }
55
+
56
+ async update(id: string, input: UpdateTodoDto) {
57
+ const _id = objectId(id);
58
+ await this.todos.updateOne({ _id }, { $set: { ...input, updatedAt: new Date() } });
59
+ const todo = await this.todos.findOne({ _id });
60
+ if (!todo) {
61
+ throw new NotFoundException("Todo not found");
62
+ }
63
+ return serialize(todo);
64
+ }
65
+
66
+ async delete(id: string) {
67
+ const result = await this.todos.deleteOne({ _id: objectId(id) });
68
+ if (result.deletedCount === 0) {
69
+ throw new NotFoundException("Todo not found");
70
+ }
71
+ return { message: "Todo deleted successfully" };
72
+ }
73
+ }
74
+ {{else}}
1
75
  {{#if (and hasMongodb useRawSql)}}
2
76
  import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
3
- import { ObjectId, type WithId } from "mongodb";
77
+ import { ObjectId } from "mongodb";
78
+ import type { Document, WithId } from "mongodb";
79
+
4
80
  import { DbService } from "../../database/db.service.js";
5
81
  import { CreateTodoDto } from "./dto/create-todo.dto.js";
6
82
  import { UpdateTodoDto } from "./dto/update-todo.dto.js";
7
- interface TodoDocument { title: string; completed: boolean; description?: string; }
8
- const serialize = ({ _id, ...todo }: WithId<TodoDocument>) => ({ ...todo, id: _id.toHexString() });
83
+
84
+ interface TodoDocument extends Document {
85
+ completed: boolean;
86
+ description?: string;
87
+ title: string;
88
+ }
89
+
90
+ const serialize = ({ _id, ...todo }: WithId<TodoDocument>) => ({
91
+ ...todo,
92
+ id: _id.toHexString(),
93
+ });
94
+
9
95
  function objectId(id: string): ObjectId {
10
- if (!/^[a-f0-9]{24}$/iu.test(id)) throw new BadRequestException("Invalid todo identifier");
96
+ if (!/^[a-f0-9]{24}$/iu.test(id)) {
97
+ throw new BadRequestException("Invalid todo identifier");
98
+ }
11
99
  return new ObjectId(id);
12
100
  }
101
+
13
102
  @Injectable()
14
103
  export class TodoService {
15
104
  constructor(private readonly db: DbService) {}
16
- private get todos() { return this.db.raw.mongo.collection<TodoDocument>("todos"); }
105
+
106
+ private get todos() {
107
+ return this.db.raw.mongo.collection<TodoDocument>("todos");
108
+ }
109
+
17
110
  async findAll() {
18
111
  const todos = await this.todos.find().sort({ _id: 1 }).limit(100).toArray();
19
112
  return todos.map(serialize);
20
113
  }
114
+
21
115
  async create(input: CreateTodoDto) {
22
- const todo = { title: input.title, completed: input.completed ?? false, ...(input.description === undefined ? {} : { description: input.description }) };
116
+ const todo = {
117
+ completed: input.completed ?? false,
118
+ ...(input.description === undefined ? {} : { description: input.description }),
119
+ title: input.title,
120
+ };
23
121
  const result = await this.todos.insertOne(todo);
24
122
  return { ...todo, id: result.insertedId.toHexString() };
25
123
  }
@@ -29,18 +127,22 @@ export class TodoService {
29
127
  async update(id: string, input: UpdateTodoDto) {
30
128
  {{/if}}
31
129
  const todo = await this.todos.findOneAndUpdate({ _id: objectId(id) }, { $set: input }, { returnDocument: "after" });
32
- if (!todo) throw new NotFoundException("Todo not found");
130
+ if (!todo) {
131
+ throw new NotFoundException("Todo not found");
132
+ }
33
133
  return serialize(todo);
34
134
  }
35
135
  async delete(id: string) {
36
136
  const result = await this.todos.deleteOne({ _id: objectId(id) });
37
- if (result.deletedCount === 0) throw new NotFoundException("Todo not found");
137
+ if (result.deletedCount === 0) {
138
+ throw new NotFoundException("Todo not found");
139
+ }
38
140
  return { message: "Todo deleted successfully" };
39
141
  }
40
142
  }
41
143
  {{else}}
42
- {{#if (or hasTodoExample hasAuth isPrisma8 (and hasDatabase useTypeorm) (and hasDatabase useDrizzle hasMysql))}}
43
- import { Injectable, NotFoundException{{#if (and hasAuth hasPostgres useRawSql)}}, type OnModuleInit{{/if}} } from "@nestjs/common";
144
+ {{#if (or (and hasTodoExample (not hasDatabase)) hasAuth isPrisma8 (and hasDatabase useTypeorm) (and hasDatabase useDrizzle (or hasMysql hasSqlserver)) (and hasMongodb usePrisma))}}
145
+ import { {{#if (and hasMongodb usePrisma (not isPrisma8))}}BadRequestException, {{/if}}Injectable, NotFoundException{{#if (and hasAuth hasPostgres useRawSql)}}, type OnModuleInit{{/if}} } from "@nestjs/common";
44
146
  {{else}}
45
147
  import { Injectable } from "@nestjs/common";
46
148
  {{/if}}
@@ -58,7 +160,7 @@ import { and, eq } from "drizzle-orm";
58
160
  import { eq } from "drizzle-orm";
59
161
  {{/if}}
60
162
  {{/if}}
61
- {{#if useTypeorm}}
163
+ {{#if (and useTypeorm (not hasMongodb))}}
62
164
  import { Todo } from "../../database/entities/todo.entity.js";
63
165
  {{/if}}
64
166
  {{#if (and hasAuth hasPostgres useRawSql)}}
@@ -79,6 +181,13 @@ export class TodoService implements OnModuleInit {
79
181
  {{else}}
80
182
  export class TodoService {
81
183
  {{/if}}
184
+ {{#if (and hasMongodb usePrisma (not isPrisma8))}}
185
+ private assertMongoId(id: string): void {
186
+ if (!/^[a-f0-9]{24}$/iu.test(id)) {
187
+ throw new BadRequestException("Invalid todo identifier");
188
+ }
189
+ }
190
+ {{/if}}
82
191
  {{#if hasDatabase}}
83
192
  constructor(private readonly db: DbService) {}
84
193
  {{else}}
@@ -333,6 +442,13 @@ export class TodoService {
333
442
  }
334
443
  return todo;
335
444
  {{else if usePrisma}}
445
+ {{#if (and hasMongodb (not isPrisma8))}}
446
+ this.assertMongoId(id);
447
+ const existingTodo = await this.db.prisma.todo.findUnique({ where: { id } });
448
+ if (!existingTodo) {
449
+ throw new NotFoundException("Todo not found");
450
+ }
451
+ {{/if}}
336
452
  const result = await this.db.prisma.todo.update({ data: input, where: { id } });
337
453
  return result;
338
454
  {{else if useTypeorm}}
@@ -369,6 +485,13 @@ export class TodoService {
369
485
  {{#if isPrisma8}}
370
486
  await this.db.prisma.orm.public.Todo.where({ id: Number(id) }).delete();
371
487
  {{else if usePrisma}}
488
+ {{#if (and hasMongodb (not isPrisma8))}}
489
+ this.assertMongoId(id);
490
+ const existingTodo = await this.db.prisma.todo.findUnique({ where: { id } });
491
+ if (!existingTodo) {
492
+ throw new NotFoundException("Todo not found");
493
+ }
494
+ {{/if}}
372
495
  await this.db.prisma.todo.delete({ where: { id } });
373
496
  {{else if useTypeorm}}
374
497
  await this.db.typeorm.getRepository(Todo).delete(id);
@@ -423,3 +546,4 @@ export class TodoService {
423
546
  }
424
547
 
425
548
  {{/if}}
549
+ {{/if}}
@@ -1,6 +1,29 @@
1
1
  PORT=3000
2
+ NODE_ENV=development
3
+ TRUSTED_PROXY_IPS={{#each trustedProxyIps}}{{#unless @first}},{{/unless}}{{this}}{{/each}}
2
4
  MICROSERVICE_PORT=3001
3
- CORS_ORIGIN=http://localhost:3001
5
+ CORS_ORIGIN=http://localhost:3000
6
+ {{#if hasPostgres}}
7
+ POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/{{projectNameKebab}}_db?schema=public
8
+ {{/if}}
9
+ {{#if hasMysql}}
10
+ MYSQL_URL=mysql://root:mysql@localhost:3306/{{projectNameKebab}}_db
11
+ {{/if}}
12
+ {{#if hasSqlite}}
13
+ SQLITE_PATH=./src/database/dev.db
14
+ {{/if}}
15
+ {{#if hasSqlserver}}
16
+ SQLSERVER_HOST=localhost
17
+ SQLSERVER_PORT=1433
18
+ SQLSERVER_DATABASE={{projectNameKebab}}_db
19
+ SQLSERVER_USER=sa
20
+ SQLSERVER_PASSWORD=DevOnly_Strong_Password_2026!X9
21
+ SQLSERVER_ENCRYPT=true
22
+ SQLSERVER_TRUST_SERVER_CERTIFICATE=true
23
+ {{/if}}
24
+ {{#if hasMongodb}}
25
+ MONGODB_URL=mongodb://localhost:27017/{{projectNameKebab}}_db
26
+ {{/if}}
4
27
  {{#if (includes microservices "redis")}}
5
28
  REDIS_PORT=6379
6
29
  REDIS_URL=redis://127.0.0.1:6379
@@ -5,9 +5,22 @@ package app;
5
5
  service AppService {
6
6
  rpc Ping (PingRequest) returns (PingResponse);
7
7
  rpc Log (LogRequest) returns (LogResponse);
8
+ {{#if (and hasDatabase hasTodoExample)}}
9
+ rpc CreateTodo (CreateTodoRequest) returns (StorageResponse);
10
+ rpc ListTodos (PingRequest) returns (StorageResponse);
11
+ {{/if}}
8
12
  }
9
13
 
10
14
  message PingRequest {}
15
+ {{#if (and hasDatabase hasTodoExample)}}
16
+ message CreateTodoRequest {
17
+ string title = 1;
18
+ }
19
+
20
+ message StorageResponse {
21
+ string json = 1;
22
+ }
23
+ {{/if}}
11
24
 
12
25
  message PingResponse {
13
26
  string message = 1;
@@ -40,6 +40,30 @@ export class AppController {
40
40
 
41
41
  constructor(private readonly appService: AppService) {}
42
42
 
43
+ {{#if (and hasDatabase hasTodoExample)}}
44
+ {{#if (includes microservices "grpc")}}
45
+ @GrpcMethod("AppService", "CreateTodo")
46
+ async createTodo(data: { title: string }): Promise<{ json: string }> {
47
+ return { json: JSON.stringify(await this.appService.createTodo(data)) };
48
+ }
49
+
50
+ @GrpcMethod("AppService", "ListTodos")
51
+ async listTodos(): Promise<{ json: string }> {
52
+ return { json: JSON.stringify(await this.appService.findTodos()) };
53
+ }
54
+ {{else}}
55
+ @MessagePattern("todo.create")
56
+ createTodo(data: { title: string }) {
57
+ return this.appService.createTodo(data);
58
+ }
59
+
60
+ @MessagePattern("todo.list")
61
+ listTodos() {
62
+ return this.appService.findTodos();
63
+ }
64
+ {{/if}}
65
+ {{/if}}
66
+
43
67
  @Get()
44
68
  getHello(): string {
45
69
  return this.appService.getHello();
@@ -13,6 +13,18 @@ import { AuthModule } from "./auth/auth.module.js";
13
13
 
14
14
  import { AppController } from "./app.controller.js";
15
15
  import { AppService } from "./app.service.js";
16
+ {{#if hasTrpc}}
17
+ import { TRPCModule } from "nestjs-trpc";
18
+ import { AppRouterClass as AppRouter } from "./app.router.js";
19
+ import { AppContext } from "./libs/trpc/context.js";
20
+ import { AppErrorHandler } from "./libs/trpc/error.handler.js";
21
+ {{#if hasRateLimiting}}
22
+ import { RateLimitTRPCMiddleware } from "./libs/middlewares/rate-limit.trpc.middleware.js";
23
+ {{/if}}
24
+ {{#if hasTodoExample}}
25
+ import { TodoRouter } from "./api/todo/todo.router.js";
26
+ {{/if}}
27
+ {{/if}}
16
28
  {{#if hasRateLimiting}}
17
29
  import { HttpThrottlerGuard } from "./http-throttler.guard.js";
18
30
  {{/if}}
@@ -31,12 +43,22 @@ import { HealthModule } from "./health/health.module.js";
31
43
  {{#if hasPostgres}}postgresUrl: env.POSTGRES_URL,{{/if}}
32
44
  {{#if hasMysql}}mysqlUrl: env.MYSQL_URL,{{/if}}
33
45
  {{#if hasSqlite}}sqlitePath: env.SQLITE_PATH,{{/if}}
46
+ {{#if hasSqlserver}}sqlserver: { database: env.SQLSERVER_DATABASE, options: { encrypt: env.SQLSERVER_ENCRYPT, trustServerCertificate: env.SQLSERVER_TRUST_SERVER_CERTIFICATE }, password: env.SQLSERVER_PASSWORD, port: env.SQLSERVER_PORT, server: env.SQLSERVER_HOST, user: env.SQLSERVER_USER },{{/if}}
34
47
  {{#if hasMongodb}}mongodbUrl: env.MONGODB_URL,{{/if}}
35
48
  }),
36
49
  {{/if}}
37
50
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
38
51
  AuthModule,
39
52
  {{/if}}
53
+ {{#if hasTrpc}}
54
+ TRPCModule.forRoot({
55
+ context: AppContext,
56
+ {{#if hasRateLimiting}}
57
+ globalMiddlewares: [RateLimitTRPCMiddleware],
58
+ {{/if}}
59
+ onError: AppErrorHandler,
60
+ }),
61
+ {{/if}}
40
62
  {{#if hasRateLimiting}}
41
63
  ThrottlerModule.forRoot([{ limit: 60, ttl: 60_000 }]),
42
64
  {{/if}}
@@ -49,6 +71,17 @@ import { HealthModule } from "./health/health.module.js";
49
71
  ],
50
72
  providers: [
51
73
  AppService,
74
+ {{#if hasTrpc}}
75
+ AppContext,
76
+ AppErrorHandler,
77
+ AppRouter,
78
+ {{#if hasRateLimiting}}
79
+ RateLimitTRPCMiddleware,
80
+ {{/if}}
81
+ {{#if hasTodoExample}}
82
+ TodoRouter,
83
+ {{/if}}
84
+ {{/if}}
52
85
  {{#if hasRateLimiting}}
53
86
  { provide: APP_GUARD, useClass: HttpThrottlerGuard },
54
87
  {{/if}}
@@ -1,6 +1,7 @@
1
1
  import { Injectable } from "@nestjs/common";
2
- {{#if (or hasDatabase hasOrm)}}
3
- import { DbService } from "./database/db.service.js";
2
+ {{#if (and hasDatabase hasTodoExample)}}
3
+ import { RpcException } from "@nestjs/microservices";
4
+ import { TodoService } from "./api/todo/todo.service.js";
4
5
  {{/if}}
5
6
 
6
7
  @Injectable()
@@ -26,8 +27,19 @@ export class AppService {
26
27
  }
27
28
 
28
29
  {{/unless}}
29
- {{#if (or hasDatabase hasOrm)}}
30
- constructor(private readonly db: DbService) {}
30
+ {{#if (and hasDatabase hasTodoExample)}}
31
+ constructor(private readonly todos: TodoService) {}
32
+
33
+ createTodo(data: { title: string }) {
34
+ if (typeof data?.title !== "string" || data.title.trim().length === 0) {
35
+ throw new RpcException({ code: 3, message: "A non-empty title is required" });
36
+ }
37
+ return this.todos.create({ completed: false, title: data.title });
38
+ }
39
+
40
+ findTodos() {
41
+ return this.todos.findAll();
42
+ }
31
43
 
32
44
  {{/if}}
33
45
  getHello(): string {
@@ -1,7 +1,15 @@
1
+ {{#if (includes microservices "grpc")}}
2
+ import { fileURLToPath } from "node:url";
3
+ {{/if}}
1
4
  {{#if (eq httpProvider "fastify")}}
2
5
  import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
3
6
  {{/if}}
7
+ {{#if (includes addons "nestjs-zod")}}
4
8
  import { Logger } from "@nestjs/common";
9
+ import { ZodValidationPipe } from "nestjs-zod";
10
+ {{else}}
11
+ import { Logger, StandardSchemaValidationPipe } from "@nestjs/common";
12
+ {{/if}}
5
13
  import { NestFactory } from "@nestjs/core";
6
14
  import { Transport } from "@nestjs/microservices";
7
15
  import type { MicroserviceOptions } from "@nestjs/microservices";
@@ -22,9 +30,20 @@ import { AuthService } from "@thallesp/nestjs-better-auth";
22
30
  {{/if}}
23
31
 
24
32
  async function bootstrap() {
25
- const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter()){{else}}(AppModule){{/if}};
33
+ const trustedProxyIps = env.TRUSTED_PROXY_IPS.split(",").map((ip) => ip.trim()).filter(Boolean);
34
+ const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter({ trustProxy: trustedProxyIps })){{else}}(AppModule){{/if}};
35
+ {{#unless (eq httpProvider "fastify")}}
36
+ if (trustedProxyIps.length > 0) {
37
+ app.getHttpAdapter().getInstance().set("trust proxy", trustedProxyIps);
38
+ }
39
+ {{/unless}}
26
40
  app.enableCors({ origin: env.CORS_ORIGIN });
27
41
  app.setGlobalPrefix("api");
42
+ {{#if (includes addons "nestjs-zod")}}
43
+ app.useGlobalPipes(new ZodValidationPipe());
44
+ {{else}}
45
+ app.useGlobalPipes(new StandardSchemaValidationPipe());
46
+ {{/if}}
28
47
  app.enableShutdownHooks();
29
48
 
30
49
  app.connectMicroservice<MicroserviceOptions>({
@@ -34,7 +53,7 @@ async function bootstrap() {
34
53
  {{/if}}
35
54
  {{#if (includes microservices "grpc")}}
36
55
  package: "app",
37
- protoPath: "proto/app.proto",
56
+ protoPath: fileURLToPath(new URL("proto/app.proto", import.meta.url)),
38
57
  url: env.GRPC_URL ?? "0.0.0.0:5000",
39
58
  {{/if}}
40
59
  {{#if (includes microservices "rabbitmq")}}
@@ -78,8 +97,8 @@ async function bootstrap() {
78
97
  {{/if}}
79
98
  {{#if (includes apis "trpc")}}
80
99
  trpc: {
81
- routerFilePath: "./{{trpcGeneratePath}}/server.ts",
82
100
  pathPrefix: "/trpc",
101
+ routerFilePath: "./{{trpcGeneratePath}}/server.ts",
83
102
  },
84
103
  {{/if}}
85
104
  zod: {
@@ -88,10 +107,10 @@ async function bootstrap() {
88
107
  },
89
108
  title: "{{projectNamePascalCase}} API",
90
109
  version: "1.0.0",
91
- }),
92
- {{#if (eq httpProvider "fastify")}}withFastify: true,{{/if}}
93
- pageTitle: "{{projectNamePascalCase}} API Docs",
110
+ }),
111
+ pageTitle: "{{projectNamePascalCase}} API Docs",
94
112
  theme: "default",
113
+ {{#if (eq httpProvider "fastify")}}withFastify: true,{{/if}}
95
114
  }),
96
115
  );
97
116
  {{else}}
@@ -1,137 +1,22 @@
1
1
  import type { INestApplication } from "@nestjs/common";
2
- import { ClientProxyFactory, Transport } from "@nestjs/microservices";
3
- import type { {{#if (includes microservices "grpc")}}ClientGrpc{{else if (includes microservices "kafka")}}ClientKafka{{else}}ClientProxy{{/if}}, MicroserviceOptions } from "@nestjs/microservices";
4
- import { Test } from "@nestjs/testing";
5
- {{#unless (includes microservices "grpc")}}
6
- import { setTimeout as delay } from "node:timers/promises";
7
- import request from "supertest";
8
- {{/unless}}
9
- {{#if (and (includes microservices "grpc") (or hasTodoExample hasHealthCheck hasRateLimiting))}}
10
- import request from "supertest";
11
- {{/if}}
12
- import { firstValueFrom } from "rxjs";
13
- {{#if (includes microservices "grpc")}}
14
- import type { Observable } from "rxjs";
2
+ {{#if (eq httpProvider "fastify")}}
3
+ import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
15
4
  {{/if}}
5
+ import { Test } from "@nestjs/testing";
16
6
  import { afterAll, expect, it } from "vitest";
17
7
 
18
8
  import { AppModule } from "../src/app.module.js";
19
- import { env } from "../src/config/env.js";
20
-
21
- const transport = {{#if (includes microservices "redis")}}Transport.REDIS{{else if (includes microservices "mqtt")}}Transport.MQTT{{else if (includes microservices "nats")}}Transport.NATS{{else if (includes microservices "rabbitmq")}}Transport.RMQ{{else if (includes microservices "kafka")}}Transport.KAFKA{{else if (includes microservices "grpc")}}Transport.GRPC{{else}}Transport.TCP{{/if}};
22
-
23
- const options = {
24
- {{#if (includes microservices "kafka")}}
25
- client: { brokers: [env.KAFKA_BROKER] },
26
- consumer: { groupId: "ping-consumer" },
27
- {{/if}}
28
- {{#if (includes microservices "grpc")}}
29
- package: "app",
30
- protoPath: "proto/app.proto",
31
- url: env.GRPC_URL,
32
- {{/if}}
33
- {{#if (includes microservices "rabbitmq")}}
34
- queue: "ping",
35
- queueOptions: { durable: false },
36
- urls: [env.RABBITMQ_URL],
37
- {{/if}}
38
- {{#if (includes microservices "redis")}}
39
- host: new URL(env.REDIS_URL).hostname,
40
- port: Number(new URL(env.REDIS_URL).port || 6379),
41
- {{/if}}
42
- {{#if (includes microservices "nats")}}
43
- servers: [env.NATS_URL],
44
- {{/if}}
45
- {{#if (includes microservices "mqtt")}}
46
- url: env.MQTT_URL,
47
- {{/if}}
48
- };
49
9
 
50
10
  let app: INestApplication;
51
- const client{{#if (includes microservices "grpc")}}: ClientGrpc = ClientProxyFactory.create({ options, transport } as MicroserviceOptions) as unknown as ClientGrpc;{{else if (includes microservices "kafka")}}: ClientKafka = ClientProxyFactory.create({ options, transport } as MicroserviceOptions) as ClientKafka;{{else}}: ClientProxy = ClientProxyFactory.create({ options, transport } as MicroserviceOptions);{{/if}}
52
11
 
53
- it("sends ping through the selected message transport", async () => {
12
+ it("serves the generated HTTP application", async () => {
54
13
  const module = await Test.createTestingModule({ imports: [AppModule] }).compile();
55
- app = module.createNestApplication();
56
- app.connectMicroservice<MicroserviceOptions>({ options, transport } as MicroserviceOptions);
14
+ app = module.createNestApplication{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(new FastifyAdapter()){{else}}(){{/if}};
57
15
  app.setGlobalPrefix("api");
58
- await app.startAllMicroservices();
59
16
  await app.init();
60
- {{#if (includes microservices "kafka")}}
61
- client.subscribeToResponseOf("ping");
62
- client.subscribeToResponseOf("log");
63
- {{/if}}
64
- {{#unless (includes microservices "grpc")}}
65
- {{#if (includes microservices "kafka")}}
66
- try {
67
- await client.connect();
68
- } catch {
69
- await delay(1000);
70
- await client.connect();
71
- }
72
- {{else}}
73
- await client.connect();
74
- {{/if}}
75
- {{/unless}}
76
- {{#if (includes microservices "grpc")}}
77
- const service = client.getService<{
78
- ping: (request: Record<string, never>) => Observable<{ message: string }>;
79
- log: (request: { id: string; source: string; payload: string }) => Observable<{ id: string; source: string; payload: string }>;
80
- }>("AppService");
81
- await expect(firstValueFrom(service.ping({}))).resolves.toEqual({ message: "pong" });
82
- const event = { id: "event-1", payload: "healthy", source: "scenario" };
83
- await expect(firstValueFrom(service.log(event))).resolves.toEqual(event);
84
- {{else}}
85
- await expect(firstValueFrom(client.send<string, Record<string, never>>("ping", {}))).resolves.toBe("pong");
86
- const event = { id: "event-1", payload: { healthy: true }, source: "scenario" };
87
- await expect(firstValueFrom(client.send("log", event))).resolves.toEqual({ received: event });
88
- const auditEvent = {
89
- id: "audit-1",
90
- payload: { healthy: true },
91
- source: "scenario",
92
- };
93
- await firstValueFrom(client.emit("audit", auditEvent));
94
- await delay(250);
95
- const auditResponse = await request(app.getHttpServer())
96
- .get(`/api/events/${auditEvent.id}`)
97
- .expect(200);
98
- expect(auditResponse.body).toEqual(auditEvent);
99
- {{/if}}
100
- }, 60_000);
101
-
102
- {{#if (or hasTodoExample hasHealthCheck hasRateLimiting)}}
103
- it("serves the selected HTTP extras alongside the message transport", async () => {
104
- const server = app.getHttpServer();
105
- {{#if hasTodoExample}}
106
- const created = await request(server)
107
- .post("/api/todos")
108
- .send({ completed: false, title: "Microservice todo" })
109
- .expect(201);
110
- const todo = created.body as { id: string };
111
- const listed = await request(server).get("/api/todos").expect(200);
112
- expect(listed.body).toEqual(
113
- expect.arrayContaining([expect.objectContaining({ id: todo.id })])
114
- );
115
- await request(server)
116
- .put(`/api/todos/${todo.id}`)
117
- .send({ completed: true })
118
- .expect(200);
119
- await request(server).delete(`/api/todos/${todo.id}`).expect(200);
120
- {{/if}}
121
- {{#if hasHealthCheck}}
122
- const health = await request(server).get("/api/health").expect(200);
123
- expect(health.body.info.app.status).toBe("up");
124
- {{/if}}
125
- {{#if hasRateLimiting}}
126
- const responses = await Promise.all(
127
- Array.from({ length: 80 }, () => request(server).get("/api"))
128
- );
129
- expect(responses.some((response) => response.status === 429)).toBe(true);
130
- {{/if}}
131
- }, 60_000);
132
- {{/if}}
17
+ expect(app.getHttpServer()).toBeDefined();
18
+ });
133
19
 
134
20
  afterAll(async () => {
135
21
  await app?.close();
136
- client.close();
137
22
  });
@@ -35,9 +35,15 @@ export const env = createEnv({
35
35
  {{#if (or (includes microservices "redis") (includes microservices "mqtt") (includes microservices "nats") (includes microservices "rabbitmq") (includes microservices "kafka") (includes microservices "grpc"))}}
36
36
  MICROSERVICE_PORT: z.string().transform(Number).default(3001),
37
37
  {{/if}}
38
+ {{#if hasMongodb}}
39
+ MONGODB_URL: z.url(),
40
+ {{/if}}
38
41
  {{#if (includes microservices "mqtt")}}
39
42
  MQTT_URL: z.url().default("mqtt://127.0.0.1:1883"),
40
43
  {{/if}}
44
+ {{#if hasMysql}}
45
+ MYSQL_URL: z.url(),
46
+ {{/if}}
41
47
  {{#if (includes microservices "nats")}}
42
48
  NATS_URL: z.url().default("nats://127.0.0.1:4222"),
43
49
  {{/if}}
@@ -48,34 +54,28 @@ export const env = createEnv({
48
54
  .string()
49
55
  .transform(Number)
50
56
  .default(3000),
57
+ {{#if hasPostgres}}
58
+ POSTGRES_URL: z.url(),
59
+ {{/if}}
51
60
  {{#if (includes microservices "rabbitmq")}}
52
61
  RABBITMQ_URL: z.url().default("amqp://127.0.0.1:5672"),
53
62
  {{/if}}
54
63
  {{#if (includes microservices "redis")}}
55
64
  REDIS_URL: z.url().default("redis://127.0.0.1:6379"),
56
65
  {{/if}}
57
- TRUSTED_PROXY_IPS: z.string().default("{{#each trustedProxyIps}}{{#unless @first}},{{/unless}}{{this}}{{/each}}"),
58
- {{#if hasPostgres}}
59
- POSTGRES_URL: z.url(),
60
- {{/if}}
61
- {{#if hasMysql}}
62
- MYSQL_URL: z.url(),
63
- {{/if}}
64
66
  {{#if hasSqlite}}
65
67
  SQLITE_PATH: z.string().default("sqlite.db"),
66
68
  {{/if}}
67
69
  {{#if hasSqlserver}}
68
- SQLSERVER_HOST: z.string().default("localhost"),
69
- SQLSERVER_PORT: z.string().transform(Number).default(1433),
70
70
  SQLSERVER_DATABASE: z.string().default("{{projectNameKebab}}_db"),
71
- SQLSERVER_USER: z.string(),
72
- SQLSERVER_PASSWORD: z.string(),
73
71
  SQLSERVER_ENCRYPT: z.string().transform((value) => value !== "false").default(true),
72
+ SQLSERVER_HOST: z.string().default("localhost"),
73
+ SQLSERVER_PASSWORD: z.string(),
74
+ SQLSERVER_PORT: z.string().transform(Number).default(1433),
74
75
  SQLSERVER_TRUST_SERVER_CERTIFICATE: z.string().transform((value) => value === "true").default(false),
76
+ SQLSERVER_USER: z.string(),
75
77
  {{/if}}
76
- {{#if hasMongodb}}
77
- MONGODB_URL: z.url(),
78
- {{/if}}
78
+ TRUSTED_PROXY_IPS: z.string().default("{{#each trustedProxyIps}}{{#unless @first}},{{/unless}}{{this}}{{/each}}"),
79
79
  {{#if hasGraphql}}
80
80
  GRAPHQL_PLAYGROUND: z
81
81
  .string()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-arch/tui",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "private": false,
5
5
  "description": "Interactive terminal UI for generating NestJS projects",
6
6
  "keywords": [
@@ -38,6 +38,7 @@
38
38
  "handlebars": "^4.7.8",
39
39
  "ink": "^5.0.1",
40
40
  "ink-text-input": "^6.0.0",
41
+ "jsonc-parser": "^3.3.1",
41
42
  "open": "^11.0.0",
42
43
  "react": "^18.3.1",
43
44
  "semver": "^7.8.5",