pbl_evidence/scripts/check_crud_endpoints.py

106 lines
3.9 KiB
Python
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
# -*- coding: utf-8 -*-
"""CRUD 端点存在性自检QC 硬门禁 #2 的 CI 化)。
json/*.json 里 `params.editable` / `new|update|delete_data_url` /
`browserfields.alters[].dataurl` 引用的每个 `../api/xxx.dspy`,都必须在
`wwwroot/api/xxx.dspy` 真实落盘 —— 少一个文件,前端点了就是 404。
用法:
python3 scripts/check_crud_endpoints.py # 只核对手写 api/ 端点
python3 scripts/check_crud_endpoints.py --generated # 连 xls2ui 生成物一起核(部署机用)
退出码0=全部命中1=存在缺失端点。不连库、不依赖第三方包,可直接放 CI。
"""
import argparse
import io
import json
import os
import re
import sys
ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
JSON_DIR = os.path.join(ROOT, 'json')
API_DIR = os.path.join(ROOT, 'wwwroot', 'api')
# 匹配 ../api/xxx.dspy 与 /pbl_evidence/api/xxx.dspy 两种写法
URL_RE = re.compile(r'api/([A-Za-z0-9_]+\.dspy)')
def referenced_endpoints():
"""返回 {dspy 文件名: [引用它的 json 文件, ...]}。"""
refs = {}
if not os.path.isdir(JSON_DIR):
return refs
for fn in sorted(os.listdir(JSON_DIR)):
if not fn.endswith('.json'):
continue
path = os.path.join(JSON_DIR, fn)
try:
json.loads(io.open(path, encoding='utf-8').read())
except Exception as exc:
refs.setdefault('__INVALID_JSON__:%s' % fn, []).append(str(exc))
continue
text = io.open(path, encoding='utf-8').read()
for name in URL_RE.findall(text):
refs.setdefault(name, []).append('json/' + fn)
return refs
def generated_endpoints():
"""xls2ui 为每个 CRUD 定义生成 wwwroot/{alias}/{get,add,update,delete}_{alias}.dspy。
注意落点是 **wwwroot/{alias}/** 子目录(不是 wwwroot/api/)——那是构建产物目录,
开发仓库被 .gitignore 排除,只有部署机跑过 build.sh 后才存在。
"""
out = []
for fn in sorted(os.listdir(JSON_DIR)):
if not fn.endswith('.json'):
continue
d = json.loads(io.open(os.path.join(JSON_DIR, fn), encoding='utf-8').read())
alias = d.get('alias') or d.get('tblname')
if not alias:
continue
for op in ('get', 'add', 'update', 'delete'):
out.append(os.path.join(alias, '%s_%s.dspy' % (op, alias)))
out.append(os.path.join(alias, 'index.ui'))
return out
def main():
ap = argparse.ArgumentParser(description='verify CRUD-referenced .dspy endpoints exist')
ap.add_argument('--generated', action='store_true',
help='also check xls2ui-generated get_/add_/update_/delete_{alias}.dspy '
'(build artifacts; run on the deployment host after build.sh)')
args = ap.parse_args()
refs = referenced_endpoints()
bad = []
for name, srcs in sorted(refs.items()):
if name.startswith('__INVALID_JSON__:'):
bad.append('%s 不是合法 JSON%s' % (name, srcs[0]))
continue
if not os.path.exists(os.path.join(API_DIR, name)):
bad.append('%s 缺失(被 %s 引用)' % (name, ', '.join(srcs)))
gen_missing = []
if args.generated:
for name in generated_endpoints():
if not os.path.exists(os.path.join(ROOT, 'wwwroot', name)):
gen_missing.append(name)
print('[pbl_evidence] json/ 引用的端点: %d' % len(refs))
for line in bad:
print('MISSING ' + line)
if args.generated:
print('[pbl_evidence] xls2ui 生成物端点: %d' % len(generated_endpoints()))
for name in gen_missing:
print('MISSING(gen) wwwroot/' + name)
ok = not bad and not gen_missing
print('[pbl_evidence] crud endpoints: %s' % ('PASS' if ok else 'FAIL'))
return 0 if ok else 1
if __name__ == '__main__':
sys.exit(main())