阶段1-rpa-core建包: engine+31个纯Node能力迁入packages/rpa-core, 领域类型单一源化, createEngine工厂收拢模块级状态
- engine/(registry/types/runtime/expression)物理迁入包, 解耦shared/types(域类型归包, 宿主type-only re-export) - 31个纯Node能力+27个测试迁入包; 宿主留守Electron/浏览器/AI依赖能力, index.ts经@rpa/core/capabilities拉起 - 新增createEngine()/defaultEngine: excel缓存/Excel句柄/db连接/FTP客户端/OCR实例/拟真设置挂引擎实例 - remaining-core子流程依赖改为host-bridge注入, 包内不再import宿主flows - 宿主main/index.ts启动时注入子流程源; electron-vite/vitest/tsconfig配@rpa/core源码别名 - 验证: typecheck绿, 快照909个id diff为零, 332测试过(2个桌面冒烟因会话无交互SetForegroundWindow被拒, 环境阻塞)
This commit is contained in:
1 parent
4139400dbd
commit
caa8e19332
111 files changed
+581
-337
No files matched your search
@@ -0,0 +1,114 @@
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const run = promisify(execFile);
|
||||
const ps = (script: string) => run("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { windowsHide: true, timeout: 60000 });
|
||||
// cmdkey 在中文 Windows 上输出 GBK,直接 execFile 按 UTF-8 解码会乱码;统一经 PowerShell 强制 UTF-8 控制台编码捕获
|
||||
const cmdkey = async (args: string[]): Promise<string> => {
|
||||
const quoted = args.map((item) => "'" + item.replace(/'/g, "''") + "'");
|
||||
const script = "[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new($false); & cmdkey " + quoted.join(" ");
|
||||
const { stdout } = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { windowsHide: true, timeout: 60000 });
|
||||
return stdout;
|
||||
};
|
||||
|
||||
function reqText(args: Record<string, unknown>, keys: string[], label: string, nodeId: string): string {
|
||||
for (const key of keys) {
|
||||
const v = String(args[key] ?? "").trim();
|
||||
if (v) return v;
|
||||
}
|
||||
throw new RuntimeError("validation_error", label + "为空", { nodeId, componentId: nodeId });
|
||||
}
|
||||
|
||||
register("ScreenControl_Screenlock", async () => {
|
||||
await run("rundll32.exe", ["user32.dll,LockWorkStation"], { windowsHide: true });
|
||||
return { locked: true };
|
||||
});
|
||||
|
||||
register("ScreenControl_ScreenUnlock", async () => {
|
||||
// 仅唤醒屏幕/解除屏保;系统锁屏需人工输入密码,无法通用自动解锁
|
||||
await ps("Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern void mouse_event(uint f, uint dx, uint dy, uint d, UIntPtr e);' -Name W32U -Namespace WinRpa | Out-Null; [WinRpa.W32U]::mouse_event(0x0001, 1, 1, 0, [UIntPtr]::Zero); Start-Sleep -Milliseconds 200; [WinRpa.W32U]::mouse_event(0x0001, 0xFFFFFFFF, 0, 0, [UIntPtr]::Zero)");
|
||||
return { awakened: true, loginUnlockSupported: false, note: "仅尝试唤醒屏幕;Windows 登录锁屏仍需本人手动解锁" };
|
||||
});
|
||||
|
||||
const SC_SCREENSAVE = 0xF140;
|
||||
const WM_SYSCOMMAND = 0x0112;
|
||||
register(["System_ScreenSaverShow", "System_ScreenSaverShowV1"], async (_ctx, node, args) => {
|
||||
if ([true, 1, "1", "true", "True"].includes(args.set_password as never)) {
|
||||
throw new RuntimeError("validation_error", "系统屏保不支持设置自定义解锁密码,请关闭该选项并使用 Windows 原有登录保护", { nodeId: node.id, componentId: node.componentId });
|
||||
}
|
||||
await ps("Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern IntPtr SendMessage(IntPtr h, uint m, IntPtr w, IntPtr l);' -Name W32S -Namespace WinRpa | Out-Null; [WinRpa.W32S]::SendMessage([IntPtr]0xFFFF, [uint32]0x0112, [IntPtr]0xF140, [IntPtr]::Zero) | Out-Null");
|
||||
return { screensaver: true };
|
||||
});
|
||||
|
||||
register("System_ScreenSaverStop", async () => {
|
||||
await ps("Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern IntPtr FindWindow(string cls, string win); [DllImport(\"user32.dll\")] public static extern bool PostMessage(IntPtr h, uint m, IntPtr w, IntPtr l);' -Name W32P -Namespace WinRpa | Out-Null; $h = [WinRpa.W32P]::FindWindow('WindowsScreenSaverClass', $null); if ($h -ne [IntPtr]::Zero) { [WinRpa.W32P]::PostMessage($h, 0x0010, [IntPtr]::Zero, [IntPtr]::Zero) | Out-Null }");
|
||||
return { stopped: true };
|
||||
});
|
||||
|
||||
register("System_ReadCredential", async (ctx, node, args) => {
|
||||
const app = reqText(args, ["application"], "凭据目标", node.id);
|
||||
const stdout = await cmdkey(["/list"]);
|
||||
const exists = stdout.includes(app);
|
||||
// cmdkey /list 每条凭据是「目标/类型/用户」多行块(空行分隔);取整块才有用户名等有效信息
|
||||
const lines = stdout.split(/\r?\n/);
|
||||
const idx = lines.findIndex((l) => l.includes(app));
|
||||
const block: string[] = [];
|
||||
if (idx >= 0) {
|
||||
for (let i = idx; i < lines.length && lines[i].trim(); i++) block.push(lines[i].trim());
|
||||
}
|
||||
return { target: app, exists, detail: exists ? block.join(" | ") : "" };
|
||||
});
|
||||
|
||||
register("System_AddCredential", async (ctx, node, args) => {
|
||||
const app = reqText(args, ["application"], "凭据目标", node.id);
|
||||
const user = reqText(args, ["name"], "用户名", node.id);
|
||||
const pwd = String(args.password ?? "");
|
||||
const psArgs = pwd ? ["/generic:" + app, "/user:" + user, "/pass:" + pwd] : ["/generic:" + app, "/user:" + user];
|
||||
await cmdkey(psArgs);
|
||||
return { added: true, target: app };
|
||||
});
|
||||
|
||||
register("System_DeleteCredential", async (ctx, node, args) => {
|
||||
const app = reqText(args, ["application"], "凭据目标", node.id);
|
||||
await cmdkey(["/delete:" + app]);
|
||||
return { deleted: true, target: app };
|
||||
});
|
||||
|
||||
register("System_ConnectRemoteDesktop", async (ctx, node, args) => {
|
||||
const ip = reqText(args, ["ip"], "远程地址", node.id);
|
||||
const port = String(args.port ?? "").trim();
|
||||
const target = port ? ip + ":" + port : ip;
|
||||
await run("mstsc.exe", ["/v:" + target], { windowsHide: true });
|
||||
return { connected: true, target };
|
||||
});
|
||||
|
||||
register("System_CloseRemoteDesktop", async () => {
|
||||
await run("tsdiscon.exe", [], { windowsHide: true });
|
||||
return { disconnected: true };
|
||||
});
|
||||
|
||||
register("System_GetInputMethodState", async () => {
|
||||
const { stdout } = await ps("Add-Type -AssemblyName System.Windows.Forms | Out-Null; $cur = [System.Windows.Forms.InputLanguage]::CurrentInputLanguage; Write-Output ($cur.Culture.Name + '|' + $cur.LayoutName)");
|
||||
const [culture, layout] = stdout.split("|");
|
||||
return { culture, layout };
|
||||
});
|
||||
|
||||
register("System_SetInputMethodState", async (ctx, node, args) => {
|
||||
const mode = reqText(args, ["mode"], "输入法文化名", node.id);
|
||||
await ps("Add-Type -AssemblyName System.Windows.Forms | Out-Null; $lang = [System.Windows.Forms.InputLanguage]::FromCulture('" + mode.replace(/'/g, "''") + "'); if ($lang) { [System.Windows.Forms.InputLanguage]::CurrentInputLanguage = $lang }");
|
||||
return { set: true, mode };
|
||||
});
|
||||
|
||||
register("File_OpenFile", async (ctx, node, args) => {
|
||||
const p = reqText(args, ["path"], "文件夹路径", node.id);
|
||||
try {
|
||||
await run("explorer.exe", [p], { windowsHide: true });
|
||||
} catch (error) {
|
||||
// explorer.exe 打开成功也会返回退出码 1(Windows 已知行为),仅路径不存在时才是真失败
|
||||
const code = (error as { code?: unknown }).code;
|
||||
if (code !== 1) throw error;
|
||||
}
|
||||
return { opened: true, path: p };
|
||||
});
|
||||
Reference in new issue
Block a user