@lark-apaas/fullstack-nestjs-core 1.0.3-alpha.8 → 1.0.3

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.
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # @lark-apaas/fullstack-nestjs-core
2
2
 
3
- FullStack NestJS Core 是一个为 NestJS 应用提供核心功能的工具包,包括 CSRF 保护、用户上下文管理和开发工具等。
3
+ FullStack NestJS Core 是一个为 NestJS 全栈应用提供核心功能的工具包,包括平台集成、CSRF 保护、用户上下文管理和开发工具等。
4
4
 
5
5
  ## 特性
6
6
 
7
+ - **平台模块**: 一站式集成 Config、Logger、Database、Auth 等核心功能
7
8
  - **CSRF 保护**: 提供完整的 CSRF Token 生成和验证机制
8
9
  - **用户上下文**: 自动从请求头中提取并注入用户上下文信息
9
- - **开发工具**: 自动生成 Swagger 文档、OpenAPI JSON 和客户端 SDK
10
+ - **视图上下文**: 将用户信息和 CSRF Token 注入到模板渲染上下文
11
+ - **开发工具**: 自动生成 Swagger 文档、OpenAPI JSON 和 TypeScript 客户端 SDK
12
+ - **应用配置**: 一键配置 Logger、Cookie Parser、全局前缀等
10
13
 
11
14
  ## 安装
12
15
 
@@ -27,226 +30,330 @@ yarn add @lark-apaas/fullstack-nestjs-core
27
30
 
28
31
  ## 快速开始
29
32
 
30
- ### 1. CSRF 保护
33
+ ### 方案 1: 使用 PlatformModule(推荐)
31
34
 
32
- CSRF 保护由两个中间件组成:`CsrfTokenMiddleware` 用于生成 token,`CsrfMiddleware` 用于验证 token。
35
+ 最简单的方式是使用 `PlatformModule`,它会自动集成所有核心功能:
33
36
 
34
- #### 使用默认配置
37
+ ```typescript
38
+ import { Module } from '@nestjs/common';
39
+ import { PlatformModule } from '@lark-apaas/fullstack-nestjs-core';
40
+
41
+ @Module({
42
+ imports: [
43
+ PlatformModule.forRoot({
44
+ enableCsrf: true, // 是否启用 CSRF 保护,默认 true
45
+ csrfRoutes: '/api/*', // CSRF 保护的路由,默认 '/api/*'
46
+ alwaysNeedLogin: true, // 是否所有路由都需要登录,默认 true
47
+ }),
48
+ ],
49
+ })
50
+ export class AppModule {}
51
+ ```
52
+
53
+ `PlatformModule` 自动集成:
54
+ - ✅ ConfigModule (环境变量配置)
55
+ - ✅ LoggerModule (日志系统)
56
+ - ✅ DataPaasModule (数据库连接)
57
+ - ✅ AuthNPaasModule (认证系统)
58
+ - ✅ UserContextMiddleware (用户上下文)
59
+ - ✅ CsrfTokenMiddleware + CsrfMiddleware (CSRF 保护)
60
+ - ✅ ViewContextMiddleware (视图上下文)
61
+ - ✅ ValidationPipe (全局验证管道)
62
+
63
+ ### 方案 2: 使用 configureApp 辅助函数
64
+
65
+ 在 `main.ts` 中使用 `configureApp` 快速配置应用:
35
66
 
36
67
  ```typescript
37
- import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
38
- import { CsrfTokenMiddleware, CsrfMiddleware } from '@lark-apaas/fullstack-nestjs-core';
68
+ import { NestFactory } from '@nestjs/core';
69
+ import { NestExpressApplication } from '@nestjs/platform-express';
70
+ import { configureApp } from '@lark-apaas/fullstack-nestjs-core';
71
+ import { AppModule } from './app.module';
39
72
 
40
- @Module({})
41
- export class AppModule implements NestModule {
42
- configure(consumer: MiddlewareConsumer) {
43
- // 直接应用中间件,使用默认配置
44
- consumer
45
- .apply(CsrfTokenMiddleware)
46
- .forRoutes('*'); // 所有路由都生成 token
73
+ async function bootstrap() {
74
+ const app = await NestFactory.create<NestExpressApplication>(AppModule);
47
75
 
48
- consumer
49
- .apply(CsrfMiddleware)
50
- .forRoutes('*'); // 所有路由都验证 CSRF token
51
- }
76
+ // 一键配置:Logger、CookieParser、GlobalPrefix、DevTools
77
+ await configureApp(app);
78
+
79
+ await app.listen(3000);
52
80
  }
81
+ bootstrap();
53
82
  ```
54
83
 
55
- #### 自定义配置(可选)
84
+ `configureApp` 会自动:
85
+ - ✅ 注册 AppLogger
86
+ - ✅ 注册 cookie-parser 中间件
87
+ - ✅ 根据 `CLIENT_BASE_PATH` 环境变量设置全局前缀
88
+ - ✅ 在非生产环境自动挂载 `DevToolsV2Module`
89
+
90
+ ### 方案 3: 手动配置各个模块
56
91
 
57
- 如需自定义配置,可在应用中间件前调用 `configure` 方法:
92
+ 如果需要更细粒度的控制,可以单独使用各个模块:
58
93
 
59
94
  ```typescript
60
95
  import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
61
- import { CsrfTokenMiddleware, CsrfMiddleware } from '@lark-apaas/fullstack-nestjs-core';
96
+ import {
97
+ CsrfTokenMiddleware,
98
+ CsrfMiddleware,
99
+ UserContextMiddleware,
100
+ ViewContextMiddleware,
101
+ } from '@lark-apaas/fullstack-nestjs-core';
62
102
 
63
103
  @Module({})
64
104
  export class AppModule implements NestModule {
65
105
  configure(consumer: MiddlewareConsumer) {
66
- // 自定义配置 CSRF Token 生成中间件
67
- CsrfTokenMiddleware.configure({
68
- cookieKey: 'CSRF-TOKEN', // 默认: 'suda-csrf-token'
69
- cookieMaxAge: 86400000, // 默认: 2592000000 (30天)
70
- cookiePath: '/', // 默认: '/'
71
- });
72
-
73
- // 自定义配置 CSRF 验证中间件
74
- CsrfMiddleware.configure({
75
- headerKey: 'X-CSRF-TOKEN', // 默认: 'x-suda-csrf-token'
76
- cookieKey: 'CSRF-TOKEN', // 默认: 'suda-csrf-token'
77
- });
106
+ // 用户上下文中间件
107
+ consumer
108
+ .apply(UserContextMiddleware)
109
+ .forRoutes('*');
78
110
 
79
- // 应用中间件
111
+ // CSRF Token 生成(用于视图渲染)
80
112
  consumer
81
113
  .apply(CsrfTokenMiddleware)
114
+ .exclude('/api/(.*)')
82
115
  .forRoutes('*');
83
116
 
117
+ // 视图上下文注入
84
118
  consumer
85
- .apply(CsrfMiddleware)
119
+ .apply(ViewContextMiddleware)
120
+ .exclude('/api/(.*)')
86
121
  .forRoutes('*');
122
+
123
+ // CSRF 验证(用于 API 保护)
124
+ consumer
125
+ .apply(CsrfMiddleware)
126
+ .forRoutes('/api/*');
87
127
  }
88
128
  }
89
129
  ```
