@lark-apaas/fullstack-nestjs-core 0.1.0-alpha.5 → 0.1.0-alpha.7

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/dist/index.cjs CHANGED
@@ -98,6 +98,7 @@ function resolveOptsWithDefaultValue(options) {
98
98
  const docsPath = normalizeBasePath(options.docsPath || `api/docs`);
99
99
  return {
100
100
  ...options,
101
+ needSetupServer: options.needSetupServer ?? false,
101
102
  basePath,
102
103
  docsPath: `${basePath}${docsPath}`,
103
104
  openapiOut: options.openapiOut || "./client/src/api/gen/openapi.json",
@@ -126,11 +127,14 @@ var DevToolsModule = class {
126
127
  const document = import_swagger.SwaggerModule.createDocument(app, builder.build(), {
127
128
  operationIdFactory: (_c, m) => m
128
129
  });
129
- import_swagger.SwaggerModule.setup(options.docsPath, app, document, {
130
- customSiteTitle: options.swaggerOptions.customSiteTitle,
131
- customCss: options.swaggerOptions.customCss,
132
- swaggerOptions: { persistAuthorization: true }
133
- });
130
+ if (options.needSetupServer) {
131
+ import_swagger.SwaggerModule.setup(options.docsPath, app, document, {
132
+ customSiteTitle: options.swaggerOptions.customSiteTitle,
133
+ customCss: options.swaggerOptions.customCss,
134
+ swaggerOptions: { persistAuthorization: true }
135
+ });
136
+ console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
137
+ }
134
138
  const openapiPath = (0, import_node_path2.resolve)(baseDirname, options.openapiOut);
135
139
  ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
136
140
  if (options.needGenerateClientSdk) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import './types/express.d';\n\nexport { DevToolsModule } from './modules/devtool';\n\n// Middlewares\nexport { CsrfTokenMiddleware } from './middlewares/csrf_token';\nexport { CsrfMiddleware } from './middlewares/csrf';\nexport { UserContextMiddleware } from './middlewares/user-context';\n","import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions;\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 7,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions;\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","crypto","import_common","_init","import_common","_init"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import './types/express.d';\n\nexport { DevToolsModule } from './modules/devtool';\n\n// Middlewares\nexport { CsrfTokenMiddleware } from './middlewares/csrf_token';\nexport { CsrfMiddleware } from './middlewares/csrf';\nexport { UserContextMiddleware } from './middlewares/user-context';\n","import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n // 1.1) 挂载 Swagger UI(可关)\n if(options.needSetupServer){\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n console.log(`[OpenAPI] Swagger UI 已挂载至 ${options.docsPath}`);\n }\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n needSetupServer: options.needSetupServer ?? false,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions;\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 7,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions;\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","crypto","import_common","_init","import_common","_init"]}
package/dist/index.d.cts CHANGED
@@ -18,6 +18,7 @@ interface DevToolsOptions {
18
18
  basePath?: string;
19
19
  docsPath?: string;
20
20
  openapiOut?: string;
21
+ needSetupServer?: boolean;
21
22
  needGenerateClientSdk?: boolean;
22
23
  clientSdkOut?: string;
23
24
  swaggerOptions?: {
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ interface DevToolsOptions {
18
18
  basePath?: string;
19
19
  docsPath?: string;
20
20
  openapiOut?: string;
21
+ needSetupServer?: boolean;
21
22
  needGenerateClientSdk?: boolean;
22
23
  clientSdkOut?: string;
23
24
  swaggerOptions?: {
package/dist/index.js CHANGED
@@ -63,6 +63,7 @@ function resolveOptsWithDefaultValue(options) {
63
63
  const docsPath = normalizeBasePath(options.docsPath || `api/docs`);
64
64
  return {
65
65
  ...options,
66
+ needSetupServer: options.needSetupServer ?? false,
66
67
  basePath,
67
68
  docsPath: `${basePath}${docsPath}`,
68
69
  openapiOut: options.openapiOut || "./client/src/api/gen/openapi.json",
@@ -91,11 +92,14 @@ var DevToolsModule = class {
91
92
  const document = SwaggerModule.createDocument(app, builder.build(), {
92
93
  operationIdFactory: (_c, m) => m
93
94
  });
94
- SwaggerModule.setup(options.docsPath, app, document, {
95
- customSiteTitle: options.swaggerOptions.customSiteTitle,
96
- customCss: options.swaggerOptions.customCss,
97
- swaggerOptions: { persistAuthorization: true }
98
- });
95
+ if (options.needSetupServer) {
96
+ SwaggerModule.setup(options.docsPath, app, document, {
97
+ customSiteTitle: options.swaggerOptions.customSiteTitle,
98
+ customCss: options.swaggerOptions.customCss,
99
+ swaggerOptions: { persistAuthorization: true }
100
+ });
101
+ console.log(`[OpenAPI] Swagger UI \u5DF2\u6302\u8F7D\u81F3 ${options.docsPath}`);
102
+ }
99
103
  const openapiPath = resolve(baseDirname, options.openapiOut);
100
104
  ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));
101
105
  if (options.needGenerateClientSdk) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions;\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 7,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions;\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["mkdirSync","mkdirSync","Injectable","_init","Injectable","Injectable","_init","Injectable"]}
1
+ {"version":3,"sources":["../src/modules/devtool/index.ts","../src/modules/devtool/helper.ts","../src/middlewares/csrf_token/index.ts","../src/middlewares/csrf_token/helper.ts","../src/middlewares/csrf/index.ts","../src/middlewares/csrf/helper.ts","../src/middlewares/user-context/index.ts","../src/middlewares/user-context/helper.ts"],"sourcesContent":["import type { INestApplication } from '@nestjs/common';\n\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { mkdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { resolveOptsWithDefaultValue, ensureDirAndWrite } from './helper';\nimport { DevToolsOptions } from './type';\n\nexport class DevToolsModule {\n static async mount(app: INestApplication, opts: DevToolsOptions = {}) {\n const options = resolveOptsWithDefaultValue(opts);\n const baseDirname = process.cwd(); // 跟随命令的根目录\n\n // 1) 生成 Swagger 文档\n const builder = new DocumentBuilder()\n .setTitle(options.swaggerOptions.title)\n .setVersion(options.swaggerOptions.version);\n const document = SwaggerModule.createDocument(app, builder.build(), {\n operationIdFactory: (_c, m) => m,\n });\n // 1.1) 挂载 Swagger UI(可关)\n if(options.needSetupServer){\n SwaggerModule.setup(options.docsPath, app, document, {\n customSiteTitle: options.swaggerOptions.customSiteTitle,\n customCss: options.swaggerOptions.customCss,\n swaggerOptions: { persistAuthorization: true },\n });\n console.log(`[OpenAPI] Swagger UI 已挂载至 ${options.docsPath}`);\n }\n\n // 2) 导出 openapi.json\n const openapiPath = resolve(baseDirname, options.openapiOut);\n ensureDirAndWrite(openapiPath, JSON.stringify(document, null, 2));\n\n // 3) 生成 axios SDK(可关)\n if (options.needGenerateClientSdk) {\n const clientSdkOutPath = resolve(baseDirname, options.clientSdkOut);\n mkdirSync(clientSdkOutPath, { recursive: true });\n const { generate } = await import('openapi-typescript-codegen');\n await generate({\n input: openapiPath,\n output: clientSdkOutPath,\n httpClient: 'axios',\n useOptions: false,\n exportServices: true,\n });\n console.log('[OpenAPI] 导出 openapi.json 并生成 axios SDK ✅');\n }\n }\n}\n","import { dirname } from 'node:path';\nimport { writeFileSync, mkdirSync } from 'node:fs';\n\nimport { DevToolsOptions } from './type';\n/**\n * 标准化基础路径,确保以 '/' 开头且以 '/' 结尾\n *\n * @param rawBasePath 原始的基础路径,可能以 '/' 开头或结尾\n * @returns 标准化后的基础路径,确保以 '/' 开头且不以 '/' 结尾\n */\nexport function normalizeBasePath(rawBasePath: string): string {\n const normalizedBasePath = rawBasePath.startsWith('/')\n ? rawBasePath\n : `/${rawBasePath}`;\n return normalizedBasePath.endsWith('/')\n ? normalizedBasePath.slice(0, -1)\n : normalizedBasePath;\n}\n\ntype ResolvedDevToolsOptions = Required<\n Omit<DevToolsOptions, 'swaggerOptions'>\n> & {\n swaggerOptions: Required<NonNullable<DevToolsOptions['swaggerOptions']>>;\n};\n\nexport function resolveOptsWithDefaultValue(\n options: DevToolsOptions,\n): ResolvedDevToolsOptions {\n const basePath = normalizeBasePath(options.basePath || '/');\n const docsPath = normalizeBasePath(options.docsPath || `api/docs`);\n return {\n ...options,\n needSetupServer: options.needSetupServer ?? false,\n basePath,\n docsPath: `${basePath}${docsPath}`,\n openapiOut: options.openapiOut || './client/src/api/gen/openapi.json',\n clientSdkOut: options.clientSdkOut || './client/src/api/gen',\n needGenerateClientSdk: options.needGenerateClientSdk ?? true,\n swaggerOptions: {\n title: options.swaggerOptions?.title ?? 'NestJS Fullstack API',\n version: options.swaggerOptions?.version ?? '1.0.0',\n customSiteTitle:\n options.swaggerOptions?.customSiteTitle ?? 'API Documentation',\n customCss:\n options.swaggerOptions?.customCss ??\n '.swagger-ui .topbar { display: none }',\n },\n };\n}\n\nexport function ensureDirAndWrite(filePath: string, content: string) {\n // 1. 拿到文件的上级目录\n const dir = dirname(filePath);\n\n // 2. 确保目录存在,不存在就递归创建\n mkdirSync(dir, { recursive: true });\n\n // 3. 写文件\n writeFileSync(filePath, content);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\nimport { resolveCsrfTokenOptions, genToken } from './helper';\n\n@Injectable()\nexport class CsrfTokenMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfTokenOptions;\n\n public static configure(opts: CsrfTokenOptions) {\n this.options = resolveCsrfTokenOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { cookieKey, cookieMaxAge, cookiePath } = CsrfTokenMiddleware.options;\n const originToken = req.cookies[cookieKey.toLowerCase()];\n // 如果存在 Cookie,则直接消费,无需生成\n if (originToken) {\n req.csrfToken = originToken;\n next();\n } else {\n // 如果不存在 token,则生成新的 csrfToken,并 setCookie\n const token = genToken();\n req.csrfToken = token;\n res.cookie(cookieKey, token, {\n maxAge: cookieMaxAge,\n path: cookiePath,\n httpOnly: true,\n secure: true,\n sameSite: 'none',\n partitioned: true, // 默认开启 Partitioned Cookie\n });\n next();\n }\n }\n}\n","import crypto from 'crypto';\nimport { CsrfTokenOptions, ResolvedCsrfTokenOptions } from './type';\n\nexport function resolveCsrfTokenOptions(\n options: CsrfTokenOptions,\n): ResolvedCsrfTokenOptions {\n return {\n ...options,\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n cookieMaxAge: options.cookieMaxAge ?? 60 * 60 * 24 * 7,\n cookiePath: options.cookiePath ?? '/',\n };\n}\n\n// 生成 CsrfToken\nexport function genToken() {\n // 时间戳(秒级,和 Go 一致)\n const ts = Math.floor(Date.now() / 1000);\n\n // 随机 int64 模拟:生成 8 字节随机数并转成十进制字符串\n const randInt64 = BigInt(\n '0x' + crypto.randomBytes(8).toString('hex'),\n ).toString();\n\n // 拼接 \"<rand>.<timestamp>\"\n const s = `${randInt64}.${ts}`;\n\n // 计算 sha1\n const sha1 = crypto.createHash('sha1');\n sha1.update(s);\n\n // 返回 \"<sha1Hex>-<timestamp>\"\n return `${sha1.digest('hex')}-${ts}`;\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\n\nimport type { CsrfOptions, ResolvedCsrfOptions } from './type';\nimport { resolveCsrfOptions, sendForbidden } from './helper';\n\n@Injectable()\nexport class CsrfMiddleware implements NestMiddleware {\n private static options: ResolvedCsrfOptions;\n\n public static configure(opts: CsrfOptions) {\n this.options = resolveCsrfOptions(opts);\n }\n\n public use(req: Request, res: Response, next: NextFunction) {\n const { headerKey, cookieKey } = CsrfMiddleware.options;\n const cookieCsrfToken = req.cookies[cookieKey.toLowerCase()];\n if (!cookieCsrfToken) {\n sendForbidden(res, 'csrf token not found in cookie.');\n return;\n }\n const headerCsrfToken = req.headers[headerKey.toLowerCase()];\n if (!headerCsrfToken) {\n sendForbidden(res, 'csrf token not found in header.');\n return;\n }\n if (cookieCsrfToken !== headerCsrfToken) {\n sendForbidden(res, 'csrf token not match.');\n return;\n }\n next();\n }\n}\n","import type { Response } from 'express';\n\nimport { CsrfOptions, ResolvedCsrfOptions } from './type';\n\nexport function resolveCsrfOptions(options: CsrfOptions): ResolvedCsrfOptions {\n return {\n ...options,\n headerKey: options.headerKey ?? 'x-suda-csrf-token',\n cookieKey: options.cookieKey ?? 'suda-csrf-token',\n };\n}\n\nexport function sendForbidden(res: Response, message: string) {\n res.status(403).send(`Forbidden,${message}`);\n}\n","import { Injectable, NestMiddleware } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { getWebUserFromHeader } from './helper';\n\n@Injectable()\nexport class UserContextMiddleware implements NestMiddleware {\n\n public use(req: Request, _res: Response, next: NextFunction) {\n const webUser = getWebUserFromHeader(req);\n req.userContext = {\n userId: webUser?.user_id,\n tenantId: webUser?.tenant_id,\n appId: webUser?.app_id ?? '',\n }\n next();\n }\n}\n","import type { Request } from 'express';\n\nconst sudaWebUserHeaderKey = 'x-larkgw-suda-webuser';\n\ninterface SudaWebUser {\n user_id: string;\n tenant_id: number;\n app_id: string;\n}\n\nexport function getWebUserFromHeader(req: Request): SudaWebUser | null {\n const sudaWebUserContent = req.headers[sudaWebUserHeaderKey] as\n | string\n | undefined;\n if (!sudaWebUserContent) {\n return null;\n }\n try {\n const sudaWebUserJsonStr = decodeURIComponent(sudaWebUserContent);\n const sudaWebUserJson = JSON.parse(sudaWebUserJsonStr) as SudaWebUser;\n return sudaWebUserJson;\n } catch (err) {\n console.error('parse suda webuser from header failed, err=%o', err);\n return null;\n }\n return null;\n}\n"],"mappings":"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","names":["mkdirSync","mkdirSync","Injectable","_init","Injectable","Injectable","_init","Injectable"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-nestjs-core",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "FullStack Nestjs Core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,6 +28,15 @@
28
28
  "require": "./dist/index.cjs"
29
29
  }
30
30
  },
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "dev": "tsup --watch",
34
+ "typecheck": "tsc --noEmit",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "lint": "echo 'ESLint skipped for TypeScript files'",
38
+ "lint:fix": "eslint src --ext .ts --fix"
39
+ },
31
40
  "dependencies": {
32
41
  "openapi-typescript-codegen": "^0.29.0"
33
42
  },
@@ -44,14 +53,5 @@
44
53
  "@nestjs/common": "^10.4.20",
45
54
  "@nestjs/platform-express": "^10.4.20",
46
55
  "@nestjs/swagger": "^7.4.2"
47
- },
48
- "scripts": {
49
- "build": "tsup",
50
- "dev": "tsup --watch",
51
- "typecheck": "tsc --noEmit",
52
- "test": "vitest run",
53
- "test:watch": "vitest",
54
- "lint": "echo 'ESLint skipped for TypeScript files'",
55
- "lint:fix": "eslint src --ext .ts --fix"
56
56
  }
57
- }
57
+ }