feat: add windows helper bridge

This commit is contained in:
2026-05-21 20:19:40 +03:00
parent 39eca49f62
commit f7e8138ab1
2 changed files with 128 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
import { spawn } from "node:child_process";
import { settings } from "./config.js";
function defaultRunner(command, args, options = {}) {
return new Promise((resolve) => {
const child = spawn(command, args, {
stdio: ["pipe", "pipe", "pipe"],
windowsHide: true,
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
stdout += chunk.toString("utf8");
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString("utf8");
});
child.on("error", (error) => {
resolve({ status: 1, stdout, stderr: error.message });
});
child.on("close", (status) => {
resolve({ status, stdout, stderr });
});
child.stdin.end(options.input || "");
});
}
export function createWindowsHelper(options = {}) {
const helperPath = options.helperPath || settings.windowsHelperPath;
const command = options.command || "pwsh";
const runner = options.runner || defaultRunner;
return {
async run(action, payload = {}) {
const input = JSON.stringify({ action, payload });
const result = await runner(
command,
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", helperPath],
{ input },
);
if (result.status !== 0) {
throw new Error(
`Windows helper failed: ${(result.stderr || result.stdout || "helper exited without stderr").trim()}`,
);
}
try {
return JSON.parse(result.stdout);
} catch {
throw new Error(`Windows helper returned invalid JSON: ${result.stdout}`);
}
},
};
}
export const windowsHelper = createWindowsHelper();

View File

@@ -0,0 +1,74 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createWindowsHelper } from "../../src/server/windowsHelper.js";
test("windows helper sends action and payload as JSON", async () => {
const calls = [];
const helper = createWindowsHelper({
helperPath: "scripts/windows/helper.ps1",
runner: async (command, args, options) => {
calls.push({ command, args, input: options.input });
return {
status: 0,
stdout: JSON.stringify({
success: true,
action: "status.get",
result: { proxifyre: "Running" },
}),
stderr: "",
};
},
});
const result = await helper.run("status.get", { service: "ProxiFyre" });
assert.deepEqual(result, {
success: true,
action: "status.get",
result: { proxifyre: "Running" },
});
assert.equal(calls[0].command, "pwsh");
assert.deepEqual(calls[0].args, [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"scripts/windows/helper.ps1",
]);
assert.deepEqual(JSON.parse(calls[0].input), {
action: "status.get",
payload: { service: "ProxiFyre" },
});
});
test("windows helper normalizes non-zero exit into structured error", async () => {
const helper = createWindowsHelper({
helperPath: "scripts/windows/helper.ps1",
runner: async () => ({
status: 1,
stdout: "",
stderr: "service failed",
}),
});
await assert.rejects(
() => helper.run("service.restart", { name: "proxifyre" }),
/Windows helper failed: service failed/,
);
});
test("windows helper rejects invalid JSON stdout", async () => {
const helper = createWindowsHelper({
helperPath: "scripts/windows/helper.ps1",
runner: async () => ({
status: 0,
stdout: "not-json",
stderr: "",
}),
});
await assert.rejects(
() => helper.run("status.get", {}),
/Windows helper returned invalid JSON/,
);
});