- 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 通过
79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
#!/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)`);
|