90
130
 
91
- **注意**: 如果使用自定义的 `cookieKey`,需要确保 `CsrfTokenMiddleware` 和 `CsrfMiddleware` 配置的 `cookieKey` 一致。
131
+ ## 核心模块
92
132
 
93
- ### 2. 用户上下文
133
+ ### PlatformModule
94
134
 
95
- 自动从请求头中提取用户信息并注入到请求对象中。
135
+ 全局平台模块,集成了所有核心功能。
96
136
 
97
- ```typescript
98
- import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
99
- import { UserContextMiddleware } from '@lark-apaas/fullstack-nestjs-core';
137
+ #### 配置选项
100
138
 
101
- @Module({})
102
- export class AppModule implements NestModule {
103
- configure(consumer: MiddlewareConsumer) {
104
- consumer
105
- .apply(UserContextMiddleware)
106
- .forRoutes('*');
107
- }
108
- }
109
- ```
139
+ | 选项 | 类型 | 默认值 | 描述 |
140
+ |------|------|--------|------|
141
+ | enableCsrf | boolean | `true` | 是否启用 CSRF 保护 |
142
+ | csrfRoutes | string \| string[] | `'/api/*'` | CSRF 保护应用的路由 |
143
+ | alwaysNeedLogin | boolean | `true` | 是否所有路由都需要登录 |
110
144
 
111
- 在控制器中使用:
145
+ #### 使用示例
112
146
 
113
147
  ```typescript
114
- import { Controller, Get, Req } from '@nestjs/common';
115
- import { Request } from 'express';
116
-
117
- @Controller()
118
- export class AppController {
119
- @Get()
120
- getUser(@Req() req: Request) {
121
- // 访问用户上下文
122
- const { userId, tenantId, appId } = req.userContext;
123
- return { userId, tenantId, appId };
124
- }
125
- }
148
+ @Module({
149
+ imports: [
150
+ PlatformModule.forRoot({
151
+ enableCsrf: true,
152
+ csrfRoutes: ['/api/*', '/admin/*'],
153
+ alwaysNeedLogin: false,
154
+ }),
155
+ ],
156
+ })
157
+ export class AppModule {}
126
158
  ```
127
159
 
128
- ### 3. 开发工具模块
160
+ ### DevToolsV2Module(推荐)
161
+
162
+ 使用 `@hey-api/openapi-ts` 生成高质量的 TypeScript 客户端 SDK。
163
+
164
+ **新特性:**
165
+ - ✅ 使用最新的 `@hey-api/openapi-ts` 生成器
166
+ - ✅ 自动注入 `baseURL` 配置到生成的客户端
167
+ - ✅ 自动给所有生成的文件添加 `// @ts-nocheck` 注释
168
+
169
+ #### 配置选项
129
170
 
130
- 在开发环境中自动生成 Swagger 文档和客户端 SDK。
171
+ | 选项 | 类型 | 默认值 | 描述 |
172
+ |------|------|--------|------|
173
+ | basePath | string | `'/'` | API 基础路径 |
174
+ | docsPath | string | `'/api/docs'` | Swagger UI 的挂载路径 |
175
+ | openapiOut | string | `'./client/src/api/gen/openapi.json'` | OpenAPI JSON 输出路径 |
176
+ | needSetupServer | boolean | `false` | 是否挂载 Swagger UI 服务器 |
177
+ | needGenerateClientSdk | boolean | `true` | 是否生成客户端 SDK |
178
+ | clientSdkOut | string | `'./client/src/api/gen'` | 客户端 SDK 输出目录 |
179
+ | swaggerOptions | object | - | Swagger 文档配置 |
180
+
181
+ #### 使用示例
131
182
 
132
183
  ```typescript
133
184
  import { NestFactory } from '@nestjs/core';
134
- import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';
185
+ import { DevToolsV2Module } from '@lark-apaas/fullstack-nestjs-core';
135
186
  import { AppModule } from './app.module';
136
187
 
137
188
  async function bootstrap() {
138
189
  const app = await NestFactory.create(AppModule);
139
190
 
140
- // 挂载开发工具
141
- await DevToolsModule.mount(app, {
142
- docsPath: 'api-docs',
143
- openapiOut: './openapi.json',
144
- needSetupServer: true,
145
- needGenerateClientSdk: true,
146
- clientSdkOut: './src/sdk',
147
- swaggerOptions: {
148
- title: 'My API',
149
- version: '1.0.0',
150
- customSiteTitle: 'My API Documentation',
151
- },
152
- });
191
+ // 在开发环境挂载开发工具
192
+ if (process.env.NODE_ENV !== 'production') {
193
+ await DevToolsV2Module.mount(app, {
194
+ basePath: '/app',
195
+ docsPath: '/api_docs',
196
+ needSetupServer: true,
197
+ needGenerateClientSdk: true,
198
+ swaggerOptions: {
199
+ title: 'My API',
200
+ version: '1.0.0',
201
+ },
202
+ });
203
+ }
153
204
 
154
205
  await app.listen(3000);
155
206
  }
156
207
  bootstrap();
157
208
  ```
158
209
 
159
- ## API 文档
210
+ #### 生成的文件
211
+
212
+ ```
213
+ client/src/api/gen/
214
+ ├── openapi.json # OpenAPI 规范文件
215
+ ├── client.config.ts # 客户端配置(自动生成 baseURL)
216
+ ├── types.gen.ts # TypeScript 类型定义
217
+ ├── sdk.gen.ts # SDK 函数
218
+ └── client/
219
+ └── client.gen.ts # Axios 客户端
220
+ ```
221
+
222
+ 所有生成的 `.ts` 文件都会自动添加 `// @ts-nocheck` 注释,避免类型检查错误。
223
+
224
+ ### DevToolsModule(旧版)
225
+
226
+ 使用 `openapi-typescript-codegen` 生成客户端 SDK(不推荐,建议迁移到 `DevToolsV2Module`)。
227
+
228
+ ```typescript
229
+ import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';
230
+
231
+ await DevToolsModule.mount(app, {
232
+ docsPath: 'api-docs',
233
+ openapiOut: './openapi.json',
234
+ needSetupServer: true,
235
+ needGenerateClientSdk: true,
236
+ clientSdkOut: './src/sdk',
237
+ });
238
+ ```
239
+
240
+ ## 中间件
160
241
 
161
242
  ### CsrfTokenMiddleware
162
243
 
163
- 生成 CSRF Token 并设置到 Cookie 中。
244
+ 生成 CSRF Token 并设置到 Cookie 中,用于视图渲染场景。
164
245
 
165
- #### 配置选项 (CsrfTokenOptions)
246
+ #### 配置选项
166
247
 
167
248
  | 选项 | 类型 | 默认值 | 描述 |
168
249
  |------|------|--------|------|
