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:
105
scripts/smoke.ts
105
scripts/smoke.ts
@@ -1,8 +1,9 @@
|
||||
import { access } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { access } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import type { HealthResponse, SummaryResponse } from "../src/shared/api";
|
||||
|
||||
const executablePath = process.argv[2] ?? fileURLToPath(new URL("../dist/dial-server", import.meta.url));
|
||||
@@ -12,7 +13,7 @@ await assertExecutableExists(executablePath);
|
||||
const tempDir = mkdtempSync(join(tmpdir(), "dial-smoke-"));
|
||||
const configPath = join(tempDir, "probes.yaml");
|
||||
|
||||
const port = await getFreePort();
|
||||
const port = getFreePort();
|
||||
const baseUrl = `http://127.0.0.1:${port}`;
|
||||
|
||||
writeFileSync(
|
||||
@@ -31,9 +32,9 @@ targets:
|
||||
`,
|
||||
);
|
||||
const app = Bun.spawn([executablePath, configPath], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: { ...process.env },
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const stdout = readStream(app.stdout);
|
||||
const stderr = readStream(app.stderr);
|
||||
@@ -49,10 +50,10 @@ try {
|
||||
assert(summary.total === 1, "总览统计: total 应为 1");
|
||||
assertSecurityHeaders(await fetch(`${baseUrl}/api/summary`), "/api/summary");
|
||||
|
||||
const { body: targets } = await expectJson(`${baseUrl}/api/targets`, 200);
|
||||
const { body: targets } = await expectJson<unknown[]>(`${baseUrl}/api/targets`, 200);
|
||||
assert(Array.isArray(targets), "/api/targets 应返回数组");
|
||||
assert(targets.length === 1, "/api/targets 应有 1 个目标");
|
||||
assert(targets[0].name === "httpbin", "目标名称应为 httpbin");
|
||||
assert((targets[0] as { name: string }).name === "httpbin", "目标名称应为 httpbin");
|
||||
|
||||
const missingApi = await fetch(`${baseUrl}/api/not-found`);
|
||||
assert(missingApi.status === 404, "未知 API 应返回 404");
|
||||
@@ -67,7 +68,7 @@ try {
|
||||
const { body: fallbackHtml } = await expectText(`${baseUrl}/dashboard`, 200);
|
||||
assert(fallbackHtml.includes("DiAL"), "SPA fallback 未返回前端入口页面");
|
||||
|
||||
const assetPath = rootHtml.match(/(?:src|href)="(\/assets\/[^"]+)"/)?.[1];
|
||||
const assetPath = /(?:src|href)="(\/assets\/[^"]+)"/.exec(rootHtml)?.[1];
|
||||
assert(assetPath !== undefined, "前端入口页面未引用 /assets/* 资源");
|
||||
|
||||
const asset = await fetch(`${baseUrl}${assetPath}`);
|
||||
@@ -85,7 +86,13 @@ try {
|
||||
throw new Error(`executable smoke test 失败: ${message}\nstdout:\n${out}\nstderr:\n${err}`, { cause: error });
|
||||
} finally {
|
||||
app.kill();
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
rmSync(tempDir, { force: true, recursive: true });
|
||||
}
|
||||
|
||||
function assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertExecutableExists(path: string) {
|
||||
@@ -96,37 +103,12 @@ async function assertExecutableExists(path: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getFreePort(): Promise<number> {
|
||||
const server = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
fetch: () => new Response("ok"),
|
||||
});
|
||||
const port = server.port;
|
||||
|
||||
server.stop(true);
|
||||
|
||||
if (port === undefined) {
|
||||
throw new Error("无法分配 smoke test 端口");
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
async function waitForServer(url: string) {
|
||||
const deadline = Date.now() + 8_000;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (response.ok) return;
|
||||
} catch {
|
||||
await Bun.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`服务未在超时时间内启动: ${url}`);
|
||||
function assertSecurityHeaders(response: Response, label: string) {
|
||||
assert(response.headers.get("x-content-type-options") === "nosniff", `${label} 缺少 nosniff 安全头`);
|
||||
assert(
|
||||
response.headers.get("referrer-policy") === "strict-origin-when-cross-origin",
|
||||
`${label} 缺少 Referrer-Policy 安全头`,
|
||||
);
|
||||
}
|
||||
|
||||
async function expectJson<T = unknown>(url: string, status: number): Promise<{ body: T; response: Response }> {
|
||||
@@ -146,22 +128,41 @@ async function expectText(url: string, status: number): Promise<{ body: string;
|
||||
return { body: await response.text(), response };
|
||||
}
|
||||
|
||||
function assertSecurityHeaders(response: Response, label: string) {
|
||||
assert(response.headers.get("x-content-type-options") === "nosniff", `${label} 缺少 nosniff 安全头`);
|
||||
assert(
|
||||
response.headers.get("referrer-policy") === "strict-origin-when-cross-origin",
|
||||
`${label} 缺少 Referrer-Policy 安全头`,
|
||||
);
|
||||
}
|
||||
function getFreePort(): number {
|
||||
const server = Bun.serve({
|
||||
fetch: () => new Response("ok"),
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
});
|
||||
const port = server.port;
|
||||
|
||||
function assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
void server.stop(true);
|
||||
|
||||
if (port === undefined) {
|
||||
throw new Error("无法分配 smoke test 端口");
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
async function readStream(stream: ReadableStream<Uint8Array> | null): Promise<string> {
|
||||
async function readStream(stream: null | ReadableStream<Uint8Array>): Promise<string> {
|
||||
if (!stream) return "";
|
||||
|
||||
return new Response(stream).text();
|
||||
}
|
||||
|
||||
async function waitForServer(url: string) {
|
||||
const deadline = Date.now() + 8_000;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (response.ok) return;
|
||||
} catch {
|
||||
await Bun.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`服务未在超时时间内启动: ${url}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user