create-skweb 3.2.0 → 3.3.1
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/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/dist/index.js +15 -51
- package/.rollup.cache/Users/stayknight/projects/skweb-mono/tools/create-skweb/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +12 -0
- package/dist/cli.js +15 -51
- package/dist/index.js +15 -51
- package/package.json +1 -1
- package/src/index.ts +21 -50
- package/templates/cjs/{.env.example.auth-jwks → .env.example} +7 -0
- package/templates/cjs/ecosystem.config.js +2 -2
- package/templates/cjs/package.json +4 -4
- package/templates/cjs/src/controllers/user.js +2 -2
- package/templates/cjs/src/cron-app.js +40 -0
- package/templates/cjs/src/cron-framework.js +44 -0
- package/templates/cjs/src/{swagger.auth-jwks.js → swagger.js} +9 -0
- package/templates/cjs/src/utils/{config.auth-jwks.js → config.js} +18 -0
- package/templates/cjs/src/{app.auth-jwks.js → web-app.js} +34 -21
- package/templates/cjs/src/web-framework.js +23 -0
- package/templates/{ts/.env.example.auth-jwks → mjs/.env.example} +7 -0
- package/templates/mjs/ecosystem.config.js +2 -2
- package/templates/mjs/package.json +4 -4
- package/templates/mjs/src/controllers/user.js +2 -2
- package/templates/mjs/src/cron-app.js +45 -0
- package/templates/mjs/src/cron-framework.js +44 -0
- package/templates/mjs/src/{swagger.auth-jwks.js → swagger.js} +9 -0
- package/templates/mjs/src/utils/{config.auth-jwks.js → config.js} +14 -0
- package/templates/mjs/src/{app.auth-jwks.js → web-app.js} +29 -23
- package/templates/mjs/src/web-framework.js +23 -0
- package/templates/{mjs/.env.example.auth-jwks → ts/.env.example} +7 -0
- package/templates/ts/ecosystem.config.js +2 -2
- package/templates/ts/package.json +4 -4
- package/templates/ts/src/controllers/user.ts +2 -2
- package/templates/ts/src/cron-app.ts +45 -0
- package/templates/ts/src/cron-framework.ts +44 -0
- package/templates/ts/src/{swagger.auth-jwks.ts → swagger.ts} +9 -0
- package/templates/ts/src/utils/{config.auth-jwks.ts → config.ts} +14 -0
- package/templates/ts/src/{app.auth-jwks.ts → web-app.ts} +28 -21
- package/templates/ts/src/web-framework.ts +23 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/templates/cjs/.env.example.auth-static +0 -54
- package/templates/cjs/src/app.auth-static.js +0 -129
- package/templates/cjs/src/cronWorker.js +0 -80
- package/templates/cjs/src/index.js +0 -7
- package/templates/cjs/src/services/database.js +0 -15
- package/templates/cjs/src/swagger.auth-static.js +0 -35
- package/templates/cjs/src/utils/config.auth-static.js +0 -65
- package/templates/mjs/.env.example.auth-static +0 -54
- package/templates/mjs/src/app.auth-static.js +0 -131
- package/templates/mjs/src/cronWorker.js +0 -86
- package/templates/mjs/src/index.js +0 -8
- package/templates/mjs/src/services/database.js +0 -16
- package/templates/mjs/src/swagger.auth-static.js +0 -35
- package/templates/mjs/src/utils/config.auth-static.js +0 -63
- package/templates/ts/.env.example.auth-static +0 -54
- package/templates/ts/src/app.auth-static.ts +0 -131
- package/templates/ts/src/cronWorker.ts +0 -85
- package/templates/ts/src/index.ts +0 -8
- package/templates/ts/src/services/database.ts +0 -16
- package/templates/ts/src/swagger.auth-static.ts +0 -36
- package/templates/ts/src/utils/config.auth-static.ts +0 -68
|
@@ -2,7 +2,7 @@ module.exports = {
|
|
|
2
2
|
apps: [
|
|
3
3
|
{
|
|
4
4
|
name: '{{name}}-web',
|
|
5
|
-
script: 'src/
|
|
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/
|
|
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/
|
|
13
|
-
"cron:dev": "npx dotenvx run -- node --watch src/
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -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 bootstrap() {
|
|
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
|
+
bootstrap().catch((err) => {
|
|
40
|
+
logger.error('[cron-worker] Fatal error:', err)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { bootstrap }
|
|
@@ -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()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import 'dotenv/config'
|
|
2
3
|
|
|
3
4
|
import path from 'path'
|
|
@@ -5,13 +6,27 @@ import { fileURLToPath } from 'url'
|
|
|
5
6
|
import express from 'skweb-express'
|
|
6
7
|
import expressLogger, { errorHandler as expressErrorHandler } from 'skweb-express-logger'
|
|
7
8
|
import swaggerUi from 'swagger-ui-express'
|
|
9
|
+
import { framework } from './web-framework.js'
|
|
8
10
|
import logger from './utils/logger.js'
|
|
9
|
-
import { checkRequiredEnv, checkOIDCConfig } from './utils/config.js'
|
|
11
|
+
import { checkRequiredEnv{{#if_eq auth "static"}}, checkJwtSecret{{/if_eq}}{{#if_eq auth "jwks"}}, checkOIDCConfig{{/if_eq}} } from './utils/config.js'
|
|
10
12
|
import { buildOpenApiSpec } from './swagger.js'
|
|
11
13
|
|
|
12
14
|
const __filename = fileURLToPath(import.meta.url)
|
|
13
15
|
const __dirname = path.dirname(__filename)
|
|
14
16
|
|
|
17
|
+
{{#if_eq auth "static"}}
|
|
18
|
+
/**
|
|
19
|
+
* JWT 配置(static 模式,HS256 共享密钥)。
|
|
20
|
+
*/
|
|
21
|
+
function buildJwtOptions() {
|
|
22
|
+
return {
|
|
23
|
+
provider: 'static',
|
|
24
|
+
secret: process.env.JWT_SECRET || 'please-change-this-in-production',
|
|
25
|
+
algorithms: ['HS256'],
|
|
26
|
+
sign: { expiresIn: process.env.JWT_EXPIRES_IN || '7d' },
|
|
27
|
+
requestProperty: 'resolvedToken'
|
|
28
|
+
}
|
|
29
|
+
}{{else}}
|
|
15
30
|
/**
|
|
16
31
|
* JWT 配置(jwks 模式,通过 OIDC 兼容的 JWKS 端点验证 RS256 签名)。
|
|
17
32
|
*/
|
|
@@ -30,6 +45,7 @@ function buildJwtOptions() {
|
|
|
30
45
|
requestProperty: 'resolvedToken'
|
|
31
46
|
}
|
|
32
47
|
}
|
|
48
|
+
{{/if_eq}}
|
|
33
49
|
|
|
34
50
|
function shouldEnableSwagger() {
|
|
35
51
|
const env = (process.env.NODE_ENV || '').toLowerCase()
|
|
@@ -45,31 +61,17 @@ async function bootstrap() {
|
|
|
45
61
|
logger.error('[app] Configuration validation failed, exiting...')
|
|
46
62
|
process.exit(1)
|
|
47
63
|
}
|
|
64
|
+
{{#if_eq auth "static"}}
|
|
65
|
+
if (!checkJwtSecret()) {
|
|
66
|
+
logger.error('[app] JWT configuration validation failed, exiting...')
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
{{else}}
|
|
48
70
|
if (!checkOIDCConfig()) {
|
|
49
71
|
logger.error('[app] OIDC configuration validation failed, exiting...')
|
|
50
72
|
process.exit(1)
|
|
51
73
|
}
|
|
52
|
-
|
|
53
|
-
const { WebFramework } = await import('skweb-framework')
|
|
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 || 'mysql',
|
|
63
|
-
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
64
|
-
underscored: true,
|
|
65
|
-
logging: process.env.DEBUG_DB === 'true' ? (msg) => 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
|
-
})
|
|
74
|
+
{{/if_eq}}
|
|
73
75
|
|
|
74
76
|
const app = framework.createApp()
|
|
75
77
|
|
|
@@ -91,7 +93,11 @@ async function bootstrap() {
|
|
|
91
93
|
app.setJWT('default', jwtOptions)
|
|
92
94
|
app.setJwtHintHeader('JwtHint')
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
{{#if_eq auth "static"}}
|
|
97
|
+
logger.info('[app] JWT auth provider: static (HS256)')
|
|
98
|
+
{{else}}
|
|
99
|
+
logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks?.issuer || jwtOptions.jwks?.jwksUri})`)
|
|
100
|
+
{{/if_eq}}
|
|
95
101
|
|
|
96
102
|
app.use(expressLogger())
|
|
97
103
|
app.use(express.static(path.join(__dirname, '..', 'public')))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { WebFramework } from '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
|
+
export { 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/
|
|
5
|
+
script: 'src/web-app.ts',
|
|
6
6
|
interpreter: 'npx',
|
|
7
7
|
interpreter_args: 'tsx',
|
|
8
8
|
instances: 'max',
|
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
name: '{{name}}-cron',
|
|
21
|
-
script: 'src/
|
|
21
|
+
script: 'src/cron-app.ts',
|
|
22
22
|
interpreter: 'npx',
|
|
23
23
|
interpreter_args: 'tsx',
|
|
24
24
|
instances: 1,
|
|
@@ -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 -- tsx watch src/
|
|
13
|
-
"cron:dev": "npx dotenvx run -- tsx watch src/
|
|
12
|
+
"web:dev": "npx dotenvx run -- tsx watch src/web-app.ts",
|
|
13
|
+
"cron:dev": "npx dotenvx run -- tsx watch src/cron-app.ts",
|
|
14
14
|
"seed": "npx dotenvx run -- tsx src/commands/seed.ts",
|
|
15
15
|
"migrate": "npx dotenvx run -- tsx src/commands/migrate.ts",
|
|
16
16
|
"build": "tsc && rollup -c rollup.config.mjs",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@dotenvx/dotenvx": "^1.75.1",
|
|
26
26
|
"http-errors": "^2.0.1",
|
|
27
|
-
"skweb-express": "^2.0.
|
|
27
|
+
"skweb-express": "^2.0.4",
|
|
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.1.
|
|
33
|
+
"skweb-framework": "^2.1.1",
|
|
34
34
|
"skweb-plugin": "^2.1.1",
|
|
35
35
|
"skweb-util": "^2.0.0",
|
|
36
36
|
"winston": "^3.19.0",
|
|
@@ -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 bootstrap(): Promise<void> {
|
|
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
|
+
bootstrap().catch((err) => {
|
|
40
|
+
logger.error('[cron-worker] Fatal error:', err)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { bootstrap }
|
|
@@ -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 as any) || '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: { taskId: string; cache: unknown; data: unknown }) => {
|
|
25
|
+
const SysCronTask = framework.dbInstance?.sequelize?.models.SysCronTask as any
|
|
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.dbInstance?.sequelize?.models.SysCronTask as any
|
|
35
|
+
if (!SysCronTask) return []
|
|
36
|
+
return (await SysCronTask.findAll()).map((t: any) => ({
|
|
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(): object {
|
|
|
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(): object {
|
|
|
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
|
interface ConfigValidationResult {
|
|
@@ -43,6 +44,18 @@ export function validateConfig(): ConfigValidationResult {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
{{#if_eq auth "static"}}
|
|
48
|
+
export function checkJwtSecret(): boolean {
|
|
49
|
+
if (process.env.JWT_SECRET === 'please-change-this-in-production' || !process.env.JWT_SECRET) {
|
|
50
|
+
if (process.env.NODE_ENV === 'production') {
|
|
51
|
+
logger.error('[config] JWT_SECRET must be set in production!')
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
|
|
55
|
+
}
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
{{else}}
|
|
46
59
|
export function checkOIDCConfig(): boolean {
|
|
47
60
|
const issuer = process.env.OIDC_ISSUER
|
|
48
61
|
const jwksUri = process.env.OIDC_JWKS_URI
|
|
@@ -55,6 +68,7 @@ export function checkOIDCConfig(): boolean {
|
|
|
55
68
|
}
|
|
56
69
|
return true
|
|
57
70
|
}
|
|
71
|
+
{{/if_eq}}
|
|
58
72
|
|
|
59
73
|
export function checkRequiredEnv(): boolean {
|
|
60
74
|
const result = validateConfig()
|
|
@@ -1,19 +1,33 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
import 'dotenv/config'
|
|
2
3
|
|
|
3
4
|
import path from 'path'
|
|
4
5
|
import { fileURLToPath } from 'url'
|
|
5
6
|
import express from 'skweb-express'
|
|
6
7
|
import expressLogger, { errorHandler as expressErrorHandler } from 'skweb-express-logger'
|
|
7
|
-
import { WebFramework } from 'skweb-framework'
|
|
8
8
|
import type { JWTOptions } from 'skweb-express-jwt'
|
|
9
9
|
import swaggerUi from 'swagger-ui-express'
|
|
10
|
+
import { framework } from './web-framework.js'
|
|
10
11
|
import logger from './utils/logger.js'
|
|
11
|
-
import { checkRequiredEnv, checkOIDCConfig } from './utils/config.js'
|
|
12
|
+
import { checkRequiredEnv{{#if_eq auth "static"}}, checkJwtSecret{{/if_eq}}{{#if_eq auth "jwks"}}, checkOIDCConfig{{/if_eq}} } from './utils/config.js'
|
|
12
13
|
import { buildOpenApiSpec } from './swagger.js'
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url)
|
|
15
16
|
const __dirname = path.dirname(__filename)
|
|
16
17
|
|
|
18
|
+
{{#if_eq auth "static"}}
|
|
19
|
+
/**
|
|
20
|
+
* JWT 配置(static 模式,HS256 共享密钥)。
|
|
21
|
+
*/
|
|
22
|
+
function buildJwtOptions(): JWTOptions {
|
|
23
|
+
return {
|
|
24
|
+
provider: 'static',
|
|
25
|
+
secret: process.env.JWT_SECRET || 'please-change-this-in-production',
|
|
26
|
+
algorithms: ['HS256'],
|
|
27
|
+
sign: { expiresIn: (process.env.JWT_EXPIRES_IN || '7d') as any },
|
|
28
|
+
requestProperty: 'resolvedToken'
|
|
29
|
+
}
|
|
30
|
+
}{{else}}
|
|
17
31
|
/**
|
|
18
32
|
* JWT 配置(jwks 模式,通过 OIDC 兼容的 JWKS 端点验证 RS256 签名)。
|
|
19
33
|
*/
|
|
@@ -32,6 +46,7 @@ function buildJwtOptions(): JWTOptions {
|
|
|
32
46
|
requestProperty: 'resolvedToken'
|
|
33
47
|
}
|
|
34
48
|
}
|
|
49
|
+
{{/if_eq}}
|
|
35
50
|
|
|
36
51
|
function shouldEnableSwagger(): boolean {
|
|
37
52
|
const env = (process.env.NODE_ENV || '').toLowerCase()
|
|
@@ -47,29 +62,17 @@ async function bootstrap(): Promise<void> {
|
|
|
47
62
|
logger.error('[app] Configuration validation failed, exiting...')
|
|
48
63
|
process.exit(1)
|
|
49
64
|
}
|
|
65
|
+
{{#if_eq auth "static"}}
|
|
66
|
+
if (!checkJwtSecret()) {
|
|
67
|
+
logger.error('[app] JWT configuration validation failed, exiting...')
|
|
68
|
+
process.exit(1)
|
|
69
|
+
}
|
|
70
|
+
{{else}}
|
|
50
71
|
if (!checkOIDCConfig()) {
|
|
51
72
|
logger.error('[app] OIDC configuration validation failed, exiting...')
|
|
52
73
|
process.exit(1)
|
|
53
74
|
}
|
|
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
|
-
})
|
|
75
|
+
{{/if_eq}}
|
|
73
76
|
|
|
74
77
|
const app = framework.createApp() as express.Express & Record<string, any>
|
|
75
78
|
|
|
@@ -91,7 +94,11 @@ async function bootstrap(): Promise<void> {
|
|
|
91
94
|
app.setJWT('default', jwtOptions as any)
|
|
92
95
|
app.setJwtHintHeader('JwtHint')
|
|
93
96
|
|
|
97
|
+
{{#if_eq auth "static"}}
|
|
98
|
+
logger.info('[app] JWT auth provider: static (HS256)')
|
|
99
|
+
{{else}}
|
|
94
100
|
logger.info(`[app] JWT auth provider: jwks (issuer: ${jwtOptions.jwks?.issuer || jwtOptions.jwks?.jwksUri})`)
|
|
101
|
+
{{/if_eq}}
|
|
95
102
|
|
|
96
103
|
app.use(expressLogger())
|
|
97
104
|
app.use(express.static(path.join(__dirname, '..', 'public')))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { WebFramework } from '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 as any) || '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
|
+
export { framework }
|