nsgm-cli 2.1.22 → 2.1.23

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/lib/utils.js CHANGED
@@ -21,7 +21,7 @@ exports.firstUpperCase = firstUpperCase;
21
21
  const mkdirSync = (dirPath) => {
22
22
  if (mkdirFlag) {
23
23
  if (!fs_1.default.existsSync(dirPath)) {
24
- fs_1.default.mkdirSync(dirPath);
24
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
25
25
  }
26
26
  }
27
27
  };
@@ -1,3 +1,5 @@
1
+ const path = require('path');
2
+
1
3
  /**
2
4
  * @type {import('next-i18next').UserConfig}
3
5
  */
@@ -5,14 +7,14 @@ module.exports = {
5
7
  i18n: {
6
8
  defaultLocale: 'zh-CN',
7
9
  locales: ['zh-CN', 'en-US', 'ja-JP'],
8
- localeDetection: false, // 禁用自动语言检测
10
+ localeDetection: false,
9
11
  },
10
- localePath: './public/locales',
11
- /** To avoid issues when deploying to some platforms, we can configure the cache */
12
+ localePath: path.resolve(process.cwd(), 'public/locales'),
12
13
  saveMissing: false,
13
- strictMode: true,
14
- serializeConfig: false,
14
+ strictMode: false,
15
+ serializeConfig: true,
15
16
  react: {
16
17
  useSuspense: false,
17
18
  },
19
+ reloadOnPrerender: process.env.NODE_ENV === 'development',
18
20
  }
package/next.config.js CHANGED
@@ -4,7 +4,13 @@
4
4
  const { PHASE_DEVELOPMENT_SERVER, PHASE_EXPORT } = require('next/constants')
5
5
  const fs = require('fs')
6
6
  const path = require('path')
7
- const { i18n } = require('./next-i18next.config')
7
+
8
+ // 直接定义 i18n 配置,避免在 Vercel 上找不到配置文件
9
+ const i18n = {
10
+ defaultLocale: 'zh-CN',
11
+ locales: ['zh-CN', 'en-US', 'ja-JP'],
12
+ localeDetection: false,
13
+ }
8
14
 
