@mindbase/express-common 1.0.0 → 1.0.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.
Files changed (48) hide show
  1. package/dist/index.d.mts +375 -0
  2. package/dist/index.mjs +2635 -0
  3. package/dist/index.mjs.map +1 -0
  4. package/package.json +24 -6
  5. package/bin/mindbase.ts +0 -52
  6. package/commands/precache.ts +0 -54
  7. package/core/app.ts +0 -200
  8. package/core/module/CreateModule.ts +0 -38
  9. package/core/module/FindPackageRoot.ts +0 -58
  10. package/core/module/GetModulePath.ts +0 -58
  11. package/core/state.ts +0 -72
  12. package/feature/cron/CronManager.ts +0 -63
  13. package/feature/scanner/FileScanner.ts +0 -288
  14. package/index.ts +0 -10
  15. package/middleware/Cors.ts +0 -17
  16. package/middleware/IpParser.ts +0 -81
  17. package/middleware/UaParser.ts +0 -50
  18. package/routes/Doc.route.ts +0 -118
  19. package/tests/Cors.test.ts +0 -34
  20. package/tests/Dayjs.test.ts +0 -24
  21. package/tests/FileScanner.test.ts +0 -85
  22. package/tests/GetModulePath.test.ts +0 -32
  23. package/tests/IpParser.test.ts +0 -72
  24. package/tests/Logger.test.ts +0 -68
  25. package/tests/UaParser.test.ts +0 -41
  26. package/tsconfig.json +0 -9
  27. package/types/DocTypes.ts +0 -111
  28. package/types/index.ts +0 -19
  29. package/utils/ComponentRegistry.ts +0 -34
  30. package/utils/DatabaseMigration.ts +0 -121
  31. package/utils/Dayjs.ts +0 -16
  32. package/utils/DocManager.ts +0 -274
  33. package/utils/HttpServer.ts +0 -41
  34. package/utils/InitDatabase.ts +0 -149
  35. package/utils/InitErrorHandler.ts +0 -71
  36. package/utils/InitExpress.ts +0 -35
  37. package/utils/Logger.ts +0 -206
  38. package/utils/MiddlewareRegistry.ts +0 -14
  39. package/utils/ProjectInitializer.ts +0 -283
  40. package/utils/RouteParser.ts +0 -408
  41. package/utils/RouteRegistry.ts +0 -66
  42. package/utils/SchemaMigrate.ts +0 -73
  43. package/utils/SchemaSync.ts +0 -47
  44. package/utils/TSTypeParser.ts +0 -455
  45. package/utils/Validate.ts +0 -25
  46. package/utils/ZodSchemaParser.ts +0 -420
  47. package/vitest.config.ts +0 -18
  48. package/zod/Doc.schema.ts +0 -9
