-
Notifications
You must be signed in to change notification settings - Fork 40
ci: validate decision document metadata #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { readdirSync, readFileSync } from "node:fs"; | ||
| import { join, relative, resolve } from "node:path"; | ||
|
|
||
| const root = resolve("docs"); | ||
| const requiredDecisionFields = [ | ||
| "decision-status", | ||
| "created", | ||
| "last-reviewed", | ||
| "applies-to", | ||
| "owner", | ||
| "related-issues", | ||
| "related-prs", | ||
| "supersedes", | ||
| ]; | ||
|
|
||
| function markdownFiles(directory) { | ||
| return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { | ||
| const path = join(directory, entry.name); | ||
| if (entry.isDirectory()) return markdownFiles(path); | ||
| return entry.isFile() && entry.name.endsWith(".md") ? [path] : []; | ||
| }); | ||
| } | ||
|
|
||
| function frontmatter(text) { | ||
| const match = text.match(/^---\n([\s\S]*?)\n---\n/); | ||
| if (!match) return null; | ||
| return new Map( | ||
| match[1] | ||
| .split("\n") | ||
| .map((line) => line.match(/^([\w-]+):\s*(.*)$/)) | ||
| .filter(Boolean) | ||
| .map(([, key, value]) => [key, value.trim()]), | ||
| ); | ||
| } | ||
|
|
||
| const files = markdownFiles(root); | ||
| const decisions = files.filter( | ||
| (file) => | ||
| file.includes(`${join("docs", "decisions")}${"/"}`) && | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Windows 上 join("docs", "decisions") 返回反斜杠路径,这里又拼接 请从明确的 decisions 目录枚举,或统一使用原生路径组件和 basename;补一个 Windows 路径下必须发现记录并拒绝缺失元数据的测试,不能把零记录扫描当成验证成功。 |
||
| !["README.md", "TEMPLATE.md"].includes(file.split("/").pop()), | ||
| ); | ||
| const errors = []; | ||
|
|
||
| for (const file of decisions) { | ||
| const metadata = frontmatter(readFileSync(file, "utf8")); | ||
| if (!metadata) { | ||
| errors.push(`${relative(process.cwd(), file)}: missing YAML frontmatter`); | ||
| continue; | ||
| } | ||
| for (const field of requiredDecisionFields) { | ||
| if (!metadata.get(field)) errors.push(`${relative(process.cwd(), file)}: missing ${field}`); | ||
| } | ||
| } | ||
|
|
||
| if (errors.length) { | ||
| console.error(errors.join("\n")); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| console.log(`docs contract (${decisions.length} decision records)`); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当前 Map 保存的是未经解析的文本,所以
owner: ""、owner: null、owner: # no owner都被第 51 行当成有值;反过来,合法的related-issues:\n - "#198"因首行值为空而被判缺失。对实际脚本的内存 fixture 复现了这四种情况。这会让必填信息遗漏漏过 gate,同时拒绝正常 YAML 写法。请使用可靠的 YAML 解析并校验所需字段的非空值/允许类型,或者明确规定并严格校验受限格式;加上空字符串、null、注释和列表回归测试。保留当前小检查器即可,不必扩为通用文档框架。