aps-agent/scripts/check-cross-database.mjs

93 lines
3.1 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* 跨库访问检查(任务 A1.6,依据 ADR-0008)。
*
* 平台代码禁止直连 APS 库:库各自私有,平台只能经 Provider 契约访问业务系统。
* 本脚本扫平台源码,出现下面任一项即失败:
* 1. APS 侧的表名(平台侧不该认识这些表)
* 2. 指向 APS 库的连接串或库名
*
* 用法:node scripts/check-cross-database.mjs [仓库根目录]
*/
import { existsSync } from 'node:fs';
import { readFile, readdir } from 'node:fs/promises';
import { join, relative, sep } from 'node:path';
import process from 'node:process';
import { pathToFileURL } from 'node:url';
/** 平台包目录:只有这些地方的代码属于平台 */
export const PLATFORM_DIRS = ['contracts', 'server', 'client', 'providers'];
/** APS 侧的表名(ADR-0008:APS 6 张表) */
export const APS_TABLES = [
'master_records',
'project_settings',
'routing_templates',
'routing_template_steps',
'aps_schedule_runs',
'aps_schedule_results',
];
/** 指向 APS 库的连接串特征 */
export const APS_DATABASE_HINTS = [/aps[_-]?db/i, /database\s*=\s*aps/i, /jms_mes/i];
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs']);
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'coverage']);
async function collectSources(dir, acc = []) {
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return acc;
}
for (const entry of entries) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
if (SKIP_DIRS.has(entry.name)) continue;
await collectSources(full, acc);
} else if (SOURCE_EXTENSIONS.has(entry.name.slice(entry.name.lastIndexOf('.')))) {
acc.push(full);
}
}
return acc;
}
export async function findCrossDatabaseAccess(root) {
const violations = [];
for (const dir of PLATFORM_DIRS) {
const abs = join(root, dir);
if (!existsSync(abs)) continue;
for (const file of await collectSources(abs)) {
const rel = relative(root, file).split(sep).join('/');
const text = await readFile(file, 'utf8');
for (const table of APS_TABLES) {
if (new RegExp(`\\b${table}\\b`).test(text)) {
violations.push({ file: rel, reason: `出现 APS 表名 ${table}(平台不该认识业务系统的表)` });
}
}
for (const hint of APS_DATABASE_HINTS) {
if (hint.test(text)) {
violations.push({ file: rel, reason: `出现指向 APS 库的线索:${hint}` });
}
}
}
}
return violations;
}
async function main() {
const root = process.cwd();
const violations = await findCrossDatabaseAccess(root);
if (violations.length === 0) {
console.log('跨库访问检查通过:平台代码未出现 APS 表名或 APS 库连接');
return;
}
console.error(`跨库访问检查发现 ${violations.length} 处:`);
for (const violation of violations) console.error(` ${violation.file}:${violation.reason}`);
process.exitCode = 1;
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
await main();
}