169
- | cookieKey | string | `'suda-csrf-token'` | Cookie 中存储 CSRF Token 的键名 |
170
- | cookieMaxAge | number | `2592000000` (30天) | Cookie 的过期时间(毫秒) |
171
- | cookiePath | string | `'/'` | Cookie 的路径 |
250
+ | cookieKey | string | `'suda-csrf-token'` | Cookie 中存储 Token 的键名 |
251
+ | cookieMaxAge | number | `2592000000` (30天) | Cookie 过期时间(毫秒) |
252
+ | cookiePath | string | `'/'` | Cookie 路径 |
172
253
 
173
- #### 静态方法
254
+ #### 使用示例
174
255
 
175
- - `configure(opts: CsrfTokenOptions)`: 配置中间件选项
256
+ ```typescript
257
+ CsrfTokenMiddleware.configure({
258
+ cookieKey: 'my-csrf-token',
259
+ cookieMaxAge: 86400000, // 1天
260
+ });
261
+
262
+ consumer
263
+ .apply(CsrfTokenMiddleware)
264
+ .exclude('/api/(.*)')
265
+ .forRoutes('*');
266
+ ```
176
267
 
177
268
  ### CsrfMiddleware
178
269
 
179
- 验证请求中的 CSRF Token
270
+ 验证请求中的 CSRF Token,用于保护 API 接口。
180
271
 
181
- #### 配置选项 (CsrfOptions)
272
+ #### 配置选项
182
273
 
183
274
  | 选项 | 类型 | 默认值 | 描述 |
184
275
  |------|------|--------|------|
185
- | headerKey | string | `'x-suda-csrf-token'` | 请求头中 CSRF Token 的键名 |
186
- | cookieKey | string | `'suda-csrf-token'` | Cookie 中 CSRF Token 的键名 |
276
+ | headerKey | string | `'x-suda-csrf-token'` | 请求头中 Token 的键名 |
277
+ | cookieKey | string | `'suda-csrf-token'` | Cookie 中 Token 的键名 |
187
278
 
188
- #### 静态方法
279
+ #### 使用示例
189
280
 
190
- - `configure(opts: CsrfOptions)`: 配置中间件选项
191
-
192
- #### 验证规则
193
-
194
- 1. 检查 Cookie 中是否存在 CSRF Token
195
- 2. 检查请求头中是否存在 CSRF Token
196
- 3. 验证两者是否匹配
281
+ ```typescript
282
+ CsrfMiddleware.configure({
283
+ headerKey: 'x-my-csrf-token',
284
+ cookieKey: 'my-csrf-token',
285
+ });
286
+
287
+ consumer
288
+ .apply(CsrfMiddleware)
289
+ .forRoutes('/api/*');
290
+ ```
197
291
 
198
- 如果任何一步验证失败,将返回 403 Forbidden。
292
+ **注意**: 确保 `CsrfTokenMiddleware` 和 `CsrfMiddleware` 的 `cookieKey` 配置一致。
199
293
 
200
294
  ### UserContextMiddleware
201
295
 
202
- 从请求头中提取用户上下文信息。
296
+ 从请求头中提取用户上下文信息并注入到 `req.userContext`。
203
297
 
204
298
  #### 注入的上下文
205
299
 
206
300
  ```typescript
207
301
  req.userContext = {
208
- userId: string | undefined; // 用户 ID
209
- tenantId: string | undefined; // 租户 ID
210
- appId: string; // 应用 ID
302
+ userId: string | undefined; // 用户 ID(来自 x-user-id 请求头)
303
+ tenantId: string | undefined; // 租户 ID(来自 x-tenant-id 请求头)
304
+ appId: string; // 应用 ID(来自 x-app-id 请求头)
305
+ }
306
+ ```
307
+
308
+ #### 使用示例
309
+
310
+ ```typescript
311
+ @Controller()
312
+ export class AppController {
313
+ @Get('profile')
314
+ getProfile(@Req() req: Request) {
315
+ const { userId, tenantId, appId } = req.userContext;
316
+ return { userId, tenantId, appId };
317
+ }
211
318
  }
212
319
  ```
213
320
 
214
- ### DevToolsModule
321
+ ### ViewContextMiddleware
215
322
 
216
- 开发工具模块,用于生成 API 文档和客户端 SDK。
323
+ 将用户上下文和 CSRF Token 注入到 `res.locals`,用于模板渲染。
217
324
 
218
- #### 配置选项 (DevToolsOptions)
325
+ #### 注入的变量
219
326
 
220
- | 选项 | 类型 | 默认值 | 描述 |
221
- |------|------|--------|------|
222
- | basePath | string | - | 基础路径 |
223
- | docsPath | string | - | Swagger UI 的挂载路径 |
224
- | openapiOut | string | - | OpenAPI JSON 文件的输出路径 |
225
- | needSetupServer | boolean | - | 是否需要挂载 Swagger UI |
226
- | needGenerateClientSdk | boolean | - | 是否生成客户端 SDK |
227
- | clientSdkOut | string | - | 客户端 SDK 的输出目录 |
228
- | swaggerOptions | object | - | Swagger 文档配置 |
229
- | swaggerOptions.title | string | - | Swagger 文档标题 |
230
- | swaggerOptions.version | string | - | API 版本号 |
231
- | swaggerOptions.customSiteTitle | string | - | 自定义站点标题 |
232
- | swaggerOptions.customCss | string | - | 自定义 CSS 样式 |
327
+ ```typescript
328
+ res.locals = {
329
+ csrfToken: string; // CSRF Token
330
+ userId: string; // 用户 ID
331
+ tenantId: string; // 租户 ID
332
+ appId: string; // 应用 ID
333
+ }
334
+ ```
233
335
 
234
- #### 静态方法
336
+ #### 使用示例
235
337
 
236
- - `mount(app: INestApplication, opts?: DevToolsOptions)`: 挂载开发工具到应用
338
+ ```typescript
339
+ // 在模板引擎中可以直接使用这些变量
340
+ // EJS 示例:
341
+ <input type="hidden" name="_csrf" value="<%= csrfToken %>">
342
+ <div>User ID: <%= userId %></div>
343
+ ```
237
344
 
238
345
  ## TypeScript 支持
239
346
 
240
347
  本包完全使用 TypeScript 编写,并提供完整的类型定义。
241
348
 
242
- 扩展 Express Request 类型:
349
+ ### 扩展 Express Request 类型
243
350
 
244
351
  ```typescript
245
352
  declare global {
246
353
  namespace Express {
247
354
  interface Request {
248
355
  csrfToken?: string;
249
- userContext?: {
356
+ userContext: {
250
357
  userId?: string;
251
358
  tenantId?: string;
252
359
  appId: string;
@@ -256,6 +363,13 @@ declare global {
256
363
  }
257
364
  ```
258
365
 
366
+ ## 环境变量
367
+
368
+ | 变量名 | 描述 | 默认值 |
369
+ |--------|------|--------|
370
+ | CLIENT_BASE_PATH | 应用的全局路径前缀 | - |
371
+ | NODE_ENV | 运行环境 | - |
372
+
259
373
  ## 许可证
260
374
 
261
375
  MIT
