create-skweb 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/dist/index.js +19 -1
  2. package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/tsconfig.tsbuildinfo +1 -1
  3. package/.turbo/turbo-build.log +16 -17
  4. package/.turbo/turbo-lint.log +11 -12
  5. package/.turbo/turbo-test.log +66 -1
  6. package/CHANGELOG.md +49 -0
  7. package/dist/cli.js +19 -1
  8. package/dist/index.js +19 -1
  9. package/package.json +1 -1
  10. package/src/index.ts +19 -1
  11. package/templates/cjs/{.env.example → .env.example.auth-jwks} +1 -12
  12. package/templates/cjs/.env.example.auth-static +54 -0
  13. package/templates/cjs/package.json +3 -3
  14. package/templates/cjs/src/app.auth-jwks.js +131 -0
  15. package/templates/cjs/src/{app.js → app.auth-static.js} +7 -34
  16. package/templates/cjs/src/swagger.auth-jwks.js +38 -0
  17. package/templates/cjs/src/{swagger.js → swagger.auth-static.js} +1 -18
  18. package/templates/cjs/src/utils/config.auth-jwks.js +67 -0
  19. package/templates/cjs/src/utils/{config.js → config.auth-static.js} +13 -13
  20. package/templates/mjs/{.env.example → .env.example.auth-jwks} +1 -12
  21. package/templates/mjs/.env.example.auth-static +54 -0
  22. package/templates/mjs/package.json +3 -3
  23. package/templates/mjs/src/app.auth-jwks.js +137 -0
  24. package/templates/mjs/src/{app.js → app.auth-static.js} +7 -35
  25. package/templates/mjs/src/swagger.auth-jwks.js +38 -0
  26. package/templates/mjs/src/{swagger.js → swagger.auth-static.js} +1 -18
  27. package/templates/mjs/src/utils/config.auth-jwks.js +65 -0
  28. package/templates/mjs/src/utils/{config.js → config.auth-static.js} +13 -13
  29. package/templates/ts/{.env.example → .env.example.auth-jwks} +1 -12
  30. package/templates/ts/.env.example.auth-static +54 -0
  31. package/templates/ts/package.json +3 -3
  32. package/templates/ts/src/app.auth-jwks.ts +137 -0
  33. package/templates/ts/src/{app.ts → app.auth-static.ts} +7 -43
  34. package/templates/ts/src/swagger.auth-jwks.ts +39 -0
  35. package/templates/ts/src/{swagger.ts → swagger.auth-static.ts} +1 -19
  36. package/templates/ts/src/utils/config.auth-jwks.ts +70 -0
  37. package/templates/ts/src/utils/{config.ts → config.auth-static.ts} +13 -13
  38. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,54 @@
