docs(admin): 后台文档与 hint 补齐 + 修复 config.yml 重复键结构缺陷(5g T6)
- docs/管理台.md 扩写 25→156 行(原文要点全保留,131 增 0 删):四集合
总表、块编辑九类(字段+示例 yaml+预览限制)、页面排版覆盖(含整页换字体
的体积成本提醒)、theme 17 旋钮分组约束、首页三字段、开源页六字段 +
include 未命中排查 + README 回落矩阵四级(把 T5 的"相对链接不重写/
徽章不阻断"两条限制落到用户文档)
- docs/写作指南.md:首页可配项补三行、新增「页面块」字段速查节(含 ≤24
块/≤4000 字/未知块跳过/空则回落 intro 的边界)、字体子集化段
(694KB→420KB −40% + subset-fonts.py 命令)、加字流程第④步挂子集化
- 后台 hint 49→64:九类块型说明、tagline/blurb、font 下拉体积提醒、scale
取值范围
- **修 config.yml 结构缺陷(重要)**:typography 对象被插进双语字段的
name: 与 widget: object 之间,导致重复键 widget/fields(后者覆盖前者)→
后台 tagline/blurb 退化成字符串写入、排版选择器字段被劫持。已挪到双语
字段之后,恢复 object+zh/en 结构(重复键检测:修前 4 处 → 修后 0)
- 新增 scripts/build-perf/config-check.py:用自定义 StrictLoader 在解析阶段
对重复键报错(Python 默认 safe_load 静默覆盖是本次漏检根因)+ hint 覆盖
率体检;build.sh 守卫挂载([ -f ] 先行,与既有闸门同级)
验证: 构建 48 页 exit 0;E2E 37/37;content-lint 49 页 0/0/0;对比度闸门
4 项 PASS;config-check 五文件全 OK + 206 字段/64 hint
Spec: 2026-11-02_blog-5g-pages (T6)
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
3e09a362cc
commit
8274180f81
6 files changed
+304
-12
No files matched your search
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
站点 YAML 配置结构校验(2026-11-02 教训驱动)
|
||||
|
||||
为什么需要:Python 默认 yaml.safe_load 对重复键是"后者覆盖前者"的静默行为,
|
||||
收编时用它验收会漏。本包实测 public/admin/config.yml 里 typography 被插进双语
|
||||
字段的 name: 与 widget: object 之间 → 同一 mapping 出现重复键 widget/fields,
|
||||
后台双语编辑器退化成字符串、排版选择器字段被劫持。本脚本用自定义构造器在
|
||||
解析阶段对重复键直接报错,并附带后台 hint 覆盖率体检。
|
||||
|
||||
用法:python3 scripts/build-perf/config-check.py [repo根=.]
|
||||
退出码:0 通过;1 有失败项
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print('[config-check] 缺少 pyyaml:pip install pyyaml')
|
||||
sys.exit(1)
|
||||
|
||||
FILES = [
|
||||
'public/admin/config.yml',
|
||||
'content/site/home.yaml',
|
||||
'content/site/about.yaml',
|
||||
'content/site/opensource.yaml',
|
||||
'content/site/theme.yaml',
|
||||
]
|
||||
|
||||
|
||||
class StrictLoader(yaml.SafeLoader):
|
||||
"""对重复键报错的 SafeLoader(默认行为是静默覆盖)。"""
|
||||
|
||||
|
||||
def _construct_mapping(loader, node, deep=False):
|
||||
seen = set()
|
||||
for key_node, _value_node in node.value:
|
||||
key = loader.construct_object(key_node, deep=deep)
|
||||
if key in seen:
|
||||
raise yaml.YAMLError(
|
||||
f'duplicate key {key!r} at line {key_node.start_mark.line + 1}'
|
||||
)
|
||||
seen.add(key)
|
||||
return yaml.SafeLoader.construct_mapping(loader, node, deep=deep)
|
||||
|
||||
|
||||
StrictLoader.add_constructor(
|
||||
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _construct_mapping
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = Path(sys.argv[1] if len(sys.argv) > 1 else '.')
|
||||
failed = 0
|
||||
loaded = {}
|
||||
|
||||
for rel in FILES:
|
||||
path = root / rel
|
||||
if not path.exists():
|
||||
print(f'[config-check] SKIP {rel}(不存在)')
|
||||
continue
|
||||
try:
|
||||
loaded[rel] = yaml.load(path.read_text(encoding='utf-8'), Loader=StrictLoader)
|
||||
print(f'[config-check] OK {rel}')
|
||||
except yaml.YAMLError as exc:
|
||||
failed += 1
|
||||
first = str(exc).splitlines()[0]
|
||||
print(f'[config-check] FAIL {rel}\n {first}')
|
||||
|
||||
admin = loaded.get('public/admin/config.yml')
|
||||
if admin:
|
||||
fields = hints = 0
|
||||
|
||||
def walk(node):
|
||||
nonlocal fields, hints
|
||||
if isinstance(node, dict):
|
||||
if 'name' in node:
|
||||
fields += 1
|
||||
if 'hint' in node:
|
||||
hints += 1
|
||||
for value in node.values():
|
||||
walk(value)
|
||||
elif isinstance(node, list):
|
||||
for value in node:
|
||||
walk(value)
|
||||
|
||||
walk(admin)
|
||||
print(f'[config-check] 后台字段 {fields} 个,其中带 hint {hints} 个')
|
||||
if hints < 20:
|
||||
failed += 1
|
||||
print('[config-check] FAIL hint 覆盖不足(<20),后台字段缺少中文说明')
|
||||
|
||||
if failed:
|
||||
print(f'[config-check] {failed} 项未通过')
|
||||
return 1
|
||||
print('[config-check] 全部通过')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user