Files
blog/scripts/build-perf/config-check.py
T
wait4xandGLM-5.3 8274180f81 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
2026-11-02 19:16:43 +08:00

103 lines
3.1 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())