create-skweb 3.2.0 → 3.3.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 (60) hide show
  1. package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/dist/index.js +11 -16
  2. package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/tsconfig.tsbuildinfo +1 -1
  3. package/.turbo/turbo-build.log +4 -4
  4. package/CHANGELOG.md +6 -0
  5. package/dist/cli.js +11 -16
  6. package/dist/index.js +11 -16
  7. package/package.json +1 -1
  8. package/src/index.ts +18 -16
  9. package/templates/cjs/{.env.example.auth-jwks → .env.example} +7 -0
  10. package/templates/cjs/ecosystem.config.js +2 -2
  11. package/templates/cjs/package.json +4 -4
  12. package/templates/cjs/src/controllers/user.js +2 -2
  13. package/templates/cjs/src/cron-app.js +40 -0
  14. package/templates/cjs/src/cron-framework.js +44 -0
  15. package/templates/cjs/src/{swagger.auth-jwks.js → swagger.js} +9 -0
  16. package/templates/cjs/src/utils/{config.auth-jwks.js → config.js} +18 -0
  17. package/templates/cjs/src/{app.auth-jwks.js → web-app.js} +34 -21
  18. package/templates/cjs/src/web-framework.js +23 -0
  19. package/templates/{ts/.env.example.auth-jwks → mjs/.env.example} +7 -0
  20. package/templates/mjs/ecosystem.config.js +2 -2
  21. package/templates/mjs/package.json +4 -4
  22. package/templates/mjs/src/controllers/user.js +2 -2
  23. package/templates/mjs/src/cron-app.js +45 -0
  24. package/templates/mjs/src/cron-framework.js +44 -0
  25. package/templates/mjs/src/{swagger.auth-jwks.js → swagger.js} +9 -0
  26. package/templates/mjs/src/utils/{config.auth-jwks.js → config.js} +14 -0
  27. package/templates/mjs/src/{app.auth-jwks.js → web-app.js} +29 -23
  28. package/templates/mjs/src/web-framework.js +23 -0
  29. package/templates/{mjs/.env.example.auth-jwks → ts/.env.example} +7 -0
  30. package/templates/ts/ecosystem.config.js +2 -2
  31. package/templates/ts/package.json +4 -4
  32. package/templates/ts/src/controllers/user.ts +2 -2
  33. package/templates/ts/src/cron-app.ts +45 -0
  34. package/templates/ts/src/cron-framework.ts +44 -0
  35. package/templates/ts/src/{swagger.auth-jwks.ts → swagger.ts} +9 -0
  36. package/templates/ts/src/utils/{config.auth-jwks.ts → config.ts} +14 -0
  37. package/templates/ts/src/{app.auth-jwks.ts → web-app.ts} +28 -21
  38. package/templates/ts/src/web-framework.ts +23 -0
  39. package/tsconfig.tsbuildinfo +1 -1
  40. package/templates/cjs/.env.example.auth-static +0 -54
  41. package/templates/cjs/src/app.auth-static.js +0 -129
  42. package/templates/cjs/src/cronWorker.js +0 -80
  43. package/templates/cjs/src/index.js +0 -7
  44. package/templates/cjs/src/services/database.js +0 -15
  45. package/templates/cjs/src/swagger.auth-static.js +0 -35
  46. package/templates/cjs/src/utils/config.auth-static.js +0 -65
  47. package/templates/mjs/.env.example.auth-static +0 -54
  48. package/templates/mjs/src/app.auth-static.js +0 -131
  49. package/templates/mjs/src/cronWorker.js +0 -86
  50. package/templates/mjs/src/index.js +0 -8
  51. package/templates/mjs/src/services/database.js +0 -16
  52. package/templates/mjs/src/swagger.auth-static.js +0 -35
  53. package/templates/mjs/src/utils/config.auth-static.js +0 -63
  54. package/templates/ts/.env.example.auth-static +0 -54
  55. package/templates/ts/src/app.auth-static.ts +0 -131
  56. package/templates/ts/src/cronWorker.ts +0 -85
  57. package/templates/ts/src/index.ts +0 -8
  58. package/templates/ts/src/services/database.ts +0 -16
  59. package/templates/ts/src/swagger.auth-static.ts +0 -36
  60. package/templates/ts/src/utils/config.auth-static.ts +0 -68
