106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
#!/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())
|