iter: d06 新增 ua verify 门禁(清单/文档/演示锚点一致性)
This commit is contained in:
@@ -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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user