#!/usr/bin/env node /** * rpa-core 依赖护栏(任务 2.4,规范 v1 第二节的机器强制版)。 * * 禁止: * 1. import "electron"(含 "electron/..." 子路径); * 2. 相对 import 逃出包 src/(如指向宿主 src/main 的 ../../../src/main/...); * 3. 任何 import 路径中出现 src/main(宿主模块横向依赖)。 * * 用法:node scripts/check-deps.mjs(退出码非 0 表示违规) */ import { readdir, readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; import { fileURLToPath } from "node:url"; const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const srcRoot = path.join(pkgRoot, "src"); const IMPORT_RE = /(?:import|export)\s+(?:[\s\S]*?from\s*)?["']([^"']+)["']/g; async function walk(dir) { const entries = await readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const full = path.join(dir, entry.name); if (entry.isDirectory()) files.push(...(await walk(full))); else if (/\.tsx?$/.test(entry.name)) files.push(full); } return files; } const offenders = []; for (const file of await walk(srcRoot)) { const raw = await readFile(file, "utf8"); // 剥掉注释,避免文档示例里的 "electron" 字样误报 const source = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, ""); for (const match of source.matchAll(IMPORT_RE)) { const spec = match[1]; const rel = path.relative(pkgRoot, file).replaceAll("\\", "/"); if (spec === "electron" || spec.startsWith("electron/")) { offenders.push(`${rel}: 禁止 import "${spec}"(宿主能力走 HostBridge 注入)`); continue; } if (spec.includes("src/main")) { offenders.push(`${rel}: 禁止引用宿主模块 "${spec}"`); continue; } if (spec.startsWith(".")) { const resolved = path.resolve(path.dirname(file), spec); if (!resolved.startsWith(srcRoot + path.sep) && resolved !== srcRoot) { offenders.push(`${rel}: 相对引用逃出包 src/ → "${spec}"`); } } } } if (offenders.length) { console.error("rpa-core 依赖护栏检查失败:"); for (const line of offenders) console.error(" - " + line); process.exit(1); } console.log(`rpa-core 依赖护栏检查通过(src/ 下 ${offenders.length === 0 ? "无" : ""}违规 import)`);