fix: use PATH-aware resolution for provider availability and fix Windows cmd escaping

Provider isAvailable() was using executableExists() which only checks
filesystem paths, not PATH. Commands like ["claude", "--flag"] would
show as unavailable even though they'd launch fine. Switch to
isCommandAvailable() which uses findExecutable() for proper PATH
resolution. Un-export executableExists from the public API.

Fix Windows cmd.exe metacharacter escaping — &, |, ^, <, >, (, ), !
are now properly escaped with ^, and % is doubled. Add shared
escapeWindowsCmdValue helper used by both quoteWindowsCommand and
quoteWindowsArgument. Replace local quoteForCmd in spawn.ts.

Add server-tests-windows CI job to catch Windows-specific regressions.
This commit is contained in:
Mohamed Boudra
2026-04-13 20:14:33 +07:00
parent 6f28027687
commit 95e112c185
9 changed files with 138 additions and 25 deletions
@@ -240,6 +240,46 @@ describe("quoteWindowsCommand", () => {
expect(quoteWindowsCommand("C:\\nvm4w\\nodejs\\codex")).toBe("C:\\nvm4w\\nodejs\\codex");
});
test("escapes ampersands", async () => {
setPlatform("win32");
const { quoteWindowsCommand } = await loadExecutableModule();
expect(quoteWindowsCommand("feature&bugfix")).toBe("feature^&bugfix");
});
test("escapes pipes", async () => {
setPlatform("win32");
const { quoteWindowsCommand } = await loadExecutableModule();
expect(quoteWindowsCommand("feature|bugfix")).toBe("feature^|bugfix");
});
test("doubles percent signs", async () => {
setPlatform("win32");
const { quoteWindowsCommand } = await loadExecutableModule();
expect(quoteWindowsCommand("100%")).toBe("100%%");
});
test("escapes carets", async () => {
setPlatform("win32");
const { quoteWindowsCommand } = await loadExecutableModule();
expect(quoteWindowsCommand("feature^bugfix")).toBe("feature^^bugfix");
});
test("escapes multiple metacharacters", async () => {
setPlatform("win32");
const { quoteWindowsCommand } = await loadExecutableModule();
expect(quoteWindowsCommand("build&(test|deploy)!<output>")).toBe(
"build^&^(test^|deploy^)^!^<output^>",
);
});
test("quotes commands with spaces after escaping metacharacters", async () => {
setPlatform("win32");
const { quoteWindowsCommand } = await loadExecutableModule();
expect(quoteWindowsCommand("C:\\Program Files\\My Tool&Stuff\\run 100%.cmd")).toBe(
'"C:\\Program Files\\My Tool^&Stuff\\run 100%%.cmd"',
);
});
test("returns the command unchanged on non-Windows platforms", async () => {
setPlatform("darwin");
const { quoteWindowsCommand } = await loadExecutableModule();