package/utils/Logger.ts DELETED
@@ -1,206 +0,0 @@
1
- import dayjs from "./Dayjs";
2
-
3
- export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
4
-
5
- const LOG_LEVELS = {
6
- debug: 0,
7
- info: 1,
8
- warn: 2,
9
- error: 3,
10
- silent: 4,
11
- } as const;
12
-
13
- let currentLogLevel: LogLevel = "info";
14
-
15
- const COLORS = {
16
- debug: "\x1b[36m",
17
- info: "\x1b[32m",
18
- warn: "\x1b[33m",
19
- error: "\x1b[31m",
20
- reset: "\x1b[0m",
21
- } as const;
22
-
23
- /**
24
- * 计算字符串在终端中的视觉宽度
25
- * 修复:区分单宽符号 (如 ✓) 和双宽符号 (如 中文、Emoji)
26
- */
27
- function getVisualWidth(str: string): number {
28
- let width = 0;
29
- const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
30
- for (const { segment } of segmenter.segment(str)) {
31
- const charCode = segment.charCodeAt(0);
32
-
33
- // 1. 基础 ASCII
34
- if (charCode <= 255) {
35
- width += 1;
36
- continue;
37
- }
38
-
39
- // 2. CJK 字符集范围 (常用中文、标点、全角符号)
40
- const isCJK =
41
- (charCode >= 0x4e00 && charCode <= 0x9fff) || // CJK Unified Ideographs
42
- (charCode >= 0x3000 && charCode <= 0x303f) || // CJK Symbols and Punctuation
43
- (charCode >= 0xff00 && charCode <= 0xffef); // Fullwidth Forms
44
-
45
- // 3. Emoji 或代理对 (通常 segment.length > 1)
46
- const isMultiByte = segment.length > 1;
47
-
48
- if (isCJK || isMultiByte) {
49
- width += 2;
50
- } else {
51
- // 4. 其他特殊符号 (如 ✓, ★, ☎) 在大多数终端占 1 格
52
- width += 1;
53
- }
54
- }
55
- return width;
56
- }
57
-
58
- /**
59
- * 截断字符串以适应视觉宽度,从头部截断并保留末尾
60
- */
61
- function truncateToWidth(str: string, maxWidth: number): string {
62
- const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
63
- const segments = Array.from(segmenter.segment(str)).map((s) => s.segment);
64
-
65
- if (getVisualWidth(str) <= maxWidth) return str;
66
-
67
- let currentWidth = 3; // 为 "..." 预留
68
- let result = "";
69
-
70
- // 从后往前遍历 segments
71
- for (let i = segments.length - 1; i >= 0; i--) {
72
- const segment = segments[i];
73
- const segmentWidth = getVisualWidth(segment);
74
- if (currentWidth + segmentWidth > maxWidth) break;
75
- result = segment + result;
76
- currentWidth += segmentWidth;
77
- }
78
- return "..." + result;
79
- }
80
-
81
- function formatMessage(level: LogLevel, message: string): string {
82
- const timestamp = dayjs().format("YYYY-MM-DD HH:mm:ss");
83
- const terminalWidth = process.stdout.columns || 80;
84
- const timeStr = ` ${timestamp}`;
85
- const timeWidth = getVisualWidth(timeStr);
86
- const maxMessageWidth = terminalWidth - timeWidth - 2;
87
-
88
- let displayMessage = message;
89
- const messageWidth = getVisualWidth(displayMessage);
90
-
91
- if (messageWidth > maxMessageWidth) {
92
- displayMessage = truncateToWidth(displayMessage, maxMessageWidth);
93
- }
94
-
95
- const currentMsgWidth = getVisualWidth(displayMessage);
96
- const paddingCount = Math.max(0, terminalWidth - currentMsgWidth - timeWidth);
97
- const padding = " ".repeat(paddingCount);
98
-
99
- return `${COLORS[level]}${displayMessage}${padding}${timeStr}${COLORS.reset}`;
100
- }
101
-
102
- const startupTime = Date.now();
103
-
104
- // 标签填充函数(目标显示宽度 8,空格分散到字符间)
105
- function padTag(tag: string, targetWidth = 8): string {
106
- const currentWidth = getVisualWidth(tag);
107
- const padding = Math.max(0, targetWidth - currentWidth);
108
-
109
- if (padding === 0) return tag;
110
-
111
- // 将空格均匀分散到字符之间
112
- const chars = [...tag]; // 按码点分割
113
- const gapCount = chars.length + 1; // 间隙数 = 字符数 + 1(前后和中间)
114
-
115
- // 计算每个间隙的空格数
116
- const baseSpaces = Math.floor(padding / gapCount);
117
- const extraSpaces = padding % gapCount;
118
-
119
- let result = "";
120
- for (let i = 0; i < chars.length; i++) {
121
- // 每个间隙的空格 = 基础空格 + 额外空格(前 extraSpaces 个间隙多1个)
122
- const spaces = baseSpaces + (i < extraSpaces ? 1 : 0);
123
- result += " ".repeat(spaces) + chars[i];
124
- }
125
- // 最后一个间隙
126
- result += " ".repeat(baseSpaces + (chars.length < extraSpaces ? 1 : 0));
127
-
128
- return result;
129
- }
130
-
131
- function startupMessage(message: string, tag?: string): string {
132
- const diff = Date.now() - startupTime;
133
- const timeStr = diff.toString().padStart(6, "0");
134
- const tagStr = tag ? `【${padTag(tag)}】` : "";
135
- return `${COLORS["warn"]}[${timeStr}] ${tagStr}${message}${COLORS["reset"]}`;
136
- }
137
-
138
- function shouldLog(level: LogLevel): boolean {
139
- return LOG_LEVELS[level] >= LOG_LEVELS[currentLogLevel];
140
- }
141
-
142
- function formatArgs(args: any[]): string {
143
- return args
144
- .map((arg) => {
145
- if (arg instanceof Error) {
146
- return arg.message;
147
- }
148
- if (typeof arg === "object") {
149
- return JSON.stringify(arg, null, 2);
150
- }
151
- return String(arg);
152
- })
153
- .join(" ");
154
- }
155
-
156
- export const logger = {
157
- debug(...args: any[]): void {
158
- if (shouldLog("debug")) {
159
- console.log(formatMessage("debug", formatArgs(args)));
160
- }
161
- },
162
-
163
- info(...args: any[]): void {
164
- if (shouldLog("info")) {
165
- console.log(formatMessage("info", formatArgs(args)));
166
- }
167
- },
168
-
169
- warn(...args: any[]): void {
170
- if (shouldLog("warn")) {
171
- console.warn(formatMessage("warn", formatArgs(args)));
172
- }
173
- },
174
-
175
- error(...args: any[]): void {
176
- if (shouldLog("error")) {
177
- // 先打印带颜色的格式化消息
178
- console.error(formatMessage("error", formatArgs(args)));
179
- // 如果有 Error 对象,用 console.error 单独打印以显示堆栈
180
- const errorArg = args.find((arg) => arg instanceof Error);
181
- if (errorArg) {
182
- console.error(errorArg);
183
- }
184
- }
185
- },
186
-
187
- startup(tagOrMessage: string, ...args: any[]): void {
188
- // 如果有额外参数,第一个是标签,其余是消息
189
- // 否则第一个参数是消息,无标签
190
- if (args.length > 0) {
191
- console.log(startupMessage(formatArgs(args), tagOrMessage));
192
- } else {
193
- console.log(startupMessage(tagOrMessage));
194
- }
195
- },
196
-
197
- setLevel(level: LogLevel): void {
198
- currentLogLevel = level;
199
- },
200
-
201
- getLevel(): LogLevel {
202
- return currentLogLevel;
203
- },
204
- };
205
-
206
- export default logger;
@@ -1,14 +0,0 @@
1
- import { Express } from "express";
2
- import { ScanResult } from "../types/Index";
3
- import logger from "./Logger";
4
-
5
- export async function registerMiddleware(app: Express, config: ScanResult): Promise<void> {
6
- const { fileName = "anonymous", defaultExport: handler } = config;
7
-
8
- try {
9
- app.use(handler);
10
- logger.startup("中间件", `注册: ${fileName}`);
11
- } catch (error) {
12
- logger.error(`注册中间件失败 ${fileName}:`, error);
13
- }
14
- }
@@ -1,283 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import prompts from "prompts";
4
- import logger from "./Logger";
5
-
6
- export interface ProjectConfig {
7
- name: string;
8
- description: string;
9
- author: string;
10
- port: number;
11
- dbPath: string;
12
- staticPath: string;
13
- apiPrefix: string;
14
- userAgent: boolean;
15
- ip: boolean;
16
- cors: boolean;
17
- }
18
-
19
- /**
20
- * 收集项目初始化配置
21
- */
22
- export async function collectConfig(cwd: string): Promise<ProjectConfig> {
23
- const folderName = path.basename(cwd);
24
-
25
- const response = await prompts([
26
- {
27
- type: "text",
28
- name: "name",
29
- message: "项目名称",
30
- initial: folderName,
31
- validate: (value: string) => value.length > 0 || "项目名称不能为空",
32
- },
33
- {
34
- type: "text",
35
- name: "description",
36
- message: "项目描述",
37
- initial: "MindBase 驱动的应用项目",
38
- },
39
- {
40
- type: "text",
41
- name: "author",
42
- message: "作者",
43
- initial: "",
44
- },
45
- {
46
- type: "number",
47
- name: "port",
48
- message: "服务端口",
49
- initial: 3000,
50
- validate: (value: number) => (value >= 1 && value <= 65535 ? true : "端口号必须在 1-65535 之间"),
51
- },
52
- {
53
- type: "text",
54
- name: "dbPath",
55
- message: "数据库路径 (建议: ./data/app.db)",
56
- initial: "./data/app.db",
57
- },
58
- {
59
- type: "text",
60
- name: "staticPath",
61
- message: "静态目录路径 (留空则不创建,例如: public)",
62
- },
63
- {
64
- type: "text",
65
- name: "apiPrefix",
66
- message: "API 前缀 (留空则无前缀,例如: /api)",
67
- },
68
- {
69
- type: "confirm",
70
- name: "userAgent",
71
- message: "启用 User-Agent 解析中间件?",
72
- initial: true,
73
- },
74
- {
75
- type: "confirm",
76
- name: "ip",
77
- message: "启用 IP 解析中间件?",
78
- initial: true,
79
- },
80
- {
81
- type: "confirm",
82
- name: "cors",
83
- message: "启用 CORS 跨域中间件?",
84
- initial: true,
85
- },
86
- ]);
87
-
88
- if (Object.keys(response).length < 10) {
89
- throw new Error("用户取消了初始化流程");
90
- }
91
-
92
- return response as ProjectConfig;
93
- }
94
-
95
- /**
96
- * 初始化 package.json
97
- */
98
- export function setupPackageJson(cwd: string, config: ProjectConfig) {
99
- const pkgPath = path.join(cwd, "package.json");
100
- let pkg: any = {};
101
-
102
- if (fs.existsSync(pkgPath)) {
103
- pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
104
- logger.info("✓ 检测到已存在的 package.json,正在更新配置...");
105
- } else {
106
- pkg = {
107
- name: config.name,
108
- version: "1.0.0",
109
- description: config.description,
110
- author: config.author,
111
- main: "dist/index.js",
112
- scripts: {},
113
- dependencies: {
114
- "@mindbase/express-common": "*",
115
- express: "^5.2.1",
116
- "cross-env": "^7.0.3",
117
- },
118
- devDependencies: {
119
- "@types/express": "^5.0.6",
120
- "@types/node": "^20.0.0",
121
- "ts-node": "^10.9.0",
122
- "ts-node-dev": "^2.0.0",
123
- typescript: "^5.1.3",
124
- "tsconfig-paths": "^4.2.0",
125
- "drizzle-kit": "^0.31.7",
126
- },
127
- };
128
- logger.info("✓ 正在生成 package.json...");
129
- }
130
-
131
- // 注入标准脚本
132
- pkg.scripts = pkg.scripts || {};
133
- pkg.scripts["dev"] = "ts-node-dev --transpile-only --require tsconfig-paths/register ./src/app.ts";
134
- pkg.scripts["start"] = "ts-node --transpile-only --require tsconfig-paths/register ./src/app.ts";
135
- pkg.scripts["db:migrate"] = "cross-env MIND_BASE_MODE=migrate ts-node --transpile-only --require tsconfig-paths/register ./src/app.ts";
136
- pkg.scripts["db:sync"] = "cross-env MIND_BASE_MODE=sync ts-node --transpile-only --require tsconfig-paths/register ./src/app.ts";
137
- pkg.scripts["db:generate"] = "npm run db:sync && drizzle-kit generate";
138
- pkg.scripts["db:push"] = "drizzle-kit push";
139
-
140
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
141
- logger.info("✓ package.json 已就绪");
142
- }
143
-
144
- /**
145
- * 初始化应用配置 src/config/index.ts
146
- */
147
- export function setupAppConfig(cwd: string, config: ProjectConfig) {
148
- const configDir = path.join(cwd, "src/config");
149
- const configFilePath = path.join(configDir, "index.ts");
150
- const configContent = `import { MindBaseAppOptions } from "@mindbase/express-common";
151
-
152
- const config: MindBaseAppOptions = {
153
- port: ${config.port},
154
- logging: true,
155
- autoMigrate: true,
156
- database: {
157
- path: "${config.dbPath}",
158
- },
159
- staticPath: ${config.staticPath ? `"${config.staticPath}"` : "undefined"},
160
- apiPrefix: ${config.apiPrefix ? `"${config.apiPrefix}"` : "undefined"},
161
- userAgent: ${config.userAgent},
162
- ip: ${config.ip},
163
- cors: ${config.cors},
164
- };
165
-
166
- export default config;
167
- `;
168
- if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
169
- fs.writeFileSync(configFilePath, configContent);
170
- logger.info("✓ src/config/index.ts 已就绪");
171
-
172
- // 同步创建静态目录
173
- if (config.staticPath) {
174
- const staticDir = path.join(cwd, config.staticPath);
175
- if (!fs.existsSync(staticDir)) {
176
- fs.mkdirSync(staticDir, { recursive: true });
177
- logger.info(`✓ 静态目录已创建: ${config.staticPath}`);
178
- }
179
- }
180
- }
181
-
182
- /**
183
- * 初始化 Drizzle 配置 drizzle.config.ts
184
- */
185
- export function setupDrizzleConfig(cwd: string, config: ProjectConfig) {
186
- const drizzleConfigPath = path.join(cwd, "drizzle.config.ts");
187
- const drizzleConfigContent = `import { defineConfig } from "drizzle-kit";
188
- import fs from "fs";
189
-
190
- // 动态读取由 db:sync 脚本生成的 schema 路径清单
191
- const schemaJson = "./.drizzle-schemas.json";
192
- const schemas = fs.existsSync(schemaJson)
193
- ? JSON.parse(fs.readFileSync(schemaJson, "utf-8"))
194
- : ["./src/**/*.schema.ts"];
195
-
196
- export default defineConfig({
197
- schema: schemas,
198
- out: "./drizzle",
199
- dialect: "sqlite",
200
- dbCredentials: {
201
- url: "${config.dbPath}",
202
- },
203
- });
204
- `;
205
- fs.writeFileSync(drizzleConfigPath, drizzleConfigContent);
206
- logger.info("✓ drizzle.config.ts 已就绪");
207
- }
208
-
209
- /**
210
- * 初始化 TypeScript 配置 tsconfig.json
211
- */
212
- export function setupTsConfig(cwd: string) {
213
- const tsconfigPath = path.join(cwd, "tsconfig.json");
214
- if (!fs.existsSync(tsconfigPath)) {
215
- const tsconfigContent = {
216
- compilerOptions: {
217
- target: "ESNext",
218
- module: "CommonJS",
219
- moduleResolution: "node",
220
- baseUrl: ".",
221
- paths: {
222
- "#/*": ["src/*"],
223
- },
224
- strict: true,
225
- esModuleInterop: true,
226
- skipLibCheck: true,
227
- forceConsistentCasingInFileNames: true,
228
- experimentalDecorators: true,
229
- emitDecoratorMetadata: true,
230
- outDir: "dist",
231
- },
232
- include: ["src/**/*"],
233
- exclude: ["node_modules"],
234
- };
235
- fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfigContent, null, 2));
236
- logger.info("✓ tsconfig.json 已就绪");
237
- }
238
- }
239
-
240
- /**
241
- * 初始化应用入口 src/app.ts
242
- */
243
- export function setupAppEntry(cwd: string) {
244
- const appPath = path.join(cwd, "src/app.ts");
245
- if (!fs.existsSync(appPath)) {
246
- const appContent = `import { createApp } from "@mindbase/express-common";
247
- import config from "./config";
248
-
249
- async function bootstrap() {
250
- const app = await createApp(config);
251
-
252
- // 在此处使用插件或中间件
253
- // app.use(...);
254
-
255
- await app.startup();
256
- }
257
-
258
- bootstrap();
259
- `;
260
- if (!fs.existsSync(path.join(cwd, "src"))) fs.mkdirSync(path.join(cwd, "src"));
261
- fs.writeFileSync(appPath, appContent);
262
- logger.info("✓ src/app.ts 已就绪");
263
- }
264
- }
265
-
266
- /**
267
- * 拷贝静态资源
268
- */
269
- export function copyAssets(cwd: string) {
270
- const ipdbPath = path.join(cwd, "ipipfree.ipdb");
271
- const sourceIpdb = path.join(__dirname, "..", "ipipfree.ipdb");
272
-
273
- if (fs.existsSync(sourceIpdb)) {
274
- if (!fs.existsSync(ipdbPath)) {
275
- fs.copyFileSync(sourceIpdb, ipdbPath);
276
- logger.info("✓ ipipfree.ipdb 已就绪");
277
- } else {
278
- logger.info("- ipipfree.ipdb 已存在,跳过拷贝");
279
- }
280
- } else {
281
- logger.warn(`! 未能找到 ipipfree.ipdb 源文件: ${sourceIpdb}`);
282
- }
283
- }