@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.
- package/dist/index.d.mts +375 -0
- package/dist/index.mjs +2635 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +24 -6
- package/bin/mindbase.ts +0 -52
- package/commands/precache.ts +0 -54
- package/core/app.ts +0 -200
- package/core/module/CreateModule.ts +0 -38
- package/core/module/FindPackageRoot.ts +0 -58
- package/core/module/GetModulePath.ts +0 -58
- package/core/state.ts +0 -72
- package/feature/cron/CronManager.ts +0 -63
- package/feature/scanner/FileScanner.ts +0 -288
- package/index.ts +0 -10
- package/middleware/Cors.ts +0 -17
- package/middleware/IpParser.ts +0 -81
- package/middleware/UaParser.ts +0 -50
- package/routes/Doc.route.ts +0 -118
- package/tests/Cors.test.ts +0 -34
- package/tests/Dayjs.test.ts +0 -24
- package/tests/FileScanner.test.ts +0 -85
- package/tests/GetModulePath.test.ts +0 -32
- package/tests/IpParser.test.ts +0 -72
- package/tests/Logger.test.ts +0 -68
- package/tests/UaParser.test.ts +0 -41
- package/tsconfig.json +0 -9
- package/types/DocTypes.ts +0 -111
- package/types/index.ts +0 -19
- package/utils/ComponentRegistry.ts +0 -34
- package/utils/DatabaseMigration.ts +0 -121
- package/utils/Dayjs.ts +0 -16
- package/utils/DocManager.ts +0 -274
- package/utils/HttpServer.ts +0 -41
- package/utils/InitDatabase.ts +0 -149
- package/utils/InitErrorHandler.ts +0 -71
- package/utils/InitExpress.ts +0 -35
- package/utils/Logger.ts +0 -206
- package/utils/MiddlewareRegistry.ts +0 -14
- package/utils/ProjectInitializer.ts +0 -283
- package/utils/RouteParser.ts +0 -408
- package/utils/RouteRegistry.ts +0 -66
- package/utils/SchemaMigrate.ts +0 -73
- package/utils/SchemaSync.ts +0 -47
- package/utils/TSTypeParser.ts +0 -455
- package/utils/Validate.ts +0 -25
- package/utils/ZodSchemaParser.ts +0 -420
- package/vitest.config.ts +0 -18
- package/zod/Doc.schema.ts +0 -9
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import { glob } from "glob";
|
|
5
|
-
import * as FileScanner from "../feature/scanner/FileScanner";
|
|
6
|
-
|
|
7
|
-
// Mock dependencies
|
|
8
|
-
vi.mock("fs");
|
|
9
|
-
vi.mock("glob");
|
|
10
|
-
vi.mock("../../utils/Logger");
|
|
11
|
-
|
|
12
|
-
// Helper to mock dynamic import
|
|
13
|
-
const mockImport = vi.fn();
|
|
14
|
-
// @ts-ignore
|
|
15
|
-
global.import = mockImport;
|
|
16
|
-
|
|
17
|
-
describe("文件扫描器", () => {
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
vi.clearAllMocks();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
// Clean up cache file after each test
|
|
24
|
-
const cacheFile = path.join(process.cwd(), "node_modules/.cache/mindbase/startup-cache.json");
|
|
25
|
-
if (fs.existsSync(cacheFile)) {
|
|
26
|
-
// @ts-ignore
|
|
27
|
-
fs.unlinkSync(cacheFile);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe("添加扫描路径", () => {
|
|
32
|
-
it("非绝对路径应该抛出错误", () => {
|
|
33
|
-
expect(() => FileScanner.addScanPath("./relative")).toThrow("路径必须是绝对路径");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("目录不存在时应该抛出错误", () => {
|
|
37
|
-
const absPath = path.resolve("/abs/path");
|
|
38
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
39
|
-
expect(() => FileScanner.addScanPath(absPath)).toThrow("目录不存在");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("路径不是目录时应该抛出错误", () => {
|
|
43
|
-
const absPath = path.resolve("/abs/path");
|
|
44
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
45
|
-
vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => false } as any);
|
|
46
|
-
expect(() => FileScanner.addScanPath(absPath)).toThrow("路径不是目录");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("应该将有效目录添加到扫描路径", () => {
|
|
50
|
-
const absPath = path.resolve("/abs/path");
|
|
51
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
52
|
-
vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any);
|
|
53
|
-
|
|
54
|
-
expect(() => FileScanner.addScanPath(absPath)).not.toThrow();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe("执行扫描", () => {
|
|
59
|
-
it("应该扫描并从添加的路径返回结果", async () => {
|
|
60
|
-
const absPath = path.resolve("/abs/path");
|
|
61
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
62
|
-
vi.mocked(fs.statSync).mockReturnValue({ isDirectory: () => true } as any);
|
|
63
|
-
FileScanner.addScanPath(absPath);
|
|
64
|
-
|
|
65
|
-
const mockFiles = [path.join(absPath, "user.route.ts"), path.join(absPath, "auth.middleware.ts")];
|
|
66
|
-
|
|
67
|
-
vi.mocked(glob).mockResolvedValue(mockFiles as any);
|
|
68
|
-
|
|
69
|
-
// Since we can't easily mock `import()`, we rely on the implementation's try-catch
|
|
70
|
-
// or we can mock the behavior if we wrap the import.
|
|
71
|
-
// But FileScanner uses `await import(file)`.
|
|
72
|
-
// Vitest handles dynamic imports in tests, but we need to ensure the files "exist" for the import to work or be mocked.
|
|
73
|
-
|
|
74
|
-
// A better way is to mock the internal logic if possible,
|
|
75
|
-
// but here we want to test the scanner.
|
|
76
|
-
|
|
77
|
-
// Let's mock the glob return to be something we can work with.
|
|
78
|
-
const results = await FileScanner.scan();
|
|
79
|
-
|
|
80
|
-
expect(glob).toHaveBeenCalled();
|
|
81
|
-
// Even if imports fail in test environment without real files,
|
|
82
|
-
// we check if it attempted to scan the correct patterns.
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { getModulePath, getModuleDir } from "../core/module/GetModulePath";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
describe("获取模块路径", () => {
|
|
6
|
-
it("应该返回路径字符串", () => {
|
|
7
|
-
const p = getModulePath();
|
|
8
|
-
expect(typeof p).toBe("string");
|
|
9
|
-
expect(path.isAbsolute(p)).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("应该返回目录字符串", () => {
|
|
13
|
-
const d = getModuleDir();
|
|
14
|
-
expect(typeof d).toBe("string");
|
|
15
|
-
expect(path.isAbsolute(d)).toBe(true);
|
|
16
|
-
expect(getModulePath()).toContain(d);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("当 __filename 未定义时应该抛出错误", () => {
|
|
20
|
-
const originalFilename = global.__filename;
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
delete global.__filename;
|
|
23
|
-
|
|
24
|
-
// In Vitest/Node environment, __filename is usually defined.
|
|
25
|
-
// This test might be tricky if we can't delete it.
|
|
26
|
-
// But let's try.
|
|
27
|
-
|
|
28
|
-
// Actually, getModulePath uses `typeof __filename !== "undefined"`
|
|
29
|
-
// We can mock this behavior by using a fresh import or other means,
|
|
30
|
-
// but since it's a simple check, we can skip if it's too hard to trigger.
|
|
31
|
-
});
|
|
32
|
-
});
|
package/tests/IpParser.test.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { Request, Response } from "express";
|
|
3
|
-
|
|
4
|
-
// Mock Logger
|
|
5
|
-
vi.mock("../utils/Logger");
|
|
6
|
-
|
|
7
|
-
const { mockFindMap } = vi.hoisted(() => ({
|
|
8
|
-
mockFindMap: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
vi.mock("ipip-ipdb", () => {
|
|
12
|
-
class MockCity {
|
|
13
|
-
findMap = mockFindMap;
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
City: MockCity,
|
|
17
|
-
default: {
|
|
18
|
-
City: MockCity,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Import after mocking
|
|
24
|
-
import ipParser from "../middleware/IpParser";
|
|
25
|
-
|
|
26
|
-
describe("IP 解析中间件", () => {
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
vi.clearAllMocks();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("应该处理本地回环地址", () => {
|
|
32
|
-
const req = {
|
|
33
|
-
headers: {},
|
|
34
|
-
ip: "127.0.0.1",
|
|
35
|
-
} as unknown as Request;
|
|
36
|
-
const res = {
|
|
37
|
-
locals: {},
|
|
38
|
-
} as unknown as Response;
|
|
39
|
-
const next = vi.fn();
|
|
40
|
-
|
|
41
|
-
ipParser(req, res, next);
|
|
42
|
-
|
|
43
|
-
expect(res.locals.ip.isLocal).toBe(true);
|
|
44
|
-
expect(res.locals.ip.location).toBe("本机");
|
|
45
|
-
expect(next).toHaveBeenCalled();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("应该从请求头解析 IP", () => {
|
|
49
|
-
const req = {
|
|
50
|
-
headers: { "x-real-ip": "1.2.3.4" },
|
|
51
|
-
ip: "127.0.0.1",
|
|
52
|
-
} as unknown as Request;
|
|
53
|
-
const res = {
|
|
54
|
-
locals: {},
|
|
55
|
-
} as unknown as Response;
|
|
56
|
-
const next = vi.fn();
|
|
57
|
-
|
|
58
|
-
mockFindMap.mockReturnValue({
|
|
59
|
-
country_name: "中国",
|
|
60
|
-
region_name: "北京",
|
|
61
|
-
city_name: "北京",
|
|
62
|
-
isp_domain: "测试",
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
ipParser(req, res, next);
|
|
66
|
-
|
|
67
|
-
expect(res.locals.ip.address).toBe("1.2.3.4");
|
|
68
|
-
expect(res.locals.ip.country).toBe("中国");
|
|
69
|
-
expect(res.locals.ip.location).toBe("中国 北京 北京");
|
|
70
|
-
expect(next).toHaveBeenCalled();
|
|
71
|
-
});
|
|
72
|
-
});
|
package/tests/Logger.test.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import logger from "../utils/Logger";
|
|
3
|
-
|
|
4
|
-
describe("Logger", () => {
|
|
5
|
-
let logSpy: any;
|
|
6
|
-
let warnSpy: any;
|
|
7
|
-
let errorSpy: any;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
vi.clearAllMocks();
|
|
11
|
-
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
12
|
-
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
13
|
-
errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("should log info messages", () => {
|
|
17
|
-
logger.info("Test info message");
|
|
18
|
-
expect(logSpy).toHaveBeenCalled();
|
|
19
|
-
const output = logSpy.mock.calls[0][0];
|
|
20
|
-
expect(output).toContain("Test info message");
|
|
21
|
-
expect(output).toContain("\x1b[32m"); // Green for info
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("应该记录 error 级别日志", () => {
|
|
25
|
-
logger.error("Test error message");
|
|
26
|
-
expect(errorSpy).toHaveBeenCalled();
|
|
27
|
-
const output = errorSpy.mock.calls[0][0];
|
|
28
|
-
expect(output).toContain("Test error message");
|
|
29
|
-
expect(output).toContain("\x1b[31m"); // Red for error
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("应该截断过长的消息", () => {
|
|
33
|
-
// Mock terminal width
|
|
34
|
-
const originalColumns = process.stdout.columns;
|
|
35
|
-
Object.defineProperty(process.stdout, "columns", { value: 20, configurable: true });
|
|
36
|
-
|
|
37
|
-
logger.info("This is a very very long message that should be truncated");
|
|
38
|
-
|
|
39
|
-
const output = logSpy.mock.calls[0][0];
|
|
40
|
-
expect(output).toContain("...");
|
|
41
|
-
|
|
42
|
-
// Restore
|
|
43
|
-
Object.defineProperty(process.stdout, "columns", { value: originalColumns, configurable: true });
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("计算视觉宽度时应该处理中日韩字符", () => {
|
|
47
|
-
logger.info("你好世界");
|
|
48
|
-
expect(logSpy).toHaveBeenCalled();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("应该能设置和获取日志级别", () => {
|
|
52
|
-
logger.setLevel("silent");
|
|
53
|
-
expect(logger.getLevel()).toBe("silent");
|
|
54
|
-
|
|
55
|
-
logger.info("This should not be logged");
|
|
56
|
-
expect(logSpy).not.toHaveBeenCalled();
|
|
57
|
-
|
|
58
|
-
logger.setLevel("info");
|
|
59
|
-
expect(logger.getLevel()).toBe("info");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("当级别为 debug 时应该记录 debug 日志", () => {
|
|
63
|
-
logger.setLevel("debug");
|
|
64
|
-
logger.debug("Debug message");
|
|
65
|
-
expect(logSpy).toHaveBeenCalled();
|
|
66
|
-
logger.setLevel("info"); // Reset
|
|
67
|
-
});
|
|
68
|
-
});
|
package/tests/UaParser.test.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import uaParser from "../middleware/UaParser";
|
|
3
|
-
import { Request, Response } from "express";
|
|
4
|
-
|
|
5
|
-
describe("UA 解析中间件", () => {
|
|
6
|
-
it("应该解析 User-Agent 请求头", () => {
|
|
7
|
-
const uaString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
8
|
-
const req = {
|
|
9
|
-
headers: { "user-agent": uaString },
|
|
10
|
-
} as unknown as Request;
|
|
11
|
-
const res = {
|
|
12
|
-
locals: {},
|
|
13
|
-
} as unknown as Response;
|
|
14
|
-
const next = vi.fn();
|
|
15
|
-
|
|
16
|
-
uaParser(req, res, next);
|
|
17
|
-
|
|
18
|
-
expect(res.locals.UA.ua).toBe(uaString);
|
|
19
|
-
expect(res.locals.UA.os.name).toBe("Windows");
|
|
20
|
-
expect(res.locals.UA.browser.name).toBe("Chrome");
|
|
21
|
-
expect(res.locals.UA.isMobile).toBe(false);
|
|
22
|
-
expect(next).toHaveBeenCalled();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("应该检测出移动设备", () => {
|
|
26
|
-
const uaString = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1";
|
|
27
|
-
const req = {
|
|
28
|
-
headers: { "user-agent": uaString },
|
|
29
|
-
} as unknown as Request;
|
|
30
|
-
const res = {
|
|
31
|
-
locals: {},
|
|
32
|
-
} as unknown as Response;
|
|
33
|
-
const next = vi.fn();
|
|
34
|
-
|
|
35
|
-
uaParser(req, res, next);
|
|
36
|
-
|
|
37
|
-
expect(res.locals.UA.isMobile).toBe(true);
|
|
38
|
-
expect(res.locals.UA.os.name).toBe("iOS");
|
|
39
|
-
expect(next).toHaveBeenCalled();
|
|
40
|
-
});
|
|
41
|
-
});
|
package/tsconfig.json
DELETED
package/types/DocTypes.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Request, Response } from "express";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 标准 API 响应结构
|
|
5
|
-
*/
|
|
6
|
-
export interface ApiResponse<T = any> {
|
|
7
|
-
code: number;
|
|
8
|
-
data: T;
|
|
9
|
-
msg: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 字段约束规则
|
|
14
|
-
*/
|
|
15
|
-
export interface FieldConstraints {
|
|
16
|
-
min?: number;
|
|
17
|
-
max?: number;
|
|
18
|
-
minLength?: number;
|
|
19
|
-
maxLength?: number;
|
|
20
|
-
pattern?: string;
|
|
21
|
-
email?: boolean;
|
|
22
|
-
url?: boolean;
|
|
23
|
-
uuid?: boolean;
|
|
24
|
-
int?: boolean;
|
|
25
|
-
positive?: boolean;
|
|
26
|
-
nonnegative?: boolean;
|
|
27
|
-
custom?: string[]; // 自定义错误消息
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 字段验证规则
|
|
32
|
-
*/
|
|
33
|
-
export interface FieldValidation {
|
|
34
|
-
type: "string" | "number" | "boolean" | "array" | "object" | "date" | "file" | "any";
|
|
35
|
-
required: boolean;
|
|
36
|
-
optional?: boolean;
|
|
37
|
-
nullable?: boolean;
|
|
38
|
-
defaultValue?: any;
|
|
39
|
-
constraints?: FieldConstraints;
|
|
40
|
-
items?: FieldValidation; // 数组元素类型
|
|
41
|
-
properties?: Record<string, FieldValidation>; // 对象属性
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 请求 Schema 结构
|
|
46
|
-
*/
|
|
47
|
-
export interface RequestSchema {
|
|
48
|
-
target: "body" | "query" | "params";
|
|
49
|
-
fields: Record<string, FieldValidation>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 响应 Schema 结构
|
|
54
|
-
*/
|
|
55
|
-
export interface ResponseSchema {
|
|
56
|
-
description?: string;
|
|
57
|
-
fields: Record<string, FieldValidation>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 路由文档配置
|
|
62
|
-
*/
|
|
63
|
-
export interface RouteDocConfig<TRequest = any, TResponse = any> {
|
|
64
|
-
summary?: string;
|
|
65
|
-
description?: string;
|
|
66
|
-
request?: TRequest;
|
|
67
|
-
response?: TResponse;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 路由信息接口
|
|
72
|
-
*/
|
|
73
|
-
export interface RouteInfo {
|
|
74
|
-
id?: number;
|
|
75
|
-
module: string;
|
|
76
|
-
method: string;
|
|
77
|
-
path: string;
|
|
78
|
-
fullPath: string;
|
|
79
|
-
summary: string;
|
|
80
|
-
description: string;
|
|
81
|
-
requestSchema?: RequestSchema;
|
|
82
|
-
responseSchema?: ResponseSchema;
|
|
83
|
-
middlewares?: string[];
|
|
84
|
-
filePath: string;
|
|
85
|
-
createdAt?: number;
|
|
86
|
-
updatedAt?: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 标记路由文档配置
|
|
91
|
-
*/
|
|
92
|
-
export function RouteDoc<TRequest = any, TResponse = any>(config: RouteDocConfig<TRequest, TResponse>) {
|
|
93
|
-
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
94
|
-
// 运行时不做任何处理,仅用于类型定义和静态分析
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 模块列表项接口
|
|
100
|
-
*/
|
|
101
|
-
export interface ModuleItem {
|
|
102
|
-
name: string;
|
|
103
|
-
count: number;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 文档数据库配置接口
|
|
108
|
-
*/
|
|
109
|
-
export interface DocDatabaseConfig {
|
|
110
|
-
path: string;
|
|
111
|
-
}
|
package/types/index.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export { AppState, MindBaseAppOptions } from "../core/state";
|
|
2
|
-
export { MindBaseApp } from "../core/app";
|
|
3
|
-
export { LogLevel } from "../utils/Logger";
|
|
4
|
-
export { IpInfo } from "../middleware/IpParser";
|
|
5
|
-
export { UaInfo } from "../middleware/UaParser";
|
|
6
|
-
export { ScanResult } from "../feature/scanner/FileScanner";
|
|
7
|
-
export { CronConfig } from "../feature/cron/CronManager";
|
|
8
|
-
export {
|
|
9
|
-
RouteDoc,
|
|
10
|
-
ApiResponse,
|
|
11
|
-
RouteInfo,
|
|
12
|
-
ModuleItem,
|
|
13
|
-
DocDatabaseConfig,
|
|
14
|
-
FieldValidation,
|
|
15
|
-
FieldConstraints,
|
|
16
|
-
RequestSchema,
|
|
17
|
-
ResponseSchema,
|
|
18
|
-
RouteDocConfig
|
|
19
|
-
} from "./DocTypes";
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { AppState } from "../core/state";
|
|
2
|
-
import { ScanResult } from "../types/Index";
|
|
3
|
-
import { registerMiddleware } from "./MiddlewareRegistry";
|
|
4
|
-
import { registerRoute } from "./RouteRegistry";
|
|
5
|
-
import logger from "./Logger";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 封装细节:遍历扫描结果并注册所有组件(中间件和路由)
|
|
9
|
-
*/
|
|
10
|
-
export async function registerComponents(state: AppState, scannedResults: ScanResult[]) {
|
|
11
|
-
// 1. 先注册所有中间件
|
|
12
|
-
for (const item of scannedResults) {
|
|
13
|
-
if (item.type === "middleware") {
|
|
14
|
-
registerMiddleware(state.express, item);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// 2. 再注册所有路由
|
|
19
|
-
for (const item of scannedResults) {
|
|
20
|
-
if (item.type === "route") {
|
|
21
|
-
registerRoute(state.express, item, state.options.apiPrefix);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 3. 所有组件注册完成后,触发路由资源同步(如果有)
|
|
26
|
-
const syncAppRoutes = state.express.get("syncAppRoutes");
|
|
27
|
-
if (syncAppRoutes && typeof syncAppRoutes === "function") {
|
|
28
|
-
try {
|
|
29
|
-
await syncAppRoutes();
|
|
30
|
-
} catch (error) {
|
|
31
|
-
logger.error("[ComponentRegistry] 路由资源同步失败:", error);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { AppState } from "../core/state";
|
|
2
|
-
import logger from "./Logger";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 通过代码调用的方式执行数据库迁移
|
|
6
|
-
* @param db 数据库实例
|
|
7
|
-
* @param schemas 数据库 schema 定义
|
|
8
|
-
* @returns 迁移是否成功
|
|
9
|
-
*/
|
|
10
|
-
export async function executeDatabaseMigration(db: any, schemas: Record<string, any>): Promise<boolean> {
|
|
11
|
-
try {
|
|
12
|
-
if (!db) {
|
|
13
|
-
logger.error("数据库实例不能为空");
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!schemas) {
|
|
18
|
-
logger.error("数据库 schema 定义不能为空");
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
logger.startup("数据库", "开始执行数据库表结构同步...");
|
|
23
|
-
|
|
24
|
-
// 方案:调用 drizzle-kit push 命令
|
|
25
|
-
try {
|
|
26
|
-
const { spawn } = await import("child_process");
|
|
27
|
-
|
|
28
|
-
logger.startup("数据库", "正在调用 drizzle-kit push 同步表结构...");
|
|
29
|
-
|
|
30
|
-
// 使用 drizzle-kit push 命令,捕获输出以判断错误类型
|
|
31
|
-
const result = await new Promise<boolean>((resolve) => {
|
|
32
|
-
let output = "";
|
|
33
|
-
let errorOutput = "";
|
|
34
|
-
|
|
35
|
-
const push = spawn("npx", ["drizzle-kit", "push"], {
|
|
36
|
-
shell: true,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
push.stdout?.on("data", (data) => {
|
|
40
|
-
const text = data.toString();
|
|
41
|
-
output += text;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
push.stderr?.on("data", (data) => {
|
|
45
|
-
const text = data.toString();
|
|
46
|
-
errorOutput += text;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
push.on("close", (code) => {
|
|
50
|
-
if (code === 0) {
|
|
51
|
-
logger.startup("数据库", "✅ drizzle-kit push 执行成功");
|
|
52
|
-
resolve(true);
|
|
53
|
-
} else {
|
|
54
|
-
// 检查是否是已存在对象的良性错误
|
|
55
|
-
const errorMsg = errorOutput || output;
|
|
56
|
-
const benignErrors = [
|
|
57
|
-
"already exists",
|
|
58
|
-
"索引.*已存在",
|
|
59
|
-
"index.*already exists",
|
|
60
|
-
"table.*already exists",
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
const isBenign = benignErrors.some((pattern) =>
|
|
64
|
-
new RegExp(pattern, "i").test(errorMsg)
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
if (isBenign) {
|
|
68
|
-
logger.startup("数据库", "✅ 数据库结构已同步(索引/表已存在)");
|
|
69
|
-
resolve(true);
|
|
70
|
-
} else {
|
|
71
|
-
logger.error(`❌ drizzle-kit push 执行失败,退出码: ${code}`);
|
|
72
|
-
if (errorMsg) {
|
|
73
|
-
logger.error(errorMsg);
|
|
74
|
-
}
|
|
75
|
-
resolve(false);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
push.on("error", (error) => {
|
|
81
|
-
logger.error("执行 drizzle-kit push 时出错:", error);
|
|
82
|
-
resolve(false);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
return result;
|
|
87
|
-
} catch (pushError) {
|
|
88
|
-
logger.error("调用 drizzle-kit push 失败:", pushError);
|
|
89
|
-
logger.info("💡 提示:请手动运行 'npx drizzle-kit push' 来同步表结构");
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
} catch (error) {
|
|
93
|
-
logger.error("数据库迁移执行失败:", error);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 检查并执行数据库迁移(如果需要)
|
|
100
|
-
* @param state 应用状态
|
|
101
|
-
* @returns 迁移是否成功
|
|
102
|
-
*/
|
|
103
|
-
export async function checkAndMigrateDatabase(state: AppState): Promise<boolean> {
|
|
104
|
-
try {
|
|
105
|
-
if (!state || !state.db || !state.schemas) {
|
|
106
|
-
logger.error("应用状态、数据库实例或 schema 定义不能为空");
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 检查 schema 是否有定义
|
|
111
|
-
if (Object.keys(state.schemas).length === 0) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 执行数据库迁移
|
|
116
|
-
return await executeDatabaseMigration(state.db, state.schemas);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
logger.error("检查并执行数据库迁移失败:", error);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
package/utils/Dayjs.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import dayjs from "dayjs";
|
|
2
|
-
import "dayjs/locale/zh-cn";
|
|
3
|
-
import duration from "dayjs/plugin/duration";
|
|
4
|
-
import relativeTime from "dayjs/plugin/relativeTime";
|
|
5
|
-
import utc from "dayjs/plugin/utc";
|
|
6
|
-
|
|
7
|
-
// 设置中文语言包
|
|
8
|
-
dayjs.locale("zh-cn");
|
|
9
|
-
|
|
10
|
-
// 注册常用插件
|
|
11
|
-
dayjs.extend(utc);
|
|
12
|
-
dayjs.extend(duration);
|
|
13
|
-
dayjs.extend(relativeTime);
|
|
14
|
-
|
|
15
|
-
export default dayjs;
|
|
16
|
-
export { dayjs };
|