From e1c33b4002e9b014c1693ea9e688791cd7ac0ae3 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 12 May 2026 15:26:49 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=AE=8C=E5=96=84=20DEVELOPMENT.md?= =?UTF-8?q?=EF=BC=8C=E6=8C=89=E5=89=8D=E7=AB=AF/=E5=90=8E=E7=AB=AF/?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E4=B8=89=E5=A4=A7=E7=AB=A0=E8=8A=82=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=BC=80=E5=8F=91=E6=8C=87=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPMENT.md | 511 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 454 insertions(+), 57 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f1795f8..f96c262 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -4,6 +4,17 @@ 用户使用说明请参阅 [README.md](README.md)。 +## 目录 + +- [项目结构](#项目结构) +- [一、后端开发指引](#一后端开发指引) +- [二、前端开发指引](#二前端开发指引) +- [三、项目运行、集成与打包](#三项目运行集成与打包) +- [代码质量](#代码质量) +- [已知限制](#已知限制) + +--- + ## 项目结构 ```text @@ -11,11 +22,11 @@ src/ server/ app.ts Bun HTTP 路由入口(路由分发 + API 汇聚) config.ts CLI 参数解析 - dev.ts 开发期启动入口 - server.ts HTTP server 启动 + dev.ts 生产/开发启动入口 + server.ts HTTP server 启动工厂 helpers.ts 共享响应格式化工具(jsonResponse、createHeaders 等) middleware.ts API 参数校验中间件(guardGetHead、validateTargetId 等) - static.ts 静态资源服务 与 SPA fallback + static.ts 静态资源服务与 SPA fallback routes/ API 路由 handler(按端点拆分) health.ts GET /health summary.ts GET /api/summary @@ -47,53 +58,15 @@ tests/ Bun test 测试 openspec/ OpenSpec 变更与规格文档 ``` -## 构建 executable - -```bash -bun run build -``` - -构建流程: - -1. 运行 `vite build`,输出前端资源到 `dist/web` -2. 生成临时 `.build/static-assets.ts`,嵌入 Vite 产物 -3. 生成临时 `.build/server-entry.ts`,作为生产入口 -4. 运行 `Bun.build({ compile })`,输出 `dist/dial-server` - -运行 executable: - -```bash -./dist/dial-server probes.yaml -``` - -## 代码质量 - -```bash -bun run lint -bun run format:check -bun run format -bun run check -``` - -- `check` 依次运行 `typecheck`、`lint`、`format:check` 和单元测试。 - -## 测试 - -```bash -bun run check -bun run verify -``` - -- `check` 适合日常开发,包含类型检查、lint、格式检查和单元测试。 -- `verify` 先运行 `check`,再重新构建生产 executable 并运行 smoke test。 - ## 前后端边界 前端只通过 HTTP 调用后端,API 路径为 `/api/*`。共享类型放在 `src/shared`,前端不得 import `src/server` 的运行时实现。 -## 后端开发指引 +--- -### 架构概览 +## 一、后端开发指引 + +### 1.1 架构概览 ``` 启动流程: @@ -110,7 +83,7 @@ HTTP 请求: → middleware.ts(参数校验) → helpers.ts(响应格式化) → Response ``` -### 库使用优先级 +### 1.2 库使用优先级 后端代码开发遵循严格的库选择顺序: @@ -122,7 +95,9 @@ HTTP 请求: | 4 | 主流三方库 | cheerio(HTML 解析)、xpath + @xmldom/xmldom(XML 解析) | | 5 | 自行实现 | 仅在以上都无法满足时(如 `parseDuration`、`parseSize`、`evaluateJsonPath` 等专项逻辑) | -### API 路由开发 +**原则**:新增依赖前先检查上述每一层级是否已有可用方案。禁止随意引入新依赖。 + +### 1.3 API 路由开发 路由文件位于 `src/server/routes/`,每个端点一个文件。handler 函数签名统一为: @@ -142,24 +117,25 @@ export function handleXxx(params, store: ProbeStore, method: string, mode: Runti 1. 在 `src/server/routes/` 下创建 `.ts` 2. 实现 handler 函数并 export -3. 在 `app.ts` 的 `handleApiRoute` 中注册路径匹配和调用 +3. 在 `app.ts` 的 `createFetchHandler` 中注册路径匹配和调用 4. 在 `tests/server/app.test.ts` 中添加对应测试 -### 共享工具 +### 1.4 共享工具 - **`helpers.ts`**:跨路由共用的响应工具函数(`jsonResponse`、`createHeaders`、`createApiError`、`mapCheckResult`、`formatDuration`、`createHealthResponse`) - **`middleware.ts`**:API 参数校验函数(`guardGetHead`、`validateTargetId`、`validateTimeRange`、`validatePagination`) - **`static.ts`**:生产模式下的静态资源服务与 SPA fallback -### 类型定义规范 +### 1.5 类型定义规范 - **共享类型**以 `src/shared/api.ts` 为唯一源头,前后端共同引用 - 前端不得 `import src/server/` 下的任何文件 - **严格联合类型**优先于宽类型:如 `phase: "status" | "duration" | ...` 而非 `phase: string` - **后端内部扩展**:`checker/types.ts` 中 `CheckResult` 通过 `extends` 共享版本的 `ApiCheckResult` 增加 `targetName` 等内部字段 - 存储层类型(`StoredTarget`、`StoredCheckResult`)独立定义,与 API 类型分离 +- 配置类型(`ProbeConfig`、`TargetConfig`)支持 discriminated union,通过 `type` 字段区分 http/command -### 数据存储规范 +### 1.6 数据存储规范 基于 `bun:sqlite`,WAL 模式运行,数据库文件位于配置的 `dataDir` 下。 @@ -182,15 +158,16 @@ export function handleXxx(params, store: ProbeStore, method: string, mode: Runti - `check_results` 表:target_id(FK CASCADE)、timestamp、matched(0/1)、duration_ms、status_detail、failure(JSON) - 复合索引:`(target_id, timestamp)` -### 拨测引擎 +### 1.7 拨测引擎 - **调度**:`ProbeEngine` 用 `es-toolkit/groupBy` 按 interval 分组,每组独立 `setInterval` 定时触发 - **并发控制**:`es-toolkit/Semaphore` 限制全局最大并发数(`maxConcurrentChecks`),`acquire()` 阻塞等待 - **Runner 选择**:`engine.runCheck()` 按 `target.type` 分发到 `runHttpCheck` 或 `runCommandCheck` - **超时控制**:HTTP 用 `AbortController`,Command 用 `setTimeout` + `proc.kill()` - **结果写入**:检查结果通过 `store.insertCheckResult()` 写入 SQLite,engine 通过 `targetNameToId` 缓存 name→id 映射 +- **生命周期**:`start()`/`stop()` 管理定时器,`stop()` 清理所有 `setInterval` -### expect 断言系统 +### 1.8 expect 断言系统 两层模型:**观测值收集** → **规则校验**。 @@ -220,23 +197,193 @@ runCommandCheck → 收集观测(exitCode/stdout/stderr/durationMs) **操作符**:`equals`(深度比较,`es-toolkit/isEqual`)、`contains`、`match`(正则)、`empty`(`isNil`+`isEmptyObject`)、`exists`、`gte`/`lte`/`gt`/`lt` -### 错误模式 +### 1.9 错误模式 - **API 错误**:`{ error: "描述", status: }`,状态码 400/404/405/503 - **CheckFailure**:`{ kind: "error"|"mismatch", phase, path, expected?, actual?, message }` - **错误处理**:expect 校验失败记录首个失败原因;网络/超时/进程崩溃统一为 `kind:"error"` - **日志**:解析失败等非致命异常用 `console.warn`,启动失败用 `console.error` + `process.exit(1)` -### 测试规范 +### 1.10 测试规范 - 测试文件与源文件对应:`tests/server/checker/store.test.ts` ↔ `src/server/checker/store.ts` - 使用 `bun:test` 框架(`describe`/`test`/`expect`),测试数据库用临时目录 + `tmpdir()` - 新增 store 方法必须编写单元测试;新增 API 端点必须在 `app.test.ts` 中添加集成测试 - 测试后清理:`afterAll` 中 `store.close()` + `rm(tempDir, { recursive: true })` -## 前端样式规范 +--- -前端基于 TDesign React 构建UI,样式开发遵循以下优先级(从高到低): +## 二、前端开发指引 + +### 2.1 技术栈概览 + +| 层面 | 技术 | 用途 | +| -------- | ----------------------------------- | ------------------------------ | +| 框架 | React 19 | UI 组件开发 | +| 构建 | Vite 8 | 开发服务与生产构建 | +| 语言 | TypeScript 6 | 类型安全 | +| UI 库 | TDesign React + tdesign-icons-react | UI 组件与图标 | +| 数据层 | TanStack Query (React Query) | 服务端状态管理与自动轮询 | +| 图表 | Recharts | 拨测趋势折线图与状态环状图 | +| 路由 | 无(单页面 Dashboard) | 仅需 Drawer/Tab 做页面内导航 | + +**不引入的依赖**:React Router(单页面场景不需要)、状态管理库(TanStack Query 即服务端状态层,组件内用 `useState` 足够) + +### 2.2 组件树与数据流 + +``` +main.tsx +└── QueryClientProvider(TanStack Query 全局挂载) + └── App(根组件) + ├── SummaryCards(总览统计卡片) + │ └── useSummary() ─── GET /api/summary(8s 轮询) + └── TargetBoard(目标列表) + ├── useTargets() ─── GET /api/targets(8s 轮询) + └── TargetGroup[](按 group 字段分组) + └── PrimaryTable ← TARGET_TABLE_COLUMNS(列定义:排序/筛选/渲染) + └── TargetDetailDrawer(目标详情抽屉) + └── useTargetDetail() ── 按需发起 trend + history 查询 + ├── Tab: 概览 → Statistic + TrendChart + StatusDonut + Descriptions + └── Tab: 记录 → PrimaryTable(分页历史记录) +``` + +**数据层架构**: + +``` +hooks/useTargetDetail.ts(唯一的数据层入口) +├── queryKeys(结构化 query key,确保缓存粒度精确) +├── useSummary() → /api/summary(8s 自动轮询) +├── useTargets() → /api/targets(8s 自动轮询) +└── useTargetDetail()(组合 hook,管理 Drawer 全部状态) + ├── 内部复用 useTargets() 的缓存来查找 selectedTarget + ├── useQuery(/api/targets/:id/trend)(条件查询:enabled 仅当 Drawer 打开且时间范围有效) + └── useQuery(/api/targets/:id/history)(条件查询:含分页) +``` + +### 2.3 TanStack Query 数据层 + +#### Query Key 规范 + +```typescript +const queryKeys = { + summary: () => ["summary"] as const, + targets: () => ["targets"] as const, + trend: (targetId: number, from: string, to: string) => ["trend", targetId, from, to] as const, + history: (targetId: number, from: string, to: string, page: number) => ["history", targetId, from, to, page] as const, +}; +``` + +- Key 使用 **structured array**(非字符串),以便精确匹配和按 prefix 失效 +- 使用 `as const` 保持字面量类型 +- 排序:scope → id → 参数(粒度从粗到细) + +#### 查询配置规范 + +```typescript +// 全局面板级查询(需要持续刷新) +useQuery({ + queryKey: queryKeys.summary(), + queryFn: () => fetchJson("/api/summary"), + refetchInterval: 8000, // 自动轮询间隔 + refetchIntervalInBackground: false, // 切后台不轮询 +}); + +// 详情级查询(按需加载) +useQuery({ + queryKey: selectedTargetId ? queryKeys.trend(id, from, to) : ["trend", "disabled"], + queryFn: () => fetchJson(`/api/targets/${id}/trend?...`), + enabled: selectedTargetId !== null && !!timeFrom && !!timeTo, // 条件查询 +}); +``` + +#### fetch 封装 + +```typescript +async function fetchJson(url: string): Promise { + const response = await fetch(url); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.json() as Promise; +} +``` + +- 统一使用 `fetch`(不引入 axios),与后端共享 Web API 生态 +- 错误抛异常,由 TanStack Query 的 `error` 状态承接 + +#### QueryClient 全局配置 + +```typescript +new QueryClient({ + defaultOptions: { + queries: { + retry: 1, // 失败重试 1 次 + refetchOnWindowFocus: true, // 窗口聚焦时刷新 + staleTime: 5000, // 5s 内视为 fresh,避免重复请求 + }, + }, +}); +``` + +### 2.4 组件开发规范 + +#### 文件命名与导入 + +- 每个 React 组件一个 `.tsx` 文件,文件名使用 PascalCase(如 `StatusDot.tsx`) +- 组件 props 定义为 `interface XxxProps`,紧邻组件函数声明 +- 类型从 `../../shared/api` 导入,使用 `type` 导入(`import type { ... }`) + +```typescript +import type { TargetStatus } from "../../shared/api"; +import { StatusDot } from "./StatusDot"; + +interface TargetGroupProps { + name: string; + targets: TargetStatus[]; + onTargetClick: (target: TargetStatus) => void; +} + +export function TargetGroup({ name, targets, onTargetClick }: TargetGroupProps) { + // ... +} +``` + +#### 组件拆分原则 + +- **展示组件**(`components/`):纯渲染逻辑,通过 props 接收数据,通过回调返回事件 +- **容器逻辑**放在 hooks 中,组件只做数据消费 +- **常量数据**(列定义、排序器、筛选器)放在 `constants/`,不放在组件内部 +- **工具函数**(时间处理等)放在 `utils/`,保持纯函数无副作用 + +#### 现有组件清单 + +| 组件 | 文件 | 用途 | +| ----------------------- | -------------------------- | ---------------------------- | +| `App` | `app.tsx` | 根组件,编排全局状态与布局 | +| `SummaryCards` | `components/SummaryCards.tsx` | 总览统计卡片(全部/正常/异常) | +| `TargetBoard` | `components/TargetBoard.tsx` | 按分组渲染目标表格列表 | +| `TargetGroup` | `components/TargetGroup.tsx` | 单个分组标题 + PrimaryTable | +| `TargetDetailDrawer` | `components/TargetDetailDrawer.tsx` | 目标详情抽屉(概览/记录 Tab) | +| `TrendChart` | `components/TrendChart.tsx` | Recharts 双轴折线图(耗时/可用率) | +| `StatusDonut` | `components/StatusDonut.tsx` | Recharts 环状图(UP/DOWN 分布) | +| `StatusDot` | `components/StatusDot.tsx` | 圆形状态指示点(绿/红) | +| `StatusBar` | `components/StatusBar.tsx` | 最近采样状态条(多色块) | +| `GroupHeader` | `components/GroupHeader.tsx` | 分组标题(名称 + 统计) | + +### 2.5 新增功能开发步骤 + +以"新增一个详情页面 Tab"为例: + +1. **确认数据需求**:是已有 API 数据还是需要新端点? + - 如有新端点,先在 `src/server/routes/` 添加,参考 [1.3 新增路由步骤](#13-api-路由开发) + - 如有新字段,更新 `src/shared/api.ts` 类型定义 +2. **实现 hooks**:在 `src/web/hooks/useTargetDetail.ts` 中新增 `useQuery`(写好 `queryKey` 和 `enabled` 条件) +3. **编写组件**:在 `src/web/components/` 创建组件文件 + - 在 `TargetDetailDrawer.tsx` 中新增 `` 引用 +4. **编写常量**:如有列定义/排序器/筛选器,放在 `src/web/constants/` +5. **编写测试**:在 `tests/web/` 下添加对应的单元测试 + +### 2.6 样式开发规范 + +前端基于 TDesign React 构建 UI,样式开发遵循以下优先级(从高到低): 1. **使用 TDesign 组件**:布局、间距、排版优先使用 TDesign 组件(如 Space、Divider、Typography) 2. **使用 TDesign 组件 props**:通过组件的 props 参数控制外观(如 `theme`、`variant`、`size`) @@ -251,6 +398,256 @@ runCommandCheck → 收集观测(exitCode/stdout/stderr/durationMs) - **严禁使用 `!important`** - 颜色统一使用 TDesign CSS tokens(`--td-success-color`、`--td-error-color`、`--td-warning-color` 等),不使用硬编码色值 +**styles.css 组织**: + +- 自定义 CSS 变量(如可用率渐变色 `--avail-0` ~ `--avail-9`)定义在 `:root` 中 +- 布局类(`.dashboard`、`.dashboard-header`)定义全局页面结构 +- 组件修饰类(`.status-dot--up`、`.latency-ok`)为自定义视觉组件提供样式变体 +- TDesign 表格行高亮(`.row-down`)通过 `rowClassName` prop 应用 + +### 2.7 前端测试规范 + +- 测试目录:`tests/web/`,结构对应 `src/web/` +- 重点测试 **constants/** 中的纯函数(排序器、筛选器、颜色阈值等) +- 使用 `bun:test` 框架 + +--- + +## 三、项目运行、集成与打包 + +### 3.1 开发期运行 + +#### 同时启动前后端 + +```bash +bun run dev probes.yaml +``` + +`scripts/dev.ts` 通过 `Bun.spawn` 同时启动两个子进程: + +``` +bun run dev probes.yaml +├── bun run dev:server probes.yaml → Bun HTTP 后端(默认 3000 端口) +└── bun run dev:web → Vite 前端开发服务器(5173 端口) +``` + +- 任一子进程退出会导致整体退出 +- `SIGINT`/`SIGTERM` 信号会同时终止两个子进程 +- `BACKEND_PORT` 环境变量可覆盖后端端口 + +#### 分别启动 + +```bash +# 启动后端(含 watch 模式自动重启) +bun run dev:server probes.yaml + +# 另开终端启动前端 +bun run dev:web +``` + +### 3.2 前后端集成方式 + +#### 开发期代理 + +Vite 配置了开发代理(`vite.config.ts`): + +```typescript +server: { + proxy: { + "/api": { + target: `http://127.0.0.1:${backendPort}`, + changeOrigin: true, + }, + }, +} +``` + +前端访问 `/api/*` 时,Vite 开发服务器自动转发到后端 `http://127.0.0.1:${backendPort}`,无需 CORS 配置。 + +前端开发地址为 `http://127.0.0.1:5173`(严格端口 `strictPort: true`)。 + +后端在开发模式下不提供静态资源服务,访问 `http://127.0.0.1:3000` 会提示"请通过 Vite 前端地址访问"。 + +#### 生产期集成 + +生产可执行文件是单体应用:前端静态资源嵌入 binary,后端同时提供 API 和静态文件服务。 + +``` +./dist/dial-server probes.yaml + +启动后: + 访问 http://127.0.0.1:3000/ → 返回前端 SPA(index.html) + 访问 http://127.0.0.1:3000/api/* → 返回后端 API + 访问 /assets/* → 返回带不可变缓存的静态资源 +``` + +SPA fallback 逻辑(`src/server/static.ts`): +- `/` → index.html +- 匹配 `/assets/*` → 返回对应文件(未匹配则 404) +- 其他路径(如 `/dashboard`)→ fallback 到 index.html(SPA 路由) + +### 3.3 构建打包 + +#### 构建命令 + +```bash +bun run build +``` + +#### 构建流程详解 + +`scripts/build.ts` 执行以下步骤: + +``` +1. vite build + ├── 入口:src/web/index.html + └── 输出:dist/web/(index.html + assets/) + +2. 生成 .build/static-assets.ts(临时文件) + ├── import Vite 产物为 Bun.file + └── 导出 staticAssets: StaticAssets 对象 + +3. 生成 .build/server-entry.ts(临时文件) + └── import 后端入口模块 + staticAssets,作为 Bun.build 入口 + +4. Bun.build({ compile, minify, sourcemap: "linked" }) + └── 输出:dist/dial-server(单文件可执行 binary) +``` + +#### 产物 + +| 产物 | 用途 | +| ---------------------------- | ------------------------ | +| `dist/dial-server` | 生产可执行文件 | +| `dist/web/` | Vite 构建产物(中间产物) | +| `.build/` | 临时生成文件(构建后清理) | + +#### 构建参数 + +| 环境变量 | 说明 | +| ------------------- | ----------------------------------------- | +| `BUN_TARGET`/`BUILD_TARGET` | 交叉编译目标平台(如 `bun-linux-x64`) | + +#### 运行可执行文件 + +```bash +./dist/dial-server probes.yaml +``` + +#### 清理 + +```bash +bun run clean +# 清理 .build/ 缓存和 *.bun-build 临时文件 +``` + +### 3.4 开发工作流 + +#### 日常开发循环 + +```bash +bun run dev probes.yaml # 启动开发环境 +# 修改代码 → Vite HMR(前端)/ bun --watch(后端自动重启) +bun run check # 提交前运行完整质量检查 +``` + +#### 完整验证流程 + +```bash +bun run verify +# = bun run check + bun run build + bun run test:smoke +``` + +`verify` 适合 CI 或正式提交前,会完整验证类型检查、lint、格式、单元测试、构建、smoke test。 + +### 3.5 Smoke Test + +```bash +bun run test:smoke +``` + +`scripts/smoke.ts` 构建后验证流程: + +1. 动态分配空闲端口 +2. 用临时配置文件启动 `dist/dial-server` +3. 等待健康检查通过 +4. 验证所有 API 端点返回正确数据 +5. 验证静态资源服务(含 SPA fallback 和 404 处理) +6. 验证安全 headers +7. 测试结束清理临时目录和进程 + +### 3.6 脚本说明 + +| 脚本 | 文件 | 说明 | +| ----------------------- | ------------------ | ---------------------------------- | +| `bun run dev` | `scripts/dev.ts` | 同时启动前后端开发服务 | +| `bun run build` | `scripts/build.ts` | Vite 构建 + Bun 编译可执行文件 | +| `bun run test:smoke` | `scripts/smoke.ts` | 构建后的端到端验证 | +| `bun run clean` | `scripts/clean.ts` | 清理构建缓存与临时文件 | + +### 3.7 环境变量 + +| 变量 | 用途 | 默认值 | +| ----------------------- | ------------------------------------------- | ------ | +| `PORT`/`BACKEND_PORT` | 后端监听端口(开发期 Vite 代理目标、生产期监听端口) | `3000` | +| `BUN_TARGET`/`BUILD_TARGET` | 交叉编译目标平台(仅在 `bun run build` 时有效) | 当前平台 | + +### 3.8 项目配置文件 + +| 文件 | 用途 | +| --------------------- | -------------------------------------- | +| `package.json` | 项目信息、脚本、依赖声明 | +| `tsconfig.json` | TypeScript 配置(ESNext 模块、严格模式)| +| `vite.config.ts` | Vite 开发代理与构建配置 | +| `eslint.config.js` | ESLint 规则(含前端不得 import server 的检查) | +| `.prettierrc.json` | Prettier 格式化规则(`printWidth: 120`) | +| `.prettierignore` | Prettier 排除路径 | +| `probes.example.yaml` | 配置文件示例 | +| `opencode.json` | OpenCode 工具配置(TDesign MCP server) | + +### 3.9 依赖管理 + +- **包管理器**:仅使用 `bun`,禁止使用 npm、pnpm、yarn +- **安装依赖**:`bun install` +- **运行工具**:使用 `bunx`,禁止使用 `npx`、`pnpx` +- **锁文件**:`bun.lock` + +### 3.10 目录约定 + +| 目录 | 约定 | +| --------------- | ------------------------------------------ | +| `src/server/` | 后端代码,不能 import `src/web/` | +| `src/web/` | 前端代码,不能 import `src/server/` | +| `src/shared/` | 前后端共享类型,双向可引用 | +| `scripts/` | 独立运行脚本,可 import 项目源码 | +| `tests/` | 测试目录,结构镜像 src 目录 | +| `dist/` | 构建产物(gitignore) | +| `.build/` | 构建临时文件(gitignore) | +| `openspec/` | OpenSpec 变更管理与规格文档 | +| `data/` | 默认数据目录(gitignore,运行期生成 SQLite)| + +--- + +## 代码质量 + +```bash +bun run lint # ESLint 检查 +bun run format:check # Prettier 格式检查 +bun run format # Prettier 自动格式化 +bun run typecheck # TypeScript 类型检查 +bun test # 运行所有测试 +bun run check # 一键运行 typecheck + lint + format:check + test +``` + +`check` 是日常开发推荐的质量检查命令。 + +## 测试 + +```bash +bun run check # 日常开发(类型检查 + lint + 格式 + 单元测试) +bun run verify # 完整验证(check + 构建 + smoke test) +``` + ## 已知限制 当前不做告警通知、数据自动清理、拨测目标动态增删、认证鉴权和分布式部署。Command 类型拨测不支持 Windows 环境。