nsgm-cli 2.0.26 → 2.1.2
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/babel.config.js +18 -0
- package/client/components/Button.tsx +8 -0
- package/client/components/__tests__/Button.test.tsx +15 -0
- package/client/redux/store.ts +13 -11
- package/client/utils/fetch.ts +35 -6
- package/eslint.config.js +87 -0
- package/generation/eslintrc.js +16 -0
- package/generation/server/rest.js +2 -2
- package/generation/server/utils/common.js +4 -2
- package/generation/server/utils/db-pool-manager.js +64 -0
- package/generation/tsconfig.json +2 -1
- package/jest.config.js +29 -0
- package/lib/args.js +8 -9
- package/lib/generate.js +318 -420
- package/lib/index.d.ts +1 -1
- package/lib/index.js +106 -98
- package/lib/server/db.d.ts +6 -1
- package/lib/server/db.js +143 -61
- package/lib/server/graphql.js +147 -84
- package/lib/server/plugins/date.js +10 -10
- package/lib/server/utils/graphql-cache.d.ts +9 -0
- package/lib/server/utils/graphql-cache.js +69 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/next.config.js +67 -0
- package/package.json +41 -17
- package/pages/template/manage.tsx +2 -2
- package/scripts/performance-check.sh +29 -0
- package/server/modules/template/resolver.js +211 -208
- package/server/rest.js +2 -2
- package/server/utils/common.js +4 -2
- package/server/utils/db-pool-manager.js +64 -0
- package/server/utils/performance.js +70 -0
package/babel.config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/components/Button.test.js
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import Button from '../Button';
|
|
4
|
+
|
|
5
|
+
test('renders button with text', () => {
|
|
6
|
+
render(<Button>Click me</Button>);
|
|
7
|
+
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('calls onClick when clicked', () => {
|
|
11
|
+
const handleClick = jest.fn();
|
|
12
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
|
13
|
+
fireEvent.click(screen.getByText('Click me'));
|
|
14
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
15
|
+
});
|
package/client/redux/store.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { combineReducers } from 'redux'
|
|
3
|
-
import { configureStore,
|
|
4
|
-
import { thunk } from 'redux-thunk'
|
|
3
|
+
import { configureStore, type EnhancedStore } from '@reduxjs/toolkit'
|
|
5
4
|
import reducers from './reducers'
|
|
6
|
-
import _ from 'lodash'
|
|
7
5
|
|
|
8
|
-
let store:
|
|
6
|
+
let store: EnhancedStore | undefined
|
|
9
7
|
|
|
10
|
-
const reducersKeysLen =
|
|
8
|
+
const reducersKeysLen = Object.keys(reducers).length
|
|
11
9
|
|
|
12
|
-
let combineReducer: any =
|
|
10
|
+
let combineReducer: any = () => ({})
|
|
13
11
|
|
|
14
12
|
if (reducersKeysLen > 0) {
|
|
15
13
|
combineReducer = combineReducers({ ...reducers })
|
|
@@ -17,17 +15,21 @@ if (reducersKeysLen > 0) {
|
|
|
17
15
|
|
|
18
16
|
export type RootState = ReturnType<typeof combineReducer>
|
|
19
17
|
|
|
20
|
-
function initStore(initialState
|
|
18
|
+
function initStore(initialState?: any): EnhancedStore {
|
|
21
19
|
return configureStore({
|
|
22
20
|
reducer: combineReducer,
|
|
23
21
|
preloadedState: initialState,
|
|
24
|
-
devTools:
|
|
22
|
+
devTools: process.env.NODE_ENV !== 'production',
|
|
25
23
|
middleware: (getDefaultMiddleware) =>
|
|
26
|
-
getDefaultMiddleware(
|
|
24
|
+
getDefaultMiddleware({
|
|
25
|
+
serializableCheck: {
|
|
26
|
+
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
27
29
|
})
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
export const initializeStore = (preloadedState
|
|
32
|
+
export const initializeStore = (preloadedState?: any): EnhancedStore => {
|
|
31
33
|
let _store = store ?? initStore(preloadedState)
|
|
32
34
|
|
|
33
35
|
if (preloadedState && store) {
|
|
@@ -45,7 +47,7 @@ export const initializeStore = (preloadedState: any) => {
|
|
|
45
47
|
return _store
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
export function useStore(initialState
|
|
50
|
+
export function useStore(initialState?: any): EnhancedStore {
|
|
49
51
|
const store = useMemo(() => initializeStore(initialState), [initialState])
|
|
50
52
|
return store
|
|
51
53
|
}
|
package/client/utils/fetch.ts
CHANGED
|
@@ -1,25 +1,54 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import { getLocalApiPrefix } from './common'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// 添加缓存机制
|
|
5
|
+
const queryCache = new Map<string, { data: any; timestamp: number }>()
|
|
6
|
+
const CACHE_DURATION = 5 * 60 * 1000 // 5分钟缓存
|
|
7
|
+
|
|
8
|
+
export const getLocalGraphql = (query: string, variables: any = {}, useCache = false) => {
|
|
5
9
|
return new Promise((resolve, reject) => {
|
|
10
|
+
// 生成缓存键
|
|
11
|
+
const cacheKey = `${query}:${JSON.stringify(variables)}`
|
|
12
|
+
|
|
13
|
+
// 检查缓存
|
|
14
|
+
if (useCache && queryCache.has(cacheKey)) {
|
|
15
|
+
const cached = queryCache.get(cacheKey)!
|
|
16
|
+
if (Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
17
|
+
resolve(cached.data)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
axios
|
|
7
23
|
.post(getLocalApiPrefix() + '/graphql', {
|
|
8
24
|
query,
|
|
9
25
|
variables
|
|
10
26
|
})
|
|
11
27
|
.then((res) => {
|
|
12
|
-
// console.log('axios_res', res)
|
|
13
28
|
if (res) {
|
|
14
29
|
const { data } = res
|
|
30
|
+
|
|
31
|
+
// 缓存结果
|
|
32
|
+
if (useCache && data && !data.errors) {
|
|
33
|
+
queryCache.set(cacheKey, {
|
|
34
|
+
data,
|
|
35
|
+
timestamp: Date.now()
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
resolve(data)
|
|
16
40
|
} else {
|
|
17
|
-
reject()
|
|
41
|
+
reject(new Error('No data received'))
|
|
18
42
|
}
|
|
19
43
|
})
|
|
20
|
-
.catch((
|
|
21
|
-
|
|
22
|
-
reject(
|
|
44
|
+
.catch((error) => {
|
|
45
|
+
console.error('GraphQL query failed:', error)
|
|
46
|
+
reject(error)
|
|
23
47
|
})
|
|
24
48
|
})
|
|
25
49
|
}
|
|
50
|
+
|
|
51
|
+
// 清除缓存
|
|
52
|
+
export const clearGraphqlCache = () => {
|
|
53
|
+
queryCache.clear()
|
|
54
|
+
}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const js = require('@eslint/js');
|
|
2
|
+
const typescript = require('@typescript-eslint/eslint-plugin');
|
|
3
|
+
const typescriptParser = require('@typescript-eslint/parser');
|
|
4
|
+
const prettier = require('eslint-plugin-prettier');
|
|
5
|
+
|
|
6
|
+
module.exports = [
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
files: ['src/**/*.{ts,tsx}'],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: typescriptParser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: 2022,
|
|
14
|
+
sourceType: 'module',
|
|
15
|
+
project: './tsconfig.json'
|
|
16
|
+
},
|
|
17
|
+
globals: {
|
|
18
|
+
__dirname: 'readonly',
|
|
19
|
+
__filename: 'readonly',
|
|
20
|
+
process: 'readonly',
|
|
21
|
+
Buffer: 'readonly',
|
|
22
|
+
console: 'readonly',
|
|
23
|
+
setTimeout: 'readonly',
|
|
24
|
+
clearTimeout: 'readonly',
|
|
25
|
+
setInterval: 'readonly',
|
|
26
|
+
clearInterval: 'readonly'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
plugins: {
|
|
30
|
+
'@typescript-eslint': typescript,
|
|
31
|
+
'prettier': prettier
|
|
32
|
+
},
|
|
33
|
+
rules: {
|
|
34
|
+
'no-console': 'off', // 允许 console 语句用于调试
|
|
35
|
+
'no-eval': 'off',
|
|
36
|
+
'no-undef': 'off',
|
|
37
|
+
'no-unused-vars': 'off', // 关闭原生规则,使用 TypeScript 版本
|
|
38
|
+
'no-case-declarations': 'off',
|
|
39
|
+
'no-fallthrough': 'off',
|
|
40
|
+
'prefer-const': 'warn',
|
|
41
|
+
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
|
|
42
|
+
'no-unreachable': 'error',
|
|
43
|
+
'no-duplicate-imports': 'error',
|
|
44
|
+
'prefer-template': 'warn',
|
|
45
|
+
'prefer-arrow-callback': 'warn',
|
|
46
|
+
'@typescript-eslint/no-var-requires': 'off', // 允许 require
|
|
47
|
+
'@typescript-eslint/no-explicit-any': 'warn', // 警告但不报错
|
|
48
|
+
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
49
|
+
argsIgnorePattern: '^_',
|
|
50
|
+
varsIgnorePattern: '^_'
|
|
51
|
+
}],
|
|
52
|
+
'@typescript-eslint/no-inferrable-types': 'warn',
|
|
53
|
+
'@typescript-eslint/prefer-optional-chain': 'warn',
|
|
54
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
55
|
+
'prettier/prettier': 'error'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
files: ['**/*.js'],
|
|
60
|
+
languageOptions: {
|
|
61
|
+
ecmaVersion: 2022,
|
|
62
|
+
sourceType: 'commonjs',
|
|
63
|
+
globals: {
|
|
64
|
+
require: 'readonly',
|
|
65
|
+
module: 'readonly',
|
|
66
|
+
exports: 'readonly',
|
|
67
|
+
__dirname: 'readonly',
|
|
68
|
+
__filename: 'readonly',
|
|
69
|
+
process: 'readonly',
|
|
70
|
+
Buffer: 'readonly',
|
|
71
|
+
console: 'readonly'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
rules: {
|
|
75
|
+
'no-console': 'off'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
ignores: [
|
|
80
|
+
'node_modules/**',
|
|
81
|
+
'lib/**',
|
|
82
|
+
'.next/**',
|
|
83
|
+
'coverage/**',
|
|
84
|
+
'*.config.js'
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const express = require('express')
|
|
2
|
-
const
|
|
2
|
+
const dayjs = require('dayjs')
|
|
3
3
|
const sso = require('./apis/sso')
|
|
4
4
|
//const template = require('./apis/template')
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ const router = express.Router()
|
|
|
7
7
|
|
|
8
8
|
router.use((req, res, next) => {
|
|
9
9
|
const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl
|
|
10
|
-
console.log(
|
|
10
|
+
console.log(dayjs().format('YYYY-MM-DD HH:mm:ss') + ' ' + fullUrl)
|
|
11
11
|
next()
|
|
12
12
|
})
|
|
13
13
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const { serverDB } = require('nsgm-cli')
|
|
2
2
|
|
|
3
|
-
const { getConnection } = serverDB
|
|
3
|
+
const { getConnection, executeQuery, executePaginatedQuery } = serverDB
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
|
-
getConnection
|
|
6
|
+
getConnection, // 兼容旧版本
|
|
7
|
+
executeQuery, // 新的查询方法
|
|
8
|
+
executePaginatedQuery // 新的分页查询方法
|
|
7
9
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// 数据库连接池管理器
|
|
2
|
+
const { serverDB } = require('nsgm-cli')
|
|
3
|
+
|
|
4
|
+
class DatabasePoolManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.initialized = false
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// 初始化连接池
|
|
10
|
+
async initialize() {
|
|
11
|
+
if (this.initialized) {
|
|
12
|
+
console.log('数据库连接池已经初始化')
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// 测试连接池
|
|
18
|
+
await serverDB.executeQuery('SELECT 1 as test')
|
|
19
|
+
this.initialized = true
|
|
20
|
+
console.log('数据库连接池初始化成功')
|
|
21
|
+
|
|
22
|
+
// 设置进程退出时的清理函数
|
|
23
|
+
this.setupGracefulShutdown()
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('数据库连接池初始化失败:', error.message)
|
|
26
|
+
throw error
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 设置优雅关闭
|
|
31
|
+
setupGracefulShutdown() {
|
|
32
|
+
const shutdown = async (signal) => {
|
|
33
|
+
console.log(`接收到 ${signal} 信号,开始关闭数据库连接池...`)
|
|
34
|
+
try {
|
|
35
|
+
await serverDB.closePool()
|
|
36
|
+
console.log('数据库连接池关闭成功')
|
|
37
|
+
process.exit(0)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('关闭数据库连接池失败:', error.message)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 监听进程退出信号
|
|
45
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
46
|
+
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
47
|
+
process.on('SIGQUIT', () => shutdown('SIGQUIT'))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 检查连接池状态
|
|
51
|
+
async healthCheck() {
|
|
52
|
+
try {
|
|
53
|
+
const result = await serverDB.executeQuery('SELECT 1 as health_check')
|
|
54
|
+
return { status: 'healthy', result }
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return { status: 'unhealthy', error: error.message }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 导出单例实例
|
|
62
|
+
const dbPoolManager = new DatabasePoolManager()
|
|
63
|
+
|
|
64
|
+
module.exports = dbPoolManager
|
package/generation/tsconfig.json
CHANGED
package/jest.config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
testEnvironment: 'jsdom',
|
|
3
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
4
|
+
transform: {
|
|
5
|
+
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }]
|
|
6
|
+
},
|
|
7
|
+
testMatch: [
|
|
8
|
+
'**/__tests__/**/*.(ts|tsx|js)',
|
|
9
|
+
'**/*.(test|spec).(ts|tsx|js)'
|
|
10
|
+
],
|
|
11
|
+
collectCoverage: true,
|
|
12
|
+
collectCoverageFrom: [
|
|
13
|
+
'client/**/*.{js,jsx,ts,tsx}',
|
|
14
|
+
'pages/**/*.{js,jsx,ts,tsx}',
|
|
15
|
+
'server/**/*.{js,jsx,ts,tsx}',
|
|
16
|
+
'src/**/*.{js,jsx,ts,tsx}',
|
|
17
|
+
'!**/*.d.ts',
|
|
18
|
+
'!**/node_modules/**',
|
|
19
|
+
'!**/.next/**'
|
|
20
|
+
],
|
|
21
|
+
coverageDirectory: 'coverage',
|
|
22
|
+
coverageReporters: ['json', 'lcov', 'text', 'clover'],
|
|
23
|
+
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
|
24
|
+
globals: {
|
|
25
|
+
'ts-jest': {
|
|
26
|
+
useESM: true
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/lib/args.js
CHANGED
|
@@ -4,24 +4,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getProcessArgvs = void 0;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var result = {
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const getProcessArgvs = (removeItems = 2) => {
|
|
9
|
+
const args = process.argv.slice(removeItems);
|
|
10
|
+
const result = {
|
|
12
11
|
command: '', // dev, start, build, export, create, delete, init, help
|
|
13
12
|
dictionary: '', // export/init dictionary=${dictionary}
|
|
14
13
|
controller: '',
|
|
15
14
|
action: '' // create/delete controller=${controller} action=${action}
|
|
16
15
|
};
|
|
17
|
-
lodash_1.default.each(args,
|
|
16
|
+
lodash_1.default.each(args, (item, index) => {
|
|
18
17
|
if (item.indexOf('=') !== -1) {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const itemArr = item.split('=');
|
|
19
|
+
const key = itemArr[0].toLowerCase();
|
|
21
20
|
result[key] = itemArr[1];
|
|
22
21
|
}
|
|
23
22
|
else {
|
|
24
|
-
|
|
23
|
+
const { command } = result;
|
|
25
24
|
switch (index) {
|
|
26
25
|
case 0:
|
|
27
26
|
result.command = item;
|