mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 02:50:43 +00:00
fix(scripts): avoid DEP0190 when spawning .cmd files on Windows
Use the shared Windows cmd.exe command-line builder for `.cmd` and `.bat` UI runner launches so Node.js v24 no longer sees `spawn(file, args, { shell: true })` and emits DEP0190.
The launcher keeps ordinary `.exe`/`.com` and non-Windows paths on direct argv spawning, while Windows command scripts now run through `cmd.exe /d /s /c` with `shell: false` and `windowsVerbatimArguments: true`.
Local and CI verification passed, including focused UI runner tests, build, check, Real behavior proof, and ClawSweeper gates.
Co-authored-by: Nandana Dileep <nandanadileep@users.noreply.github.com>
Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
This commit is contained in:
@@ -1,49 +1,91 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
assertSafeWindowsShellArgs,
|
||||
prepareSpawnCommand,
|
||||
shouldUseShellForCommand,
|
||||
} from "../../scripts/ui.js";
|
||||
import { resolveSpawnCall, shouldUseCmdExeForCommand } from "../../scripts/ui.js";
|
||||
|
||||
describe("scripts/ui windows spawn behavior", () => {
|
||||
it("enables shell for Windows command launchers that require cmd.exe", () => {
|
||||
it("wraps Windows command launchers with cmd.exe without enabling shell mode", () => {
|
||||
expect(
|
||||
shouldUseShellForCommand("C:\\Users\\dev\\AppData\\Local\\pnpm\\pnpm.CMD", "win32"),
|
||||
shouldUseCmdExeForCommand("C:\\Users\\dev\\AppData\\Local\\pnpm\\pnpm.CMD", "win32"),
|
||||
).toBe(true);
|
||||
expect(shouldUseShellForCommand("C:\\tools\\pnpm.bat", "win32")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not enable shell for non-shell launchers", () => {
|
||||
expect(shouldUseShellForCommand("C:\\Program Files\\nodejs\\node.exe", "win32")).toBe(false);
|
||||
expect(shouldUseShellForCommand("/usr/local/bin/pnpm", "linux")).toBe(false);
|
||||
});
|
||||
|
||||
it("quotes Windows shell launcher paths before passing them to spawn", () => {
|
||||
expect(prepareSpawnCommand("C:\\Program Files\\nodejs\\pnpm.cmd", "win32")).toBe(
|
||||
'"C:\\Program Files\\nodejs\\pnpm.cmd"',
|
||||
);
|
||||
expect(prepareSpawnCommand("C:\\Program Files\\nodejs\\pnpm.exe", "win32")).toBe(
|
||||
"C:\\Program Files\\nodejs\\pnpm.exe",
|
||||
);
|
||||
expect(prepareSpawnCommand("/usr/local/bin/pnpm", "linux")).toBe("/usr/local/bin/pnpm");
|
||||
});
|
||||
|
||||
it("allows safe forwarded args when shell mode is required on Windows", () => {
|
||||
expect(
|
||||
assertSafeWindowsShellArgs(["run", "build", "--filter", "@openclaw/ui"], "win32"),
|
||||
).toBeUndefined();
|
||||
resolveSpawnCall(
|
||||
"C:\\Program Files\\nodejs\\pnpm.cmd",
|
||||
["run", "build", "-t", "path with spaces"],
|
||||
{ PATH: "C:\\bin" },
|
||||
{ comSpec: "C:\\Windows\\System32\\cmd.exe", cwd: "C:\\repo\\ui", platform: "win32" },
|
||||
),
|
||||
).toEqual({
|
||||
command: "C:\\Windows\\System32\\cmd.exe",
|
||||
args: [
|
||||
"/d",
|
||||
"/s",
|
||||
"/c",
|
||||
'"C:\\Program Files\\nodejs\\pnpm.cmd" run build -t "path with spaces"',
|
||||
],
|
||||
options: {
|
||||
cwd: "C:\\repo\\ui",
|
||||
stdio: "inherit",
|
||||
env: { PATH: "C:\\bin" },
|
||||
shell: false,
|
||||
windowsVerbatimArguments: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects dangerous forwarded args when shell mode is required on Windows", () => {
|
||||
expect(() => assertSafeWindowsShellArgs(["run", "build", "evil&calc"], "win32")).toThrow(
|
||||
/unsafe windows shell argument/i,
|
||||
);
|
||||
expect(() => assertSafeWindowsShellArgs(["run", "build", "%PATH%"], "win32")).toThrow(
|
||||
/unsafe windows shell argument/i,
|
||||
);
|
||||
it("does not use cmd.exe for non-command launchers", () => {
|
||||
expect(shouldUseCmdExeForCommand("C:\\Program Files\\nodejs\\node.exe", "win32")).toBe(false);
|
||||
expect(shouldUseCmdExeForCommand("C:\\tools\\pnpm.com", "win32")).toBe(false);
|
||||
expect(shouldUseCmdExeForCommand("/usr/local/bin/pnpm", "linux")).toBe(false);
|
||||
|
||||
expect(
|
||||
resolveSpawnCall(
|
||||
"C:\\Program Files\\nodejs\\pnpm.exe",
|
||||
["run", "build"],
|
||||
{ PATH: "C:\\bin" },
|
||||
{ cwd: "C:\\repo\\ui", platform: "win32" },
|
||||
),
|
||||
).toEqual({
|
||||
command: "C:\\Program Files\\nodejs\\pnpm.exe",
|
||||
args: ["run", "build"],
|
||||
options: {
|
||||
cwd: "C:\\repo\\ui",
|
||||
stdio: "inherit",
|
||||
env: { PATH: "C:\\bin" },
|
||||
shell: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not reject args on non-windows platforms", () => {
|
||||
expect(assertSafeWindowsShellArgs(["contains&metacharacters"], "linux")).toBeUndefined();
|
||||
it("rejects unsafe cmd.exe arguments before launch", () => {
|
||||
expect(() =>
|
||||
resolveSpawnCall("C:\\tools\\pnpm.cmd", ["run", "build", "evil&calc"], undefined, {
|
||||
platform: "win32",
|
||||
}),
|
||||
).toThrow(/unsafe windows cmd\.exe argument/i);
|
||||
expect(() =>
|
||||
resolveSpawnCall("C:\\tools\\pnpm.cmd", ["run", "build", "%PATH%"], undefined, {
|
||||
platform: "win32",
|
||||
}),
|
||||
).toThrow(/unsafe windows cmd\.exe argument/i);
|
||||
});
|
||||
|
||||
it("keeps non-Windows launches direct even with shell metacharacters", () => {
|
||||
expect(
|
||||
resolveSpawnCall(
|
||||
"/usr/local/bin/pnpm",
|
||||
["run", "build", "contains&metacharacters"],
|
||||
{ PATH: "/bin" },
|
||||
{ cwd: "/repo/ui", platform: "linux" },
|
||||
),
|
||||
).toEqual({
|
||||
command: "/usr/local/bin/pnpm",
|
||||
args: ["run", "build", "contains&metacharacters"],
|
||||
options: {
|
||||
cwd: "/repo/ui",
|
||||
stdio: "inherit",
|
||||
env: { PATH: "/bin" },
|
||||
shell: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user