1
+ # ============================================
2
+ # SKWeb Environment Variables Template
3
+ # ============================================
4
+ # Copy this file to .env and fill in your values:
5
+ # cp .env.example .env
6
+ #
7
+ # Variables with default values can be omitted.
8
+ # Variables marked [REQUIRED] must be set in production.
9
+
10
+ # ---------- Node Environment ----------
11
+ # Development | Production | Test
12
+ NODE_ENV=development
13
+
14
+ # ---------- Server ----------
15
+ PORT=3000
16
+ HOST=0.0.0.0
17
+
18
+ # ---------- Database (MySQL / MariaDB) ----------
19
+ DB_HOST=127.0.0.1
20
+ DB_PORT=3306
21
+ DB_USER=root
22
+ DB_PASS=
23
+ DB_NAME={{name}}
24
+ DB_DIALECT=mysql
25
+ DB_CHARSET=utf8mb4
26
+ DEBUG_DB=false
27
+
28
+ # ---------- Redis ----------
29
+ REDIS_HOST=127.0.0.1
30
+ REDIS_PORT=6379
31
+ REDIS_PASSWORD=
32
+ REDIS_DB=0
33
+
34
+ # ---------- Swagger / OpenAPI ----------
35
+ # Mounts Swagger UI at /api-docs and exposes raw spec at /api-docs.json.
36
+ # Only enabled when NODE_ENV is 'development' or 'test'.
37
+ # Set SWAGGER_ENABLED=false to force-disable in dev/test as well.
38
+ # (Production is always disabled to avoid exposing internal API details.)
39
+ SWAGGER_ENABLED=true
40
+
41
+ # ---------- Auth: static (HS256 shared secret) ----------
42
+ # [REQUIRED in production] Use a strong random string, e.g. `openssl rand -hex 32`
43
+ JWT_SECRET=please-change-this-in-production
44
+ JWT_EXPIRES_IN=7d
45
+
46
+ # ---------- Logging ----------
47
+ # error | warn | info | http | verbose | debug | silly
48
+ LOG_LEVEL=info
49
+ LOG_DIR=./logs
50
+ LOG_CONSOLE=true
51
+
52
+ # ---------- CORS ----------
53
+ # Comma-separated origins, or * for all
54
+ CORS_ORIGIN=*
@@ -24,14 +24,14 @@
24
24
  "dependencies": {
25
25
  "@dotenvx/dotenvx": "^1.75.1",
26
26
  "http-errors": "^2.0.1",
27
- "skweb-express": "^2.0.0",
27
+ "skweb-express": "^2.0.3",
28
28
  "skweb-express-jwt": "^3.0.0",
29
29
  "skweb-express-logger": "^2.0.0",
30
30
  "skweb-sequelize": "^2.0.0",
31
31
  "skweb-redis": "^2.0.0",
32
32
  "skweb-cron": "^2.0.0",
33
- "skweb-framework": "^2.0.0",
34
- "skweb-plugin": "^2.0.0",
33
+ "skweb-framework": "^2.1.0",
34
+ "skweb-plugin": "^2.1.1",
35
35
  "skweb-util": "^2.0.0",
36
36
  "winston": "^3.19.0",
37
37
  "sequelize": "^6.37.8",
@@ -0,0 +1,137 @@
1
+ import 'dotenv/config'
2
+
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import express from 'skweb-express'
6
+ import expressLogger, { errorHandler as expressErrorHandler } from 'skweb-express-logger'
7
+ import { WebFramework } from 'skweb-framework'
8
+ import type { JWTOptions } from 'skweb-express-jwt'
9
+ import swaggerUi from 'swagger-ui-express'
10
+ import logger from './utils/logger.js'
11
+ import { checkRequiredEnv, checkOIDCConfig } from './utils/config.js'
12
+ import { buildOpenApiSpec } from './swagger.js'
13
+
14
+ const __filename = fileURLToPath(import.meta.url)
15
+ const __dirname = path.dirname(__filename)
16
+
17
+ /**
18
+ * JWT 配置(jwks 模式,通过 OIDC 兼容的 JWKS 端点验证 RS256 签名)。
19
+ */
20
+ function buildJwtOptions(): JWTOptions {
21
+ const issuer = process.env.OIDC_ISSUER
22
+ const jwksUri = process.env.OIDC_JWKS_URI
23
+ const audience = process.env.OIDC_AUDIENCE
24
+ if (!issuer && !jwksUri) {
25
+ throw new Error('jwks mode requires OIDC_ISSUER or OIDC_JWKS_URI')
26
+ }
27
+ return {
28
+ provider: 'jwks',
29
+ algorithms: ['RS256'],
30
+ jwks: { jwksUri, issuer, audience },
31
+ verify: { issuer, audience },
32
+ requestProperty: 'resolvedToken'
33
+ }
34
+ }
35
+
36
+ function shouldEnableSwagger(): boolean {
37
+ const env = (process.env.NODE_ENV || '').toLowerCase()
38
+ if (env !== 'development' && env !== 'test') return false
39
+ if ((process.env.SWAGGER_ENABLED || '').toLowerCase() === 'false') return false
40
+ return true
41
+ }
42
+
43
+ async function bootstrap(): Promise<void> {
44
+ logger.info('[app] Initializing web process...')
45
+
46
+ if (!checkRequiredEnv()) {
47
+ logger.error('[app] Configuration validation failed, exiting...')
48
+ process.exit(1)
49
+ }
50
+ if (!checkOIDCConfig()) {
51
+ logger.error('[app] OIDC configuration validation failed, exiting...')
52
+ process.exit(1)
53
+ }
54
+
55
+ const framework = new WebFramework({
56
+ db: {
57
+ host: process.env.DB_HOST || '127.0.0.1',
58
+ port: Number(process.env.DB_PORT) || 3306,
59
+ username: process.env.DB_USER || 'root',
60
+ password: process.env.DB_PASS || '',
61
+ database: process.env.DB_NAME || 'test-project',
62
+ dialect: (process.env.DB_DIALECT as 'mysql' | 'postgres' | 'sqlite' | 'mariadb') || 'mysql',
63
+ charset: process.env.DB_CHARSET || 'utf8mb4',
64
+ underscored: true,
65
+ logging: process.env.DEBUG_DB === 'true' ? (msg: string) => logger.debug(msg) : false
66
+ },
67
+ redis: {
68
+ host: process.env.REDIS_HOST || '127.0.0.1',
69
+ port: Number(process.env.REDIS_PORT) || 6379,
70
+ password: process.env.REDIS_PASSWORD || undefined
71
+ }
72
+ })
73
+
74
+ const app = framework.createApp() as express.Express & Record<string, any>
75
+
76
+ await framework.setup({
77
+ modelsDir: path.join(__dirname, './models'),
78
+ controllersDir: path.join(__dirname, './controllers')
79
+ })
80
+
81
+ app.disable('x-powered-by')
82
+ app.disable('etag')
83
+
84
+ app.set('trust proxy', true)
85
+
86
+ app.set('wrap response', true)
87
+ app.setOkCode(0)
88
+ app.setOkMessage('success')
89
+
90
+ const jwtOptions = buildJwtOptions()
91
+ app.setJWT('default', jwtOptions as any)
92
+ app.setJwtHintHeader('JwtHint')
93
+
94
+ logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks?.issuer || jwtOptions.jwks?.jwksUri})`)
95
+
96
+ app.use(expressLogger())
97
+ app.use(express.static(path.join(__dirname, '..', 'public')))
98
+
99
+ if (shouldEnableSwagger()) {
100
+ const openapiSpec = buildOpenApiSpec()
101
+ app.get('/api-docs.json', (_req, res) => res.json(openapiSpec))
102
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapiSpec, {
103
+ customSiteTitle: `${process.env.APP_NAME || 'SKWeb'} API Docs`,
104
+ swaggerOptions: { persistAuthorization: true }
105
+ }))
106
+ logger.info(`[app] Swagger UI mounted at http://localhost:${process.env.PORT || 3000}/api-docs (env: ${process.env.NODE_ENV})`)
107
+ } else {
108
+ logger.info(`[app] Swagger UI disabled (env: ${process.env.NODE_ENV || 'unset'})`)
109
+ }
110
+
111
+ await framework.loadControllers()
112
+
113
+ app.use(expressErrorHandler())
114
+
115
+ framework.start(Number(process.env.PORT) || 3000)
116
+
117
+ logger.info('[app] Initialization complete (web process)')
118
+
119
+ process.on('uncaughtException', (err) => {
120
+ logger.error(`[app] uncaughtException: ${err.stack || err.message}`)
121
+ process.exit(1)
122
+ })
123
+ process.on('unhandledRejection', (reason) => {
124
+ logger.error(`[app] unhandledRejection: ${reason instanceof Error ? reason.stack : String(reason)}`)
125
+ process.exit(1)
126
+ })
127
+ }
128
+
129
+ const isMainModule = import.meta.url === `file://${process.argv[1]}`
130
+ if (isMainModule) {
131
+ bootstrap().catch((err) => {
132
+ logger.error('[app] Fatal error:', err)
133
+ process.exit(1)
134
+ })
135
+ }
136
+
137
+ export { bootstrap }
@@ -8,43 +8,16 @@ import { WebFramework } from 'skweb-framework'
8
8
  import type { JWTOptions } from 'skweb-express-jwt'