package/dist/index.js CHANGED
@@ -27,25 +27,20 @@ async function createProject(options) {
27
27
  await fs.mkdirs(path.join(targetDir, fileName));
28
28
  continue;
29
29
  }
30
- // 处理 auth-后缀:例如 src/app.auth-static.js -> 生成 src/app.js
31
- // .env.example.auth-static -> 生成 .env.example
32
- let outputName = fileName;
33
- const authMatch = fileName.match(/^(.*?)\.auth-(static|jwks)(\.[^.]+)?$/);
34
- if (authMatch) {
35
- const [, base, fileAuth, ext] = authMatch;
36
- if (fileAuth !== auth) {
37
- // 不匹配用户选择的 auth,跳过
38
- continue;
39
- }
40
- outputName = ext ? `${base}${ext}` : base;
30
+ // 跳过 auth- 后缀的文件(已合并到统一模板)
31
+ if (/\.auth-(static|jwks)(\.[^.]+)?$/.test(fileName)) {
32
+ continue;
41
33
  }
42
- else {
43
- // 没有 auth- 后缀的 .env.example:跳过(避免与 auth- 版本冲突)
44
- if (fileName === '.env.example' || fileName.endsWith('/.env.example')) {
45
- continue;
46
- }
34
+ // 跳过无 auth 后缀的 .env.example(改用条件模板)
35
+ if (fileName === '.env.example' || fileName.endsWith('/.env.example')) {
36
+ continue;
47
37
  }
38
+ const outputName = fileName;
48
39
  let content = await fs.readFile(srcPath, 'utf-8');
40
+ // 处理条件块:{{#if_eq auth "static"}}...{{else}}...{{/if_eq}}
41
+ content = content.replace(/\{\{#if_eq auth "(static|jwks)"\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if_eq\}\}/g, (_match, value, mainContent, elseContent) => {
42
+ return value === auth ? mainContent : (elseContent || '');
43
+ });
49
44
  content = content
50
45
  .replace(/\{\{name\}\}/g, name)
51
46
  .replace(/\{\{description\}\}/g, description)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-skweb",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Create a new SKWeb project with TypeScript, CJS, or MJS support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -42,26 +42,28 @@ export async function createProject(options: CreateOptions): Promise<void> {
42
42
  continue
43
43
  }
44
44
 
45
- // 处理 auth-后缀:例如 src/app.auth-static.js -> 生成 src/app.js
46
- // .env.example.auth-static -> 生成 .env.example
47
- let outputName = fileName
48
- const authMatch = fileName.match(/^(.*?)\.auth-(static|jwks)(\.[^.]+)?$/)
49
- if (authMatch) {
50
- const [, base, fileAuth, ext] = authMatch
51
- if (fileAuth !== auth) {
52
- // 不匹配用户选择的 auth,跳过
53
- continue
54
- }
55
- outputName = ext ? `${base}${ext}` : base
56
- } else {
57
- // 没有 auth- 后缀的 .env.example:跳过(避免与 auth- 版本冲突)
58
- if (fileName === '.env.example' || fileName.endsWith('/.env.example')) {
59
- continue
60
- }
45
+ // 跳过 auth- 后缀的文件(已合并到统一模板)
46
+ if (/\.auth-(static|jwks)(\.[^.]+)?$/.test(fileName)) {
47
+ continue
61
48
  }
62
49
 
50
+ // 跳过无 auth 后缀的 .env.example(改用条件模板)
51
+ if (fileName === '.env.example' || fileName.endsWith('/.env.example')) {
52
+ continue
53
+ }
54
+
55
+ const outputName = fileName
56
+
63
57
  let content = await fs.readFile(srcPath, 'utf-8')
64
58
 
59
+ // 处理条件块:{{#if_eq auth "static"}}...{{else}}...{{/if_eq}}
60
+ content = content.replace(
61
+ /\{\{#if_eq auth "(static|jwks)"\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if_eq\}\}/g,
62
+ (_match: string, value: string, mainContent: string, elseContent: string) => {
63
+ return value === auth ? mainContent : (elseContent || '')
64
+ }
65
+ )
66
+
65
67
  content = content
66
68
  .replace(/\{\{name\}\}/g, name)
67
69
  .replace(/\{\{description\}\}/g, description)
@@ -38,6 +38,12 @@ REDIS_DB=0
38
38
  # (Production is always disabled to avoid exposing internal API details.)
39
39
  SWAGGER_ENABLED=true
40
40
 
41
+ {{#if_eq auth "static"}}
42
+ # ---------- Auth: static (HS256 shared secret) ----------
43
+ # [REQUIRED in production] Use a strong random string, e.g. `openssl rand -hex 32`
44
+ JWT_SECRET=please-change-this-in-production
45
+ JWT_EXPIRES_IN=7d
46
+ {{else}}
41
47
  # ---------- Auth: jwks (OIDC compatible) ----------
42
48
  # Works with any OIDC-compliant IdP. Common issuer formats:
43
49
  # Keycloak: https://kc.example.com/realms/myrealm
@@ -50,6 +56,7 @@ SWAGGER_ENABLED=true
50
56
  OIDC_ISSUER=
51
57
  OIDC_AUDIENCE=
52
58
  OIDC_JWKS_URI=
59
+ {{/if_eq}}
53
60
 
54
61
  # ---------- Logging ----------
55
62
  # error | warn | info | http | verbose | debug | silly
@@ -2,7 +2,7 @@ module.exports = {
2
2
  apps: [
3
3
  {
4
4
  name: '{{name}}-web',
5
- script: 'src/index.js',
5
+ script: 'src/web-app.js',
6
6
  instances: 'max',
7
7
  exec_mode: 'cluster',
8
8
  env: {
@@ -16,7 +16,7 @@ module.exports = {
16
16
  },
17
17
  {
18
18
  name: '{{name}}-cron',
19
- script: 'src/cronWorker.js',
19
+ script: 'src/cron-app.js',
20
20
  instances: 1,
21
21
  exec_mode: 'fork',
22
22
  autorestart: true,
@@ -9,8 +9,8 @@
9
9
  "restart": "pm2 restart all",
10
10
  "delete": "pm2 delete all",
11
11
  "logs": "pm2 logs",
12
- "web:dev": "npx dotenvx run -- node --watch src/index.js",
13
- "cron:dev": "npx dotenvx run -- node --watch src/cronWorker.js",
12
+ "web:dev": "npx dotenvx run -- node --watch src/web-app.js",
13
+ "cron:dev": "npx dotenvx run -- node --watch src/cron-app.js",
14
14
  "seed": "npx dotenvx run -- node src/commands/seed.js",
15
15
  "migrate": "npx dotenvx run -- node src/commands/migrate.js",
16
16
  "build": "cp -r src dist",
@@ -22,13 +22,13 @@
22
22
  "dependencies": {
23
23
  "@dotenvx/dotenvx": "^1.75.1",
24
24
  "http-errors": "^2.0.1",
25
- "skweb-express": "^2.0.3",
25
+ "skweb-express": "^2.0.4",
26
26
  "skweb-express-jwt": "^3.0.0",
27
27
  "skweb-express-logger": "^2.0.0",
28
28
  "skweb-sequelize": "^2.0.0",
29
29
  "skweb-redis": "^2.0.0",
30
30
  "skweb-cron": "^2.0.0",
31
- "skweb-framework": "^2.1.0",
31
+ "skweb-framework": "^2.1.1",
32
32
  "skweb-plugin": "^2.1.1",
33
33
  "skweb-util": "^2.0.0",
34
34
  "winston": "^3.19.0",
@@ -1,7 +1,7 @@
1
1
  const createHttpError = require('http-errors')
2
- const { db } = require('../services/database.js')
2
+ const { framework } = require('../web-framework.js')
3
3
 
4
- const { SysUser } = db.sequelize.models
4
+ const { SysUser } = framework.models
5
5
 
6
6
  /**
7
7
  * @swagger
@@ -0,0 +1,40 @@
1
+ require('dotenv/config')
2
+
3
+ const path = require('path')
4
+ const { framework } = require('./cron-framework.js')
5
+ const { logger } = require('./utils/logger.js')
6
+ const { checkRequiredEnv } = require('./utils/config.js')
7
+
8
+ async function startCronWorker() {
9
+ logger.info('[cron-worker] Starting cron worker process...')
10
+
11
+ if (!checkRequiredEnv()) {
12
+ logger.error('[cron-worker] Configuration validation failed, exiting...')
13
+ process.exit(1)
14
+ }
15
+
16
+ await framework.setup({
17
+ modelsDir: path.join(__dirname, './models'),
18
+ cronActionsDir: path.join(__dirname, './cronActions')
19
+ })
20
+ await framework.start()
21
+ logger.info('[cron-worker] Cron worker started')
22
+
23
+ process.on('uncaughtException', (err) => {
24
+ logger.error(`[cron-worker] uncaughtException: ${err.stack || err.message}`)
25
+ process.exit(1)
26
+ })
27
+ process.on('unhandledRejection', (reason) => {
28
+ logger.error(`[cron-worker] unhandledRejection: ${reason instanceof Error ? reason.stack : String(reason)}`)
29
+ process.exit(1)
30
+ })
31
+ }
32
+
33
+ if (require.main === module) {
34
+ startCronWorker().catch((err) => {
35
+ logger.error('[cron-worker] Fatal error:', err)
36
+ process.exit(1)
37
+ })
38
+ }
39
+
40
+ module.exports = { startCronWorker }
@@ -0,0 +1,44 @@
1
+ // @ts-nocheck
2
+ const { CronFramework } = require('skweb-framework')
3
+ const { logger } = require('./utils/logger.js')
4
+
5
+ const framework = new CronFramework({
6
+ db: {
7
+ host: process.env.DB_HOST || '127.0.0.1',
8
+ port: Number(process.env.DB_PORT) || 3306,
9
+ username: process.env.DB_USER || 'root',
10
+ password: process.env.DB_PASS || '',
11
+ database: process.env.DB_NAME || 'test-project',
12
+ dialect: process.env.DB_DIALECT || 'mysql',
13
+ charset: process.env.DB_CHARSET || 'utf8mb4',
14
+ underscored: true,
15
+ logging: process.env.DEBUG_DB === 'true'
16
+ },
17
+ redis: {
18
+ host: process.env.REDIS_HOST || '127.0.0.1',
19
+ port: Number(process.env.REDIS_PORT) || 6379,
20
+ password: process.env.REDIS_PASSWORD || undefined
21
+ },
22
+ channel: 'cron:task:cmd',
23
+ logger,
24
+ onSaveTask: async (taskData) => {
25
+ const SysCronTask = framework.db?.sequelize?.models.SysCronTask
26
+ if (SysCronTask) {
27
+ await SysCronTask.update(
28
+ { cache: taskData.cache, data: taskData.data },
29
+ { where: { id: taskData.taskId } }
30
+ )
31
+ }
32
+ },
33
+ loadTasks: async () => {
34
+ const SysCronTask = framework.db?.sequelize?.models.SysCronTask
35
+ if (!SysCronTask) return []
36
+ return (await SysCronTask.findAll()).map((t) => ({
37
+ id: String(t.id),
38
+ cronTime: t.cronTime,
39
+ method: t.method
40
+ }))
41
+ }
42
+ })
43
+
44
+ module.exports = { framework }
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  'use strict'
2
3
 
3
4
  const path = require('path')
@@ -18,6 +19,13 @@ function buildOpenApiSpec() {
18
19
  ],
19
20
  components: {
20
21
  securitySchemes: {
22
+ {{#if_eq auth "static"}}
23
+ bearerAuth: {
24
+ type: 'http',
25
+ scheme: 'bearer',
26
+ bearerFormat: 'JWT'
27
+ }
28
+ {{else}}
21
29
  oidcAuth: {
22
30
  type: 'openIdConnect',
23
31
  openIdConnectUrl: process.env.OIDC_JWKS_URI
@@ -26,6 +34,7 @@ function buildOpenApiSpec() {
26
34
  ? `${process.env.OIDC_ISSUER.replace(/\/$/, '')}/.well-known/openid-configuration`
27
35
  : undefined)
28
36
  }
37
+ {{/if_eq}}
29
38
  }
30
39
  }
31
40
  },
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  const { logger } = require('./logger.js')
2
3
 
3
4
  function validateConfig() {
@@ -38,6 +39,18 @@ function validateConfig() {
38
39
  }
39
40
  }
40
41
 
42
+ {{#if_eq auth "static"}}
43
+ function checkJwtSecret() {
44
+ if (process.env.JWT_SECRET === 'please-change-this-in-production' || !process.env.JWT_SECRET) {
45
+ if (process.env.NODE_ENV === 'production') {
46
+ logger.error('[config] JWT_SECRET must be set in production!')
47
+ return false
48
+ }
49
+ logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
50
+ }
51
+ return true
52
+ }
53
+ {{else}}
41
54
  function checkOIDCConfig() {
42
55
  const issuer = process.env.OIDC_ISSUER
43
56
  const jwksUri = process.env.OIDC_JWKS_URI
@@ -50,6 +63,7 @@ function checkOIDCConfig() {
50
63
  }
51
64
  return true
52
65
  }
66
+ {{/if_eq}}
53
67
 
54
68
  function checkRequiredEnv() {
55
69
  const result = validateConfig()
@@ -64,4 +78,8 @@ function checkRequiredEnv() {
64
78
  return true
65
79
  }
66
80
 
81
+ {{#if_eq auth "static"}}
82
+ module.exports = { validateConfig, checkJwtSecret, checkRequiredEnv }
83
+ {{else}}
67
84
  module.exports = { validateConfig, checkOIDCConfig, checkRequiredEnv }
85
+ {{/if_eq}}
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  require('dotenv/config')
2
3
 
3
4
  const path = require('path')
@@ -5,11 +6,25 @@ const express = require('skweb-express').default
5
6
  const expressLogger = require('skweb-express-logger').default
6
7
  const expressErrorHandler = require('skweb-express-logger').errorHandler
7
8
  const swaggerUi = require('swagger-ui-express')
8
- const { WebFramework } = require('skweb-framework')
9
+ const { framework } = require('./web-framework.js')
9
10
  const { logger } = require('./utils/logger.js')
10
- const { checkRequiredEnv, checkOIDCConfig } = require('./utils/config.js')
11
+ const { checkRequiredEnv{{#if_eq auth "static"}}, checkJwtSecret{{/if_eq}}{{#if_eq auth "jwks"}}, checkOIDCConfig{{/if_eq}} } = require('./utils/config.js')
11
12
  const { buildOpenApiSpec } = require('./swagger.js')
12
13
 
14
+ {{#if_eq auth "static"}}
15
+ /**
16
+ * JWT 配置(static 模式,HS256 共享密钥)。
17
+ */
18
+ function buildJwtOptions() {
19
+ return {
20
+ provider: 'static',
21
+ secret: process.env.JWT_SECRET || 'please-change-this-in-production',
22
+ algorithms: ['HS256'],
23
+ sign: { expiresIn: process.env.JWT_EXPIRES_IN || '7d' },
24
+ requestProperty: 'resolvedToken'
25
+ }
26
+ }
27
+ {{else}}
13
28
  /**
14
29
  * JWT 配置(jwks 模式,通过 OIDC 兼容的 JWKS 端点验证 RS256 签名)。
15
30
  */
@@ -28,7 +43,12 @@ function buildJwtOptions() {
28
43
  requestProperty: 'resolvedToken'
29
44
  }
30
45
  }
46
+ {{/if_eq}}
31
47
 
48
+ /**
49
+ * Swagger UI 仅在 NODE_ENV 为 development / test,且 SWAGGER_ENABLED !== 'false' 时启用。
50
+ * 生产环境(production / unset)默认禁用,避免暴露内部 API 细节。
51
+ */
32
52
  function shouldEnableSwagger() {
33
53
  const env = (process.env.NODE_ENV || '').toLowerCase()
34
54
  if (env !== 'development' && env !== 'test') return false
@@ -43,29 +63,18 @@ async function bootstrap() {
43
63
  logger.error('[app] Configuration validation failed, exiting...')
44
64
  process.exit(1)
45
65
  }
66
+ {{#if_eq auth "static"}}
67
+ if (!checkJwtSecret()) {
68
+ logger.error('[app] JWT configuration validation failed, exiting...')
69
+ process.exit(1)
70
+ }
71
+ {{else}}
46
72
  if (!checkOIDCConfig()) {
47
73
  logger.error('[app] OIDC configuration validation failed, exiting...')
48
74
  process.exit(1)
49
75
  }
76
+ {{/if_eq}}
50
77
 
51
- const framework = new WebFramework({
52
- db: {
53
- host: process.env.DB_HOST || '127.0.0.1',
54
- port: Number(process.env.DB_PORT) || 3306,
55
- username: process.env.DB_USER || 'root',
56
- password: process.env.DB_PASS || '',
57
- database: process.env.DB_NAME || 'test-project',
58
- dialect: process.env.DB_DIALECT || 'mysql',
59
- charset: process.env.DB_CHARSET || 'utf8mb4',
60
- underscored: true,
61
- logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
62
- },
63
- redis: {
64
- host: process.env.REDIS_HOST || '127.0.0.1',
65
- port: Number(process.env.REDIS_PORT) || 6379,
66
- password: process.env.REDIS_PASSWORD || undefined
67
- }
68
- })
69
78
  const app = framework.createApp()
70
79
 
71
80
  await framework.setup({
@@ -86,7 +95,11 @@ async function bootstrap() {
86
95
  app.setJWT('default', jwtOptions)
87
96
  app.setJwtHintHeader('JwtHint')
88
97
 
89
- logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks.issuer || jwtOptions.jwks.jwksUri})`)
98
+ {{#if_eq auth "static"}}
99
+ logger.info('[app] JWT auth provider: static (HS256)')
100
+ {{else}}
101
+ logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks?.issuer || jwtOptions.jwks?.jwksUri})`)
102
+ {{/if_eq}}
90
103
 
91
104
  app.use(expressLogger())
92
105
  app.use(express.static(path.join(__dirname, '..', 'public')))
@@ -0,0 +1,23 @@
1
+ // @ts-nocheck
2
+ const { WebFramework } = require('skweb-framework')
3
+
4
+ const framework = new WebFramework({
5
+ db: {
6
+ host: process.env.DB_HOST || '127.0.0.1',
7
+ port: Number(process.env.DB_PORT) || 3306,
8
+ username: process.env.DB_USER || 'root',
9
+ password: process.env.DB_PASS || '',
10
+ database: process.env.DB_NAME || 'test-project',
11
+ dialect: process.env.DB_DIALECT || 'mysql',
12
+ charset: process.env.DB_CHARSET || 'utf8mb4',
13
+ underscored: true,
14
+ logging: process.env.DEBUG_DB === 'true'
15
+ },
16
+ redis: {
17
+ host: process.env.REDIS_HOST || '127.0.0.1',
18
+ port: Number(process.env.REDIS_PORT) || 6379,
19
+ password: process.env.REDIS_PASSWORD || undefined
20
+ }
21
+ })
22
+
23
+ module.exports = { framework }
@@ -38,6 +38,12 @@ REDIS_DB=0
38
38
  # (Production is always disabled to avoid exposing internal API details.)
39
39
  SWAGGER_ENABLED=true
40
40
 
41
+ {{#if_eq auth "static"}}
42
+ # ---------- Auth: static (HS256 shared secret) ----------
43
+ # [REQUIRED in production] Use a strong random string, e.g. `openssl rand -hex 32`
44
+ JWT_SECRET=please-change-this-in-production
45
+ JWT_EXPIRES_IN=7d
46
+ {{else}}
41
47
  # ---------- Auth: jwks (OIDC compatible) ----------
42
48
  # Works with any OIDC-compliant IdP. Common issuer formats:
43
49
  # Keycloak: https://kc.example.com/realms/myrealm
@@ -50,6 +56,7 @@ SWAGGER_ENABLED=true
50
56
  OIDC_ISSUER=
51
57
  OIDC_AUDIENCE=
52
58
  OIDC_JWKS_URI=
59
+ {{/if_eq}}
53
60
 
54
61
  # ---------- Logging ----------
55
62
  # error | warn | info | http | verbose | debug | silly
@@ -2,7 +2,7 @@ module.exports = {
2
2
  apps: [
3
3
  {
4
4
  name: '{{name}}-web',
5
- script: 'src/index.js',
5
+ script: 'src/web-app.js',
6
6
  instances: 'max',
7
7
  exec_mode: 'cluster',
8
8
  env: {
@@ -16,7 +16,7 @@ module.exports = {
16
16
  },
17
17
  {
18
18
  name: '{{name}}-cron',
19
- script: 'src/cronWorker.js',
19
+ script: 'src/cron-app.js',
20
20
  instances: 1,
21
21
  exec_mode: 'fork',
22
22
  autorestart: true,
@@ -9,8 +9,8 @@
9
9
  "restart": "pm2 restart all",
10
10
  "delete": "pm2 delete all",
11
11
  "logs": "pm2 logs",
12
- "web:dev": "npx dotenvx run -- node --watch src/index.js",
13
- "cron:dev": "npx dotenvx run -- node --watch src/cronWorker.js",
12
+ "web:dev": "npx dotenvx run -- node --watch src/web-app.js",
13
+ "cron:dev": "npx dotenvx run -- node --watch src/cron-app.js",
14
14
  "seed": "npx dotenvx run -- node src/commands/seed.js",
15
15
  "migrate": "npx dotenvx run -- node src/commands/migrate.js",
16
16
  "build": "cp -r src dist",
@@ -22,13 +22,13 @@
22
22
  "dependencies": {
23
23
  "@dotenvx/dotenvx": "^1.75.1",
24
24
  "http-errors": "^2.0.1",
25
- "skweb-express": "^2.0.3",
25
+ "skweb-express": "^2.0.4",
26
26
  "skweb-express-jwt": "^3.0.0",
27
27
  "skweb-express-logger": "^2.0.0",
28
28
  "skweb-sequelize": "^2.0.0",
29
29
  "skweb-redis": "^2.0.0",
30
30
  "skweb-cron": "^2.0.0",
31
- "skweb-framework": "^2.1.0",
31
+ "skweb-framework": "^2.1.1",
32
32
  "skweb-plugin": "^2.1.1",
33
33
  "skweb-util": "^2.0.0",
34
34
  "winston": "^3.19.0",
@@ -1,7 +1,7 @@
1
1
  import createHttpError from 'http-errors'
2
- import { db } from '../services/database.js'
2
+ import { framework } from '../web-framework.js'
3
3
 
4
- const { SysUser } = db.sequelize.models
4
+ const { SysUser } = framework.models
5
5
 
6
6
  /**
7
7
  * @swagger
@@ -0,0 +1,45 @@
1
+ import 'dotenv/config'
2
+
3
+ import path from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { framework } from './cron-framework.js'
6
+ import logger from './utils/logger.js'
7
+ import { checkRequiredEnv } from './utils/config.js'
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = path.dirname(__filename)
11
+
12
+ async function startCronWorker() {
13
+ logger.info('[cron-worker] Starting cron worker process...')
14
+
15
+ if (!checkRequiredEnv()) {
16
+ logger.error('[cron-worker] Configuration validation failed, exiting...')
17
+ process.exit(1)
18
+ }
19
+
20
+ await framework.setup({
21
+ modelsDir: path.join(__dirname, './models'),
22
+ cronActionsDir: path.join(__dirname, './cronActions')
23
+ })
24
+ await framework.start()
25
+ logger.info('[cron-worker] Cron worker started')
26
+
27
+ process.on('uncaughtException', (err) => {
28
+ logger.error(`[cron-worker] uncaughtException: ${err.stack || err.message}`)
29
+ process.exit(1)
30
+ })
31
+ process.on('unhandledRejection', (reason) => {
32
+ logger.error(`[cron-worker] unhandledRejection: ${reason instanceof Error ? reason.stack : String(reason)}`)
33
+ process.exit(1)
34
+ })
35
+ }
36
+
37
+ const isMainModule = import.meta.url === `file://${process.argv[1]}`
38
+ if (isMainModule) {
39
+ startCronWorker().catch((err) => {
40
+ logger.error('[cron-worker] Fatal error:', err)
41
+ process.exit(1)
42
+ })
43
+ }
44
+
45
+ export { startCronWorker }
@@ -0,0 +1,44 @@
1
+ // @ts-nocheck
2
+ import { CronFramework } from 'skweb-framework'
3
+ import logger from './utils/logger.js'
4
+
5
+ const framework = new CronFramework({
6
+ db: {
7
+ host: process.env.DB_HOST || '127.0.0.1',
8
+ port: Number(process.env.DB_PORT) || 3306,
9
+ username: process.env.DB_USER || 'root',
10
+ password: process.env.DB_PASS || '',
11
+ database: process.env.DB_NAME || 'test-project',
12
+ dialect: process.env.DB_DIALECT || 'mysql',
13
+ charset: process.env.DB_CHARSET || 'utf8mb4',
14
+ underscored: true,
15
+ logging: process.env.DEBUG_DB === 'true'
16
+ },
17
+ redis: {
18
+ host: process.env.REDIS_HOST || '127.0.0.1',
19
+ port: Number(process.env.REDIS_PORT) || 6379,
20
+ password: process.env.REDIS_PASSWORD || undefined
21
+ },
22
+ channel: 'cron:task:cmd',
23
+ logger,
24
+ onSaveTask: async (taskData) => {
25
+ const SysCronTask = framework.db?.sequelize?.models.SysCronTask
26
+ if (SysCronTask) {
27
+ await SysCronTask.update(
28
+ { cache: taskData.cache, data: taskData.data },
29
+ { where: { id: taskData.taskId } }
30
+ )
31
+ }
32
+ },
33
+ loadTasks: async () => {
34
+ const SysCronTask = framework.db?.sequelize?.models.SysCronTask
35
+ if (!SysCronTask) return []
36
+ return (await SysCronTask.findAll()).map((t) => ({
37
+ id: String(t.id),
38
+ cronTime: t.cronTime,
39
+ method: t.method
40
+ }))
41
+ }
42
+ })
43
+
44
+ export { framework }
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import path from 'path'
2
3
  import { fileURLToPath } from 'url'
3
4
  import swaggerJSDoc from 'swagger-jsdoc'
@@ -20,6 +21,13 @@ export function buildOpenApiSpec() {
20
21
  ],
21
22
  components: {
22
23
  securitySchemes: {
24
+ {{#if_eq auth "static"}}
25
+ bearerAuth: {
26
+ type: 'http',
27
+ scheme: 'bearer',
28
+ bearerFormat: 'JWT'
29
+ }
30
+ {{else}}
23
31
  oidcAuth: {
24
32
  type: 'openIdConnect',
25
33
  openIdConnectUrl: process.env.OIDC_JWKS_URI
@@ -28,6 +36,7 @@ export function buildOpenApiSpec() {
28
36
  ? `${process.env.OIDC_ISSUER.replace(/\/$/, '')}/.well-known/openid-configuration`
29
37
  : undefined)
30
38
  }
39
+ {{/if_eq}}
31
40
  }
32
41
  }
33
42
  },
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import logger from './logger.js'
2
3
 
3
4
  export function validateConfig() {
@@ -38,6 +39,18 @@ export function validateConfig() {
38
39
  }
39
40
  }
40
41
 
42
+ {{#if_eq auth "static"}}
43
+ export function checkJwtSecret() {
44
+ if (process.env.JWT_SECRET === 'please-change-this-in-production' || !process.env.JWT_SECRET) {
45
+ if (process.env.NODE_ENV === 'production') {
46
+ logger.error('[config] JWT_SECRET must be set in production!')
47
+ return false
48
+ }
49
+ logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
50
+ }
51
+ return true
52
+ }
53
+ {{else}}
41
54
  export function checkOIDCConfig() {
42
55
  const issuer = process.env.OIDC_ISSUER
43
56
  const jwksUri = process.env.OIDC_JWKS_URI
@@ -50,6 +63,7 @@ export function checkOIDCConfig() {
50
63
  }
51
64
  return true
52
65
  }
66
+ {{/if_eq}}
53
67
 
54
68
  export function checkRequiredEnv() {
55
69
  const result = validateConfig()