Step 6: 契约校验脚本完成
This commit is contained in:
parent
8a21861539
commit
21b9d43709
|
|
@ -1,50 +1,50 @@
|
||||||
// scripts/contract-check.js
|
// 用途:确保每个 HLI 路由文件都有对应的 JSON Schema 且格式合法
|
||||||
// 用途:确保每个 HLI 路由文件都有对应的 JSON Schema 且路径一致
|
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const glob = require('glob');
|
|
||||||
|
|
||||||
const ROUTE_DIR = 'src/routes/hli';
|
const ROUTE_DIR = 'src/routes/hli';
|
||||||
const SCHEMA_DIR = 'src/schemas/hli';
|
const SCHEMA_DIR = 'src/schemas/hli';
|
||||||
|
|
||||||
// 扫描所有 HLI 路由文件
|
|
||||||
const routeFiles = glob.sync(`${ROUTE_DIR}/**/*.js`);
|
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
routeFiles.forEach(routeFile => {
|
// 获取所有业务域目录
|
||||||
const domain = path.basename(path.dirname(routeFile));
|
const domains = fs.readdirSync(ROUTE_DIR).filter(f => {
|
||||||
const name = path.basename(routeFile, '.js');
|
return fs.statSync(path.join(ROUTE_DIR, f)).isDirectory();
|
||||||
|
});
|
||||||
|
|
||||||
// 跳过 index.js(路由注册中心,无需 schema)
|
domains.forEach(domain => {
|
||||||
if (name === 'index') return;
|
const routeDir = path.join(ROUTE_DIR, domain);
|
||||||
|
const schemaDir = path.join(SCHEMA_DIR, domain);
|
||||||
|
|
||||||
const schemaPath = path.join(SCHEMA_DIR, domain, `${name}.schema.json`);
|
// 扫描路由文件
|
||||||
|
const routeFiles = fs.readdirSync(routeDir).filter(f => f.endsWith('.js'));
|
||||||
|
|
||||||
// 检查1: 对应 schema 文件是否存在
|
routeFiles.forEach(routeFile => {
|
||||||
if (!fs.existsSync(schemaPath)) {
|
const name = path.basename(routeFile, '.js');
|
||||||
errors.push(`❌ [MISSING SCHEMA] ${routeFile} → 缺少 ${schemaPath}`);
|
const schemaPath = path.join(schemaDir, name + '.schema.json');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查2: schema 文件格式是否合法
|
if (!fs.existsSync(schemaPath)) {
|
||||||
try {
|
errors.push('❌ [MISSING] ' + routeFile + ' → 缺少 ' + schemaPath);
|
||||||
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
return;
|
||||||
if (!schema.input || !schema.output) {
|
|
||||||
errors.push(`❌ [INVALID SCHEMA] ${schemaPath} → 缺少 input/output 定义`);
|
|
||||||
}
|
}
|
||||||
if (!schema.hli_id) {
|
|
||||||
errors.push(`❌ [MISSING HLI_ID] ${schemaPath} → 缺少 hli_id 字段`);
|
try {
|
||||||
|
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
||||||
|
if (!schema.input || !schema.output) {
|
||||||
|
errors.push('❌ [INVALID] ' + schemaPath + ' → 缺少 input/output');
|
||||||
|
}
|
||||||
|
if (!schema.hli_id) {
|
||||||
|
errors.push('❌ [NO ID] ' + schemaPath + ' → 缺少 hli_id');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
errors.push('❌ [PARSE] ' + schemaPath + ' → ' + e.message);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
});
|
||||||
errors.push(`❌ [PARSE ERROR] ${schemaPath} → ${e.message}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
console.error('\n🚫 HLI Contract Check FAILED:\n');
|
console.error('\n❌ HLI Contract Check FAILED:\n');
|
||||||
errors.forEach(e => console.error(e));
|
errors.forEach(e => console.error(e));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
console.log('✅ HLI Contract Check PASSED — 所有路由均有合法 schema');
|
console.log('✅ HLI Contract Check PASSED');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,58 @@
|
||||||
// scripts/route-align-check.js
|
// 用途:检查 HLI 注册表里的 17 个接口,代码里实现了几个
|
||||||
// 用途:检查路由路径与 HLI 注册表编号的一致性
|
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
// HLI 路由路径映射(从 Notion 注册表同步)
|
|
||||||
const HLI_ROUTES = {
|
const HLI_ROUTES = {
|
||||||
'HLI-AUTH-001': '/hli/auth/login',
|
'HLI-AUTH-001': '/hli/auth/login',
|
||||||
'HLI-AUTH-002': '/hli/auth/register',
|
'HLI-AUTH-002': '/hli/auth/register',
|
||||||
'HLI-AUTH-003': '/hli/auth/verify',
|
'HLI-AUTH-003': '/hli/auth/verify',
|
||||||
'HLI-PERSONA-001': '/hli/persona/load',
|
'HLI-PERSONA-001': '/hli/persona/load',
|
||||||
'HLI-PERSONA-002': '/hli/persona/switch',
|
'HLI-PERSONA-002': '/hli/persona/switch',
|
||||||
'HLI-USER-001': '/hli/user/profile',
|
'HLI-USER-001': '/hli/user/profile',
|
||||||
'HLI-USER-002': '/hli/user/profile/update',
|
'HLI-USER-002': '/hli/user/profile/update',
|
||||||
'HLI-TICKET-001': '/hli/ticket/create',
|
'HLI-TICKET-001': '/hli/ticket/create',
|
||||||
'HLI-TICKET-002': '/hli/ticket/query',
|
'HLI-TICKET-002': '/hli/ticket/query',
|
||||||
'HLI-TICKET-003': '/hli/ticket/status',
|
'HLI-TICKET-003': '/hli/ticket/status',
|
||||||
'HLI-DIALOGUE-001': '/hli/dialogue/send',
|
'HLI-DIALOGUE-001': '/hli/dialogue/send',
|
||||||
'HLI-DIALOGUE-002': '/hli/dialogue/stream',
|
'HLI-DIALOGUE-002': '/hli/dialogue/stream',
|
||||||
'HLI-DIALOGUE-003': '/hli/dialogue/history',
|
'HLI-DIALOGUE-003': '/hli/dialogue/history',
|
||||||
'HLI-STORAGE-001': '/hli/storage/upload',
|
'HLI-STORAGE-001': '/hli/storage/upload',
|
||||||
'HLI-STORAGE-002': '/hli/storage/download',
|
'HLI-STORAGE-002': '/hli/storage/download',
|
||||||
'HLI-DASHBOARD-001': '/hli/dashboard/status',
|
'HLI-DASHBOARD-001': '/hli/dashboard/status',
|
||||||
'HLI-DASHBOARD-002': '/hli/dashboard/realtime',
|
'HLI-DASHBOARD-002': '/hli/dashboard/realtime'
|
||||||
};
|
};
|
||||||
|
|
||||||
const schemaDir = 'src/schemas/hli';
|
const schemaDir = 'src/schemas/hli';
|
||||||
const errors = [];
|
let implemented = 0;
|
||||||
|
let missing = 0;
|
||||||
|
|
||||||
Object.entries(HLI_ROUTES).forEach(([hliId, expectedPath]) => {
|
Object.entries(HLI_ROUTES).forEach(([hliId, route]) => {
|
||||||
const domain = hliId.split('-')[1].toLowerCase();
|
const domain = hliId.split('-')[1].toLowerCase();
|
||||||
const domainDir = path.join(schemaDir, domain);
|
const domainDir = path.join(schemaDir, domain);
|
||||||
|
|
||||||
// 若 domain 目录尚不存在,视为未实现
|
|
||||||
if (!fs.existsSync(domainDir)) {
|
if (!fs.existsSync(domainDir)) {
|
||||||
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
|
console.warn('△ [UNIMPLEMENTED] ' + hliId + ' (' + route + ')');
|
||||||
|
missing++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaFiles = fs.readdirSync(domainDir).filter(f => f.endsWith('.schema.json'));
|
const files = fs.readdirSync(domainDir).filter(f => f.endsWith('.schema.json'));
|
||||||
|
const found = files.some(f => {
|
||||||
const matched = schemaFiles.find(f => {
|
|
||||||
try {
|
try {
|
||||||
const schema = JSON.parse(fs.readFileSync(path.join(domainDir, f), 'utf8'));
|
const schema = JSON.parse(fs.readFileSync(path.join(domainDir, f), 'utf8'));
|
||||||
return schema.hli_id === hliId;
|
return schema.hli_id === hliId;
|
||||||
} catch (_) {
|
} catch { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!matched) {
|
if (found) {
|
||||||
errors.push(`⚠️ [UNIMPLEMENTED] ${hliId} (${expectedPath}) — 尚无 schema 文件`);
|
implemented++;
|
||||||
|
} else {
|
||||||
|
console.warn('△ [UNIMPLEMENTED] ' + hliId + ' (' + route + ')');
|
||||||
|
missing++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (errors.length > 0) {
|
console.log(`\n\n📊 覆盖率: ${implemented} / ${implemented + missing}`);
|
||||||
console.warn('\n⚠️ Route Alignment Report:\n');
|
if (missing === 0) {
|
||||||
errors.forEach(e => console.warn(e));
|
console.log('✅ Route Alignment PASSED - 所有 HLI 接口均已实现');
|
||||||
// 非阻断,仅报告
|
|
||||||
console.warn(`\n📊 覆盖率: ${Object.keys(HLI_ROUTES).length - errors.length}/${Object.keys(HLI_ROUTES).length}`);
|
|
||||||
} else {
|
|
||||||
console.log('✅ Route Alignment PASSED — 所有 HLI 接口均已实现');
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue