nsgm-cli 2.1.41 → 2.1.43
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/README.md +6 -6
- package/client/components/ClientProviders.tsx +3 -3
- package/client/components/LanguageSwitcher.tsx +3 -3
- package/client/layout/index.tsx +2 -2
- package/client/styled/template/manage.ts +1 -1
- package/generation/README.md +11 -11
- package/generation/package.json +25 -20
- package/lib/generate.js +3 -3
- package/lib/generators/dataloader-generator.d.ts +8 -0
- package/lib/generators/dataloader-generator.js +63 -11
- package/lib/generators/file-generator.js +4 -4
- package/lib/index.js +0 -0
- package/lib/server/csrf.d.ts +2 -2
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +55 -58
- package/pages/_app.tsx +11 -4
- package/pages/_document.tsx +65 -0
- package/scripts/performance-check.sh +0 -0
- package/scripts/shutdown.sh +0 -0
- package/scripts/startup.sh +0 -0
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ cp .env.example .env
|
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
115
|
# Generate secure password hash
|
|
116
|
-
|
|
116
|
+
pnpm run generate-password yourNewPassword
|
|
117
117
|
|
|
118
118
|
# Edit .env file with generated hash
|
|
119
119
|
nano .env
|
|
@@ -123,10 +123,10 @@ nano .env
|
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
125
|
# Install dependencies
|
|
126
|
-
|
|
126
|
+
pnpm install
|
|
127
127
|
|
|
128
128
|
# Start development server
|
|
129
|
-
|
|
129
|
+
pnpm run dev
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
Your application will be available at `http://localhost:3000` with:
|
|
@@ -173,9 +173,9 @@ nsgm upgrade # Upgrade project base files
|
|
|
173
173
|
nsgm export # Export static pages
|
|
174
174
|
|
|
175
175
|
# Development tools
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
pnpm run lint # Code linting
|
|
177
|
+
pnpm run test # Run tests
|
|
178
|
+
pnpm run test:coverage # Test coverage report
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
## 🎨 Generated Controller Features
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, ReactNode, FC } from "react";
|
|
2
2
|
import { ThemeProvider } from "styled-components";
|
|
3
3
|
import { GlobalStyle } from "@/styled/common";
|
|
4
4
|
|
|
5
5
|
interface ClientProvidersProps {
|
|
6
|
-
children:
|
|
6
|
+
children: ReactNode;
|
|
7
7
|
theme: any;
|
|
8
8
|
whiteColor?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const ClientProviders:
|
|
11
|
+
const ClientProviders: FC<ClientProvidersProps> = ({ children, theme, whiteColor = true }) => {
|
|
12
12
|
const [isClient, setIsClient] = useState(false);
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, CSSProperties, FC } from "react";
|
|
2
2
|
import { Select } from "antd";
|
|
3
3
|
import { useRouter } from "next/router";
|
|
4
4
|
import { GlobalOutlined } from "@ant-design/icons";
|
|
@@ -6,11 +6,11 @@ import { GlobalOutlined } from "@ant-design/icons";
|
|
|
6
6
|
const { Option } = Select;
|
|
7
7
|
|
|
8
8
|
interface LanguageSwitcherProps {
|
|
9
|
-
style?:
|
|
9
|
+
style?: CSSProperties;
|
|
10
10
|
size?: "small" | "middle" | "large";
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const LanguageSwitcher:
|
|
13
|
+
const LanguageSwitcher: FC<LanguageSwitcherProps> = ({ style, size = "middle" }) => {
|
|
14
14
|
const router = useRouter();
|
|
15
15
|
const [mounted, setMounted] = useState(false);
|
|
16
16
|
const [currentLocale, setCurrentLocale] = useState("zh-CN");
|
package/client/layout/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, ReactNode } from "react";
|
|
2
2
|
import { Layout, Menu, Dropdown, Space } from "antd";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
@@ -28,7 +28,7 @@ interface MenuItem {
|
|
|
28
28
|
key: string;
|
|
29
29
|
text: string;
|
|
30
30
|
url: string;
|
|
31
|
-
icon?:
|
|
31
|
+
icon?: ReactNode;
|
|
32
32
|
subMenus?: SubMenuItem[];
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -171,7 +171,7 @@ export const StyledInput = styled(Input)`
|
|
|
171
171
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
|
|
172
172
|
`;
|
|
173
173
|
|
|
174
|
-
export const StyledTable = styled(Table)`
|
|
174
|
+
export const StyledTable: any = styled(Table)`
|
|
175
175
|
margin-top: 16px;
|
|
176
176
|
border-radius: 12px;
|
|
177
177
|
overflow: hidden;
|
package/generation/README.md
CHANGED
|
@@ -17,27 +17,27 @@
|
|
|
17
17
|
|
|
18
18
|
| 命令 | 说明 |
|
|
19
19
|
| ---------------- | ------------ |
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
20
|
+
| `pnpm run dev` | 开发模式 |
|
|
21
|
+
| `pnpm run start` | 生产模式 |
|
|
22
|
+
| `pnpm run build` | 编译项目 |
|
|
23
|
+
| `pnpm run export` | 导出静态页面 |
|
|
24
24
|
|
|
25
25
|
### 测试命令
|
|
26
26
|
|
|
27
27
|
| 命令 | 说明 |
|
|
28
28
|
| ----------------------- | ---------------- |
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
29
|
+
| `pnpm test` | 运行所有测试 |
|
|
30
|
+
| `pnpm run test:watch` | 监视模式运行测试 |
|
|
31
|
+
| `pnpm run test:coverage` | 生成覆盖率报告 |
|
|
32
32
|
|
|
33
33
|
### 代码生成命令
|
|
34
34
|
|
|
35
35
|
| 命令 | 说明 |
|
|
36
36
|
| ----------------------- | --------------------- |
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
37
|
+
| `pnpm run create` | 创建模板页面 |
|
|
38
|
+
| `pnpm run delete` | 删除模板页面 |
|
|
39
|
+
| `pnpm run create-config` | 从配置文件批量创建模块 |
|
|
40
|
+
| `pnpm run delete-config` | 从配置文件批量删除模块 |
|
|
41
41
|
|
|
42
42
|
### 项目维护命令
|
|
43
43
|
|
package/generation/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nsgm-cli-project",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"packageManager": "pnpm@10.28.0",
|
|
4
5
|
"description": "",
|
|
5
6
|
"main": "app.js",
|
|
6
7
|
"scripts": {
|
|
@@ -27,24 +28,28 @@
|
|
|
27
28
|
"nsgm-cli": "^2"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^
|
|
31
|
-
"@types/react": "^18",
|
|
32
|
-
"@types/
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"eslint-plugin
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
31
|
+
"@types/node": "^24.1.0",
|
|
32
|
+
"@types/react": "^18.3.23",
|
|
33
|
+
"@types/react-dom": "^18.3.7",
|
|
34
|
+
"@types/lodash": "^4.17.20",
|
|
35
|
+
"@types/express-serve-static-core": "^5.1.1",
|
|
36
|
+
"@types/qs": "^6.14.0",
|
|
37
|
+
"@types/hoist-non-react-statics": "^3.3.7",
|
|
38
|
+
"typescript": "^5.8.3",
|
|
39
|
+
"jest": "^30.0.5",
|
|
40
|
+
"jest-environment-jsdom": "^30.0.5",
|
|
41
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
42
|
+
"@testing-library/react": "^16.3.0",
|
|
43
|
+
"@testing-library/user-event": "^14.6.1",
|
|
44
|
+
"@types/jest": "^30.0.5",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
46
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
47
|
+
"eslint": "^9.31.0",
|
|
48
|
+
"eslint-config-prettier": "^10.1.8",
|
|
49
|
+
"eslint-plugin-prettier": "^5.5.3",
|
|
50
|
+
"prettier": "^3.6.2",
|
|
51
|
+
"next-i18next": "^15.3.0",
|
|
52
|
+
"react-i18next": "^15.2.0",
|
|
53
|
+
"i18next": "^24.1.0"
|
|
49
54
|
}
|
|
50
|
-
}
|
|
55
|
+
}
|
package/lib/generate.js
CHANGED
|
@@ -13,7 +13,7 @@ const generate_init_1 = require("./generate_init");
|
|
|
13
13
|
const generate_create_1 = require("./generate_create");
|
|
14
14
|
const generate_delete_1 = require("./generate_delete");
|
|
15
15
|
// 常量提取
|
|
16
|
-
const
|
|
16
|
+
const PNPM_INSTALL_FLAGS = "--legacy-peer-deps";
|
|
17
17
|
// 辅助函数
|
|
18
18
|
const normalizeDirectory = (dictionary) => {
|
|
19
19
|
// 禁止绝对路径,强制所有生成目录都在 cwd 下
|
|
@@ -26,11 +26,11 @@ const installNpmPackages = (targetDir) => {
|
|
|
26
26
|
try {
|
|
27
27
|
const prefix = targetDir ? `cd ${targetDir} && ` : "";
|
|
28
28
|
console.log("Installing all dependencies from package.json...");
|
|
29
|
-
const installResult = shelljs_1.default.exec(`${prefix}
|
|
29
|
+
const installResult = shelljs_1.default.exec(`${prefix}pnpm install ${PNPM_INSTALL_FLAGS}`);
|
|
30
30
|
return installResult.code === 0;
|
|
31
31
|
}
|
|
32
32
|
catch (error) {
|
|
33
|
-
console.error("Failed to install
|
|
33
|
+
console.error("Failed to install pnpm packages:", error);
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
36
|
};
|
|
@@ -4,6 +4,14 @@ import { BaseGenerator } from "./base-generator";
|
|
|
4
4
|
* 自动生成对应的 DataLoader JavaScript 文件
|
|
5
5
|
*/
|
|
6
6
|
export declare class DataLoaderGenerator extends BaseGenerator {
|
|
7
|
+
/**
|
|
8
|
+
* 可能的 JSON 字段名(需要自动解析)
|
|
9
|
+
*/
|
|
10
|
+
private jsonFieldNames;
|
|
11
|
+
/**
|
|
12
|
+
* 获取可能是 JSON 的字段
|
|
13
|
+
*/
|
|
14
|
+
private getJsonFields;
|
|
7
15
|
generate(): string;
|
|
8
16
|
/**
|
|
9
17
|
* 生成外键 DataLoader
|
|
@@ -7,13 +7,66 @@ const base_generator_1 = require("./base-generator");
|
|
|
7
7
|
* 自动生成对应的 DataLoader JavaScript 文件
|
|
8
8
|
*/
|
|
9
9
|
class DataLoaderGenerator extends base_generator_1.BaseGenerator {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
/**
|
|
13
|
+
* 可能的 JSON 字段名(需要自动解析)
|
|
14
|
+
*/
|
|
15
|
+
this.jsonFieldNames = [
|
|
16
|
+
"images",
|
|
17
|
+
"photos",
|
|
18
|
+
"gallery",
|
|
19
|
+
"metadata",
|
|
20
|
+
"attributes",
|
|
21
|
+
"specs",
|
|
22
|
+
"options",
|
|
23
|
+
"settings",
|
|
24
|
+
"config",
|
|
25
|
+
"extra",
|
|
26
|
+
"data",
|
|
27
|
+
"json",
|
|
28
|
+
"params",
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 获取可能是 JSON 的字段
|
|
33
|
+
*/
|
|
34
|
+
getJsonFields() {
|
|
35
|
+
return this.fields
|
|
36
|
+
.filter((f) => this.jsonFieldNames.some((jsonName) => f.name.toLowerCase().includes(jsonName)))
|
|
37
|
+
.map((f) => f.name);
|
|
38
|
+
}
|
|
10
39
|
generate() {
|
|
11
40
|
const capitalizedController = this.getCapitalizedController();
|
|
12
41
|
const selectFields = this.fields.map((f) => f.name).join(", ");
|
|
42
|
+
const jsonFields = this.getJsonFields();
|
|
43
|
+
const hasJsonFields = jsonFields.length > 0;
|
|
13
44
|
return `const DataLoader = require('dataloader');
|
|
14
45
|
const { executeQuery } = require('../utils/common');
|
|
15
46
|
|
|
16
|
-
|
|
47
|
+
${hasJsonFields
|
|
48
|
+
? `/**
|
|
49
|
+
* 处理 ${this.controller} 行数据,将 JSON 字符串解析为对象
|
|
50
|
+
*/
|
|
51
|
+
function process${capitalizedController}Row(row) {
|
|
52
|
+
if (!row) return row;
|
|
53
|
+
|
|
54
|
+
${jsonFields
|
|
55
|
+
.map((field) => ` // 处理 ${field} 字段(JSON 字符串转对象)
|
|
56
|
+
if (row.${field} && typeof row.${field} === 'string') {
|
|
57
|
+
try {
|
|
58
|
+
row.${field} = JSON.parse(row.${field});
|
|
59
|
+
} catch (e) {
|
|
60
|
+
row.${field} = ${field === "images" || field === "photos" || field === "gallery" ? "[]" : "{}"};
|
|
61
|
+
}
|
|
62
|
+
}`)
|
|
63
|
+
.join(",\n ")}
|
|
64
|
+
|
|
65
|
+
return row;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
`
|
|
69
|
+
: ""}/**
|
|
17
70
|
* ${capitalizedController} DataLoader
|
|
18
71
|
* 针对 ${this.controller} 表的批量数据加载器,解决 N+1 查询问题
|
|
19
72
|
*/
|
|
@@ -31,9 +84,7 @@ class ${capitalizedController}DataLoader {
|
|
|
31
84
|
const results = await executeQuery(sql, [...ids]);
|
|
32
85
|
|
|
33
86
|
// 确保返回顺序与输入 keys 一致,未找到的返回 null
|
|
34
|
-
return ids.map(id =>
|
|
35
|
-
results.find((row) => row.id === id) || null
|
|
36
|
-
);
|
|
87
|
+
return ids.map(id => ${hasJsonFields ? `process${capitalizedController}Row(results.find((row) => row.id === id) || null)` : `results.find((row) => row.id === id) || null`});
|
|
37
88
|
} catch (error) {
|
|
38
89
|
console.error('DataLoader byId 批量加载失败:', error);
|
|
39
90
|
throw error;
|
|
@@ -58,9 +109,7 @@ class ${capitalizedController}DataLoader {
|
|
|
58
109
|
const results = await executeQuery(sql, [...names]);
|
|
59
110
|
|
|
60
111
|
// 确保返回顺序与输入 keys 一致
|
|
61
|
-
return names.map(name =>
|
|
62
|
-
results.find((row) => row.name === name) || null
|
|
63
|
-
);
|
|
112
|
+
return names.map(name => ${hasJsonFields ? `process${capitalizedController}Row(results.find((row) => row.name === name) || null)` : `results.find((row) => row.name === name) || null`});
|
|
64
113
|
} catch (error) {
|
|
65
114
|
console.error('DataLoader byName 批量加载失败:', error);
|
|
66
115
|
throw error;
|
|
@@ -83,7 +132,8 @@ class ${capitalizedController}DataLoader {
|
|
|
83
132
|
const results = await Promise.all(
|
|
84
133
|
searchTerms.map(async (term) => {
|
|
85
134
|
const sql = 'SELECT ${selectFields} FROM ${this.controller} WHERE name LIKE ?';
|
|
86
|
-
|
|
135
|
+
const rows = await executeQuery(sql, [\`%\${term}%\`]);
|
|
136
|
+
${hasJsonFields ? `return rows.map(process${capitalizedController}Row);` : "return rows;"}
|
|
87
137
|
})
|
|
88
138
|
);
|
|
89
139
|
|
|
@@ -100,7 +150,7 @@ class ${capitalizedController}DataLoader {
|
|
|
100
150
|
}
|
|
101
151
|
);
|
|
102
152
|
|
|
103
|
-
${this.generateForeignKeyLoaders()}
|
|
153
|
+
${this.generateForeignKeyLoaders(hasJsonFields, capitalizedController)}
|
|
104
154
|
}
|
|
105
155
|
|
|
106
156
|
/**
|
|
@@ -170,7 +220,7 @@ module.exports = { ${capitalizedController}DataLoader, create${capitalizedContro
|
|
|
170
220
|
/**
|
|
171
221
|
* 生成外键 DataLoader
|
|
172
222
|
*/
|
|
173
|
-
generateForeignKeyLoaders() {
|
|
223
|
+
generateForeignKeyLoaders(hasJsonFields, capitalizedController) {
|
|
174
224
|
const foreignKeys = this.fields.filter((f) => f.name.endsWith("_id") && f.name !== "id");
|
|
175
225
|
if (foreignKeys.length === 0) {
|
|
176
226
|
return "";
|
|
@@ -193,7 +243,9 @@ module.exports = { ${capitalizedController}DataLoader, create${capitalizedContro
|
|
|
193
243
|
|
|
194
244
|
// 按外键分组
|
|
195
245
|
return ${fk.name}s.map(${fk.name} =>
|
|
196
|
-
results
|
|
246
|
+
results
|
|
247
|
+
.filter((row) => row.${fk.name} === ${fk.name})
|
|
248
|
+
${hasJsonFields ? `.map(process${capitalizedController}Row)` : ""}
|
|
197
249
|
);
|
|
198
250
|
} catch (error) {
|
|
199
251
|
console.error('DataLoader by${capitalizedRelated}Id 批量加载失败:', error);
|
|
@@ -180,7 +180,7 @@ export const ModalContainer = styled.div\`
|
|
|
180
180
|
}
|
|
181
181
|
\`
|
|
182
182
|
|
|
183
|
-
export const StyledButton = styled(Button)
|
|
183
|
+
export const StyledButton: any = styled(Button)\`
|
|
184
184
|
display: flex;
|
|
185
185
|
align-items: center;
|
|
186
186
|
border-radius: 6px;
|
|
@@ -216,13 +216,13 @@ export const StyledButton = styled(Button)<{ $primary?: boolean; $export?: boole
|
|
|
216
216
|
\`}
|
|
217
217
|
\`
|
|
218
218
|
|
|
219
|
-
export const StyledInput = styled(Input)\`
|
|
219
|
+
export const StyledInput: any = styled(Input)\`
|
|
220
220
|
width: 200px;
|
|
221
221
|
border-radius: 6px;
|
|
222
222
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
|
|
223
223
|
\`
|
|
224
224
|
|
|
225
|
-
export const StyledTable = styled(Table)\`
|
|
225
|
+
export const StyledTable: any = styled(Table)\`
|
|
226
226
|
margin-top: 16px;
|
|
227
227
|
border-radius: 8px;
|
|
228
228
|
overflow: hidden;
|
|
@@ -254,7 +254,7 @@ export const IconWrapper = styled.i\`
|
|
|
254
254
|
margin-right: 5px;
|
|
255
255
|
\`
|
|
256
256
|
|
|
257
|
-
export const RoundedButton = styled(Button)\`
|
|
257
|
+
export const RoundedButton: any = styled(Button)\`
|
|
258
258
|
border-radius: 4px;
|
|
259
259
|
\`
|
|
260
260
|
|
package/lib/index.js
CHANGED
|
File without changes
|
package/lib/server/csrf.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ declare module "express-serve-static-core" {
|
|
|
12
12
|
export declare const csrfProtection: (req: Request, res: Response, next: NextFunction) => unknown;
|
|
13
13
|
export declare const getCSRFToken: (req: Request, res: Response) => void;
|
|
14
14
|
export declare const securityMiddleware: {
|
|
15
|
-
basicHeaders:
|
|
15
|
+
basicHeaders: any;
|
|
16
16
|
};
|
|
17
|
-
export declare const createCSPMiddleware: () =>
|
|
17
|
+
export declare const createCSPMiddleware: () => any;
|