@@ -266,3 +380,4 @@ MIT
266
380
  - nestjs
267
381
  - server
268
382
  - typescript
383
+ - fullstack
package/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  CsrfMiddleware: () => CsrfMiddleware,
36
36
  CsrfTokenMiddleware: () => CsrfTokenMiddleware,
37
37
  DevToolsModule: () => DevToolsModule,
38
+ DevToolsV2Module: () => DevToolsV2Module,
38
39
  PlatformModule: () => PlatformModule,
39
40
  UserContextMiddleware: () => UserContextMiddleware,
40
41
  ViewContextMiddleware: () => ViewContextMiddleware,
@@ -89,7 +90,8 @@ var UserContextMiddleware = class {
89
90
  req.userContext = {
90
91
  userId: webUser?.user_id,
91
92
  tenantId: webUser?.tenant_id,
92
- appId: webUser?.app_id ?? ""
93
+ appId: webUser?.app_id ?? "",
94
+ loginUrl: webUser?.login_url ?? ""
93
95
  };
94
96
  next();
95
97
  }
@@ -170,6 +172,12 @@ var ViewContextMiddleware = class {
170
172
  use(req, res, next) {
171
173
  const { userId, tenantId, appId } = req.userContext;
172
174
  const csrfToken = req.csrfToken;
175
+ req.__platform_data__ = {
176
+ csrfToken: csrfToken ?? "",
177
+ userId: userId ?? "",
178
+ appId: appId ?? "",
179
+ tenantId
180
+ };
173
181
  res.locals = {
174
182
  ...res.locals ?? {},
175
183
  csrfToken: csrfToken ?? "",
@@ -317,9 +325,7 @@ var PlatformModule = class _PlatformModule {
317
325
  };
318
326
  }, "useFactory")
319
327
  }),
320
- import_nestjs_authnpaas.AuthNPaasModule.forRoot({
321
- alwaysNeedLogin: options.alwaysNeedLogin ?? true
322
- })
328
+ import_nestjs_authnpaas.AuthNPaasModule.forRoot()
323
329
  ],
