Files
paseo/packages/plugin/src/server/process.test.ts
T
Mohamed Boudra 9760292650 fix(providers): launch plugin CLIs on Windows and tolerate Muse startup (#6151)
* fix(providers): launch plugin CLIs on Windows and tolerate Muse startup

Plugin providers bypassed the core Windows launcher and the plugin host exposed an empty server SDK module. Share the launcher through the SDK and bind it in the host. Give Muse initialization its own deadline because native startup traverses session history before answering MSP.

* fix(providers): terminate Windows launcher process trees

A command-script launcher leaves the CLI behind cmd.exe. Expose forced process-tree termination in the server SDK, use it for Muse and SDK ACP teardown, and bound Muse closing so inherited streams cannot leave callers waiting forever.

* test(muse): keep platform setup outside the teardown assertion

* refactor(plugin): expose process capabilities without quoting helpers

* fix(providers): close process trees on uncorrelated protocol errors
2026-10-05 17:10:02 +01:00

54 lines
1.9 KiB
TypeScript

import { mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, expect, test } from "vitest";
import { execCommand, spawnProcess } from "./index.js";
const roots: string[] = [];
afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
});
async function writeArgumentReader(): Promise<string> {
const root = await mkdtemp(path.join(os.tmpdir(), "plugin process arguments "));
roots.push(root);
const script = path.join(root, "read arguments.mjs");
await writeFile(script, "process.stdout.write(JSON.stringify(process.argv.slice(2)));\n");
return script;
}
const argumentsToPreserve = [
"argument with spaces",
"--format=%(refname)%09%(committerdate:unix)",
"100%",
"feature&bugfix",
"feature|bugfix",
"feature^bugfix",
];
// A bare extensionless command selects the Windows shell; an absolute node.exe path would
// skip that path. The real child checks quoting through the public process capabilities.
test("execCommand delivers shell arguments to the real child unchanged", async () => {
const script = await writeArgumentReader();
const { stdout } = await execCommand("node", [script, ...argumentsToPreserve]);
expect(JSON.parse(stdout)).toEqual(argumentsToPreserve);
});
test("spawnProcess delivers shell arguments to the real child unchanged", async () => {
const script = await writeArgumentReader();
const child = spawnProcess("node", [script, ...argumentsToPreserve], { stdio: "pipe" });
let stdout = "";
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdout += chunk;
});
await new Promise<void>((resolve, reject) => {
child.once("error", reject);
child.once("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`Argument reader exited with ${code}`));
});
});
expect(JSON.parse(stdout)).toEqual(argumentsToPreserve);
});