From 65ddaae00fc68c116538300575347352f2557396 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Mon, 10 Aug 2026 16:36:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(scripts):=20=E6=A0=A1=E9=AA=8C=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=8E=BB=E5=81=87=E9=98=B3=E6=80=A7=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=89=8D=E7=AB=AF/=E4=BF=AE=E5=A4=8D=E7=B1=BB=E6=9D=A1?= =?UTF-8?q?=E7=9B=AE=E5=90=88=E6=B3=95=E5=8C=96+=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E4=B8=8D=E8=AF=AF=E5=88=A4=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGE_TYPES 扩:修复/前端缺陷/前端优化/前端修复(文件名+frontmatter 同步);纯前端条目 issue 段允许 frontend 字面量、backend_status 允许 not_required(接口类仍禁) - E_PLACEHOLDER 只拦双花括号/TODO/待补充,不再误杀 {orderId} 等 REST 路径参数 - E_SECTIONS 仅约束接口类条目,章节名宽匹配(变更接口/变更清单/接口变化/行为变化等) - 核心门禁保持硬:接口类必须 backend_status=deployed 才允许发布(E_BACKEND_PENDING) - 背景:规则假阳性致 CI 长期常红被忽略,真违规(未部署即推送)淹没其中;npm test 46/46 绿 Co-Authored-By: Claude Fable 5 --- scripts/validate-changelog-filenames.mjs | 8 +++---- scripts/validate-changelog-frontmatter.mjs | 27 +++++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/validate-changelog-filenames.mjs b/scripts/validate-changelog-filenames.mjs index 3f70f7d..1d62a57 100644 --- a/scripts/validate-changelog-filenames.mjs +++ b/scripts/validate-changelog-filenames.mjs @@ -8,7 +8,7 @@ const CONTROLLED_ROOTS = new Map([ ['changelogs-v2', '管理后台'], ['changelogs-v2-mp', '小程序端'], ]); -const CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口']); +const CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口', '修复', '前端缺陷', '前端优化', '前端修复']); const PATH_ALIAS_MANIFEST = 'changelog-path-aliases.json'; function ruleError(code, path, message, expected) { @@ -180,7 +180,7 @@ export function validateChangelogPath(inputPath, now = new Date()) { 'E_CHANGE_TYPE', changelogPath, `变更类型为 ${actualType || '(缺失)'}`, - '新增接口 / 修改接口 / 删除接口', + [...CHANGE_TYPES].join(' / '), )); } @@ -203,8 +203,8 @@ export function validateChangelogPath(inputPath, now = new Date()) { if (day !== expectedDate.day) { errors.push(ruleError('E_DAY', changelogPath, `日前缀为 ${day}`, expectedDate.day)); } - if (!/^[1-9]\d*$/.test(issue)) { - errors.push(ruleError('E_ISSUE', changelogPath, `Issue 号为 ${issue || '(缺失)'}`, '不带 # 的十进制正整数')); + if (!/^[1-9]\d*[a-z]?$/.test(issue) && issue !== 'frontend') { + errors.push(ruleError('E_ISSUE', changelogPath, `Issue 号为 ${issue || '(缺失)'}`, '不带 # 的十进制正整数(可带一位小写字母后缀),或纯前端条目用 frontend')); } if (title.trim() === '') { errors.push(ruleError('E_TITLE', changelogPath, '业务标题为空', '非空业务标题')); diff --git a/scripts/validate-changelog-frontmatter.mjs b/scripts/validate-changelog-frontmatter.mjs index 7c03af2..63fe26b 100644 --- a/scripts/validate-changelog-frontmatter.mjs +++ b/scripts/validate-changelog-frontmatter.mjs @@ -19,10 +19,12 @@ const FRONTEND_STATUSES = new Set([ 'released', 'verified', ]); -const BACKEND_STATUSES = new Set(['pending', 'tested', 'deployed']); +const BACKEND_STATUSES = new Set(['pending', 'tested', 'deployed', 'not_required']); const GATEWAY_STATUSES = new Set(['pending', 'verified', 'not_required']); const CONSUMERS = new Set(['admin', 'mp', 'internal', 'multiple']); -const CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口']); +const CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口', '修复', '前端缺陷', '前端优化', '前端修复']); +// 接口契约类条目:后端必须已部署测试服并实测(E_BACKEND_PENDING 硬门禁)才允许发布 +const API_CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口']); const REQUIRED_KEYS = [ 'schema', 'ticket', @@ -180,8 +182,11 @@ export function validateV2Document(file, text, { requireV2 = false } = {}) { if (!GATEWAY_STATUSES.has(metadata.gateway_status)) { errors.push(ruleError('E_GATEWAY_STATUS', file, `gateway_status 非法: ${metadata.gateway_status || '(空)'}`)); } - if (metadata.backend_status !== 'deployed') { - errors.push(ruleError('E_BACKEND_PENDING', file, '发布的 changelog 必须是 backend_status=deployed')); + if (API_CHANGE_TYPES.has(metadata.change_type) && metadata.backend_status === 'not_required') { + errors.push(ruleError('E_BACKEND_STATUS', file, '接口类 changelog 不允许 backend_status=not_required')); + } + if (!['deployed', 'not_required'].includes(metadata.backend_status)) { + errors.push(ruleError('E_BACKEND_PENDING', file, '发布的 changelog 必须是 backend_status=deployed(未部署测试服并实测前禁止推送;纯前端条目用 not_required)')); } if (metadata.gateway_status === 'pending') { errors.push(ruleError('E_GATEWAY_PENDING', file, '发布的 changelog 不能保留 gateway_status=pending')); @@ -200,15 +205,21 @@ export function validateV2Document(file, text, { requireV2 = false } = {}) { if (issue && metadata.ticket !== issue) { errors.push(ruleError('E_TICKET_MISMATCH', file, `ticket=${metadata.ticket} 与文件名 Issue=${issue} 不一致`)); } - const filenameType = /-(新增接口|修改接口|删除接口)-(?:管理后台|小程序端)\.md$/.exec(filename)?.[1]; + const filenameType = /-(新增接口|修改接口|删除接口|修复|前端缺陷|前端优化|前端修复)-(?:管理后台|小程序端)\.md$/.exec(filename)?.[1]; if (filenameType && metadata.change_type !== filenameType) { errors.push(ruleError('E_TYPE_MISMATCH', file, `change_type=${metadata.change_type} 与文件名=${filenameType} 不一致`)); } - if (/\{[^{}\n]+\}|\bTODO\b|待补充/i.test(body)) { + // 单花括号是 REST 路径参数惯例({orderId}),只拦真正的模板残留:双花括号、TODO、待补充 + if (/\{\{[^{}\n]+\}\}|\bTODO\b|待补充/i.test(body)) { errors.push(ruleError('E_PLACEHOLDER', file, '正文仍有 TODO、待补充或模板占位符')); } - if (!body.includes('## 变更接口') || !body.includes('## 验证证据')) { - errors.push(ruleError('E_SECTIONS', file, '正文缺少“变更接口”或“验证证据”章节')); + // 接口类条目必须有接口清单章节与验证章节;修复/前端类条目结构自由,不强制 + if (API_CHANGE_TYPES.has(metadata.change_type)) { + const hasApiSection = /^##[^\n]*(变更接口|变更清单|变更内容|接口详情|变更点|接口变化|行为变化)/m.test(body); + const hasEvidenceSection = /^##[^\n]*(验证|测试|复现|证据)/m.test(body); + if (!hasApiSection || !hasEvidenceSection) { + errors.push(ruleError('E_SECTIONS', file, '接口类正文缺少接口清单(变更接口/变更清单)或验证(验证证据/测试)章节')); + } } return errors; }