Skip to content

Commit 538f310

Browse files
author
Zhiyuan Li
committed
fix: address review feedback - VBS quoting, error handling, tests
1 parent fee1791 commit 538f310

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

src/cli/update-cli/restart-helper.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ describe("restart-helper", () => {
306306
expect(vbsPath).toBe("C:\\Temp\\fake-script.vbs");
307307
expect(vbsContent).toContain("WScript.Shell");
308308
expect(vbsContent).toContain("cmd.exe");
309+
// VBScript should NOT double backslashes (VBS treats \ literally)
310+
expect(vbsContent).toContain("C:\\Temp\\fake-script.bat");
311+
expect(vbsContent).not.toContain("C:\\\\Temp");
309312

310313
// Should spawn wscript.exe with the VBS wrapper
311314
expect(spawn).toHaveBeenCalledWith("wscript.exe", [vbsPath], {
@@ -316,5 +319,40 @@ describe("restart-helper", () => {
316319
expect(mockChild.unref).toHaveBeenCalled();
317320
writeFileSpy.mockRestore();
318321
});
322+
323+
it("handles paths with spaces and special characters on Windows", async () => {
324+
Object.defineProperty(process, "platform", { value: "win32" });
325+
const scriptPath = "C:\\Users\\My User\\AppData\\Local\\Temp\\openclaw-restart-123.bat";
326+
const mockChild = { unref: vi.fn() };
327+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
328+
const writeFileSpy = vi.spyOn(fs, "writeFile").mockResolvedValueOnce(undefined);
329+
330+
await runRestartScript(scriptPath);
331+
332+
const [, vbsContent] = writeFileSpy.mock.calls[0] as [string, string, string];
333+
// Path with spaces should be preserved as-is (no backslash doubling)
334+
expect(vbsContent).toContain("C:\\Users\\My User\\AppData");
335+
expect(vbsContent).not.toContain("\\\\");
336+
writeFileSpy.mockRestore();
337+
});
338+
339+
it("falls back to cmd.exe when VBS file write fails on Windows", async () => {
340+
Object.defineProperty(process, "platform", { value: "win32" });
341+
const scriptPath = "C:\\Temp\\fake-script.bat";
342+
const mockChild = { unref: vi.fn() };
343+
vi.mocked(spawn).mockReturnValue(mockChild as unknown as ChildProcess);
344+
const writeFileSpy = vi.spyOn(fs, "writeFile").mockRejectedValueOnce(new Error("disk full"));
345+
346+
await runRestartScript(scriptPath);
347+
348+
// Should fall back to direct cmd.exe spawn
349+
expect(spawn).toHaveBeenCalledWith("cmd.exe", ["/d", "/s", "/c", scriptPath], {
350+
detached: true,
351+
stdio: "ignore",
352+
windowsHide: true,
353+
});
354+
expect(mockChild.unref).toHaveBeenCalled();
355+
writeFileSpy.mockRestore();
356+
});
319357
});
320358
});

src/cli/update-cli/restart-helper.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,31 @@ del "%~f0"
170170
export async function runRestartScript(scriptPath: string): Promise<void> {
171171
if (process.platform === "win32") {
172172
// Write a VBScript wrapper that runs the batch script fully hidden.
173+
// VBScript does not use \ as an escape character, so only quotes need doubling.
173174
const vbsPath = scriptPath.replace(/\.bat$/i, ".vbs");
174-
const quotedBat = scriptPath.replace(/\\/g, "\\\\").replace(/"/g, '""');
175+
const quotedBat = scriptPath.replace(/"/g, '""');
176+
// Quote scheme: 4 quotes before + 5 after the path.
177+
// VBScript decodes "" → " so ws.Run receives: cmd.exe /d /s /c ""<path>""
178+
// cmd.exe /s /c strips the outer pair, leaving "<path>" as the argument.
175179
const vbsContent = [
176180
'Set ws = CreateObject("WScript.Shell")',
177-
`ws.Run "cmd.exe /d /s /c """"${quotedBat}""""""", 0, False`,
181+
`ws.Run "cmd.exe /d /s /c """"${quotedBat}""""", 0, False`,
178182
// Self-cleanup: delete the VBScript wrapper.
179183
'Set fso = CreateObject("Scripting.FileSystemObject")',
180184
"fso.DeleteFile WScript.ScriptFullName",
181185
].join("\r\n");
182-
await fs.writeFile(vbsPath, vbsContent, "utf8");
186+
try {
187+
await fs.writeFile(vbsPath, vbsContent, "utf8");
188+
} catch {
189+
// Fall back: run the bat directly (console flash may be visible but restart succeeds).
190+
const child = spawn("cmd.exe", ["/d", "/s", "/c", scriptPath], {
191+
detached: true,
192+
stdio: "ignore",
193+
windowsHide: true,
194+
});
195+
child.unref();
196+
return;
197+
}
183198
const child = spawn("wscript.exe", [vbsPath], {
184199
detached: true,
185200
stdio: "ignore",

0 commit comments

Comments
 (0)