LikeC4 CLI 代码生成回归测试实践:守住 `likec4 gen` 的输出目录契约
LikeC4 CLI 代码生成回归测试实践守住likec4 gen的输出目录契约【免费下载链接】likec4Visualize, collaborate, and evolve the software architecture with always actual and live diagrams from your code项目地址: https://gitcode.com/GitHub_Trending/li/likec4本文围绕 LikeC4 仓库中.agents/skills/likec4-cli-codegen-regression/SKILL.md这一技能文档展开系统讲解如何为likec4 gendot / d2 / mermaid / plantuml 等多文件格式生成器编写回归测试保护其核心输出契约所有生成的视图文件都必须落在用户通过--outdir指定的目录内绝不逃逸。读完后你将掌握 LikeC4 CLI 代码生成的路径解析机制、完整的回归矩阵设计、临时工作区测试夹具的搭建方式以及一套可复制的聚焦测试命令从而在修改或审查 CLI 导出器、输出文件命名、include 路径行为时能迅速建立可靠的防护网。核心目标与基本规则LikeC4 的 CLI 提供gen命令族别名generate/codegen可以把 LikeC4 源文件转换为多种第三方格式产物。技能文档把回归测试的首要不变量invariant定义为多文件生成器必须把每一个产出的视图写入到请求的输出目录之下。文档给出的四条基本规则Ground rules是从packages/likec4/src/cli/codegen目录开始工作所有相关代码都在这个子目录下修复 bug 时先写回归测试再做实现test before implementation不仅测试暴露问题的格式还要测试每一个受影响的多文件格式把来自外部 include 路径的视图和Windows 路径视为一等公民级别的边界场景first-class edge cases。为什么这值得专门立一条技能从源码可以印证其风险来源。每个视图模型上携带sourcePath该视图定义所在的.c4文件路径生成器需要用它推导输出文件的相对子目录。这个推导逻辑集中在 handler.ts 的relativeOutputDir函数中第 35-56 行/** * Computes the output subdirectory for a view from its source path. * Normalizes path separators, strips Windows drive prefixes, and removes .. * segments so generated files stay under the requested output directory. */ export function relativeOutputDir(sourcePath: string | undefined): string { if (!sourcePath) { return . } const pathSegments sourcePath.split(/[\\/]/) pathSegments.pop() const segments: string[] [] for (const segment of pathSegments) { const withoutDrivePrefix segment.replace(/^[a-zA-Z]:/, ) switch (withoutDrivePrefix) { case : case .: break case ..: segments.pop() break default: segments.push(withoutDrivePrefix) } } return segments.length 0 ? join(...segments) : . }从源码结构看这个函数正是技能文档所强调的小型路径解析辅助函数它同时处理了正斜杠与反斜杠分隔符、剥离D:这类 Windows 盘符前缀、丢弃.、弹出..段——这正是源相对视图路径绝不允许逃逸输出根这一不变量的实现载体。输出路径不变量Output-path invariants技能文档针对likec4 gen dot、d2、mermaid别名mmd和plantuml别名puml四类多文件命令定义了输出路径不变量--outdir或-o定义了所有生成文件的容纳根containment root源相对视图路径绝不允许逃出输出根绝对源路径、带父级..的相对路径、Windows 盘符根路径都不能在--outdir之外创建文件来自外部 include 路径的视图产物仍应落在--outdir之下对于逃逸或外部源路径不仅要保证容纳性还要明确定义并断言输出的命名契约——只做 containment 检查是不够的单文件生成器如model、react、webcomponent应保持既有的--outfile行为不适用本契约。这些命令的参数在 CLI 定义中可以一一对应。codegen/index.ts 中dot、d2、mermaid、plantuml四个子命令都注册了同一个outdir选项并统一委托给legacyHandler而outdir选项本身在 options.ts 中定义为第 44-51 行export const outdir { alias: [o, output], string: true, desc: output directory, normalize: true, nargs: 1, coerce: resolve, } as const satisfies Optionsnormalize: true与coerce: resolve说明 yargs 会把--outdir规范化并解析为绝对路径因此后续resolve(outdir, ...)的组合是相对该绝对根进行的——这是容纳根语义的命令行侧前提。再看 handler.ts 中两个多文件动作如何消费relativeOutputDirdot 生成dotCodegenAction第 79-118 行对每个视图调用languageServices.viewsService.layouter.dot(...)后用resolve(outdir, relativeOutputDir(view.sourcePath))计算目标子目录输出文件命名为resolve(relativePath, view.id .dot)d2 / mermaid / plantuml 生成multipleFilesCodegenAction第 120-176 行按格式选择扩展名.d2/.mmd/.puml与对应生成器generateD2/generateMermaid/generatePuml路径推导方式与 dot 完全一致输出文件为resolve(relativePath, view.id ext)。也就是说文件命名契约是视图 id 格式扩展名目录结构契约是剥离盘符与..后的源路径段。回归测试必须同时断言这两者这与技能文档containment-only checks are not enough的要求完全吻合。另外注意单文件侧的行为差异views已废弃、model、react、webcomponent等使用--outfile例如 handler.ts 中singleFileCodegenAction的默认输出是工作区下的likec4.generated.ts且会补齐.ts扩展名——这些生成器不参与--outdir容纳契约技能文档将其显式排除在外。回归矩阵证明契约的最小用例集技能文档给出了一张覆盖最小回归矩阵的表格直接作为测试设计的验收清单用例期望结果视图位于项目目录内文件出现在--outdir下视图位于项目内嵌套目录嵌套文件出现在--outdir下视图来自外部 include 路径文件出现在--outdir下而不是落在被包含的源文件旁边源路径相对项目含..被清洗或重新定根到--outdir之下Windows 盘符风格源路径不产生C:盘符根逃逸到--outdir之外的输出绝对源路径不产生绝对根逃逸到--outdir之外需要覆盖的格式为.dot、.d2、.mmd、.puml四种。这张矩阵在仓库的现有测试中已经落地。handler.spec.ts 用it.each(codegenFormats)对四种格式做了参数化夹具中构造了四种典型视图const views { a_view: { id: a_view, sourcePath: ../project-a/projects/model.c4, // 含 .. 的父级相对路径 }, b_view: { id: b_view, sourcePath: model.c4, // 项目根目录内的普通路径 }, drive_view: { id: drive_view, sourcePath: D:/repo/project-a/projects/model.c4, // Windows 盘符 正斜杠 }, drive_backslash_view: { id: drive_backslash_view, sourcePath: D:\\repo\\project-a\\projects\\model.c4, // Windows 盘符 反斜杠 }, }断言部分同时验证了产物在outdir内与产物不在源目录旁两个方向第 119-123 行expect(existsSync(join(outdir, project-a, projects, a_view${ext}))).toBe(true) expect(existsSync(join(outdir, b_view ext))).toBe(true) expect(existsSync(join(outdir, repo, project-a, projects, drive_view${ext}))).toBe(true) expect(existsSync(join(outdir, repo, project-a, projects, drive_backslash_view${ext}))).toBe(true) expect(existsSync(join(tmp, project-a, projects, a_view${ext}))).toBe(false)其中sourcePath: ../project-a/projects/model.c4最终落在outdir/project-a/projects/下——..段被弹出后由后续路径段重新定根D:/...与D:\...两种写法最终都归一为outdir/repo/project-a/projects/盘符前缀被剥离。测试还额外对纯函数做了直接单测第 126-132 行并借助node:path的win32.resolve验证以C:\out为根解析相对输出目录不会产生盘符根逃逸。这正是技能文档所说如果真实夹具无法构造绝对或 Windows 盘符风格的sourcePath就抽出小路径解析辅助函数用纯单元测试覆盖的范例。临时工作区夹具与断言清单技能文档推荐的测试形态test shape是一个临时工作区workspace/ project-a/ likec4.config.ts project-view.c4 shared/ included-view.c4 out/配置project-a的 include 指向../shared然后对每个生成器以--outdir workspace/out运行。LikeC4 的 include 机制由 schema.include.ts 定义paths必须是相对路径数组不允许前导斜杠、盘符或协议例如[../shared, ../common/specs]maxDepth默认 3最大 20fileThreshold默认 30最大 10000。仓库内可参考的实例是发布功能自带的夹具 include-paths/wrapper/likec4.config.json{ name: wrapper, include: { paths: [ ../base ] } }它演示了典型的包装项目通过include.paths把相邻目录的.c4源文件纳入本项目的写法——而这恰是外部 include 路径视图这一边界场景的真实来源被包含文件的sourcePath会带出项目目录之外的路径段若无relativeOutputDir的清洗产物就可能被写到 include 源旁边而不是--outdir里。针对这类夹具技能文档要求的断言清单是期望文件确实存在于workspace/out内逃逸或外部源路径遵循明确选定的输出命名契约即视图 id 扩展名、清洗后的相对目录project-a、shared以及临时工作区根下out之外不存在任何生成文件所有生成路径都通过isInside(outdir, filepath)风格的检查。其中isInside(outdir, filepath)是文档建议抽象出来的断言辅助形式基于path.relative判断结果不以..开头之类的实现即可用于把容纳性检查从散落的existsSync点检查升级为对所有产物路径的系统性验证。聚焦测试命令修改或审查 CLI 代码生成逻辑后技能文档给出一组聚焦验证命令pnpm --filter likec4 test -- codegen pnpm --filter likec4 typecheck pnpm exec dprint check packages/likec4/src/cli/codegen git diff --check第一条用--filter likec4定位到 packages/likec4 工作区包并以codegen作为测试名称过滤器只跑相关用例即 handler.spec.ts 这类代码生成测试第二条确保类型契约如HandlerParams的 discriminated unionviews配outfile、dot | d2 | mermaid | plantuml配outdir未被破坏第三条用 dprint 校验 codegen 源码目录的格式保证只触及该目录的改动符合仓库格式约定第四条检查空白字符问题。文档还特别提示如果改动波及共享的路径工具还要运行拥有这些工具的那些包的测试例如likec4/core中的路径工具测试避免跨包契约回归被漏掉。小结这份技能文档的实质是把likec4 gen多文件输出的容纳 命名双契约固化为一套可执行的回归防线以relativeOutputDir的路径清洗逻辑为核心保护对象用四种格式 × 六类源路径场景的参数化矩阵验证产物位置用临时工作区夹具模拟外部 include 路径用isInside风格检查兜底并以pnpm --filter likec4 test -- codegen等聚焦命令快速闭环。对维护 CLI 导出器、调整输出命名或处理 include 路径行为的开发者来说这套做法可以直接作为新增生成格式例如未来支持.dot之外的新目标格式时的测试模板。【免费下载链接】likec4Visualize, collaborate, and evolve the software architecture with always actual and live diagrams from your code项目地址: https://gitcode.com/GitHub_Trending/li/likec4创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考