9
9
  import swaggerUi from 'swagger-ui-express'
10
10
  import logger from './utils/logger.js'
11
- import { checkRequiredEnv } from './utils/config.js'
11
+ import { checkRequiredEnv, checkJwtSecret } from './utils/config.js'
12
12
  import { buildOpenApiSpec } from './swagger.js'
13
13
 
14
14
  const __filename = fileURLToPath(import.meta.url)
15
15
  const __dirname = path.dirname(__filename)
16
16
 
17
17
  /**
18
- * 构建 JWT 配置,支持两种模式:
19
- * - 'static' (默认):使用共享密钥(HS256/HS384/HS512),适用于自签 token
20
- * - 'jwks':通过 JWKS 端点动态获取公钥(RS256 等),适用于 Keycloak/Auth0 等 IdP
18
+ * JWT 配置(static 模式,HS256 共享密钥)。
21
19
  */
22
20
  function buildJwtOptions(): JWTOptions {
23
- const provider = (process.env.AUTH_PROVIDER || 'static') as 'static' | 'jwks'
24
-
25
- if (provider === 'jwks') {
26
- const issuer = process.env.OIDC_ISSUER
27
- const jwksUri = process.env.OIDC_JWKS_URI
28
- const audience = process.env.OIDC_AUDIENCE
29
- if (!issuer && !jwksUri) {
30
- throw new Error('AUTH_PROVIDER=jwks requires OIDC_ISSUER or OIDC_JWKS_URI')
31
- }
32
- return {
33
- provider: 'jwks',
34
- algorithms: ['RS256'],
35
- jwks: {
36
- jwksUri,
37
- issuer,
38
- audience
39
- },
40
- verify: {
41
- issuer,
42
- audience
43
- },
44
- requestProperty: 'resolvedToken'
45
- }
46
- }
47
-
48
21
  return {
49
22
  provider: 'static',
50
23
  secret: process.env.JWT_SECRET || 'please-change-this-in-production',
@@ -54,10 +27,6 @@ function buildJwtOptions(): JWTOptions {
54
27
  }
55
28
  }
56
29
 
57
- /**
58
- * Swagger UI 仅在 NODE_ENV 为 development / test,且 SWAGGER_ENABLED !== 'false' 时启用。
59
- * 生产环境(production / unset)默认禁用,避免暴露内部 API 细节。
60
- */
61
30
  function shouldEnableSwagger(): boolean {
62
31
  const env = (process.env.NODE_ENV || '').toLowerCase()
63
32
  if (env !== 'development' && env !== 'test') return false
@@ -72,6 +41,10 @@ async function bootstrap(): Promise<void> {
72
41
  logger.error('[app] Configuration validation failed, exiting...')
73
42
  process.exit(1)
74
43
  }
44
+ if (!checkJwtSecret()) {
45
+ logger.error('[app] JWT configuration validation failed, exiting...')
46
+ process.exit(1)
47
+ }
75
48
 
76
49
  const framework = new WebFramework({
77
50
  db: {
@@ -108,21 +81,15 @@ async function bootstrap(): Promise<void> {
108
81
  app.setOkCode(0)
109
82
  app.setOkMessage('success')
110
83
 
111
- // JWT 配置(按 AUTH_PROVIDER 选择 static 或 jwks 模式)
112
84
  const jwtOptions = buildJwtOptions()
113
85
  app.setJWT('default', jwtOptions as any)
114
86
  app.setJwtHintHeader('JwtHint')
115
87
 
116
- if (jwtOptions.provider === 'jwks') {
117
- logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks.issuer || jwtOptions.jwks.jwksUri})`)
118
- } else {
119
- logger.info('[app] JWT auth provider: static (HS256)')
120
- }
88
+ logger.info('[app] JWT auth provider: static (HS256)')
121
89
 
122
90
  app.use(expressLogger())
123
91
  app.use(express.static(path.join(__dirname, '..', 'public')))
124
92
 
125
- // 仅在 dev / test 环境挂载 Swagger UI(默认关闭,需显式 SWAGGER_ENABLED=true)
126
93
  if (shouldEnableSwagger()) {
127
94
  const openapiSpec = buildOpenApiSpec()
128
95
  app.get('/api-docs.json', (_req, res) => res.json(openapiSpec))
@@ -137,15 +104,12 @@ async function bootstrap(): Promise<void> {
137
104
 
138
105
  await framework.loadControllers()
139
106
 
140
- // 错误日志中间件:挂在所有路由之后,记录业务错误并继续传递给默认错误处理。
141
107
  app.use(expressErrorHandler())
142
108
 
143
109
  framework.start(Number(process.env.PORT) || 3000)
144
110
 
145
111
  logger.info('[app] Initialization complete (web process)')
146
112
 
147
- // 未捕获异常:打印堆栈后直接退出。OS / 进程管理器(pm2 / docker / k8s)
148
- // 负责回收 socket / fd,框架不在此路径上做资源清理——简单可靠。
149
113
  process.on('uncaughtException', (err) => {
150
114
  logger.error(`[app] uncaughtException: ${err.stack || err.message}`)
151
115
  process.exit(1)
@@ -0,0 +1,39 @@
1
+ import path from 'path'
2
+ import { fileURLToPath } from 'url'
3
+ import swaggerJSDoc from 'swagger-jsdoc'
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = path.dirname(__filename)
7
+
8
+ export function buildOpenApiSpec(): object {
9
+ return swaggerJSDoc({
10
+ definition: {
11
+ openapi: '3.0.3',
12
+ info: {
13
+ title: process.env.APP_NAME || 'SKWeb API',
14
+ version: process.env.API_VERSION || '1.0.0',
15
+ description: process.env.APP_DESCRIPTION || 'SKWeb project API documentation',
16
+ contact: { name: 'API Support' }
17
+ },
18
+ servers: [
19
+ { url: `http://localhost:${process.env.PORT || 3000}`, description: 'Local development' }
20
+ ],
21
+ components: {
22
+ securitySchemes: {
23
+ oidcAuth: {
24
+ type: 'openIdConnect',
25
+ openIdConnectUrl: process.env.OIDC_JWKS_URI
26
+ ? process.env.OIDC_JWKS_URI.replace(/\/jwks\.json$/, '/.well-known/openid-configuration')
27
+ : (process.env.OIDC_ISSUER
28
+ ? `${process.env.OIDC_ISSUER.replace(/\/$/, '')}/.well-known/openid-configuration`
29
+ : undefined)
30
+ }
31
+ }
32
+ }
33
+ },
34
+ apis: [
35
+ path.join(__dirname, 'controllers', '*.ts'),
36
+ path.join(__dirname, 'controllers', '*.js')
37
+ ]
38
+ })
39
+ }
@@ -5,22 +5,6 @@ import swaggerJSDoc from 'swagger-jsdoc'
5
5
  const __filename = fileURLToPath(import.meta.url)
