@nest-arch/tui 0.2.6 → 0.2.8

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.
@@ -1,4 +1,7 @@
1
1
  import { Module } from "@nestjs/common";
2
+ {{#if (and hasAuth hasDatabase)}}
3
+ import { PassportModule } from "@nestjs/passport";
4
+ {{/if}}
2
5
  {{#if (or hasDatabase hasOrm)}}
3
6
  import { env } from "./config/env.js";
4
7
  import { DbModule } from "./database/db.module.js";
@@ -6,6 +9,10 @@ import { DbModule } from "./database/db.module.js";
6
9
  {{#if shouldImportAuthModule}}
7
10
  import { AuthModule } from "./auth/auth.module.js";
8
11
  {{/if}}
12
+ {{#if (and hasAuth hasDatabase)}}
13
+ import { GqlAuthGuard } from "./auth/guards/gql-auth.guard.js";
14
+ import { RestAuthGuard } from "./auth/guards/rest-auth.guard.js";
15
+ {{/if}}
9
16
  {{#if hasGraphql}}
10
17
  import { GraphqlAppModule } from "./graphql/graphql.module.js";
11
18
  {{/if}}
@@ -48,6 +55,9 @@ import { AppService } from "./app.service.js";
48
55
  {{#if shouldImportAuthModule}}
49
56
  AuthModule,
50
57
  {{/if}}
58
+ {{#if (and hasAuth hasDatabase)}}
59
+ PassportModule.register({ defaultStrategy: "jwt" }),
60
+ {{/if}}
51
61
  {{#if hasGraphql}}
52
62
  GraphqlAppModule,
53
63
  {{/if}}
@@ -63,6 +73,10 @@ import { AppService } from "./app.service.js";
63
73
  ],
64
74
  providers: [
65
75
  AppService,
76
+ {{#if (and hasAuth hasDatabase)}}
77
+ GqlAuthGuard,
78
+ RestAuthGuard,
79
+ {{/if}}
66
80
  {{#if hasTrpc}}
67
81
  {{#if hasTrpcAndAuth}}
68
82
  AuthTRPCMiddleware,
@@ -76,4 +90,4 @@ import { AppService } from "./app.service.js";
76
90
  {{/if}}
77
91
  ],
78
92
  })
79
- export class AppModule {}
93
+ export class AppModule {}
@@ -24,4 +24,4 @@
24
24
  ],
25
25
  "strictNullChecks": true
26
26
  }
27
- }
27
+ }
@@ -4,15 +4,8 @@
4
4
  // oxlint-disable no-unused-vars
5
5
 
6
6
  import type { INestApplication } from "@nestjs/common";
7
- {{#if hasRest}}
8
- import {
9
- DocumentBuilder,
10
- SwaggerModule,
11
- type OpenAPIObject,
12
- } from "@nestjs/swagger";
13
- {{else}}
7
+ import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
14
8
  import type { OpenAPIObject } from "@nestjs/swagger";
15
- {{/if}}
16
9
 
17
10
  interface DocsIntegrations {
18
11
  betterAuth?: {
@@ -40,65 +33,16 @@ export interface CreateDocsOptions {
40
33
  integrations?: DocsIntegrations;
41
34
  }
42
35
 
43
- function rewriteRefs(value: unknown): unknown {
44
- if (Array.isArray(value)) {
45
- return value.map((item) => rewriteRefs(item));
46
- }
47
- if (!value || typeof value !== "object") {
48
- return value;
49
- }
50
- const obj = value as Record<string, unknown>;
51
- if (typeof obj.$ref === "string") {
52
- const rest = rewriteRefs(
53
- Object.fromEntries(Object.entries(obj).filter(([k]) => k !== "$ref"))
54
- ) as Record<string, unknown>;
55
- return {
56
- ...rest,
57
- $ref: (obj.$ref as string)
58
- .replace("#/$defs/", "#/components/schemas/")
59
- .replace("#/definitions/", "#/components/schemas/"),
60
- };
61
- }
62
- return Object.fromEntries(
63
- Object.entries(obj).map(([k, v]) => [k, rewriteRefs(v)])
64
- );
65
- }
66
-
67
- function normalizeSchema(
68
- schema: unknown,
69
- components: Record<string, unknown> = {}
70
- ): ReturnType<StandardSchemaConverter> {
71
- const raw = (
72
- typeof schema === "object" && schema !== null ? schema : {}
73
- ) as Record<string, unknown>;
74
- const normalizedComponents = Object.fromEntries(
75
- Object.entries(raw.$defs ?? {}).map(([name, def]) => [
76
- name,
77
- rewriteRefs(def),
78
- ])
79
- );
80
- const filtered: Record<string, unknown> = {};
81
- for (const key of Object.keys(raw)) {
82
- if (key !== "$defs" && key !== "definitions" && key !== "$schema") {
83
- filtered[key] = raw[key];
84
- }
85
- }
86
- return {
87
- components: {
88
- ...components,
89
- ...normalizedComponents,
90
- },
91
- schema: rewriteRefs(filtered),
92
- };
93
- }
94
-
95
36
  function addPrefixToPaths(paths: Record<string, unknown>, prefix: string) {
96
37
  return Object.fromEntries(
97
38
  Object.entries(paths).map(([path, value]) => [`${prefix}${path}`, value])
98
39
  );
99
40
  }
100
41
 
101
- function mergeOpenApiSpecs(baseSpec: OpenAPIObject, ...extraSpecs: OpenAPIObject[]) {
42
+ function mergeOpenApiSpecs(
43
+ baseSpec: OpenAPIObject,
44
+ ...extraSpecs: OpenAPIObject[]
45
+ ) {
102
46
  const specs = [baseSpec, ...extraSpecs];
103
47
 
104
48
  return {
@@ -131,6 +75,28 @@ function mergeOpenApiSpecs(baseSpec: OpenAPIObject, ...extraSpecs: OpenAPIObject
131
75
  } as OpenAPIObject;
132
76
  }
133
77
 
78
+ async function applyZodCleanup(
79
+ baseSpec: OpenAPIObject,
80
+ options?: NonNullable<DocsIntegrations["zod"]>
81
+ ) {
82
+ try {
83
+ const { cleanupOpenApiDoc } = await import("nestjs-zod");
84
+ if (typeof cleanupOpenApiDoc !== "function") {
85
+ return baseSpec;
86
+ }
87
+
88
+ if (options?.withFastify === true) {
89
+ return cleanupOpenApiDoc(baseSpec, {
90
+ withFastify: true,
91
+ }) as OpenAPIObject;
92
+ }
93
+
94
+ return cleanupOpenApiDoc(baseSpec) as OpenAPIObject;
95
+ } catch {
96
+ return baseSpec;
97
+ }
98
+ }
99
+
134
100
  async function applyBetterAuthSpec(
135
101
  options?: NonNullable<DocsIntegrations["betterAuth"]>
136
102
  ) {
@@ -145,7 +111,9 @@ async function applyBetterAuthSpec(
145
111
  generateOpenAPISchema?: () => OpenAPIObject | Promise<OpenAPIObject>;
146
112
  };
147
113
 
148
- const schemaGenerator = authInstance.api?.generateOpenAPISchema ?? authInstance.generateOpenAPISchema;
114
+ const schemaGenerator =
115
+ authInstance.api?.generateOpenAPISchema ??
116
+ authInstance.generateOpenAPISchema;
149
117
 
150
118
  if (typeof schemaGenerator !== "function") {
151
119
  return null;
@@ -169,29 +137,26 @@ async function applyBetterAuthSpec(
169
137
  }
170
138
  }
171
139
 
172
- async function applyTrpcSpec(
173
- options?: NonNullable<DocsIntegrations["trpc"]>
174
- ) {
140
+ async function applyTrpcSpec(options?: NonNullable<DocsIntegrations["trpc"]>) {
175
141
  if (!options?.routerFilePath) {
176
142
  return null;
177
143
  }
178
144
 
179
145
  try {
180
- const specifier = "@trpc/openapi" as string;
181
- const mod: Record<string, unknown> = await import(specifier);
182
- const generateOpenAPIDocument = mod.generateOpenAPIDocument as
183
- | ((opts: unknown, config: unknown) => unknown)
184
- | undefined;
146
+ const { generateOpenAPIDocument } = await import("@trpc/openapi");
185
147
 
186
148
  if (typeof generateOpenAPIDocument !== "function") {
187
149
  return null;
188
150
  }
189
151
 
190
- const generatedDocument = await generateOpenAPIDocument(options.routerFilePath, {
191
- exportName: options.exportName ?? "AppRouter",
192
- title: options.title ?? "tRPC API",
193
- version: options.version ?? "1.0.0",
194
- });
152
+ const generatedDocument = await generateOpenAPIDocument(
153
+ options.routerFilePath,
154
+ {
155
+ exportName: options.exportName ?? "AppRouter",
156
+ title: options.title ?? "tRPC API",
157
+ version: options.version ?? "1.0.0",
158
+ }
159
+ );
195
160
 
196
161
  const trpcSpec = generatedDocument as OpenAPIObject;
197
162
  const prefix = options.pathPrefix ?? "/trpc";
@@ -208,11 +173,12 @@ async function applyTrpcSpec(
208
173
  return null;
209
174
  }
210
175
  }
176
+
211
177
  function createEmptyBaseSpec(options: CreateDocsOptions): OpenAPIObject {
212
178
  return {
213
179
  info: {
214
- title: options.title ?? "NestJS API",
215
180
  description: options.description ?? "Generated with Nest Arch",
181
+ title: options.title ?? "NestJS API",
216
182
  version: options.version ?? "1.0.0",
217
183
  },
218
184
  openapi: "3.1.0",
@@ -225,36 +191,14 @@ export async function createDocs(
225
191
  app: INestApplication,
226
192
  options: CreateDocsOptions = {}
227
193
  ): Promise<OpenAPIObject> {
228
- {{#if (or hasRest hasAuth)}}
229
194
  const config = new DocumentBuilder()
230
195
  .setTitle(options.title ?? "NestJS API")
231
196
  .setDescription(options.description ?? "Generated with Nest Arch")
232
197
  .setVersion(options.version ?? "1.0.0")
233
198
  .build();
234
199
 
235
- const standardSchemaConverter: StandardSchemaConverter = (
236
- schema,
237
- { schemaType }
238
- ) => {
239
- const standard = (schema as Record<string, unknown> | undefined)?.[
240
- "~standard"
241
- ] as Record<string, unknown> | undefined;
242
- const convert = standard?.jsonSchema?.[schemaType] as
243
- | ((opts: { target: string }) => unknown)
244
- | undefined;
245
- if (!convert) {
246
- return;
247
- }
248
- const converted = convert({ target: "openapi-3.0" });
249
- if (!converted || typeof converted !== "object") {
250
- return;
251
- }
252
- return normalizeSchema(converted);
253
- };
254
-
255
- let baseSpec = SwaggerModule.createDocument(app, config, {
256
- standardSchemaConverter,
257
- }) as OpenAPIObject;
200
+ let baseSpec = SwaggerModule.createDocument(app, config) as OpenAPIObject;
201
+ baseSpec = await applyZodCleanup(baseSpec, options.integrations?.zod);
258
202
 
259
203
  if (!baseSpec.paths || Object.keys(baseSpec.paths).length === 0) {
260
204
  baseSpec = {
@@ -268,13 +212,12 @@ export async function createDocs(
268
212
  tags: [],
269
213
  } as OpenAPIObject;
270
214
  }
271
- {{else}}
272
- const baseSpec = createEmptyBaseSpec(options);
273
- {{/if}}
274
215
 
275
216
  const additionalSpecs: OpenAPIObject[] = [];
276
217
 
277
- const betterAuthSpec = await applyBetterAuthSpec(options.integrations?.betterAuth);
218
+ const betterAuthSpec = await applyBetterAuthSpec(
219
+ options.integrations?.betterAuth
220
+ );
278
221
  if (betterAuthSpec) {
279
222
  additionalSpecs.push(betterAuthSpec);
280
223
  }
@@ -2,27 +2,29 @@
2
2
  import { UseGuards } from "@nestjs/common";
3
3
  {{/if}}
4
4
  import { Args, Mutation, Query, Resolver } from "@nestjs/graphql";
5
- import { CreateTodoInput } from "../dto/create-todo.input.js";
6
- import { UpdateTodoInput } from "../dto/update-todo.input.js";
5
+ {{#if (and hasAuth hasDatabase)}}
6
+ import { UserEntity } from "../../auth/decorators/index.js";
7
+ import { GqlAuthGuard } from "../../auth/guards/gql-auth.guard.js";
8
+ {{/if}}
7
9
  {{#if isMonorepo}}
8
10
  import { DbService } from "@repo/db";
9
11
  {{else}}
10
- import { DbService } from "../database/db.service.js";
12
+ import { DbService } from "../../database/db.service.js";
11
13
  {{/if}}
14
+ import { CreateTodoInput } from "../dto/create-todo.input.js";
15
+ import { UpdateTodoInput } from "../dto/update-todo.input.js";
12
16
  {{#if useTypeorm}}
13
17
  import { GraphRelationBuilder } from "typeorm-relations-graphql";
14
18
  import { GraphQLResolveInfo } from "graphql";
15
19
  {{/if}}
16
20
  {{#if useDrizzle}}
17
- import * as schema from "../database/orm/drizzle/schema.js";
21
+ import * as schema from "../../database/orm/drizzle/schema.js";
18
22
  {{/if}}
19
23
  {{#if (and hasAuth hasDatabase)}}
20
24
  import { Todo } from "../models/todo.model.js";
21
- import { UserEntity } from "../../auth/decorators/index.js";
22
- import { GqlAuthGuard } from "../../auth/guards/gql-auth.guard.js";
23
25
  {{/if}}
24
26
 
25
- @Resolver({{#if (and hasAuth hasDatabase)}}{{"("}}() => Todo){{/if}})
27
+ @Resolver(() => Todo)
26
28
  export class TodoResolver {
27
29
  constructor(private readonly db: DbService) {}
28
30
 
@@ -32,7 +34,7 @@ export class TodoResolver {
32
34
  }{{#if hasDatabase}}
33
35
 
34
36
  @Query(() => [Todo])
35
- todos({{#if useTypeorm}}info: GraphQLResolveInfo{{/if}}): Promise<Todo[]> {
37
+ todos({{#if useTypeorm}}info: GraphQLResolveInfo{{/if}}) {
36
38
  {{#if useTypeorm}}
37
39
  const builder = new GraphRelationBuilder(this.db.getDataSource());
38
40
  const relations = builder.buildForQuery(Todo, info, "todos");
@@ -46,14 +48,12 @@ export class TodoResolver {
46
48
  {{/if}}
47
49
  }
48
50
 
49
- @Mutation(() => Todo)
50
- {{#if (and hasAuth hasDatabase)}}
51
51
  @UseGuards(GqlAuthGuard)
52
- {{/if}}
52
+ @Mutation(() => Todo)
53
53
  createTodo(
54
- @Args("input") input: CreateTodoInput{{#if (and hasAuth hasDatabase)}},
55
- @UserEntity() user: { id: string }{{/if}}{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
56
- ): Promise<Todo> {
54
+ @UserEntity() user: { id: string },
55
+ @Args("input") input: CreateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
56
+ ) {
57
57
  {{#if useTypeorm}}
58
58
  const entity = this.db.getRepository(Todo).create(input);
59
59
  const saved = await this.db.getRepository(Todo).save(entity);
@@ -65,7 +65,7 @@ export class TodoResolver {
65
65
  });
66
66
  {{else if usePrisma}}
67
67
  return this.db.prisma.todo.create({
68
- data: { ...input{{#if (and hasAuth hasDatabase)}}, authorId: user.id{{/if}} },
68
+ data: { ...input, authorId: user.id },
69
69
  });
70
70
  {{else if useDrizzle}}
71
71
  const [todo] = await this.db.insert(schema.todos).values(input).returning();
@@ -75,13 +75,9 @@ export class TodoResolver {
75
75
  {{/if}}
76
76
  }
77
77
 
78
- @Mutation(() => Todo)
79
- {{#if (and hasAuth hasDatabase)}}
80
78
  @UseGuards(GqlAuthGuard)
81
- {{/if}}
82
- updateTodo(
83
- @Args("input") input: UpdateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
84
- ): Promise<Todo> {
79
+ @Mutation(() => Todo)
80
+ updateTodo(@Args("input") input: UpdateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}) {
85
81
  const { id, ...data } = input;
86
82
  {{#if useTypeorm}}
87
83
  await this.db.getRepository(Todo).update(id, data);
@@ -105,10 +101,8 @@ export class TodoResolver {
105
101
  {{/if}}
106
102
  }
107
103
 
108
- @Mutation(() => Boolean)
109
- {{#if (and hasAuth hasDatabase)}}
110
104
  @UseGuards(GqlAuthGuard)
111
- {{/if}}
105
+ @Mutation(() => Boolean)
112
106
  async deleteTodo(@Args("id") id: string): Promise<boolean> {
113
107
  {{#if useTypeorm}}
114
108
  const result = await this.db.getRepository(Todo).delete(id);
@@ -2,9 +2,13 @@
2
2
  import { UseGuards } from "@nestjs/common";
3
3
  {{/if}}
4
4
  import { Args, Mutation, Query, Resolver } from "@nestjs/graphql";
5
+ {{#if (and hasAuth hasDatabase)}}
6
+ import { UserEntity } from "../../auth/decorators/index.js";
7
+ import { GqlAuthGuard } from "../../auth/guards/gql-auth.guard.js";
8
+ {{/if}}
9
+ import { DbService } from "@repo/db";
5
10
  import { CreateTodoInput } from "../dto/create-todo.input.js";
6
11
  import { UpdateTodoInput } from "../dto/update-todo.input.js";
7
- import { DbService } from "@repo/db";
8
12
  {{#if useTypeorm}}
9
13
  import { GraphRelationBuilder } from "typeorm-relations-graphql";
10
14
  import { GraphQLResolveInfo } from "graphql";
@@ -14,11 +18,9 @@ import * as schema from "@repo/db/src/orm/drizzle/schema.js";
14
18
  {{/if}}
15
19
  {{#if (and hasAuth hasDatabase)}}
16
20
  import { Todo } from "../models/todo.model.js";
17
- import { UserEntity } from "../../auth/decorators/index.js";
18
- import { GqlAuthGuard } from "../../auth/guards/gql-auth.guard.js";
19
21
  {{/if}}
20
22
 
21
- @Resolver({{#if (and hasAuth hasDatabase)}}{{"("}}() => Todo){{/if}})
23
+ @Resolver(() => Todo)
22
24
  export class TodoResolver {
23
25
  constructor(private readonly db: DbService) {}
24
26
 
@@ -28,7 +30,7 @@ export class TodoResolver {
28
30
  }{{#if hasDatabase}}
29
31
 
30
32
  @Query(() => [Todo])
31
- todos({{#if useTypeorm}}info: GraphQLResolveInfo{{/if}}): Promise<Todo[]> {
33
+ todos({{#if useTypeorm}}info: GraphQLResolveInfo{{/if}}) {
32
34
  {{#if useTypeorm}}
33
35
  const builder = new GraphRelationBuilder(this.db.getDataSource());
34
36
  const relations = builder.buildForQuery(Todo, info, "todos");
@@ -42,14 +44,12 @@ export class TodoResolver {
42
44
  {{/if}}
43
45
  }
44
46
 
45
- @Mutation(() => Todo)
46
- {{#if (and hasAuth hasDatabase)}}
47
47
  @UseGuards(GqlAuthGuard)
48
- {{/if}}
48
+ @Mutation(() => Todo)
49
49
  createTodo(
50
- @Args("input") input: CreateTodoInput{{#if (and hasAuth hasDatabase)}},
51
- @UserEntity() user: { id: string }{{/if}}{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
52
- ): Promise<Todo> {
50
+ @UserEntity() user: { id: string },
51
+ @Args("input") input: CreateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
52
+ ) {
53
53
  {{#if useTypeorm}}
54
54
  const entity = this.db.getRepository(Todo).create(input);
55
55
  const saved = await this.db.getRepository(Todo).save(entity);
@@ -61,7 +61,7 @@ export class TodoResolver {
61
61
  });
62
62
  {{else if usePrisma}}
63
63
  return this.db.prisma.todo.create({
64
- data: { ...input{{#if (and hasAuth hasDatabase)}}, authorId: user.id{{/if}} },
64
+ data: { ...input, authorId: user.id },
65
65
  });
66
66
  {{else if useDrizzle}}
67
67
  const [todo] = await this.db.insert(schema.todos).values(input).returning();
@@ -71,13 +71,9 @@ export class TodoResolver {
71
71
  {{/if}}
72
72
  }
73
73
 
74
- @Mutation(() => Todo)
75
- {{#if (and hasAuth hasDatabase)}}
76
74
  @UseGuards(GqlAuthGuard)
77
- {{/if}}
78
- updateTodo(
79
- @Args("input") input: UpdateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
80
- ): Promise<Todo> {
75
+ @Mutation(() => Todo)
76
+ updateTodo(@Args("input") input: UpdateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}) {
81
77
  const { id, ...data } = input;
82
78
  {{#if useTypeorm}}
83
79
  await this.db.getRepository(Todo).update(id, data);
@@ -101,10 +97,8 @@ export class TodoResolver {
101
97
  {{/if}}
102
98
  }
103
99
 
104
- @Mutation(() => Boolean)
105
- {{#if (and hasAuth hasDatabase)}}
106
100
  @UseGuards(GqlAuthGuard)
107
- {{/if}}
101
+ @Mutation(() => Boolean)
108
102
  async deleteTodo(@Args("id") id: string): Promise<boolean> {
109
103
  {{#if useTypeorm}}
110
104
  const result = await this.db.getRepository(Todo).delete(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-arch/tui",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "private": false,
5
5
  "description": "Interactive terminal UI for generating NestJS projects",
6
6
  "keywords": [
@@ -48,7 +48,7 @@
48
48
  "pkgroll": "^2.5.2",
49
49
  "tsx": "^4.19.2",
50
50
  "vitest": "^4.1.7",
51
- "@nest-arch/core": "0.2.6",
51
+ "@nest-arch/core": "0.2.8",
52
52
  "@nest-arch/env": "0.0.0",
53
53
  "@nest-arch/shared": "0.0.0"
54
54
  },
@@ -1 +0,0 @@
1
- const O="Nest Arch",A="Build production-ready NestJS projects.",t="#e54545",o="#A0A0A0",C="#6B6B6B",R="#E5E5E5",c="#61AFEF",s="#3A3A3A",E="#7FD99A",n="#E8C468",_="#e96142ff";export{O as APP_NAME,A as APP_TAGLINE,c as COLOR_ACCENT,o as COLOR_DIM,C as COLOR_DIMMER,s as COLOR_DIVIDER,R as COLOR_FG,t as COLOR_RED,E as COLOR_SUCCESS,n as COLOR_WARM,_ as COLOR_WARM_SECONDARY};