- 合并 20+ 细粒度 spec 为粗粒度主题规范:dashboard、data-store、probe-engine、probe-api、probe-config 等 - 删除完全冗余规范:data-retention(被 probe-engine+data-store 覆盖)、backend-code-quality(DEVELOPMENT.md 已记录) - 补充 http-checker 规范至完整标准(配置+执行+expect+校验+observation),匹配代码 440 行实现 - 清理 tcp/udp/llm checker 规范中已废弃 defaults 配置段的残留 Scenario - 清理 checker-cohesion-structure 中的实现路径引用(src/server/...) - 统一所有 spec 格式(## Purpose 开头,去除 # Capability/Title 形式) - 更新 prompt-spec-review.md 审查提示文档
152 lines
7.8 KiB
Markdown
152 lines
7.8 KiB
Markdown
## Purpose
|
||
|
||
定义将 Vite 构建的前端资源通过 code generation 嵌入 Bun 后端、静态资源服务与 Content-Type 处理、打包为单个 standalone executable 的生产构建、运行配置和验证要求。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: 生产构建顺序
|
||
生产构建 MUST 通过三步流水线完成:Vite 前端构建 → code generation → Bun compile。
|
||
|
||
构建步骤 1-2(Vite build、code generation)的逻辑 SHALL 从 `scripts/build-common.ts` 导入,`scripts/build.ts` 只保留当前平台编译和编排逻辑。
|
||
|
||
#### Scenario: 运行生产构建
|
||
- **WHEN** 开发者运行生产构建命令
|
||
- **THEN** 系统 MUST 依次执行 Vite build、资源导入 code generation、Bun.build compile,最终输出单可执行文件
|
||
|
||
#### Scenario: Vite 构建失败
|
||
- **WHEN** Vite build 步骤失败
|
||
- **THEN** 系统 MUST 停止后续步骤,不生成 code generation 文件或 executable
|
||
|
||
#### Scenario: Bun compile 失败
|
||
- **WHEN** Bun.build compile 步骤失败
|
||
- **THEN** 系统 MUST 清理 `.build/` 临时目录,不保留 stale executable
|
||
|
||
#### Scenario: 重构后 build 行为不变
|
||
- **WHEN** `scripts/build.ts` 改为从 `scripts/build-common.ts` 导入共享构建函数
|
||
- **THEN** `bun run build` 的产出文件路径(`dist/dial-server`)、构建步骤顺序和错误处理行为 SHALL 与重构前完全一致
|
||
|
||
### Requirement: 单 executable 输出
|
||
生产构建 SHALL 输出一个 standalone executable,其中包含 Bun 后端和通过 `import with { type: "file" }` 嵌入的 Vite 前端产出。
|
||
|
||
#### Scenario: 在目标机器运行 executable
|
||
- **WHEN** 生成的 executable 在兼容目标平台上运行
|
||
- **THEN** 它 SHALL 启动全栈应用,且不要求目标机器安装 Node.js、Bun 或 `node_modules`
|
||
|
||
#### Scenario: 服务嵌入的前端
|
||
- **WHEN** executable 收到前端根路径请求
|
||
- **THEN** 它 SHALL 通过内嵌的 Vite 构建产出服务前端资源,且不需要外部 `dist/` 目录
|
||
|
||
#### Scenario: 服务嵌入 API 和页面
|
||
- **WHEN** 生成的 executable 启动,且浏览器打开前端根路径
|
||
- **THEN** 页面 SHALL 展示同一个 executable 进程中 `/api/summary` 和 `/api/targets` 返回的数据
|
||
|
||
### Requirement: 构建中间产物管理
|
||
构建流程 SHALL 使用 `.build/` 临时目录存放 code generation 产物,构建完成后清理。
|
||
|
||
清理逻辑 SHALL 定义在 `scripts/build-common.ts` 中,供 `build.ts` 和 `release.ts` 共用。
|
||
|
||
#### Scenario: 构建成功后清理中间产物
|
||
- **WHEN** 生产构建成功完成并输出 executable
|
||
- **THEN** 系统 SHALL 通过 `build-common.ts` 中的 `cleanup()` 函数删除 `.build/` 临时目录
|
||
|
||
#### Scenario: 构建失败时清理中间产物
|
||
- **WHEN** 生产构建在 Bun compile 步骤失败
|
||
- **THEN** 系统 SHALL 通过 `build-common.ts` 中的 `cleanup()` 函数删除 `.build/` 临时目录和 stale executable
|
||
|
||
### Requirement: 外部运行时配置
|
||
executable MUST 将环境相关运行时配置保留在嵌入的前端和 server bundle 之外。
|
||
|
||
#### Scenario: 修改监听端口
|
||
- **WHEN** 操作者修改受支持的 port 配置
|
||
- **THEN** 同一个 executable SHALL 在不重新构建的情况下监听新端口
|
||
|
||
#### Scenario: 缺少可选配置
|
||
- **WHEN** 可选运行时配置被省略
|
||
- **THEN** executable SHALL 使用文档化的默认值
|
||
|
||
### Requirement: 构建验证
|
||
项目 SHALL 提供 `verify` 命令执行质量检查和生产构建;原 smoke test 暂时移除,executable 路由验证由后续变更重新设计。
|
||
|
||
#### Scenario: 完整验证重新构建 executable
|
||
- **WHEN** 开发者运行完整验证命令
|
||
- **THEN** 系统 MUST 先执行质量检查,再基于当前源码执行生产构建
|
||
|
||
#### Scenario: 验证失败
|
||
- **WHEN** 质量检查或构建阶段失败
|
||
- **THEN** 验证 SHALL 使命令失败
|
||
|
||
### Requirement: 生产构建版本固化
|
||
生产构建 SHALL 在 code generation 阶段读取 `package.json.version`,并将该版本号固化到生成的 production server entry 中,使 standalone executable 能在运行时返回构建时版本。
|
||
|
||
#### Scenario: 构建时注入版本号
|
||
- **WHEN** 开发者运行生产构建命令
|
||
- **THEN** 构建脚本 SHALL 在生成 `.build/server-entry.ts` 时写入当前 `package.json.version` 对应的版本字面量
|
||
|
||
#### Scenario: executable 不依赖外部 package.json 返回版本
|
||
- **WHEN** 生成的 standalone executable 在目标机器运行且外部不存在项目根目录 `package.json`
|
||
- **THEN** `GET /api/meta` SHALL 仍返回构建时固化的 `version`
|
||
|
||
#### Scenario: 升迁后重新构建
|
||
- **WHEN** 开发者先升迁 `package.json.version` 再运行生产构建命令
|
||
- **THEN** 新生成的 standalone executable SHALL 返回升迁后的版本号
|
||
|
||
### Requirement: 构建时资源扫描与 Code Generation
|
||
构建脚本 SHALL 在 Vite build 完成后扫描 `dist/web/` 目录,自动生成 TypeScript 文件,为每个静态资源创建 `import ... with { type: "file" }` 声明。
|
||
|
||
#### Scenario: 生成资源导入文件
|
||
- **WHEN** 构建脚本扫描 `dist/web/` 目录
|
||
- **THEN** 系统 SHALL 在 `.build/static-assets.ts` 中为每个文件生成 `import fN from "<path>" with { type: "file" }` 语句,并导出 `StaticAssets` 对象
|
||
|
||
#### Scenario: StaticAssets 对象结构
|
||
- **WHEN** `static-assets.ts` 被生成
|
||
- **THEN** 导出的对象 SHALL 包含 `indexHtml: Blob` 和 `files: Record<string, Blob>` 两个字段,其中 files 的 key 为 URL 路径(如 `/assets/index-a1b2c3.js`)
|
||
|
||
#### Scenario: 生成 production server entry
|
||
- **WHEN** 构建脚本生成资源导入文件后
|
||
- **THEN** 系统 SHALL 在 `.build/server-entry.ts` 中生成 production 入口,import bootstrap、config 和 staticAssets 并调用 bootstrap
|
||
|
||
### Requirement: 运行时静态资源服务
|
||
系统 SHALL 提供 `serveStaticAsset` 函数,根据请求路径从 StaticAssets 中查找并返回对应资源。
|
||
|
||
#### Scenario: 请求根路径
|
||
- **WHEN** 请求路径为 `/`
|
||
- **THEN** 系统 SHALL 返回 `indexHtml`,Content-Type 为 `text/html; charset=utf-8`,Cache-Control 为 `no-cache`
|
||
|
||
#### Scenario: 请求已知静态资源
|
||
- **WHEN** 请求路径匹配 `files` 中的某个 key
|
||
- **THEN** 系统 SHALL 返回对应 Blob,Content-Type 根据文件扩展名推断,Cache-Control 为 `public, max-age=31536000, immutable`
|
||
|
||
#### Scenario: 请求未知带扩展名路径
|
||
- **WHEN** 请求路径包含文件扩展名但未匹配任何已知资源
|
||
- **THEN** 系统 SHALL 返回 404 响应
|
||
|
||
#### Scenario: SPA Fallback
|
||
- **WHEN** 请求路径不包含文件扩展名且不以 `/api/` 开头
|
||
- **THEN** 系统 SHALL 返回 `indexHtml`(SPA fallback)
|
||
|
||
### Requirement: Content-Type 推断
|
||
系统 SHALL 根据文件扩展名推断正确的 Content-Type header。
|
||
|
||
#### Scenario: JavaScript 文件
|
||
- **WHEN** 请求路径以 `.js` 或 `.mjs` 结尾
|
||
- **THEN** Content-Type SHALL 为 `text/javascript; charset=utf-8`
|
||
|
||
#### Scenario: CSS 文件
|
||
- **WHEN** 请求路径以 `.css` 结尾
|
||
- **THEN** Content-Type SHALL 为 `text/css; charset=utf-8`
|
||
|
||
#### Scenario: SVG 文件
|
||
- **WHEN** 请求路径以 `.svg` 结尾
|
||
- **THEN** Content-Type SHALL 为 `image/svg+xml`
|
||
|
||
### Requirement: 静态资源 import specifier SHALL 使用平台无关分隔符
|
||
构建时静态资源 code generation SHALL 将文件系统相对路径转换为 ESM import specifier,并确保生成的 import 路径在 Windows、macOS、Linux 开发环境下都使用 `/` 作为分隔符。
|
||
|
||
#### Scenario: Windows 相对路径转换为 import specifier
|
||
- **WHEN** code generation 将 Windows 文件系统相对路径 `..\\dist\\web\\assets\\app.js` 转换为静态资源 import specifier
|
||
- **THEN** 生成的 import specifier SHALL 为 `../dist/web/assets/app.js`,且 MUST NOT 包含 `\\`
|
||
|
||
#### Scenario: POSIX 相对路径保持 import specifier 形式
|
||
- **WHEN** code generation 将 POSIX 文件系统相对路径 `../dist/web/assets/app.js` 转换为静态资源 import specifier
|
||
- **THEN** 生成的 import specifier SHALL 保持为 `../dist/web/assets/app.js`
|