#!/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(); }