diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d8d3b44..342d00c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1071,6 +1071,8 @@ bun run verify `verify` 适合 CI 或正式提交前,会完整验证类型检查、lint、格式、单元测试和生产构建。 +**构建 code generation 约定**:`scripts/build-common.ts` 中的 `toImportSpecifier()` 将文件系统相对路径转换为 ESM import specifier,输出在所有平台上都必须使用 `/` 分隔符,不能包含 Windows 反斜杠。跨平台路径测试不得使用当前平台的 `path.sep` 伪装其他平台行为,应使用 `node:path.win32` 或等价注入方式显式模拟目标平台语义。 + ### 3.6 Executable/E2E 验证 原 `scripts/smoke.ts` 覆盖过薄,已从当前工作流移除。后续如需验证 production executable 的 API、静态资源服务、SPA fallback 行为,应重新设计独立的 executable/E2E 测试。 diff --git a/openspec/specs/static-asset-embedding/spec.md b/openspec/specs/static-asset-embedding/spec.md index 7417891..c4f2782 100644 --- a/openspec/specs/static-asset-embedding/spec.md +++ b/openspec/specs/static-asset-embedding/spec.md @@ -56,3 +56,14 @@ #### 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` diff --git a/openspec/specs/windows-test-compat/spec.md b/openspec/specs/windows-test-compat/spec.md index ebdd5c1..d767bb2 100644 --- a/openspec/specs/windows-test-compat/spec.md +++ b/openspec/specs/windows-test-compat/spec.md @@ -56,3 +56,14 @@ probes.example.yaml 中的 cmd 类型示例 SHALL 使用跨平台命令(如 `b #### Scenario: ICMP checker 测试使用 platform 注入 - **WHEN** 在 Windows 上运行 ICMP checker 测试,mock 的 stdout 为 Unix 格式 - **THEN** 测试 SHALL 通过 `new IcmpChecker("linux")` 构造 checker 实例,使 parsePingOutput 使用 Unix 解析器,确保测试在所有平台上通过 + +### Requirement: 路径语义测试 SHALL 显式模拟目标平台 +路径相关跨平台测试 SHALL 使用显式平台路径工具、依赖注入或等价测试 seam 表达目标平台语义,MUST NOT 依赖当前运行平台的 `path.sep` 伪装其他平台行为。 + +#### Scenario: 在非 Windows 平台验证 Windows 路径分隔符 +- **WHEN** 测试需要验证 Windows 文件系统路径中的反斜杠会被转换为平台无关输出 +- **THEN** 测试 SHALL 使用 `node:path.win32` 或等价注入方式生成 Windows relative path,并断言输出不包含 `\\` + +#### Scenario: 验证 import specifier 输出 +- **WHEN** 测试验证构建脚本生成的 ESM import specifier +- **THEN** 测试 SHALL 断言输出使用 `/` 分隔,MUST NOT 使用 `path.sep` 禁止当前平台的合法 `/` 字符 diff --git a/scripts/build-common.ts b/scripts/build-common.ts index e312592..aa8a7ad 100644 --- a/scripts/build-common.ts +++ b/scripts/build-common.ts @@ -1,5 +1,5 @@ import { readdir, rm, writeFile } from "node:fs/promises"; -import { join, relative, sep } from "node:path"; +import { join, relative } from "node:path"; import { fileURLToPath } from "node:url"; import { validateVersion } from "./bump-version-logic"; @@ -104,8 +104,12 @@ export async function scanDir(dir: string, prefix: string): Promise { return paths; } -export function toImportSpecifier(fromDir: string, targetPath: string) { - return relative(fromDir, targetPath).split(sep).join("/"); +export function toImportSpecifier( + fromDir: string, + targetPath: string, + relativePath: (from: string, to: string) => string = relative, +) { + return relativePath(fromDir, targetPath).replaceAll("\\", "/"); } export async function viteBuild() { diff --git a/tests/scripts/build-common.test.ts b/tests/scripts/build-common.test.ts index 9d947d9..3c00063 100644 --- a/tests/scripts/build-common.test.ts +++ b/tests/scripts/build-common.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; -import { join, sep } from "node:path"; +import { join, win32 } from "node:path"; import { scanDir, toImportSpecifier } from "../../scripts/build-common"; @@ -65,10 +65,8 @@ describe("toImportSpecifier", () => { }); test("Windows 路径分隔符转换为正斜杠", () => { - const fromDir = ["C:", "project", ".build"].join(sep); - const targetPath = ["C:", "project", "dist", "web", "assets", "app.js"].join(sep); - const result = toImportSpecifier(fromDir, targetPath); + const result = toImportSpecifier("C:\\project\\.build", "C:\\project\\dist\\web\\assets\\app.js", win32.relative); expect(result).toBe("../dist/web/assets/app.js"); - expect(result).not.toContain(sep); + expect(result).not.toContain("\\"); }); });