iter(g66): verify 反向校验入参接口字段均已登记文档
This commit is contained in:
+8
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T04:31:57.320Z",
|
"generatedAt": "2026-10-26T04:34:05.417Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -598,6 +598,7 @@
|
|||||||
{
|
{
|
||||||
"id": "switchButton",
|
"id": "switchButton",
|
||||||
"title": "开关 Switch",
|
"title": "开关 Switch",
|
||||||
|
"propsType": "SwitchProps",
|
||||||
"demo": "sec-form",
|
"demo": "sec-form",
|
||||||
"props": [
|
"props": [
|
||||||
{
|
{
|
||||||
@@ -1604,6 +1605,11 @@
|
|||||||
"t": "'input' | 'blur' | 'submit'",
|
"t": "'input' | 'blur' | 'submit'",
|
||||||
"d": "校验时机;缺省 blur(输入时自动清已报错误)",
|
"d": "校验时机;缺省 blur(输入时自动清已报错误)",
|
||||||
"df": "blur"
|
"df": "blur"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onReset",
|
||||||
|
"t": "() => void",
|
||||||
|
"d": "重置按钮点击回调;组件不自行清空字段,由调用方重置信号;不传 resetText 不渲染按钮"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])",
|
"example": "const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])",
|
||||||
@@ -1624,6 +1630,7 @@
|
|||||||
{
|
{
|
||||||
"id": "dataTable",
|
"id": "dataTable",
|
||||||
"title": "数据表格 DataTable",
|
"title": "数据表格 DataTable",
|
||||||
|
"propsType": "DataTableProps",
|
||||||
"demo": "sec-table",
|
"demo": "sec-table",
|
||||||
"props": [
|
"props": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
||||||
- `apps/docs`:文档站,纯产物(ua docs 生成)
|
- `apps/docs`:文档站,纯产物(ua docs 生成)
|
||||||
|
|
||||||
**门禁一条龙**:`npm run check` = build + demo + style + typecheck + smoke + index + verify,提交前必过。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、每个文档字段名必须在组件实现源码里真实出现(改名后忘改文档会直接报错)、demo 锚点必须在 `apps/demo/main.ts` 中存在,且该组件必须被 demo 真实调用 `id(` / `id.`),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)与令牌引用有效(`var(--ua-*)` 必须是 tokens.css 已定义或组件自设的变量),核对 README 的 `<!-- ua:components -->` 区块与「分组:」行都覆盖清单组件集合(漏写/多写/重复都报错),并交叉校验 backlog 的 done 条目与 `.ua/iterations.log` 的 done 留痕一一对应。
|
**门禁一条龙**:`npm run check` = build + demo + style + typecheck + smoke + index + verify,提交前必过。`ua verify` 校验清单 ↔ 文档 ↔ 演示一致(docs.components 的 id 必须在 api.exports 有对应条目、props 必须带 n/t/d、每个文档字段名必须在组件实现源码里真实出现(改名后忘改文档会直接报错)、反向也校验 `src/types.ts` 里 `<组件>Props`(可用清单的 `propsType` 覆盖接口名,`false` 表示无入参)每个字段都在文档 props 表登记(新加字段忘了写文档会报错)、demo 锚点必须在 `apps/demo/main.ts` 中存在,且该组件必须被 demo 真实调用 `id(` / `id.`),检查皮肤包样式表无硬编码颜色(`#hex` 一律禁止,必须走 `--ua-*` 令牌)与令牌引用有效(`var(--ua-*)` 必须是 tokens.css 已定义或组件自设的变量),核对 README 的 `<!-- ua:components -->` 区块与「分组:」行都覆盖清单组件集合(漏写/多写/重复都报错),并交叉校验 backlog 的 done 条目与 `.ua/iterations.log` 的 done 留痕一一对应。
|
||||||
|
|
||||||
**变更日志**:`CHANGELOG.md` 由 git 提交史生成(`npm run changelog`),**不进 `npm run check`**——它包含每个提交自身,提交后必然与历史差一条,只能作为「发版前刷新」步骤;`npm run changelog:check` 用于确认是否漂移。
|
**变更日志**:`CHANGELOG.md` 由 git 提交史生成(`npm run changelog`),**不进 `npm run check`**——它包含每个提交自身,提交后必然与历史差一条,只能作为「发版前刷新」步骤;`npm run changelog:check` 用于确认是否漂移。
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T04:31:57.320Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T04:34:05.417Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -866,6 +866,7 @@ const open = signal(false); drawer({ title: '详情', open, width: 360, onClose:
|
|||||||
- (formItem)required:boolean —— 必填:显示红星并自动追加非空规则
|
- (formItem)required:boolean —— 必填:显示红星并自动追加非空规则
|
||||||
- (formItem)rules:FormRule[] —— 校验规则:required / min / max / pattern / validator,按序执行取首个失败
|
- (formItem)rules:FormRule[] —— 校验规则:required / min / max / pattern / validator,按序执行取首个失败
|
||||||
- (formItem)validateOn(默认 blur):'input' | 'blur' | 'submit' —— 校验时机;缺省 blur(输入时自动清已报错误)
|
- (formItem)validateOn(默认 blur):'input' | 'blur' | 'submit' —— 校验时机;缺省 blur(输入时自动清已报错误)
|
||||||
|
- onReset:() => void —— 重置按钮点击回调;组件不自行清空字段,由调用方重置信号;不传 resetText 不渲染按钮
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
|
|||||||
@@ -469,6 +469,7 @@
|
|||||||
{
|
{
|
||||||
"id": "switchButton",
|
"id": "switchButton",
|
||||||
"title": "开关 Switch",
|
"title": "开关 Switch",
|
||||||
|
"propsType": "SwitchProps",
|
||||||
"demo": "sec-form",
|
"demo": "sec-form",
|
||||||
"props": [
|
"props": [
|
||||||
{
|
{
|
||||||
@@ -1475,6 +1476,11 @@
|
|||||||
"t": "'input' | 'blur' | 'submit'",
|
"t": "'input' | 'blur' | 'submit'",
|
||||||
"d": "校验时机;缺省 blur(输入时自动清已报错误)",
|
"d": "校验时机;缺省 blur(输入时自动清已报错误)",
|
||||||
"df": "blur"
|
"df": "blur"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onReset",
|
||||||
|
"t": "() => void",
|
||||||
|
"d": "重置按钮点击回调;组件不自行清空字段,由调用方重置信号;不传 resetText 不渲染按钮"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])",
|
"example": "const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: '用户名', required: true, value: name, rules: [{ min: 2, max: 12 }] }, textField({ value: name }))])",
|
||||||
@@ -1495,6 +1501,7 @@
|
|||||||
{
|
{
|
||||||
"id": "dataTable",
|
"id": "dataTable",
|
||||||
"title": "数据表格 DataTable",
|
"title": "数据表格 DataTable",
|
||||||
|
"propsType": "DataTableProps",
|
||||||
"demo": "sec-table",
|
"demo": "sec-table",
|
||||||
"props": [
|
"props": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -883,6 +883,40 @@ function checkPropsImpl(pkg, comps) {
|
|||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 反向漂移校验:入参接口的每个字段都必须在文档 props 表登记(新加字段忘了写文档会命中) ***/
|
||||||
|
function checkPropsDoc(pkg, comps) {
|
||||||
|
const issues = [];
|
||||||
|
const typesPath = path.join(ROOT, pkg.dir, 'src', 'types.ts'); // 约定:类型集中在 src/types.ts
|
||||||
|
if (!fs.existsSync(typesPath)) return issues;
|
||||||
|
const src = fs.readFileSync(typesPath, 'utf8');
|
||||||
|
const ifaces = new Map(); // 接口基名(已去掉 Props 后缀)→ 字段名数组
|
||||||
|
for (const m of src.matchAll(/export interface (\w+)Props(?:<[^>]*>)?\s*\{([\s\S]*?)\n\}/g)) {
|
||||||
|
const fields = [];
|
||||||
|
for (const line of m[2].split(/\r?\n/)) {
|
||||||
|
const hit = /^\s{2}(\w+)(\?|:)/.exec(line); // 缩进两格的字段声明
|
||||||
|
if (hit) fields.push(hit[1]);
|
||||||
|
}
|
||||||
|
ifaces.set(m[1], fields);
|
||||||
|
}
|
||||||
|
if (!ifaces.size) return issues;
|
||||||
|
const pascal = (id) => id[0].toUpperCase() + id.slice(1);
|
||||||
|
for (const c of comps) {
|
||||||
|
if (!c.id || c.propsType === false) continue; // propsType: false 表示该组件没有入参接口
|
||||||
|
const tag = `${pkg.name}#${c.id}`;
|
||||||
|
const base = String(c.propsType ?? '').replace(/Props$/, '') || pascal(c.id); // 清单可覆盖接口名
|
||||||
|
const fields = ifaces.get(base);
|
||||||
|
if (!fields) {
|
||||||
|
issues.push(`${tag}: 找不到入参接口 ${base}Props(命名不同请在清单用 propsType 指定,无入参写 false)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const documented = new Set((c.props ?? []).map((p) => String(p.n ?? '').replace(/^([^)]*)/, '').replace(/^\.\.\./, '').trim())); // 去掉前缀注记与展开符
|
||||||
|
for (const f of fields) {
|
||||||
|
if (!documented.has(f)) issues.push(`${tag}: 入参接口 ${base}Props 的字段「${f}」未在文档 props 表登记`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
||||||
function cmdVerify() {
|
function cmdVerify() {
|
||||||
const issues = [];
|
const issues = [];
|
||||||
@@ -901,6 +935,7 @@ function cmdVerify() {
|
|||||||
issues.push(...checkSkinCss(pkg));
|
issues.push(...checkSkinCss(pkg));
|
||||||
issues.push(...checkTokenRefs(pkg, tokenVars));
|
issues.push(...checkTokenRefs(pkg, tokenVars));
|
||||||
issues.push(...checkPropsImpl(pkg, comps));
|
issues.push(...checkPropsImpl(pkg, comps));
|
||||||
|
issues.push(...checkPropsDoc(pkg, comps));
|
||||||
}
|
}
|
||||||
if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`);
|
if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`);
|
||||||
|
|
||||||
@@ -940,7 +975,7 @@ function cmdVerify() {
|
|||||||
console.error(`${issues.length} violation(s)`);
|
console.error(`${issues.length} violation(s)`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props 均能落到实现,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致)');
|
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
||||||
|
|||||||
Reference in New Issue
Block a user