98 lines
3.3 KiB
JavaScript
98 lines
3.3 KiB
JavaScript
#!/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 库的连接串特征。
|
||
* 注意边界:平台自己的环境变量名(APS_DB_PATH、APS_DATABASE_URL)不算越界,
|
||
* 只有真正指向业务库的写法(jms_mes、aps_db 这种库名)才拦。
|
||
*/
|
||
export const APS_DATABASE_HINTS = [/aps[_-]?db\b/, /\bjms_mes\b/];
|
||
|
||
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();
|
||
}
|