1
0

chore: 强化代码质量与风格检查体系

ESLint 升级到 recommended-type-checked + stylistic-type-checked,
引入 perfectionist 导入排序和 import 插件导入验证。

Prettier 显式声明全部格式化参数,消除跨环境差异。
TypeScript 启用 noUnusedLocals 和 noPropertyAccessFromIndexSignature。
完善 ignore 列表,排除 .agents/、bun.lock、data/ 等。
引入 husky + lint-staged(pre-commit)+ commitlint(commit-msg)。
更新 DEVELOPMENT.md 代码质量章节。
修复所有新增规则检测到的类型和风格违规。
This commit is contained in:
2026-05-12 18:44:59 +08:00
parent ce8baae3d1
commit a5cf6065c2
83 changed files with 2654 additions and 1824 deletions

View File

@@ -1,12 +1,14 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { ProbeStore } from "../../../src/server/checker/store";
import type { CheckFailure, ResolvedTarget } from "../../../src/server/checker/types";
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { CheckFailure, ResolvedTarget } from "../../../src/server/checker/types";
import { checkerRegistry } from "../../../src/server/checker/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { CommandChecker } from "../../../src/server/checker/runner/command/runner";
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
import { ProbeStore } from "../../../src/server/checker/store";
function ensureRegistered() {
if (!checkerRegistry.supportedTypes.includes("http")) {
@@ -20,33 +22,33 @@ beforeAll(() => {
});
const httpTarget: ResolvedTarget = {
type: "http",
name: "test-http",
expect: { maxDurationMs: 3000, status: [200] },
group: "default",
http: {
url: "https://example.com/health",
method: "GET",
headers: { Accept: "application/json" },
maxBodyBytes: 104857600,
method: "GET",
url: "https://example.com/health",
},
intervalMs: 30000,
name: "test-http",
timeoutMs: 10000,
expect: { status: [200], maxDurationMs: 3000 },
type: "http",
};
const commandTarget: ResolvedTarget = {
type: "command",
name: "test-cmd",
group: "default",
command: {
exec: "ping",
args: ["-c", "1", "localhost"],
cwd: "/tmp",
env: {},
exec: "ping",
maxOutputBytes: 104857600,
},
group: "default",
intervalMs: 60000,
name: "test-cmd",
timeoutMs: 5000,
type: "command",
};
describe("ProbeStore", () => {
@@ -61,7 +63,7 @@ describe("ProbeStore", () => {
afterAll(async () => {
store.close();
await rm(tempDir, { recursive: true, force: true });
await rm(tempDir, { force: true, recursive: true });
});
test("初始化后无 targets", () => {
@@ -80,21 +82,26 @@ describe("ProbeStore", () => {
const t = store.getTargets().find((t) => t.name === "test-http")!;
expect(t.type).toBe("http");
expect(t.target).toBe("https://example.com/health");
const config = JSON.parse(t.config);
const config = JSON.parse(t.config) as {
headers: Record<string, string>;
maxBodyBytes: number;
method: string;
url: string;
};
expect(config.url).toBe("https://example.com/health");
expect(config.method).toBe("GET");
expect(config.headers).toEqual({ Accept: "application/json" });
expect(config.maxBodyBytes).toBe(104857600);
expect(t.interval_ms).toBe(30000);
expect(t.timeout_ms).toBe(10000);
expect(JSON.parse(t.expect!)).toEqual({ status: [200], maxDurationMs: 3000 });
expect(JSON.parse(t.expect!)).toEqual({ maxDurationMs: 3000, status: [200] });
});
test("command target 字段正确", () => {
const t = store.getTargets().find((t) => t.name === "test-cmd")!;
expect(t.type).toBe("command");
expect(t.target).toBe("exec ping -c 1 localhost");
const config = JSON.parse(t.config);
const config = JSON.parse(t.config) as { args: string[]; cwd: string; exec: string; maxOutputBytes: number };
expect(config.exec).toBe("ping");
expect(config.args).toEqual(["-c", "1", "localhost"]);
expect(config.cwd).toBe("/tmp");
@@ -144,39 +151,39 @@ describe("ProbeStore", () => {
const t1Id = targets[0]!.id;
store.insertCheckResult({
durationMs: 150.5,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t1Id,
timestamp: "2025-01-01T00:00:00.000Z",
matched: true,
durationMs: 150.5,
statusDetail: "200 OK",
failure: null,
});
store.insertCheckResult({
durationMs: 300,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t1Id,
timestamp: "2025-01-01T00:00:30.000Z",
matched: true,
durationMs: 300,
statusDetail: "200 OK",
failure: null,
});
const failure: CheckFailure = {
kind: "error",
phase: "duration",
path: "$.maxDurationMs",
expected: 3000,
actual: 5000,
expected: 3000,
kind: "error",
message: "请求耗时 5000ms 超过限制 3000ms",
path: "$.maxDurationMs",
phase: "duration",
};
store.insertCheckResult({
durationMs: null,
failure,
matched: false,
statusDetail: null,
targetId: t1Id,
timestamp: "2025-01-01T00:01:00.000Z",
matched: false,
durationMs: null,
statusDetail: null,
failure,
});
const history = store.getHistory(t1Id, "2025-01-01T00:00:00.000Z", "2025-12-31T23:59:59.999Z", 1, 10);
@@ -198,12 +205,12 @@ describe("ProbeStore", () => {
for (let i = 0; i < 25; i++) {
store.insertCheckResult({
durationMs: 100 + i,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t1Id,
timestamp: `2025-01-01T01:${String(i).padStart(2, "0")}:00.000Z`,
matched: true,
durationMs: 100 + i,
statusDetail: "200 OK",
failure: null,
});
}
@@ -274,32 +281,32 @@ describe("ProbeStore", () => {
test("删除 target 级联删除 check_results", () => {
const cascadeStore = new ProbeStore(join(tempDir, "cascade.db"));
const cascadeTarget: ResolvedTarget = {
type: "http",
name: "cascade-test",
group: "default",
http: { url: "http://cascade.test", method: "GET", headers: {}, maxBodyBytes: 104857600 },
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://cascade.test" },
intervalMs: 30000,
name: "cascade-test",
timeoutMs: 10000,
type: "http",
};
cascadeStore.syncTargets([cascadeTarget]);
const t = cascadeStore.getTargets()[0]!;
cascadeStore.insertCheckResult({
durationMs: 100,
failure: null,
matched: true,
statusDetail: "200 OK",
targetId: t.id,
timestamp: "2025-01-01T00:00:00.000Z",
matched: true,
durationMs: 100,
statusDetail: "200 OK",
failure: null,
});
cascadeStore.insertCheckResult({
durationMs: null,
failure: { kind: "error", message: "fail", path: "$", phase: "status" },
matched: false,
statusDetail: null,
targetId: t.id,
timestamp: "2025-01-01T00:01:00.000Z",
matched: false,
durationMs: null,
statusDetail: null,
failure: { kind: "error", phase: "status", path: "$", message: "fail" },
});
expect(cascadeStore.getLatestCheck(t.id)).not.toBeNull();
@@ -329,12 +336,12 @@ describe("ProbeStore", () => {
const freshStore = new ProbeStore(join(tempDir, "fresh-map.db"));
freshStore.syncTargets([
{
type: "http",
name: "no-records",
group: "default",
http: { url: "http://no.records", method: "GET", headers: {}, maxBodyBytes: 104857600 },
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://no.records" },
intervalMs: 30000,
name: "no-records",
timeoutMs: 10000,
type: "http",
},
]);
@@ -359,8 +366,8 @@ describe("ProbeStore", () => {
const stats2 = stats.get(t2Id);
if (stats2) {
expect(stats2!.totalChecks).toBe(0);
expect(stats2!.availability).toBe(0);
expect(stats2.totalChecks).toBe(0);
expect(stats2.availability).toBe(0);
}
});
@@ -368,12 +375,12 @@ describe("ProbeStore", () => {
const freshStore = new ProbeStore(join(tempDir, "fresh-stats.db"));
freshStore.syncTargets([
{
type: "http",
name: "no-stats",
group: "default",
http: { url: "http://no.stats", method: "GET", headers: {}, maxBodyBytes: 104857600 },
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://no.stats" },
intervalMs: 30000,
name: "no-stats",
timeoutMs: 10000,
type: "http",
},
]);