9
15
  module.exports = (phase, defaultConfig, options) => {
10
16
  let projectConfig = null
@@ -16,8 +22,18 @@ module.exports = (phase, defaultConfig, options) => {
16
22
 
17
23
  if (fs.existsSync(curProjectConfigPath)) {
18
24
  projectConfig = require(curProjectConfigPath)
19
- } else {
25
+ } else if (fs.existsSync('./project.config.js')) {
20
26
  projectConfig = require('./project.config.js')
27
+ } else {
28
+ // 默认配置(用于 Vercel 等环境)
29
+ projectConfig = {
30
+ env: 'production',
31
+ version: '1.0.0',
32
+ prefix: '',
33
+ protocol: 'https',
34
+ host: 'localhost',
35
+ port: '443'
36
+ }
21
37
  }
22
38
 
23
39
  if (fs.existsSync(curPkgPath)) {
@@ -70,7 +86,7 @@ module.exports = (phase, defaultConfig, options) => {
70
86
  esmExternals: true, // 支持 ESM 外部依赖
71
87
  },
72
88
  compiler: {
73
- removeConsole: phase !== PHASE_DEVELOPMENT_SERVER,
89
+ removeConsole: phase !== PHASE_DEVELOPMENT_SERVER ? { exclude: ['warn', 'error'] } : false,
74
90
  styledComponents: true,
75
91
  },
76
92
  ...(phase === PHASE_DEVELOPMENT_SERVER && {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsgm-cli",
3
- "version": "2.1.22",
3
+ "version": "2.1.23",
4
4
  "description": "A CLI tool to run Next/Style-components and Graphql/Mysql fullstack project",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -90,6 +90,7 @@
90
90
  "commander": "14.0.0",
91
91
  "cors": "2.8.5",
92
92
  "critters": "0.0.25",
93
+ "dataloader": "2.2.3",
93
94
  "dayjs": "1.11.13",
94
95
  "dotenv": "17.2.1",
95
96
  "exceljs": "4.4.0",
package/pages/_app.tsx CHANGED
@@ -15,7 +15,6 @@ import SSRSafeAntdProvider from "@/components/SSRSafeAntdProvider";
15
15
  import { login } from "@/utils/sso";
16
16
  import { getAntdLocale } from "@/utils/i18n";
17
17
  import { navigateToLogin } from "@/utils/navigation";
18
- import nextI18NextConfig from "../next-i18next.config.js";
19
18
  import "antd/dist/reset.css";
20
19
 
21
20
  const theme = {
@@ -46,7 +45,10 @@ const App = ({ Component, pageProps }) => {
46
45
 
47
46
  useEffect(() => {
48
47
  setMounted(true);
49
- // 只在客户端设置当前语言
48
+ }, []);
49
+
50
+ useEffect(() => {
51
+ // 同步当前语言
50
52
  if (typeof window !== "undefined") {
51
53
  const locale = router.locale || "zh-CN";
52
54
  setCurrentLocale(locale);
@@ -133,4 +135,5 @@ const App = ({ Component, pageProps }) => {
133
135
  );
134
136
  };
135
137
 
136
- export default appWithTranslation(App, nextI18NextConfig);
138
+ // appWithTranslation 会自动从 next.config.js 读取 i18n 配置
139
+ export default appWithTranslation(App);
@@ -0,0 +1,117 @@
1
+ import type { NextApiRequest, NextApiResponse } from "next";
2
+ import bcrypt from "bcrypt";
3
+
4
+ type ResponseData = {
5
+ name: string;
6
+ returnCode: number;
7
+ userAttr?: {
8
+ displayName: string;
9
+ username?: string;
10
+ };
11
+ cookieValue?: string;
12
+ cookieExpire?: number;
13
+ message?: string;
14
+ };
15
+
16
+ const getLoginCredentials = () => {
17
+ const username = process.env.LOGIN_USERNAME || "admin";
18
+ const passwordHash = process.env.LOGIN_PASSWORD_HASH;
19
+
20
+ console.warn("[SSO] getLoginCredentials - username:", username);
21
+ console.warn(
22
+ "[SSO] getLoginCredentials - passwordHash from env:",
23
+ passwordHash ? `${passwordHash.substring(0, 20)}...` : "undefined"
24
+ );
25
+
26
+ if (!passwordHash) {
27
+ console.warn("[SSO] ⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希");
28
+ return {
29
+ username,
30
+ passwordHash: "$2b$10$K5O.TJLKGPmKGHJK8KzN5u8qUYKzq5vLcXlP7vGUzq5vLcXlP7vGUz",
31
+ };
32
+ }
33
+
34
+ console.warn("[SSO] getLoginCredentials - final passwordHash:", `${passwordHash.substring(0, 20)}...`);
35
+ return { username, passwordHash };
36
+ };
37
+
38
+ const validateCredentials = async (inputUsername: string, inputPassword: string): Promise<boolean> => {
39
+ const { username, passwordHash } = getLoginCredentials();
40
+
41
+ if (inputUsername !== username) {
42
+ return false;
43
+ }
44
+
45
+ try {
46
+ return await bcrypt.compare(inputPassword, passwordHash);
47
+ } catch (error) {
48
+ console.warn("[SSO] 密码验证失败:", error);
49
+ return false;
50
+ }
51
+ };
52
+
53
+ export default async function handler(req: NextApiRequest, res: NextApiResponse<ResponseData>) {
54
+ if (req.method !== "GET") {
55
+ res.status(405).json({
56
+ name: "ticketCheck",
57
+ returnCode: -1,
58
+ message: "Method not allowed",
59
+ });
60
+ return;
61
+ }
62
+
63
+ const { name } = req.query;
64
+
65
+ console.warn("[SSO] ticketCheck called, name:", name);
66
+
67
+ try {
68
+ if (!name || typeof name !== "string") {
69
+ throw new Error("Invalid name parameter");
70
+ }
71
+
72
+ // 使用 Buffer 解码 Base64 字符串,然后使用 decodeURIComponent 处理特殊字符
73
+ const decodedBase64 = Buffer.from(name, "base64").toString("utf-8");
74
+ console.warn("[SSO] Decoded Base64:", decodedBase64);
75
+
76
+ const decodedName = decodeURIComponent(decodedBase64);
77
+ console.warn("[SSO] Decoded name:", decodedName);
78
+
79
+ const [inputUsername, inputPassword] = decodedName.split(",");
80
+ console.warn("[SSO] Input username:", inputUsername, "password length:", inputPassword?.length);
81
+
82
+ const { username: expectedUsername, passwordHash } = getLoginCredentials();
83
+ console.warn("[SSO] Expected username:", expectedUsername, "passwordHash length:", passwordHash?.length);
84
+
85
+ const isValid = await validateCredentials(inputUsername, inputPassword);
86
+ console.warn("[SSO] Credentials valid:", isValid);
87
+
88
+ if (isValid) {
89
+ console.warn("[SSO] Login successful for user:", inputUsername);
90
+ res.status(200).json({
91
+ name: "ticketCheck",
92
+ returnCode: 0,
93
+ userAttr: {
94
+ displayName: "System Admin",
95
+ username: inputUsername,
96
+ },
97
+ cookieValue: `login_cookie_${Date.now()}`,
98
+ cookieExpire: 7200000, // 2小时
99
+ });
100
+ } else {
101
+ console.warn(`[SSO] 登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}`);
102
+
103
+ res.status(200).json({
104
+ name: "ticketCheck",
105
+ returnCode: -1,
106
+ message: "用户名或密码错误",
107
+ });
108
+ }
109
+ } catch (error) {
110
+ console.warn("[SSO] 登录验证出错:", error);
111
+ res.status(500).json({
112
+ name: "ticketCheck",
113
+ returnCode: -1,
114
+ message: "服务器内部错误",
115
+ });
116
+ }
117
+ }
package/pages/index.tsx CHANGED
@@ -159,12 +159,20 @@ const Page = () => {
159
159
  };
160
160
 
161
161
  export const getServerSideProps = async ({ locale }) => {
162
- // 确保 locale 有默认值,避免 serverSideTranslations 报错
163
162
  const currentLocale = locale || "zh-CN";
164
163
 
164
+ const path = require("path");
165
+ const i18nConfig = {
166
+ i18n: {
167
+ defaultLocale: "zh-CN",
168
+ locales: ["zh-CN", "en-US", "ja-JP"],
169
+ },
170
+ localePath: path.resolve(process.cwd(), "public/locales"),
171
+ };
172
+
165
173
  return {
166
174
  props: {
167
- ...(await serverSideTranslations(currentLocale, ["common", "home", "layout"])),
175
+ ...(await serverSideTranslations(currentLocale, ["common", "home", "layout"], i18nConfig)),
168
176
  },
169
177
  };
170
178
  };
package/pages/login.tsx CHANGED
@@ -6,6 +6,7 @@ import { useState, useEffect } from "react";
6
6
  import { Input, Button, Form, Typography, message } from "antd";
7
7
  import { UserOutlined, LockOutlined } from "@ant-design/icons";
8
8
  import { directLogin } from "../client/utils/sso";
9
+ import { getLocalApiPrefix } from "../client/utils/common";
9
10
  import { serverSideTranslations } from "next-i18next/serverSideTranslations";
10
11
  import { useTranslation } from "next-i18next";
11
12
  import { useRouter } from "next/router";
@@ -27,14 +28,18 @@ const renderArr: any = [];
27
28
  renderArr.push("NSGM");
28
29
 
29
30
  const Page = ({ html }) => {
30
- const { t } = useTranslation(["login"]);
31
+ const { t } = useTranslation("login");
31
32
  const router = useRouter();
32
33
  const [userName, setUserName] = useState("");
33
34
  const [userPassword, setUserPassword] = useState("");
34
35
  const [mounted, setMounted] = useState(false);
36
+ const [debugInfo, setDebugInfo] = useState("");
35
37
 
36
38
  useEffect(() => {
37
39
  setMounted(true);
40
+ // 显示调试信息
41
+ const apiPrefix = getLocalApiPrefix();
42
+ setDebugInfo(`API: ${apiPrefix}`);
38
43
  }, []);
39
44
 
40
45
  const createMarkup = () => {
@@ -47,11 +52,11 @@ const Page = ({ html }) => {
47
52
  if (!mounted || typeof window === "undefined") return;
48
53
 
49
54
  if (userName === "") {
50
- message.error(t("login:login.errors.usernameRequired"));
55
+ message.error(t("login.errors.usernameRequired"));
51
56
  return;
52
57
  }
53
58
  if (userPassword === "") {
54
- message.error(t("login:login.errors.passwordRequired"));
59
+ message.error(t("login.errors.passwordRequired"));
55
60
  return;
56
61
  }
57
62
 
@@ -65,6 +70,7 @@ const Page = ({ html }) => {
65
70
  // 检查是否是 Promise
66
71
  if (result && typeof (result as any).then === "function") {
67
72
  (result as Promise<any>).then((loginResult) => {
73
+ setDebugInfo(`Login result: ${JSON.stringify(loginResult)}`);
68
74
  if (!loginResult.success) {
69
75
  message.error(loginResult.message);
70
76
  }
@@ -72,8 +78,9 @@ const Page = ({ html }) => {
72
78
  } else {
73
79
  // 直接返回的结果
74
80
  const syncResult = result as { success: boolean; message?: string };
81
+ setDebugInfo(`Login result: ${JSON.stringify(syncResult)}`);
75
82
  if (!syncResult.success) {
76
- message.error(syncResult.message || t("login:login.errors.loginFailed"));
83
+ message.error(syncResult.message || t("login.errors.loginFailed"));
77
84
  }
78
85
  }
79
86
  };
@@ -91,15 +98,30 @@ const Page = ({ html }) => {
91
98
  <div style={{ position: "absolute", top: "20px", right: "20px" }}>
92
99
  <LanguageSwitcher />
93
100
  </div>
101
+ {debugInfo && (
102
+ <div
103
+ style={{
104
+ position: "absolute",
105
+ top: "60px",
106
+ right: "20px",
107
+ fontSize: "12px",
108
+ color: "#999",
109
+ maxWidth: "300px",
110
+ wordBreak: "break-all",
111
+ }}
112
+ >
113
+ {debugInfo}
114
+ </div>
115
+ )}
94
116
  <div dangerouslySetInnerHTML={createMarkup()} />
95
117
  <Typography.Title level={3} style={{ textAlign: "center", marginBottom: 24 }}>
96
- {t("login:login.title")}
118
+ {t("login.title")}
97
119
  </Typography.Title>
98
120
  <Form layout="vertical" style={{ width: "100%" }}>
99
121
  <Form.Item>
100
122
  <Input
101
123
  prefix={<UserOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
102
- placeholder={t("login:login.username")}
124
+ placeholder={t("login.username")}
103
125
  size="large"
104
126
  value={userName}
105
127
  onChange={doChangeName}
@@ -109,7 +131,7 @@ const Page = ({ html }) => {
109
131
  <Form.Item>
110
132
  <Input.Password
111
133
  prefix={<LockOutlined style={{ color: "rgba(0,0,0,.25)" }} />}
112
- placeholder={t("login:login.password")}
134
+ placeholder={t("login.password")}
113
135
  size="large"
114
136
  value={userPassword}
115
137
  onChange={doChangePassword}
@@ -118,7 +140,7 @@ const Page = ({ html }) => {
118
140
  </Form.Item>
119
141
  <Form.Item>
120
142
  <Button type="primary" onClick={doLogin} size="large" block>
121
- {t("login:login.loginButton")}
143
+ {t("login.loginButton")}
122
144
  </Button>
123
145
  </Form.Item>
124
146
  </Form>
@@ -127,7 +149,6 @@ const Page = ({ html }) => {
127
149
  };
128
150
 
129
151
  export const getServerSideProps = async ({ locale }) => {
130
- // 确保 locale 有默认值,避免 serverSideTranslations 报错
131
152
  const currentLocale = locale || "zh-CN";
132
153
 
133
154
  // 处理 markdown 内容
@@ -136,10 +157,19 @@ export const getServerSideProps = async ({ locale }) => {
136
157
  html += md.render(item);
137
158
  });
138
159
 
160
+ const path = require("path");
161
+ const i18nConfig = {
162
+ i18n: {
163
+ defaultLocale: "zh-CN",
164
+ locales: ["zh-CN", "en-US", "ja-JP"],
165
+ },
166
+ localePath: path.resolve(process.cwd(), "public/locales"),
167
+ };
168
+
139
169
  return {
140
170
  props: {
141
171
  html,
142
- ...(await serverSideTranslations(currentLocale, ["common", "layout", "login"])),
172
+ ...(await serverSideTranslations(currentLocale, ["common", "layout", "login"], i18nConfig)),
143
173
  },
144
174
  };
145
175
  };
@@ -412,7 +412,21 @@ export async function getServerSideProps(context) {
412
412
  });
413
413
 
414
414
  const { locale } = context;
415
- const translations = await serverSideTranslations(locale || "zh-CN", ["common", "template", "layout", "login"]);
415
+
416
+ const path = require("path");
417
+ const i18nConfig = {
418
+ i18n: {
419
+ defaultLocale: "zh-CN",
420
+ locales: ["zh-CN", "en-US", "ja-JP"],
421
+ },
422
+ localePath: path.resolve(process.cwd(), "public/locales"),
423
+ };
424
+
425
+ const translations = await serverSideTranslations(
426
+ locale || "zh-CN",
427
+ ["common", "template", "layout", "login"],
428
+ i18nConfig
429
+ );
416
430
 
417
431
  return {
418
432
  props: {
@@ -8,8 +8,12 @@ const getLoginCredentials = () => {
8
8
  const username = process.env.LOGIN_USERNAME || 'admin'
9
9
  let passwordHash = process.env.LOGIN_PASSWORD_HASH
10
10
 
11
+ console.warn('[SSO] getLoginCredentials - username:', username)
12
+ console.warn('[SSO] getLoginCredentials - passwordHash from env:', passwordHash ? `${passwordHash.substring(0, 20)}...` : 'undefined')
13
+
11
14
  // 如果环境变量被截断,尝试手动读取 .env 文件
12
15
  if (!passwordHash || passwordHash.length < 60) {
16
+ console.warn('[SSO] passwordHash too short or missing, trying to read from .env')
13
17
  try {
14
18
  const fs = require('fs')
15
19
  const path = require('path')
@@ -21,16 +25,17 @@ const getLoginCredentials = () => {
21
25
  if (line.startsWith('LOGIN_PASSWORD_HASH=')) {
22
26
  // 移除引号和前缀
23
27
  passwordHash = line.replace('LOGIN_PASSWORD_HASH=', '').replace(/"/g, '').trim()
28
+ console.warn('[SSO] Found passwordHash in .env:', `${passwordHash.substring(0, 20)}...`)
24
29
  break
25
30
  }
26
31
  }
27
32
  } catch (error) {
28
- console.error('读取 .env 文件失败:', error.message)
33
+ console.warn('[SSO] 读取 .env 文件失败:', error.message)
29
34
  }
30
35
  }
31
36
 
32
37
  if (!passwordHash) {
33
- console.warn('⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希')
38
+ console.warn('[SSO] ⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希')
34
39
  // 默认密码 "admin123" 的哈希值(仅用于开发环境)
35
40
  return {
36
41
  username,
@@ -38,6 +43,7 @@ const getLoginCredentials = () => {
38
43
  }
39
44
  }
40
45
 
46
+ console.warn('[SSO] getLoginCredentials - final passwordHash:', `${passwordHash.substring(0, 20)}...`)
41
47
  return { username, passwordHash }
42
48
  }
43
49
 
@@ -75,15 +81,27 @@ router.get('/ticketCheck', async (req, res) => {
75
81
  const { query } = req
76
82
  const { name } = query
77
83
 
84
+ console.warn('[SSO] ticketCheck called, name:', name)
85
+
78
86
  try {
79
87
  // 使用 Buffer 解码 Base64 字符串,然后使用 decodeURIComponent 处理特殊字符
80
88
  const decodedBase64 = Buffer.from(name, 'base64').toString('utf-8')
89
+ console.warn('[SSO] Decoded Base64:', decodedBase64)
90
+
81
91
  const decodedName = decodeURIComponent(decodedBase64)
92
+ console.warn('[SSO] Decoded name:', decodedName)
93
+
82
94
  const [inputUsername, inputPassword] = decodedName.split(',')
95
+ console.warn('[SSO] Input username:', inputUsername, 'password length:', inputPassword?.length)
96
+
97
+ const { username: expectedUsername, passwordHash } = getLoginCredentials()
98
+ console.warn('[SSO] Expected username:', expectedUsername, 'passwordHash length:', passwordHash?.length)
83
99
 
84
100
  const isValid = await validateCredentials(inputUsername, inputPassword)
101
+ console.warn('[SSO] Credentials valid:', isValid)
85
102
 
86
103
  if (isValid) {
104
+ console.warn('[SSO] Login successful for user:', inputUsername)
87
105
  res.json({
88
106
  name: 'ticketCheck',
89
107
  query,
@@ -97,7 +115,7 @@ router.get('/ticketCheck', async (req, res) => {
97
115
  })
98
116
  } else {
99
117
  // 记录失败的登录尝试(用于安全审计)
100
- console.log(`登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}, IP: ${req.ip}`)
118
+ console.warn(`[SSO] 登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}, IP: ${req.ip}`)
101
119
 
102
120
  res.json({
103
121
  name: 'ticketCheck',
@@ -107,7 +125,7 @@ router.get('/ticketCheck', async (req, res) => {
107
125
  })
108
126
  }
109
127
  } catch (error) {
110
- console.error('登录验证出错:', error)
128
+ console.warn('[SSO] 登录验证出错:', error)
111
129
  res.status(500).json({
112
130
  name: 'ticketCheck',
113
131
  returnCode: -1,