PRA-only-demo/packages/rpa-core/scripts/check-deps.mjs
yu 1a20dd72d1 feat: 视觉能力去 Electron 化,经 HostBridge.screen 注入(迁移阶段1-A9)
- HostBridge 新增可选 screen 服务(listDisplays/primaryDisplay/dipToScreenPoint/
  displayMatching/captureDisplay),vision 3 处、cv-vision 2 处动态 import(electron)
  全部改为 requireScreenService();未注入时抛稳定的 host_unavailable
- demo 宿主 src/main/index.ts 用 desktopCapturer/screen 实现 screen 服务
- check-deps.mjs 升级:新增动态 import()/require() 扫描(旧正则实测 0 命中的盲区),
  合成用例验证命中,升级后真实源码 0 违规
- 包依赖闭包:补声明 @cantoo/pdf-lib、@napi-rs/canvas、pdfjs-dist
- cv-vision 测试从 vi.mock(electron) 改为宿主桥注入,11/11 通过
2026-11-06 10:53:42 +08:00

79 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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;
// 动态形式:import("x") / require("x") / createRequire 后的 require 调用。
// 旧正则要求 import 后跟空白,`import("electron")` 完全不匹配(实测 0 命中,
// vision/cv-vision 曾靠这个盲区逃过检查——迁移阶段1-A9 修复)。
const DYNAMIC_IMPORT_RE = /(?:\bimport\s*\(|\brequire\s*\()\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}"`);
}
}
}
for (const match of source.matchAll(DYNAMIC_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}"`);
}
}
}
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)`);