create-skweb 1.1.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.
- package/.turbo/turbo-build.log +17 -0
- package/.turbo/turbo-check.log +4 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +65 -0
- package/CHANGELOG.md +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +115 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/rollup.config.mjs +71 -0
- package/src/cli.ts +118 -0
- package/src/index.ts +122 -0
- package/templates/cjs/ecosystem.config.js +32 -0
- package/templates/cjs/eslint.config.js +25 -0
- package/templates/cjs/package.json +46 -0
- package/templates/cjs/src/app.js +91 -0
- package/templates/cjs/src/commands/migrate.js +35 -0
- package/templates/cjs/src/commands/seed.js +58 -0
- package/templates/cjs/src/controllers/hello.js +33 -0
- package/templates/cjs/src/controllers/user.js +101 -0
- package/templates/cjs/src/cronActions/cleanup.js +12 -0
- package/templates/cjs/src/cronWorker.js +85 -0
- package/templates/cjs/src/index.js +7 -0
- package/templates/cjs/src/models/SysUser.js +38 -0
- package/templates/cjs/src/seeds/SysUser.json +1 -0
- package/templates/cjs/src/services/database.js +15 -0
- package/templates/cjs/src/utils/config.js +65 -0
- package/templates/cjs/src/utils/logger.js +53 -0
- package/templates/cjs/test/index.js +13 -0
- package/templates/mjs/ecosystem.config.js +32 -0
- package/templates/mjs/eslint.config.js +25 -0
- package/templates/mjs/package.json +46 -0
- package/templates/mjs/src/app.js +97 -0
- package/templates/mjs/src/commands/migrate.js +38 -0
- package/templates/mjs/src/commands/seed.js +63 -0
- package/templates/mjs/src/controllers/hello.js +33 -0
- package/templates/mjs/src/controllers/user.js +101 -0
- package/templates/mjs/src/cronActions/cleanup.js +9 -0
- package/templates/mjs/src/cronWorker.js +91 -0
- package/templates/mjs/src/index.js +8 -0
- package/templates/mjs/src/models/SysUser.js +40 -0
- package/templates/mjs/src/seeds/SysUser.json +1 -0
- package/templates/mjs/src/services/database.js +16 -0
- package/templates/mjs/src/utils/config.js +63 -0
- package/templates/mjs/src/utils/logger.js +52 -0
- package/templates/mjs/test/index.mjs +13 -0
- package/templates/ts/ecosystem.config.js +36 -0
- package/templates/ts/eslint.config.js +17 -0
- package/templates/ts/package.json +57 -0
- package/templates/ts/rollup.config.mjs +39 -0
- package/templates/ts/src/app.ts +96 -0
- package/templates/ts/src/commands/migrate.ts +38 -0
- package/templates/ts/src/commands/seed.ts +63 -0
- package/templates/ts/src/controllers/hello.ts +33 -0
- package/templates/ts/src/controllers/user.ts +101 -0
- package/templates/ts/src/cronActions/cleanup.ts +10 -0
- package/templates/ts/src/cronWorker.ts +90 -0
- package/templates/ts/src/index.ts +8 -0
- package/templates/ts/src/models/SysUser.ts +49 -0
- package/templates/ts/src/seeds/SysUser.json +1 -0
- package/templates/ts/src/services/database.ts +16 -0
- package/templates/ts/src/utils/config.ts +68 -0
- package/templates/ts/src/utils/logger.ts +63 -0
- package/templates/ts/test/index.mjs +13 -0
- package/templates/ts/tsconfig.json +20 -0
- package/test/index.mjs +137 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { Database } from 'skweb-sequelize'
|
|
6
|
+
import logger from '../utils/logger.js'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
async function runSeed() {
|
|
12
|
+
logger.info('[seed] Initializing database with seed data...')
|
|
13
|
+
|
|
14
|
+
const db = new Database({
|
|
15
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
16
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
17
|
+
username: process.env.DB_USER || 'root',
|
|
18
|
+
password: process.env.DB_PASS || '',
|
|
19
|
+
database: process.env.DB_NAME || 'test-project',
|
|
20
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
21
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
22
|
+
underscored: true,
|
|
23
|
+
logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
await db.loadModelsDir(path.join(__dirname, '..', 'models'))
|
|
27
|
+
db.associateAllModels()
|
|
28
|
+
|
|
29
|
+
const seedsDir = path.join(__dirname, '..', 'seeds')
|
|
30
|
+
const fs = await import('fs/promises')
|
|
31
|
+
let files
|
|
32
|
+
try {
|
|
33
|
+
files = await fs.readdir(seedsDir)
|
|
34
|
+
} catch {
|
|
35
|
+
logger.warn('[seed] No seeds directory found')
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
if (!file.endsWith('.json')) continue
|
|
41
|
+
const modelName = path.basename(file, '.json')
|
|
42
|
+
const Model = db.sequelize.models[modelName]
|
|
43
|
+
if (!Model) {
|
|
44
|
+
logger.warn(`[seed] Model ${modelName} not found`)
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
const data = JSON.parse(await fs.readFile(path.join(seedsDir, file), 'utf-8'))
|
|
48
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
49
|
+
await Model.bulkCreate(data, { ignoreDuplicates: true })
|
|
50
|
+
logger.info(`[seed] Seeded ${modelName} with ${data.length} records`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
logger.info('[seed] Database initialization complete')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`
|
|
58
|
+
if (isMainModule) {
|
|
59
|
+
runSeed().catch((err) => {
|
|
60
|
+
logger.error('[seed] Fatal error:', err)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
method: 'get',
|
|
4
|
+
path: '/',
|
|
5
|
+
async handler() {
|
|
6
|
+
return {
|
|
7
|
+
message: 'Welcome to SKWeb!',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
timestamp: new Date().toISOString()
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
method: 'get',
|
|
15
|
+
path: '/health',
|
|
16
|
+
async handler() {
|
|
17
|
+
return {
|
|
18
|
+
status: 'ok',
|
|
19
|
+
timestamp: new Date().toISOString()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
method: 'post',
|
|
25
|
+
path: '/echo',
|
|
26
|
+
async handler(req) {
|
|
27
|
+
return {
|
|
28
|
+
message: 'Echo received',
|
|
29
|
+
data: req.body
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import createHttpError from 'http-errors'
|
|
2
|
+
import { db } from '../services/database.js'
|
|
3
|
+
|
|
4
|
+
const { SysUser } = db.sequelize.models
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
method: 'get',
|
|
9
|
+
path: '/api/users',
|
|
10
|
+
async handler(req) {
|
|
11
|
+
const { page = 1, limit = 10 } = req.query
|
|
12
|
+
const users = await SysUser.findAndCountAll({
|
|
13
|
+
offset: (Number(page) - 1) * Number(limit),
|
|
14
|
+
limit: Number(limit)
|
|
15
|
+
})
|
|
16
|
+
return {
|
|
17
|
+
list: users.rows,
|
|
18
|
+
total: users.count
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
method: 'get',
|
|
24
|
+
path: '/api/users/:id',
|
|
25
|
+
paramsSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
required: ['id'],
|
|
28
|
+
properties: { id: { type: 'integer', minimum: 1 } }
|
|
29
|
+
},
|
|
30
|
+
async handler(req) {
|
|
31
|
+
const { id } = req.validatedParams
|
|
32
|
+
const user = await SysUser.findByPk(id)
|
|
33
|
+
if (!user) {
|
|
34
|
+
throw new createHttpError.NotFound('User not found')
|
|
35
|
+
}
|
|
36
|
+
return user
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
method: 'post',
|
|
41
|
+
path: '/api/users',
|
|
42
|
+
bodySchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
required: ['username', 'password'],
|
|
45
|
+
properties: {
|
|
46
|
+
username: { type: 'string', minLength: 3 },
|
|
47
|
+
password: { type: 'string', minLength: 6 },
|
|
48
|
+
email: { type: 'string', format: 'email' },
|
|
49
|
+
phone: { type: 'string' }
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async handler(req) {
|
|
53
|
+
return await SysUser.create(req.validatedBody)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
method: 'put',
|
|
58
|
+
path: '/api/users/:id',
|
|
59
|
+
paramsSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
required: ['id'],
|
|
62
|
+
properties: { id: { type: 'integer', minimum: 1 } }
|
|
63
|
+
},
|
|
64
|
+
bodySchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
password: { type: 'string', minLength: 6 },
|
|
68
|
+
email: { type: 'string', format: 'email' },
|
|
69
|
+
phone: { type: 'string' },
|
|
70
|
+
status: { type: 'integer' }
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async handler(req) {
|
|
74
|
+
const { id } = req.validatedParams
|
|
75
|
+
const user = await SysUser.findByPk(id)
|
|
76
|
+
if (!user) {
|
|
77
|
+
throw new createHttpError.NotFound('User not found')
|
|
78
|
+
}
|
|
79
|
+
await user.update(req.validatedBody)
|
|
80
|
+
return user
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
method: 'delete',
|
|
85
|
+
path: '/api/users/:id',
|
|
86
|
+
paramsSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
required: ['id'],
|
|
89
|
+
properties: { id: { type: 'integer', minimum: 1 } }
|
|
90
|
+
},
|
|
91
|
+
async handler(req) {
|
|
92
|
+
const { id } = req.validatedParams
|
|
93
|
+
const user = await SysUser.findByPk(id)
|
|
94
|
+
if (!user) {
|
|
95
|
+
throw new createHttpError.NotFound('User not found')
|
|
96
|
+
}
|
|
97
|
+
await user.destroy()
|
|
98
|
+
return { message: 'User deleted' }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import logger from './utils/logger.js'
|
|
6
|
+
import { checkRequiredEnv } from './utils/config.js'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
async function startCronWorker() {
|
|
12
|
+
logger.info('[cron-worker] Starting cron worker process...')
|
|
13
|
+
|
|
14
|
+
if (!checkRequiredEnv()) {
|
|
15
|
+
logger.error('[cron-worker] Configuration validation failed, exiting...')
|
|
16
|
+
process.exit(1)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { CronFramework } = await import('skweb-framework')
|
|
20
|
+
|
|
21
|
+
const framework = new CronFramework({
|
|
22
|
+
db: {
|
|
23
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
24
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
25
|
+
username: process.env.DB_USER || 'root',
|
|
26
|
+
password: process.env.DB_PASS || '',
|
|
27
|
+
database: process.env.DB_NAME || 'test-project',
|
|
28
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
29
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
30
|
+
underscored: true,
|
|
31
|
+
logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
|
|
32
|
+
},
|
|
33
|
+
redis: {
|
|
34
|
+
host: process.env.REDIS_HOST || '127.0.0.1',
|
|
35
|
+
port: Number(process.env.REDIS_PORT) || 6379,
|
|
36
|
+
password: process.env.REDIS_PASSWORD || undefined
|
|
37
|
+
},
|
|
38
|
+
channel: 'cron:task:cmd',
|
|
39
|
+
logger,
|
|
40
|
+
onSaveTask: async (taskData) => {
|
|
41
|
+
const SysCronTask = framework.db?.sequelize?.models.SysCronTask
|
|
42
|
+
if (SysCronTask) {
|
|
43
|
+
await SysCronTask.update(
|
|
44
|
+
{ cache: taskData.cache, data: taskData.data },
|
|
45
|
+
{ where: { id: taskData.taskId } }
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
loadTasks: async () => {
|
|
50
|
+
const SysCronTask = framework.db?.sequelize?.models.SysCronTask
|
|
51
|
+
if (!SysCronTask) return []
|
|
52
|
+
return (await SysCronTask.findAll()).map((t) => ({
|
|
53
|
+
id: String(t.id),
|
|
54
|
+
cronTime: t.cronTime,
|
|
55
|
+
method: t.method
|
|
56
|
+
}))
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
await framework.runPluginBeforeSetup()
|
|
61
|
+
await framework.setupModels(path.join(__dirname, './models'))
|
|
62
|
+
await framework.loadPluginModels()
|
|
63
|
+
await framework.runPluginAfterSetup()
|
|
64
|
+
|
|
65
|
+
await framework.loadCronActions(path.join(__dirname, './cronActions'))
|
|
66
|
+
await framework.startWorker()
|
|
67
|
+
framework.start()
|
|
68
|
+
logger.info('[cron-worker] Cron worker started')
|
|
69
|
+
|
|
70
|
+
process.on('SIGINT', async () => {
|
|
71
|
+
logger.info('[cron-worker] Received SIGINT, shutting down...')
|
|
72
|
+
await framework.stop()
|
|
73
|
+
process.exit(0)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
process.on('SIGTERM', async () => {
|
|
77
|
+
logger.info('[cron-worker] Received SIGTERM, shutting down...')
|
|
78
|
+
await framework.stop()
|
|
79
|
+
process.exit(0)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`
|
|
84
|
+
if (isMainModule) {
|
|
85
|
+
startCronWorker().catch((err) => {
|
|
86
|
+
logger.error('[cron-worker] Fatal error:', err)
|
|
87
|
+
process.exit(1)
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { startCronWorker }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Model, DataTypes } from 'sequelize'
|
|
2
|
+
|
|
3
|
+
export default (sequelize) => {
|
|
4
|
+
class SysUser extends Model {}
|
|
5
|
+
|
|
6
|
+
SysUser.init({
|
|
7
|
+
id: {
|
|
8
|
+
type: DataTypes.INTEGER,
|
|
9
|
+
primaryKey: true,
|
|
10
|
+
autoIncrement: true
|
|
11
|
+
},
|
|
12
|
+
username: {
|
|
13
|
+
type: DataTypes.STRING(50),
|
|
14
|
+
allowNull: false,
|
|
15
|
+
unique: true
|
|
16
|
+
},
|
|
17
|
+
password: {
|
|
18
|
+
type: DataTypes.STRING(255),
|
|
19
|
+
allowNull: false
|
|
20
|
+
},
|
|
21
|
+
email: {
|
|
22
|
+
type: DataTypes.STRING(100)
|
|
23
|
+
},
|
|
24
|
+
phone: {
|
|
25
|
+
type: DataTypes.STRING(20)
|
|
26
|
+
},
|
|
27
|
+
status: {
|
|
28
|
+
type: DataTypes.TINYINT,
|
|
29
|
+
defaultValue: 1
|
|
30
|
+
}
|
|
31
|
+
}, {
|
|
32
|
+
sequelize,
|
|
33
|
+
modelName: 'SysUser',
|
|
34
|
+
tableName: 'sys_users',
|
|
35
|
+
timestamps: true,
|
|
36
|
+
underscored: true
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return SysUser
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id": 1, "username": "admin", "password": "$2b$10$example", "email": "admin@example.com", "phone": "13800138000", "status": 1}]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Database } from 'skweb-sequelize'
|
|
2
|
+
|
|
3
|
+
const db = new Database({
|
|
4
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
5
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
6
|
+
username: process.env.DB_USER || 'root',
|
|
7
|
+
password: process.env.DB_PASS || '',
|
|
8
|
+
database: process.env.DB_NAME || '{{name}}',
|
|
9
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
10
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
11
|
+
underscored: true,
|
|
12
|
+
logging: process.env.DEBUG_DB === 'true'
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export { db }
|
|
16
|
+
export default db
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logger from './logger.js'
|
|
2
|
+
|
|
3
|
+
export function validateConfig() {
|
|
4
|
+
const errors = []
|
|
5
|
+
|
|
6
|
+
if (!process.env.DB_HOST) {
|
|
7
|
+
logger.warn('[config] DB_HOST not set, using default: 127.0.0.1')
|
|
8
|
+
}
|
|
9
|
+
if (!process.env.DB_NAME) {
|
|
10
|
+
logger.warn('[config] DB_NAME not set, using default: test-project')
|
|
11
|
+
}
|
|
12
|
+
if (!process.env.DB_USER) {
|
|
13
|
+
logger.warn('[config] DB_USER not set, using default: root')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (process.env.JWT_SECRET === 'please-change-this-in-production') {
|
|
17
|
+
logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!process.env.REDIS_HOST) {
|
|
21
|
+
logger.warn('[config] REDIS_HOST not set, using default: 127.0.0.1')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const port = Number(process.env.PORT)
|
|
25
|
+
if (port && (port < 1 || port > 65535)) {
|
|
26
|
+
errors.push('PORT must be between 1 and 65535')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dbPort = Number(process.env.DB_PORT)
|
|
30
|
+
if (dbPort && (dbPort < 1 || dbPort > 65535)) {
|
|
31
|
+
errors.push('DB_PORT must be between 1 and 65535')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const redisPort = Number(process.env.REDIS_PORT)
|
|
35
|
+
if (redisPort && (redisPort < 1 || redisPort > 65535)) {
|
|
36
|
+
errors.push('REDIS_PORT must be between 1 and 65535')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
isValid: errors.length === 0,
|
|
41
|
+
errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function checkRequiredEnv() {
|
|
46
|
+
const result = validateConfig()
|
|
47
|
+
|
|
48
|
+
if (result.errors.length > 0) {
|
|
49
|
+
logger.error('[config] Configuration validation failed:')
|
|
50
|
+
result.errors.forEach((err) => logger.error(` - ${err}`))
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (process.env.NODE_ENV === 'production') {
|
|
55
|
+
if (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'please-change-this-in-production') {
|
|
56
|
+
logger.error('[config] JWT_SECRET must be set in production!')
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logger.info('[config] Configuration validation passed')
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Winston 日志配置
|
|
3
|
+
*/
|
|
4
|
+
import winston from 'winston'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
|
|
8
|
+
const LOG_LEVEL = process.env.LOG_LEVEL || 'info'
|
|
9
|
+
const LOG_DIR = process.env.LOG_DIR || path.join(process.cwd(), 'logs')
|
|
10
|
+
const LOG_CONSOLE = process.env.LOG_CONSOLE !== 'false'
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
13
|
+
fs.mkdirSync(LOG_DIR, { recursive: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { combine, timestamp, printf, colorize, errors, splat, json } = winston.format
|
|
17
|
+
|
|
18
|
+
const consoleFormat = printf(({ level, message, timestamp: ts, stack }) => {
|
|
19
|
+
return `${ts} [${level}] ${stack || message}`
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const fileFormat = combine(timestamp(), errors({ stack: true }), splat(), json())
|
|
23
|
+
|
|
24
|
+
export const logger = winston.createLogger({
|
|
25
|
+
level: LOG_LEVEL,
|
|
26
|
+
format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), errors({ stack: true }), splat()),
|
|
27
|
+
transports: [
|
|
28
|
+
new winston.transports.File({
|
|
29
|
+
filename: path.join(LOG_DIR, 'error.log'),
|
|
30
|
+
level: 'error',
|
|
31
|
+
format: fileFormat,
|
|
32
|
+
maxsize: 10 * 1024 * 1024,
|
|
33
|
+
maxFiles: 10
|
|
34
|
+
}),
|
|
35
|
+
new winston.transports.File({
|
|
36
|
+
filename: path.join(LOG_DIR, 'combined.log'),
|
|
37
|
+
format: fileFormat,
|
|
38
|
+
maxsize: 10 * 1024 * 1024,
|
|
39
|
+
maxFiles: 10
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (LOG_CONSOLE) {
|
|
45
|
+
logger.add(
|
|
46
|
+
new winston.transports.Console({
|
|
47
|
+
format: combine(colorize(), consoleFormat)
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default logger
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect } from 'chai'
|
|
2
|
+
|
|
3
|
+
describe('SKWeb App Tests', function() {
|
|
4
|
+
describe('Basic Tests', function() {
|
|
5
|
+
it('should pass basic test', function() {
|
|
6
|
+
expect(true).to.equal(true)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('should have correct package structure', function() {
|
|
10
|
+
expect('SKWeb').to.be.a('string')
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
apps: [
|
|
3
|
+
{
|
|
4
|
+
name: '{{name}}-web',
|
|
5
|
+
script: 'src/index.ts',
|
|
6
|
+
interpreter: 'npx',
|
|
7
|
+
interpreter_args: 'tsx',
|
|
8
|
+
instances: 'max',
|
|
9
|
+
exec_mode: 'cluster',
|
|
10
|
+
env: {
|
|
11
|
+
NODE_ENV: 'production',
|
|
12
|
+
PORT: 3000
|
|
13
|
+
},
|
|
14
|
+
max_memory_restart: '512M',
|
|
15
|
+
error_file: 'logs/web-err.log',
|
|
16
|
+
out_file: 'logs/web-out.log',
|
|
17
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: '{{name}}-cron',
|
|
21
|
+
script: 'src/cronWorker.ts',
|
|
22
|
+
interpreter: 'npx',
|
|
23
|
+
interpreter_args: 'tsx',
|
|
24
|
+
instances: 1,
|
|
25
|
+
exec_mode: 'fork',
|
|
26
|
+
autorestart: true,
|
|
27
|
+
env: {
|
|
28
|
+
NODE_ENV: 'production'
|
|
29
|
+
},
|
|
30
|
+
max_memory_restart: '512M',
|
|
31
|
+
error_file: 'logs/cron-err.log',
|
|
32
|
+
out_file: 'logs/cron-out.log',
|
|
33
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import tseslint from 'typescript-eslint'
|
|
2
|
+
import js from '@eslint/js'
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
{ ignores: ['dist', 'node_modules', 'logs', 'test'] },
|
|
6
|
+
{
|
|
7
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
8
|
+
languageOptions: {
|
|
9
|
+
ecmaVersion: 2022,
|
|
10
|
+
sourceType: 'module'
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
14
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "pm2 start ecosystem.config.js",
|
|
8
|
+
"stop": "pm2 stop all",
|
|
9
|
+
"restart": "pm2 restart all",
|
|
10
|
+
"delete": "pm2 delete all",
|
|
11
|
+
"logs": "pm2 logs",
|
|
12
|
+
"web:dev": "npx dotenvx run -- tsx watch src/index.ts",
|
|
13
|
+
"cron:dev": "npx dotenvx run -- tsx watch src/cronWorker.ts",
|
|
14
|
+
"seed": "npx dotenvx run -- tsx src/commands/seed.ts",
|
|
15
|
+
"migrate": "npx dotenvx run -- tsx src/commands/migrate.ts",
|
|
16
|
+
"build": "tsc && rollup -c rollup.config.mjs",
|
|
17
|
+
"build:dev": "rollup -c rollup.config.mjs --environment NODE_ENV:development",
|
|
18
|
+
"lint": "eslint --fix --ext .ts src",
|
|
19
|
+
"check": "tsc --noEmit",
|
|
20
|
+
"test": "npx dotenvx run -- mocha test/**/*.mjs",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"clean:temp": "rm -rf .rollup.cache"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@dotenvx/dotenvx": "^1.71.0",
|
|
26
|
+
"http-errors": "^2.0.0",
|
|
27
|
+
"skweb-express": "^1.0.0",
|
|
28
|
+
"skweb-express-jwt": "^1.0.0",
|
|
29
|
+
"skweb-express-logger": "^1.0.0",
|
|
30
|
+
"skweb-sequelize": "^1.0.0",
|
|
31
|
+
"skweb-redis": "^1.0.0",
|
|
32
|
+
"skweb-cron": "^1.0.0",
|
|
33
|
+
"skweb-framework": "^1.0.0",
|
|
34
|
+
"skweb-plugin": "^1.0.0",
|
|
35
|
+
"skweb-util": "^1.0.0",
|
|
36
|
+
"winston": "^3.17.0",
|
|
37
|
+
"sequelize": "^6.37.7"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^25.0.1",
|
|
41
|
+
"@types/mocha": "^10.0.10",
|
|
42
|
+
"@types/http-errors": "^2.0.4",
|
|
43
|
+
"chai": "^6.2.1",
|
|
44
|
+
"mocha": "^11.7.5",
|
|
45
|
+
"tsx": "^4.22.4",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"rollup": "^2.79.2",
|
|
48
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
49
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
50
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
51
|
+
"eslint": "^9.39.1",
|
|
52
|
+
"typescript-eslint": "^8.49.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20.18.1"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve'
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
3
|
+
import typescript from '@rollup/plugin-typescript'
|
|
4
|
+
import terser from '@rollup/plugin-terser'
|
|
5
|
+
|
|
6
|
+
const isProduction = process.env.NODE_ENV === 'production'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
input: 'src/index.ts',
|
|
10
|
+
output: {
|
|
11
|
+
file: 'dist/index.js',
|
|
12
|
+
format: 'es',
|
|
13
|
+
sourcemap: true
|
|
14
|
+
},
|
|
15
|
+
plugins: [
|
|
16
|
+
resolve(),
|
|
17
|
+
commonjs(),
|
|
18
|
+
typescript({ tsconfig: './tsconfig.json' }),
|
|
19
|
+
isProduction && terser()
|
|
20
|
+
],
|
|
21
|
+
external: [
|
|
22
|
+
'skweb-express',
|
|
23
|
+
'skweb-express-jwt',
|
|
24
|
+
'skweb-express-logger',
|
|
25
|
+
'skweb-sequelize',
|
|
26
|
+
'skweb-redis',
|
|
27
|
+
'skweb-cron',
|
|
28
|
+
'skweb-framework',
|
|
29
|
+
'skweb-plugin',
|
|
30
|
+
'skweb-util',
|
|
31
|
+
'@dotenvx/dotenvx',
|
|
32
|
+
'http-errors',
|
|
33
|
+
'express',
|
|
34
|
+
'sequelize',
|
|
35
|
+
'ioredis',
|
|
36
|
+
'path',
|
|
37
|
+
'fs'
|
|
38
|
+
]
|
|
39
|
+
}
|