324
330
  providers: [
325
331
  {
@@ -363,16 +369,14 @@ PlatformModule = _ts_decorate5([
363
369
 
364
370
  // src/setup.ts
365
371
  var import_nestjs_logger2 = require("@lark-apaas/nestjs-logger");
366
- var import_path = require("path");
367
- var import_hbs = require("hbs");
368
372
  var import_cookie_parser = __toESM(require("cookie-parser"), 1);
369
373
 
370
- // src/modules/devtool/index.ts
374
+ // src/modules/devtool-v2/index.ts
371
375
  var import_swagger = require("@nestjs/swagger");
372
- var import_node_fs3 = require("fs");
373
- var import_node_path3 = require("path");
376
+ var import_node_fs4 = require("fs");
377
+ var import_node_path4 = require("path");
374
378
 
375
- // src/modules/devtool/helper.ts
379
+ // src/modules/devtool-v2/helper.ts
376
380
  var import_node_path = require("path");
377
381
  var import_node_fs = require("fs");
378
382
  function normalizeBasePath(rawBasePath) {
@@ -409,14 +413,14 @@ function ensureDirAndWrite(filePath, content) {
409
413
  }
410
414
  __name(ensureDirAndWrite, "ensureDirAndWrite");
411
415
 
412
- // src/modules/devtool/plugins/client-config-generator.ts
416
+ // src/modules/devtool-v2/plugins/client-config-generator.ts
413
417
  var import_node_fs2 = require("fs");
414
418
  var import_node_path2 = require("path");
415
419
  function generateClientConfig(options) {
416
420
  const configFilePath = (0, import_node_path2.resolve)(options.outputPath, "client.config.ts");
417
421
  const configContent = `// This file is auto-generated by @hey-api/openapi-ts
418
422
  import axios from 'axios';
419
- import type { CreateClientConfig } from './client/client.gen';
423
+ import type { CreateClientConfig } from './client';
420
424
  export const createClientConfig: CreateClientConfig = (config) => ({
421
425
  axios
422
426
  });
@@ -426,10 +430,43 @@ export const createClientConfig: CreateClientConfig = (config) => ({
426
430
  }
427
431
  __name(generateClientConfig, "generateClientConfig");
428
432
 
429
- // src/modules/devtool/index.ts
430
- var DevToolsModule = class {
433
+ // src/modules/devtool-v2/plugins/add-ts-nocheck.ts
434
+ var import_node_fs3 = require("fs");
435
+ var import_node_path3 = require("path");
436
+ function addTsNocheckToGeneratedFiles(outputPath) {
437
+ const files = getAllTsFiles(outputPath);
438
+ for (const filePath of files) {
439
+ addTsNocheckToFile(filePath);
440
+ }
441
+ }
442
+ __name(addTsNocheckToGeneratedFiles, "addTsNocheckToGeneratedFiles");
443
+ function getAllTsFiles(dirPath) {
444
+ const files = [];
445
+ const entries = (0, import_node_fs3.readdirSync)(dirPath);
446
+ for (const entry of entries) {
447
+ const fullPath = (0, import_node_path3.join)(dirPath, entry);
448
+ const stat = (0, import_node_fs3.statSync)(fullPath);
449
+ if (stat.isDirectory()) {
450
+ files.push(...getAllTsFiles(fullPath));
451
+ } else if (stat.isFile() && entry.endsWith(".ts")) {
452
+ files.push(fullPath);
453
+ }
454
+ }
455
+ return files;
456
+ }
457
+ __name(getAllTsFiles, "getAllTsFiles");
458
+ function addTsNocheckToFile(filePath) {
459
+ const content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
460
+ const newContent = `// @ts-nocheck
461
+ ${content}`;
462
+ (0, import_node_fs3.writeFileSync)(filePath, newContent, "utf-8");
463
+ }
464
+ __name(addTsNocheckToFile, "addTsNocheckToFile");
465
+
466
+ // src/modules/devtool-v2/index.ts
467
+ var DevToolsV2Module = class {
431
468
  static {
432
- __name(this, "DevToolsModule");
469
+ __name(this, "DevToolsV2Module");
433
470
  }
434
471
  static async mount(app, opts = {}) {
435
472
  const options = resolveOptsWithDefaultValue(opts);
@@ -446,16 +483,13 @@ var DevToolsModule = class {
446
483
  });
447
484
  console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
448
485
  }
449
- const openapiPath = (0, import_node_path3.resolve)(baseDirname, options.openapiOut);
486
+ const openapiPath = (0, import_node_path4.resolve)(baseDirname, options.openapiOut);
450
487
  ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
451
488
  if (options.needGenerateClientSdk) {
452
- const clientSdkOutPath = (0, import_node_path3.resolve)(baseDirname, options.clientSdkOut);
453
- (0, import_node_fs3.mkdirSync)(clientSdkOutPath, {
489
+ const clientSdkOutPath = (0, import_node_path4.resolve)(baseDirname, options.clientSdkOut);
490
+ (0, import_node_fs4.mkdirSync)(clientSdkOutPath, {
454
491
  recursive: true
455
492
  });
456
- generateClientConfig({
457
- outputPath: clientSdkOutPath
458
- });
459
493
  const { createClient } = await import("@hey-api/openapi-ts");
460
494
  await createClient({
461
495
  input: openapiPath,
@@ -467,28 +501,29 @@ var DevToolsModule = class {
467
501
  "@hey-api/sdk",
468
502
  {
469
503
  name: "@hey-api/client-axios",
470
- runtimeConfigPath: (0, import_node_path3.join)(clientSdkOutPath, "./client.config.ts")
504
+ runtimeConfigPath: (0, import_node_path4.join)(clientSdkOutPath, "./client.config.ts")
471
505
  }
472
506
  ]
473
507
  });
508
+ addTsNocheckToGeneratedFiles(clientSdkOutPath);
474
509
  ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
510
+ generateClientConfig({
511
+ outputPath: clientSdkOutPath
512
+ });
475
513
  console.log("[OpenAPI] \u5BFC\u51FA openapi.json \u5E76\u751F\u6210 axios SDK \u2705");
476
514
  }
477
515
  }
478
516
  };
479
517
 
480
518
  // src/setup.ts
481
- async function configureApp(app, options) {
519
+ async function configureApp(app) {
482
520
  app.useLogger(app.get(import_nestjs_logger2.AppLogger));
483
521
  app.flushLogs();
484
522
  app.use((0, import_cookie_parser.default)());
485
523
  const globalPrefix = process.env.CLIENT_BASE_PATH ?? "";
486
524
  app.setGlobalPrefix(globalPrefix);
487
- app.setBaseViewsDir((0, import_path.join)(process.cwd(), options?.viewsDir ?? "dist/client"));
488
- app.setViewEngine("html");
489
- app.engine("html", import_hbs.__express);
490
525
  if (process.env.NODE_ENV !== "production") {
491
- await DevToolsModule.mount(app, {
526
+ await DevToolsV2Module.mount(app, {
492
527
  basePath: process.env.CLIENT_BASE_PATH,
493
528
  docsPath: "/api_docs"
494
529
  });
@@ -496,6 +531,90 @@ async function configureApp(app, options) {
496
531
  }
497
532
  __name(configureApp, "configureApp");
498
533
 
534
+ // src/modules/devtool/index.ts
535
+ var import_swagger2 = require("@nestjs/swagger");
536
+ var import_node_fs6 = require("fs");
537
+ var import_node_path6 = require("path");
538
+
539
+ // src/modules/devtool/helper.ts
540
+ var import_node_path5 = require("path");
541
+ var import_node_fs5 = require("fs");
542
+ function normalizeBasePath2(rawBasePath) {
543
+ const normalizedBasePath = rawBasePath.startsWith("/") ? rawBasePath : `/${rawBasePath}`;
544
+ return normalizedBasePath.endsWith("/") ? normalizedBasePath.slice(0, -1) : normalizedBasePath;
545
+ }
546
+ __name(normalizeBasePath2, "normalizeBasePath");
547
+ function resolveOptsWithDefaultValue2(options) {
548
+ const basePath = normalizeBasePath2(options.basePath || "/");
549
+ const docsPath = normalizeBasePath2(options.docsPath || `api/docs`);
550
+ return {
551
+ ...options,
552
+ needSetupServer: options.needSetupServer ?? false,
553
+ basePath,
554
+ docsPath: `${basePath}${docsPath}`,
555
+ openapiOut: options.openapiOut || "./client/src/api/gen/openapi.json",
556
+ clientSdkOut: options.clientSdkOut || "./client/src/api/gen",
557
+ needGenerateClientSdk: options.needGenerateClientSdk ?? true,
558
+ swaggerOptions: {
559
+ title: options.swaggerOptions?.title ?? "NestJS Fullstack API",
560
+ version: options.swaggerOptions?.version ?? "1.0.0",
561
+ customSiteTitle: options.swaggerOptions?.customSiteTitle ?? "API Documentation",
562
+ customCss: options.swaggerOptions?.customCss ?? ".swagger-ui .topbar { display: none }"
563
+ }
564
+ };
565
+ }
566
+ __name(resolveOptsWithDefaultValue2, "resolveOptsWithDefaultValue");
567
+ function ensureDirAndWrite2(filePath, content) {
568
+ const dir = (0, import_node_path5.dirname)(filePath);
569
+ (0, import_node_fs5.mkdirSync)(dir, {
570
+ recursive: true
571
+ });
572
+ (0, import_node_fs5.writeFileSync)(filePath, content);
573
+ }
574
+ __name(ensureDirAndWrite2, "ensureDirAndWrite");
575
+
576
+ // src/modules/devtool/index.ts
577
+ var DevToolsModule = class {
578
+ static {
579
+ __name(this, "DevToolsModule");
580
+ }
581
+ static async mount(app, opts = {}) {
582
+ const options = resolveOptsWithDefaultValue2(opts);
583
+ const baseDirname = process.cwd();
584
+ const builder = new import_swagger2.DocumentBuilder().setTitle(options.swaggerOptions.title).setVersion(options.swaggerOptions.version);
585
+ const document = import_swagger2.SwaggerModule.createDocument(app, builder.build(), {
586
+ operationIdFactory: /* @__PURE__ */ __name((_c, m) => m, "operationIdFactory")
587
+ });
588
+ if (options.needSetupServer) {
589
+ import_swagger2.SwaggerModule.setup(options.docsPath, app, document, {
590
+ customSiteTitle: options.swaggerOptions.customSiteTitle,
591
+ customCss: options.swaggerOptions.customCss,
592
+ swaggerOptions: {
593
+ persistAuthorization: true
594
+ }
595
+ });
596
+ console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
597
+ }
598
+ const openapiPath = (0, import_node_path6.resolve)(baseDirname, options.openapiOut);
599
+ ensureDirAndWrite2(openapiPath, JSON.stringify(document, null, 2));
600
+ if (options.needGenerateClientSdk) {
601
+ const clientSdkOutPath = (0, import_node_path6.resolve)(baseDirname, options.clientSdkOut);
602
+ (0, import_node_fs6.mkdirSync)(clientSdkOutPath, {
603
+ recursive: true
604
+ });
605
+ const { generate } = await import("openapi-typescript-codegen");
606
+ await generate({
607
+ input: openapiPath,
608
+ output: clientSdkOutPath,
609
+ httpClient: "axios",
610
+ useOptions: false,
611
+ exportServices: true
612
+ });
613
+ console.log("[OpenAPI] \u5BFC\u51FA openapi.json \u5E76\u751F\u6210 axios SDK \u2705");
614
+ }
615
+ }
616
+ };
617
+
499
618
  // src/index.ts
500
619
  __reExport(index_exports, require("@lark-apaas/nestjs-authnpaas"), module.exports);
501
620
  __reExport(index_exports, require("@lark-apaas/nestjs-datapaas"), module.exports);
@@ -504,6 +623,7 @@ __reExport(index_exports, require("@lark-apaas/nestjs-datapaas"), module.exports
504
623
  CsrfMiddleware,
505
624
  CsrfTokenMiddleware,
506
625
  DevToolsModule,
626
+ DevToolsV2Module,
507
627
  PlatformModule,
508
628
  UserContextMiddleware,
509
629
  ViewContextMiddleware,
package/dist/index.d.cts CHANGED
@@ -7,12 +7,19 @@ export * from '@lark-apaas/nestjs-datapaas';
7
7
  declare global {
8
8
  namespace Express {
9
9
  interface Request {
10
- csrfToken?: string;
10
+ __platform_data__?: {
11
+ userId?: string;
12
+ tenantId?: number;
13
+ csrfToken?: string;
14
+ [key: string]: unknown;
15
+ };
11
16
  userContext: {
12
17
  userId?: string;
13
18
  tenantId?: number;
14
19
  appId?: string;
15
- }
20
+ loginUrl?: string;
21
+ },
22
+ csrfToken?: string;
16
23
  }
17
24
  }
18
25
  }
@@ -31,11 +38,6 @@ interface PlatformModuleOptions {
31
38
  * 默认: '/api/*'
32
39
  */
33
40
  csrfRoutes?: string | string[];
34
- /**
35
- * AuthN 能力,是否全部依赖登录
36
- * 默认:true
37
- */
38
- alwaysNeedLogin?: boolean;
39
41
  }
40
42
 
41
43
  declare class PlatformModule implements NestModule {
@@ -47,11 +49,9 @@ declare class PlatformModule implements NestModule {
47
49
  configure(consumer: MiddlewareConsumer): void;
48
50
  }
49
51
 
50
- declare function configureApp(app: NestExpressApplication, options?: {
51
- viewsDir?: string;
52
- }): Promise<void>;
52
+ declare function configureApp(app: NestExpressApplication): Promise<void>;
53
53
 
54
- interface DevToolsOptions {
54
+ interface DevToolsOptions$1 {
55
55
  basePath?: string;
56
56
  docsPath?: string;
57
57
  openapiOut?: string;
@@ -66,7 +66,29 @@ interface DevToolsOptions {
66
66
  };
67
67
  }
68
68
 
69
+ /**
70
+ * @deprecated 该模块已被废弃,不建议使用,建议使用 DevToolsV2Module
71
+ */
69
72
  declare class DevToolsModule {
73
+ static mount(app: INestApplication, opts?: DevToolsOptions$1): Promise<void>;
74
+ }
75
+
76
+ interface DevToolsOptions {
77
+ basePath?: string;
78
+ docsPath?: string;
79
+ openapiOut?: string;
80
+ needSetupServer?: boolean;
81
+ needGenerateClientSdk?: boolean;
82
+ clientSdkOut?: string;
83
+ swaggerOptions?: {
84
+ title?: string;
85
+ version?: string;
86
+ customSiteTitle?: string;
87
+ customCss?: string;
88
+ };
89
+ }
90
+
91
+ declare class DevToolsV2Module {
70
92
  static mount(app: INestApplication, opts?: DevToolsOptions): Promise<void>;
71
93
  }
72
94
 
@@ -101,4 +123,4 @@ declare class ViewContextMiddleware implements NestMiddleware {
101
123
  use(req: Request, res: Response, next: NextFunction): void;
102
124
  }
103
125
 
104
- export { CsrfMiddleware, CsrfTokenMiddleware, DevToolsModule, PlatformModule, type PlatformModuleOptions, UserContextMiddleware, ViewContextMiddleware, configureApp };
126
+ export { CsrfMiddleware, CsrfTokenMiddleware, DevToolsModule, DevToolsV2Module, PlatformModule, type PlatformModuleOptions, UserContextMiddleware, ViewContextMiddleware, configureApp };
package/dist/index.d.ts CHANGED
@@ -7,12 +7,19 @@ export * from '@lark-apaas/nestjs-datapaas';
7
7
  declare global {
8
8
  namespace Express {
9
9
  interface Request {
10
- csrfToken?: string;
10
+ __platform_data__?: {
11
+ userId?: string;
12
+ tenantId?: number;
13
+ csrfToken?: string;
14
+ [key: string]: unknown;
15
+ };
11
16
  userContext: {
12
17
  userId?: string;
13
18
  tenantId?: number;
14
19
  appId?: string;
15
- }
20
+ loginUrl?: string;
21
+ },
22
+ csrfToken?: string;
16
23
  }
17
24
  }
18
25
  }
@@ -31,11 +38,6 @@ interface PlatformModuleOptions {
31
38
  * 默认: '/api/*'
32
39
  */
33
40
  csrfRoutes?: string | string[];
34
- /**
35
- * AuthN 能力,是否全部依赖登录
36
- * 默认:true
37
- */
38
- alwaysNeedLogin?: boolean;
39
41
  }
40
42
 
41
43
  declare class PlatformModule implements NestModule {
@@ -47,11 +49,9 @@ declare class PlatformModule implements NestModule {
47
49
  configure(consumer: MiddlewareConsumer): void;
48
50
  }
49
51
 
50
- declare function configureApp(app: NestExpressApplication, options?: {
51
- viewsDir?: string;
52
- }): Promise<void>;
52
+ declare function configureApp(app: NestExpressApplication): Promise<void>;
53
53
 
54
- interface DevToolsOptions {
54
+ interface DevToolsOptions$1 {
55
55
  basePath?: string;
56
56
  docsPath?: string;
57
57
  openapiOut?: string;
@@ -66,7 +66,29 @@ interface DevToolsOptions {
66
66
  };
67
67
  }
68
68
 
69
+ /**
70
+ * @deprecated 该模块已被废弃,不建议使用,建议使用 DevToolsV2Module
71
+ */
69
72
  declare class DevToolsModule {
73
+ static mount(app: INestApplication, opts?: DevToolsOptions$1): Promise<void>;
74
+ }
75
+
76
+ interface DevToolsOptions {
77
+ basePath?: string;
78
+ docsPath?: string;
79
+ openapiOut?: string;
80
+ needSetupServer?: boolean;
81
+ needGenerateClientSdk?: boolean;
82
+ clientSdkOut?: string;
83
+ swaggerOptions?: {
84
+ title?: string;
85
+ version?: string;
86
+ customSiteTitle?: string;
87
+ customCss?: string;
88
+ };
89
+ }
90
+
91
+ declare class DevToolsV2Module {
70
92
  static mount(app: INestApplication, opts?: DevToolsOptions): Promise<void>;
71
93
  }
72
94
 
@@ -101,4 +123,4 @@ declare class ViewContextMiddleware implements NestMiddleware {
101
123
  use(req: Request, res: Response, next: NextFunction): void;
102
124
  }
103
125
 
104
- export { CsrfMiddleware, CsrfTokenMiddleware, DevToolsModule, PlatformModule, type PlatformModuleOptions, UserContextMiddleware, ViewContextMiddleware, configureApp };
126
+ export { CsrfMiddleware, CsrfTokenMiddleware, DevToolsModule, DevToolsV2Module, PlatformModule, type PlatformModuleOptions, UserContextMiddleware, ViewContextMiddleware, configureApp };
package/dist/index.js CHANGED
@@ -48,7 +48,8 @@ var UserContextMiddleware = class {
48
48
  req.userContext = {
49
49
  userId: webUser?.user_id,
50
50
  tenantId: webUser?.tenant_id,
51
- appId: webUser?.app_id ?? ""
51
+ appId: webUser?.app_id ?? "",
52
+ loginUrl: webUser?.login_url ?? ""
52
53
  };
53
54
  next();
54
55
  }
@@ -129,6 +130,12 @@ var ViewContextMiddleware = class {
129
130
  use(req, res, next) {
130
131
  const { userId, tenantId, appId } = req.userContext;
131
132
  const csrfToken = req.csrfToken;
133
+ req.__platform_data__ = {
134
+ csrfToken: csrfToken ?? "",
135
+ userId: userId ?? "",
136
+ appId: appId ?? "",
137
+ tenantId
138
+ };
132
139
  res.locals = {
133
140
  ...res.locals ?? {},
134
141
  csrfToken: csrfToken ?? "",
@@ -276,9 +283,7 @@ var PlatformModule = class _PlatformModule {
276
283
  };
277
284
  }, "useFactory")
278
285
  }),
279
- AuthNPaasModule.forRoot({
280
- alwaysNeedLogin: options.alwaysNeedLogin ?? true
281
- })
286
+ AuthNPaasModule.forRoot()
282
287
  ],
283
288
  providers: [
284
289
  {
@@ -322,16 +327,14 @@ PlatformModule = _ts_decorate5([
322
327
 
323
328
  // src/setup.ts
324
329
  import { AppLogger as AppLogger2 } from "@lark-apaas/nestjs-logger";
325
- import { join as join2 } from "path";
326
- import { __express as hbsExpressEngine } from "hbs";
327
330
  import cookieParser from "cookie-parser";
328
331
 
329
- // src/modules/devtool/index.ts
332
+ // src/modules/devtool-v2/index.ts
330
333
  import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
331
334
  import { mkdirSync as mkdirSync2 } from "fs";
332
- import { resolve as resolve2, join } from "path";
335
+ import { resolve as resolve2, join as join2 } from "path";
333
336
 
334
- // src/modules/devtool/helper.ts
337
+ // src/modules/devtool-v2/helper.ts
335
338
  import { dirname } from "path";
336
339
  import { writeFileSync, mkdirSync } from "fs";
337
340
  function normalizeBasePath(rawBasePath) {
@@ -368,14 +371,14 @@ function ensureDirAndWrite(filePath, content) {
368
371
  }
369
372
  __name(ensureDirAndWrite, "ensureDirAndWrite");
370
373
 
371
- // src/modules/devtool/plugins/client-config-generator.ts
374
+ // src/modules/devtool-v2/plugins/client-config-generator.ts
372
375
  import { writeFileSync as writeFileSync2 } from "fs";
373
376
  import { resolve } from "path";
374
377
  function generateClientConfig(options) {
375
378
  const configFilePath = resolve(options.outputPath, "client.config.ts");
376
379
  const configContent = `// This file is auto-generated by @hey-api/openapi-ts
377
380
  import axios from 'axios';
378
- import type { CreateClientConfig } from './client/client.gen';
381
+ import type { CreateClientConfig } from './client';
379
382
  export const createClientConfig: CreateClientConfig = (config) => ({
380
383
  axios
381
384
  });
@@ -385,10 +388,43 @@ export const createClientConfig: CreateClientConfig = (config) => ({
385
388
  }
386
389
  __name(generateClientConfig, "generateClientConfig");
387
390
 
388
- // src/modules/devtool/index.ts
389
- var DevToolsModule = class {
391
+ // src/modules/devtool-v2/plugins/add-ts-nocheck.ts
392
+ import { readdirSync, readFileSync, writeFileSync as writeFileSync3, statSync } from "fs";
393
+ import { join } from "path";
394
+ function addTsNocheckToGeneratedFiles(outputPath) {
395
+ const files = getAllTsFiles(outputPath);
396
+ for (const filePath of files) {
397
+ addTsNocheckToFile(filePath);
398
+ }
399
+ }
400
+ __name(addTsNocheckToGeneratedFiles, "addTsNocheckToGeneratedFiles");
401
+ function getAllTsFiles(dirPath) {
402
+ const files = [];
403
+ const entries = readdirSync(dirPath);
404
+ for (const entry of entries) {
405
+ const fullPath = join(dirPath, entry);
406
+ const stat = statSync(fullPath);
407
+ if (stat.isDirectory()) {
408
+ files.push(...getAllTsFiles(fullPath));
409
+ } else if (stat.isFile() && entry.endsWith(".ts")) {
410
+ files.push(fullPath);
411
+ }
412
+ }
413
+ return files;
414
+ }
415
+ __name(getAllTsFiles, "getAllTsFiles");
416
+ function addTsNocheckToFile(filePath) {
417
+ const content = readFileSync(filePath, "utf-8");
418
+ const newContent = `// @ts-nocheck
419
+ ${content}`;
420
+ writeFileSync3(filePath, newContent, "utf-8");
421
+ }
422
+ __name(addTsNocheckToFile, "addTsNocheckToFile");
423
+
424
+ // src/modules/devtool-v2/index.ts
425
+ var DevToolsV2Module = class {
390
426
  static {
391
- __name(this, "DevToolsModule");
427
+ __name(this, "DevToolsV2Module");
392
428
  }
393
429
  static async mount(app, opts = {}) {
394
430
  const options = resolveOptsWithDefaultValue(opts);
@@ -412,9 +448,6 @@ var DevToolsModule = class {
412
448
  mkdirSync2(clientSdkOutPath, {
413
449
  recursive: true
414
450
  });
415
- generateClientConfig({
416
- outputPath: clientSdkOutPath
417
- });
418
451
  const { createClient } = await import("@hey-api/openapi-ts");
419
452
  await createClient({
420
453
  input: openapiPath,
@@ -426,28 +459,29 @@ var DevToolsModule = class {
426
459
  "@hey-api/sdk",
427
460
  {
428
461
  name: "@hey-api/client-axios",
429
- runtimeConfigPath: join(clientSdkOutPath, "./client.config.ts")
462
+ runtimeConfigPath: join2(clientSdkOutPath, "./client.config.ts")
430
463
  }
431
464
  ]
432
465
  });
466
+ addTsNocheckToGeneratedFiles(clientSdkOutPath);
433
467
  ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
468
+ generateClientConfig({
469
+ outputPath: clientSdkOutPath
470
+ });
434
471
  console.log("[OpenAPI] \u5BFC\u51FA openapi.json \u5E76\u751F\u6210 axios SDK \u2705");
435
472
  }
436
473
  }
437
474
  };
438
475
 
439
476
  // src/setup.ts
440
- async function configureApp(app, options) {
477
+ async function configureApp(app) {
441
478
  app.useLogger(app.get(AppLogger2));
442
479
  app.flushLogs();
443
480
  app.use(cookieParser());
444
481
  const globalPrefix = process.env.CLIENT_BASE_PATH ?? "";
445
482
  app.setGlobalPrefix(globalPrefix);
446
- app.setBaseViewsDir(join2(process.cwd(), options?.viewsDir ?? "dist/client"));
447
- app.setViewEngine("html");
448
- app.engine("html", hbsExpressEngine);
449
483
  if (process.env.NODE_ENV !== "production") {
450
- await DevToolsModule.mount(app, {
484
+ await DevToolsV2Module.mount(app, {
451
485
  basePath: process.env.CLIENT_BASE_PATH,
452
486
  docsPath: "/api_docs"
453
487
  });
@@ -455,6 +489,90 @@ async function configureApp(app, options) {
455
489
  }
456
490
  __name(configureApp, "configureApp");
457
491
 
492
+ // src/modules/devtool/index.ts
493
+ import { SwaggerModule as SwaggerModule2, DocumentBuilder as DocumentBuilder2 } from "@nestjs/swagger";
494
+ import { mkdirSync as mkdirSync4 } from "fs";
495
+ import { resolve as resolve3 } from "path";
496
+
497
+ // src/modules/devtool/helper.ts
498
+ import { dirname as dirname2 } from "path";
499
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
500
+ function normalizeBasePath2(rawBasePath) {
501
+ const normalizedBasePath = rawBasePath.startsWith("/") ? rawBasePath : `/${rawBasePath}`;
502
+ return normalizedBasePath.endsWith("/") ? normalizedBasePath.slice(0, -1) : normalizedBasePath;
503
+ }
504
+ __name(normalizeBasePath2, "normalizeBasePath");
505
+ function resolveOptsWithDefaultValue2(options) {
506
+ const basePath = normalizeBasePath2(options.basePath || "/");
507
+ const docsPath = normalizeBasePath2(options.docsPath || `api/docs`);
508
+ return {
509
+ ...options,
510
+ needSetupServer: options.needSetupServer ?? false,
511
+ basePath,
512
+ docsPath: `${basePath}${docsPath}`,
513
+ openapiOut: options.openapiOut || "./client/src/api/gen/openapi.json",
514
+ clientSdkOut: options.clientSdkOut || "./client/src/api/gen",
515
+ needGenerateClientSdk: options.needGenerateClientSdk ?? true,
516
+ swaggerOptions: {
517
+ title: options.swaggerOptions?.title ?? "NestJS Fullstack API",
518
+ version: options.swaggerOptions?.version ?? "1.0.0",
519
+ customSiteTitle: options.swaggerOptions?.customSiteTitle ?? "API Documentation",
520
+ customCss: options.swaggerOptions?.customCss ?? ".swagger-ui .topbar { display: none }"
521
+ }
522
+ };
523
+ }
524
+ __name(resolveOptsWithDefaultValue2, "resolveOptsWithDefaultValue");
525
+ function ensureDirAndWrite2(filePath, content) {
526
+ const dir = dirname2(filePath);
527
+ mkdirSync3(dir, {
528
+ recursive: true
529
+ });
530
+ writeFileSync4(filePath, content);
531
+ }
532
+ __name(ensureDirAndWrite2, "ensureDirAndWrite");
533
+
534
+ // src/modules/devtool/index.ts
535
+ var DevToolsModule = class {
536
+ static {
537
+ __name(this, "DevToolsModule");
538
+ }
539
+ static async mount(app, opts = {}) {
540
+ const options = resolveOptsWithDefaultValue2(opts);
541
+ const baseDirname = process.cwd();
542
+ const builder = new DocumentBuilder2().setTitle(options.swaggerOptions.title).setVersion(options.swaggerOptions.version);
543
+ const document = SwaggerModule2.createDocument(app, builder.build(), {
544
+ operationIdFactory: /* @__PURE__ */ __name((_c, m) => m, "operationIdFactory")
545
+ });
546
+ if (options.needSetupServer) {
547
+ SwaggerModule2.setup(options.docsPath, app, document, {
548
+ customSiteTitle: options.swaggerOptions.customSiteTitle,
549
+ customCss: options.swaggerOptions.customCss,
550
+ swaggerOptions: {
551
+ persistAuthorization: true
552
+ }
553
+ });
554
+ console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
555
+ }
556
+ const openapiPath = resolve3(baseDirname, options.openapiOut);
557
+ ensureDirAndWrite2(openapiPath, JSON.stringify(document, null, 2));
558
+ if (options.needGenerateClientSdk) {
559
+ const clientSdkOutPath = resolve3(baseDirname, options.clientSdkOut);
560
+ mkdirSync4(clientSdkOutPath, {
561
+ recursive: true
562
+ });
563
+ const { generate } = await import("openapi-typescript-codegen");
564
+ await generate({
565
+ input: openapiPath,
566
+ output: clientSdkOutPath,
567
+ httpClient: "axios",
568
+ useOptions: false,
569
+ exportServices: true
570
+ });
571
+ console.log("[OpenAPI] \u5BFC\u51FA openapi.json \u5E76\u751F\u6210 axios SDK \u2705");
572
+ }
573
+ }
574
+ };
575
+
458
576
  // src/index.ts
459
577
  export * from "@lark-apaas/nestjs-authnpaas";
460
578
  export * from "@lark-apaas/nestjs-datapaas";
@@ -462,6 +580,7 @@ export {
462
580
  CsrfMiddleware,
463
581
  CsrfTokenMiddleware,
464
582
  DevToolsModule,
583
+ DevToolsV2Module,
465
584
  PlatformModule,
466
585
  UserContextMiddleware,
467
586
  ViewContextMiddleware,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-nestjs-core",
3
- "version": "1.0.3-alpha.8",
3
+ "version": "1.0.3",
4
4
  "description": "FullStack Nestjs Core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,9 +19,7 @@
19
19
  "node": ">=18.0.0"
20
20
  },
21
21
  "files": [
22
- "dist",
23
- "templates",
24
- "postinstall.config.mjs"
22
+ "dist"
25
23
  ],
26
24
  "exports": {
27
25
  ".": {
@@ -40,12 +38,12 @@
40
38
  "lint:fix": "eslint src --ext .ts --fix"
41
39
  },
42
40
  "dependencies": {
43
- "@hey-api/openapi-ts": "^0.86.11",
41
+ "@hey-api/openapi-ts": "^0.87.0",
44
42
  "@lark-apaas/nestjs-authnpaas": "^1.0.0",
45
43
  "@lark-apaas/nestjs-datapaas": "^1.0.1",
46
44
  "@lark-apaas/nestjs-logger": "^1.0.0",
47
45
  "cookie-parser": "^1.4.7",
48
- "hbs": "^4.2.0"
46
+ "openapi-typescript-codegen": "^0.29.0"
49
47
  },
50
48
  "devDependencies": {
51
49
  "@nestjs/common": "^10.4.20",
@@ -53,7 +51,6 @@
53
51
  "@nestjs/swagger": "^7.4.2",
54
52
  "@types/cookie-parser": "^1.4.9",
55
53
  "@types/express": "^5.0.3",
56
- "@types/hbs": "^4.0.5",
57
54
  "class-transformer": "^0.5.1",
58
55
  "class-validator": "^0.14.2",
59
56
  "drizzle-orm": "0.44.6",