iter: d06 新增 ua verify 门禁(清单/文档/演示锚点一致性)

This commit is contained in:
ui-arch-bot
2026-10-23 16:50:49 +08:00
parent 60c052439f
commit 582318adf1
8 changed files with 76 additions and 13 deletions
+6 -3
View File
@@ -501,7 +501,8 @@
"example": "badge({ label: '成功', variant: 'success' })",
"steps": [
"选择语义色:default 中性、success 成功、warning 警告、danger 危险",
"传入 label 即可,纯展示组件无状态"
"传入 label 即可,纯展示组件无状态",
"状态语义靠 variant 承载,不要自造颜色;纯展示不可交互,需要操作请配 button"
]
},
{
@@ -525,7 +526,8 @@
"example": "alert({ message: '操作可能存在风险', variant: 'warning' })",
"steps": [
"按信息级别选 variant:info 提示、success 成功、warning 警告、danger 错误",
"传入 message 即可,入场自带下滑动画"
"传入 message 即可,入场自带下滑动画",
"需要可关闭或带操作按钮时,外层用 h() 组合 button 自行拼装,alert 只负责语义底色"
]
},
{
@@ -641,7 +643,8 @@
"example": "spinner(); spinner({ size: 26 })",
"steps": [
"数据加载时插入 spinner(),加载完成移除",
"用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)"
"用 size 匹配上下文尺寸(按钮内用小号,整页 loading 用大号)",
"与 skeleton 分工:已知内容结构用 skeleton 占位,只有『在跑』的短等待用 spinner"
]
},
{
+45 -1
View File
@@ -560,6 +560,47 @@ function cmdStyle() {
console.log(`style OK (${files.length} files)`);
}
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
function cmdVerify() {
const issues = [];
const demoPath = path.join(ROOT, 'apps', 'demo', 'main.ts'); // 演示区块定义在源码里(产物是打包后的 JS)
const demoSource = fs.existsSync(demoPath) ? fs.readFileSync(demoPath, 'utf8') : '';
const anchors = new Set(demoSource.match(/sec-[a-z0-9-]+/g) ?? []); // 已定义的区块锚点
if (!demoSource) console.log('verify: 未找到 apps/demo/main.ts,跳过 demo 锚点校验');
for (const pkg of collectManifests()) {
const comps = pkg.docs?.components ?? []; // 清单已被 collectManifests 摊平
const exports = pkg.api?.exports ?? [];
if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`);
const seen = new Set(); // 组件 id 去重(同一清单内不允许重复)
for (const c of comps) {
const tag = `${pkg.name}#${c.id ?? '?'}`;
if (!c.id || !c.title) issues.push(`${tag}: 缺少 id 或 title`);
if (seen.has(c.id)) issues.push(`${tag}: id 重复`);
seen.add(c.id);
if (!c.example) issues.push(`${tag}: 缺少 example`);
if ((c.steps ?? []).length < 3) issues.push(`${tag}: steps 少于 3 条`);
if (!exports.some((e) => e.startsWith(`${c.id}(`) || e.startsWith(`${c.id}.`))) {
issues.push(`${tag}: api.exports 中没有以「${c.id}(」或「${c.id}.」开头的条目`);
}
for (const prop of c.props ?? []) {
if (!prop.n || !prop.t || !prop.d) issues.push(`${tag}: props 条目 ${prop.n ?? '?'} 缺 n/t/d`);
}
if (c.demo && demoSource && !anchors.has(c.demo)) {
issues.push(`${tag}: demo 锚点 ${c.demo} 在 apps/demo/main.ts 中不存在(缺对应 section)`);
}
}
}
if (issues.length) {
for (const it of issues) console.error('verify: ' + it);
console.error(`${issues.length} violation(s)`);
process.exit(1);
}
console.log('verify OK (manifest ↔ docs ↔ demo 一致)');
}
// ---- 入口 ----
const [cmd, ...args] = process.argv.slice(2);
@@ -583,10 +624,13 @@ switch (cmd) {
case 'style':
cmdStyle();
break;
case 'verify':
cmdVerify();
break;
case 'mcp':
cmdMcp();
break;
default:
console.log('usage: ua <index|list|info <name>|docs|style|mcp>');
console.log('usage: ua <index|list|info <name>|docs|style|verify|mcp>');
process.exit(cmd ? 1 : 0);
}