6
6
  const __dirname = path.dirname(__filename)
7
7
 
8
- /**
9
- * 构建 OpenAPI 3.0 规范对象。
10
- *
11
- * 控制器中应使用 @swagger JSDoc 注释描述端点,例如:
12
- *
13
- * /**
14
- * * @swagger
15
- * * /api/users:
16
- * * get:
17
- * * summary: 获取用户列表
18
- * * tags: [Users]
19
- * * responses:
20
- * * 200:
21
- * * description: OK
22
- * *\/
23
- */
24
8
  export function buildOpenApiSpec(): object {
25
9
  return swaggerJSDoc({
26
10
  definition: {
@@ -39,13 +23,11 @@ export function buildOpenApiSpec(): object {
39
23
  bearerAuth: {
40
24
  type: 'http',
41
25
  scheme: 'bearer',
42
- bearerFormat: 'JWT',
43
- description: 'Paste a JWT token here (only needed when AUTH_PROVIDER=static)'
26
+ bearerFormat: 'JWT'
44
27
  }
45
28
  }
46
29
  }
47
30
  },
48
- // 扫描 controllers 目录与 src 根下的 JSDoc 注释
49
31
  apis: [
50
32
  path.join(__dirname, 'controllers', '*.ts'),
51
33
  path.join(__dirname, 'controllers', '*.js')
@@ -0,0 +1,70 @@
1
+ import logger from './logger.js'
2
+
3
+ interface ConfigValidationResult {
4
+ isValid: boolean
5
+ errors: string[]
6
+ }
7
+
8
+ export function validateConfig(): ConfigValidationResult {
9
+ const errors: string[] = []
10
+
11
+ if (!process.env.DB_HOST) {
12
+ logger.warn('[config] DB_HOST not set, using default: 127.0.0.1')
13
+ }
14
+ if (!process.env.DB_NAME) {
15
+ logger.warn('[config] DB_NAME not set, using default: test-project')
16
+ }
17
+ if (!process.env.DB_USER) {
18
+ logger.warn('[config] DB_USER not set, using default: root')
19
+ }
20
+
21
+ if (!process.env.REDIS_HOST) {
22
+ logger.warn('[config] REDIS_HOST not set, using default: 127.0.0.1')
23
+ }
24
+
25
+ const port = Number(process.env.PORT)
26
+ if (port && (port < 1 || port > 65535)) {
27
+ errors.push('PORT must be between 1 and 65535')
28
+ }
29
+
30
+ const dbPort = Number(process.env.DB_PORT)
31
+ if (dbPort && (dbPort < 1 || dbPort > 65535)) {
32
+ errors.push('DB_PORT must be between 1 and 65535')
33
+ }
34
+
35
+ const redisPort = Number(process.env.REDIS_PORT)
36
+ if (redisPort && (redisPort < 1 || redisPort > 65535)) {
37
+ errors.push('REDIS_PORT must be between 1 and 65535')
38
+ }
39
+
40
+ return {
41
+ isValid: errors.length === 0,
42
+ errors
43
+ }
44
+ }
45
+
46
+ export function checkOIDCConfig(): boolean {
47
+ const issuer = process.env.OIDC_ISSUER
48
+ const jwksUri = process.env.OIDC_JWKS_URI
49
+ if (!issuer && !jwksUri) {
50
+ logger.error('[config] OIDC_ISSUER or OIDC_JWKS_URI must be set for jwks auth')
51
+ return false
52
+ }
53
+ if (process.env.NODE_ENV === 'production' && !process.env.OIDC_AUDIENCE) {
54
+ logger.warn('[config] OIDC_AUDIENCE is not set; audience verification will be skipped')
55
+ }
56
+ return true
57
+ }
58
+
59
+ export function checkRequiredEnv(): boolean {
60
+ const result = validateConfig()
61
+
62
+ if (result.errors.length > 0) {
63
+ logger.error('[config] Configuration validation failed:')
64
+ result.errors.forEach((err) => logger.error(` - ${err}`))
65
+ return false
66
+ }
67
+
68
+ logger.info('[config] Configuration validation passed')
69
+ return true
70
+ }
@@ -18,10 +18,6 @@ export function validateConfig(): ConfigValidationResult {
18
18
  logger.warn('[config] DB_USER not set, using default: root')
19
19
  }
20
20
 
21
- if (process.env.JWT_SECRET === 'please-change-this-in-production') {
22
- logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
23
- }
24
-
25
21
  if (!process.env.REDIS_HOST) {
26
22
  logger.warn('[config] REDIS_HOST not set, using default: 127.0.0.1')
27
23
  }
@@ -47,22 +43,26 @@ export function validateConfig(): ConfigValidationResult {
47
43
  }
48
44
  }
49
45
 
46
+ export function checkJwtSecret(): boolean {
47
+ if (process.env.JWT_SECRET === 'please-change-this-in-production' || !process.env.JWT_SECRET) {
48
+ if (process.env.NODE_ENV === 'production') {
49
+ logger.error('[config] JWT_SECRET must be set in production!')
50
+ return false
51
+ }
52
+ logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
53
+ }
54
+ return true
55
+ }
56
+
50
57
  export function checkRequiredEnv(): boolean {
51
58
  const result = validateConfig()
52
-
59
+
53
60
  if (result.errors.length > 0) {
54
61
  logger.error('[config] Configuration validation failed:')
55
62
  result.errors.forEach((err) => logger.error(` - ${err}`))
56
63
  return false
57
64
  }
58
65
 
59
- if (process.env.NODE_ENV === 'production') {
60
- if (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'please-change-this-in-production') {
61
- logger.error('[config] JWT_SECRET must be set in production!')
62
- return false
63
- }
64
- }
65
-
66
66
  logger.info('[config] Configuration validation passed')
67
67
  return true
68
- }
68
+ }