Skip to content

Commit 6c4028e

Browse files
committed
fix(scripts): resolve Windows cmd shim launcher
1 parent 9242137 commit 6c4028e

24 files changed

Lines changed: 217 additions & 114 deletions

scripts/build-all.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ export function resolveBuildAllStep(step, params = {}) {
341341
pnpmArgs: step.pnpmArgs,
342342
nodeExecPath: params.nodeExecPath ?? nodeBin,
343343
npmExecPath: params.npmExecPath ?? env.npm_execpath,
344-
comSpec: params.comSpec ?? env.ComSpec,
344+
comSpec: params.comSpec,
345345
platform,
346346
});
347347
return {

scripts/crabbox-wrapper.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { homedir, tmpdir } from "node:os";
2020
import { delimiter, dirname, extname, isAbsolute, relative, resolve } from "node:path";
2121
import { fileURLToPath } from "node:url";
22-
import { resolvePathEnvKey } from "./windows-cmd-helpers.mjs";
22+
import { resolvePathEnvKey, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
2323

2424
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
2525
const ignoreRepoBinary = process.env.OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY === "1";
@@ -119,7 +119,7 @@ function spawnInvocation(command, commandArgs, env, platform) {
119119
const extension = extname(command).toLowerCase();
120120
if (platform === "win32" && (extension === ".cmd" || extension === ".bat")) {
121121
return {
122-
command: env.ComSpec ?? "cmd.exe",
122+
command: resolveWindowsCmdExePath(env),
123123
args: ["/d", "/s", "/c", buildBatchCommandLine(command, commandArgs)],
124124
windowsVerbatimArguments: true,
125125
};

scripts/e2e/lib/upgrade-survivor/config-recipe.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import fs from "node:fs";
55
import path from "node:path";
66
import { pathToFileURL } from "node:url";
77
import { parseReleaseVersion } from "../../../lib/npm-publish-plan.mjs";
8-
import { buildCmdExeCommandLine } from "../../../windows-cmd-helpers.mjs";
8+
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "../../../windows-cmd-helpers.mjs";
99

1010
const args = process.argv.slice(2);
1111
const command = args.shift();
@@ -215,7 +215,7 @@ function adaptStepForBaseline(step, baselineVersion, summary) {
215215
export function resolveUpgradeSurvivorOpenClawCommand(argv, params = {}) {
216216
const platform = params.platform ?? process.platform;
217217
if (platform === "win32") {
218-
const comSpec = params.comSpec ?? process.env.ComSpec ?? "cmd.exe";
218+
const comSpec = params.comSpec ?? resolveWindowsCmdExePath(params.env ?? process.env);
219219
return {
220220
command: comSpec,
221221
args: ["/d", "/s", "/c", buildCmdExeCommandLine("openclaw.cmd", argv)],

scripts/e2e/parallels/host-command.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { finished } from "node:stream/promises";
66
import { fileURLToPath } from "node:url";
77
import { resolveNpmRunner } from "../../npm-runner.mjs";
88
import { resolvePnpmRunner } from "../../pnpm-runner.mjs";
9-
import { buildCmdExeCommandLine } from "../../windows-cmd-helpers.mjs";
9+
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "../../windows-cmd-helpers.mjs";
1010
import type { CommandResult, RunOptions } from "./types.ts";
1111

1212
export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
@@ -338,19 +338,14 @@ function isBareCommand(command: string, name: "npm" | "pnpm"): boolean {
338338
return portableBasename(command) === command && command.toLowerCase() === name;
339339
}
340340

341-
function resolveEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
342-
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
343-
return key === undefined ? undefined : env[key];
344-
}
345-
346341
export function resolveHostCommandInvocation(
347342
command: string,
348343
args: string[],
349344
options: ResolveHostCommandOptions = {},
350345
): HostCommandInvocation {
351346
const env = options.env ?? process.env;
352347
const platform = options.platform ?? process.platform;
353-
const comSpec = options.comSpec ?? resolveEnvValue(env, "ComSpec") ?? "cmd.exe";
348+
const comSpec = options.comSpec ?? resolveWindowsCmdExePath(env);
354349

355350
if (isBareCommand(command, "pnpm")) {
356351
const runner = resolvePnpmRunner({

scripts/ensure-playwright-chromium.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function resolvePlaywrightInstallRunner(options = {}) {
5252
const env = options.env ?? process.env;
5353
const targets = options.targets ?? ["chromium"];
5454
return resolvePnpmRunner({
55-
comSpec: options.comSpec ?? env.ComSpec ?? env.COMSPEC,
55+
comSpec: options.comSpec,
5656
env,
5757
npmExecPath: env === process.env ? env.npm_execpath : (env.npm_execpath ?? ""),
5858
platform: options.platform,

scripts/format-docs.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import os from "node:os";
77
import path from "node:path";
88
import { pathToFileURL } from "node:url";
99
import { repairMintlifyAccordionIndentation } from "./lib/mintlify-accordion.mjs";
10-
import { buildCmdExeCommandLine } from "./windows-cmd-helpers.mjs";
10+
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
1111

1212
const ROOT = path.resolve(import.meta.dirname, "..");
1313
const CHECK = process.argv.includes("--check");
@@ -128,7 +128,7 @@ export function resolveOxfmtInvocation(args, params = {}) {
128128

129129
if (existsSync(shimPath)) {
130130
if (platform === "win32") {
131-
const comSpec = params.comSpec ?? process.env.ComSpec ?? "cmd.exe";
131+
const comSpec = params.comSpec ?? resolveWindowsCmdExePath(params.env ?? process.env);
132132
return {
133133
command: comSpec,
134134
args: ["/d", "/s", "/c", buildCmdExeCommandLine(shimPath, args)],

scripts/lib/codex-app-server-protocol-source.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,13 @@ type ResolvePnpmCommandOptions = {
4242
platform?: NodeJS.Platform;
4343
};
4444

45-
function resolveEnvValue(env: NodeJS.ProcessEnv, name: string): string | undefined {
46-
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
47-
return key === undefined ? undefined : env[key];
48-
}
49-
5045
export function resolveCodexProtocolPnpmCommand(
5146
args: string[],
5247
options: ResolvePnpmCommandOptions = {},
5348
): PnpmCommand {
5449
const env = options.env ?? process.env;
5550
const command = resolvePnpmRunner({
56-
comSpec: options.comSpec ?? resolveEnvValue(env, "ComSpec"),
51+
comSpec: options.comSpec,
5752
env,
5853
npmExecPath: options.npmExecPath ?? env.npm_execpath,
5954
nodeExecPath: options.execPath ?? process.execPath,

scripts/lib/managed-child-process.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Runs child commands with process-group signal forwarding and Windows shell normalization.
22
import { spawn, spawnSync } from "node:child_process";
33
import { constants as osConstants } from "node:os";
4-
import { buildCmdExeCommandLine } from "../windows-cmd-helpers.mjs";
4+
import { buildCmdExeCommandLine, resolveWindowsCmdExePath } from "../windows-cmd-helpers.mjs";
55
import { resolveWindowsTaskkillPath } from "./windows-taskkill.mjs";
66

77
const FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
@@ -276,7 +276,7 @@ export function createManagedCommandInvocation({
276276
if (platform === "win32" && shell && args.length > 0) {
277277
return {
278278
args: ["/d", "/s", "/c", buildCmdExeCommandLine(bin, args)],
279-
command: comSpec ?? env?.ComSpec ?? env?.COMSPEC ?? process.env.ComSpec ?? "cmd.exe",
279+
command: comSpec ?? resolveWindowsCmdExePath(env ?? process.env),
280280
shell: false,
281281
windowsVerbatimArguments: true,
282282
};

scripts/lib/windows-taskkill.mjs

Lines changed: 6 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,11 @@
1-
// Resolves the Windows taskkill binary without trusting PATH.
2-
import path from "node:path";
1+
// Resolves Windows system binaries without trusting PATH.
2+
import {
3+
resolveWindowsPowerShellPath,
4+
resolveWindowsSystem32Path,
5+
} from "../windows-cmd-helpers.mjs";
36

4-
const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
5-
6-
function getEnvValueCaseInsensitive(env, expectedKey) {
7-
const direct = env[expectedKey];
8-
if (direct !== undefined) {
9-
return direct;
10-
}
11-
const expected = expectedKey.toUpperCase();
12-
const actualKey = Object.keys(env).find((key) => key.toUpperCase() === expected);
13-
return actualKey ? env[actualKey] : undefined;
14-
}
15-
16-
function normalizeWindowsSystemRoot(raw) {
17-
const trimmed = raw?.trim();
18-
if (
19-
!trimmed ||
20-
trimmed.includes("\0") ||
21-
trimmed.includes("\r") ||
22-
trimmed.includes("\n") ||
23-
trimmed.includes(";")
24-
) {
25-
return null;
26-
}
27-
const normalized = path.win32.normalize(trimmed);
28-
if (!path.win32.isAbsolute(normalized) || normalized.startsWith("\\\\")) {
29-
return null;
30-
}
31-
const parsed = path.win32.parse(normalized);
32-
if (!/^[A-Za-z]:\\$/.test(parsed.root) || normalized.length <= parsed.root.length) {
33-
return null;
34-
}
35-
return normalized.replace(/[\\/]+$/, "");
36-
}
7+
export { resolveWindowsPowerShellPath, resolveWindowsSystem32Path };
378

389
export function resolveWindowsTaskkillPath(env = process.env) {
3910
return resolveWindowsSystem32Path("taskkill.exe", env);
4011
}
41-
42-
function resolveWindowsSystemRoot(env) {
43-
return (
44-
normalizeWindowsSystemRoot(getEnvValueCaseInsensitive(env, "SystemRoot")) ??
45-
normalizeWindowsSystemRoot(getEnvValueCaseInsensitive(env, "WINDIR")) ??
46-
DEFAULT_WINDOWS_SYSTEM_ROOT
47-
);
48-
}
49-
50-
export function resolveWindowsSystem32Path(executableName, env = process.env) {
51-
if (
52-
path.win32.basename(executableName) !== executableName ||
53-
!/^[A-Za-z0-9_.-]+\.exe$/u.test(executableName)
54-
) {
55-
throw new Error(`Invalid Windows System32 executable name: ${executableName}`);
56-
}
57-
const systemRoot = resolveWindowsSystemRoot(env);
58-
return path.win32.join(systemRoot, "System32", executableName);
59-
}
60-
61-
export function resolveWindowsPowerShellPath(env = process.env) {
62-
const systemRoot = resolveWindowsSystemRoot(env);
63-
return path.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
64-
}

scripts/npm-runner.mjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// Resolves npm commands from the active Node toolchain, especially on Windows.
22
import fs from "node:fs";
33
import path from "node:path";
4-
import { buildCmdExeCommandLine, resolvePathEnvKey } from "./windows-cmd-helpers.mjs";
4+
import {
5+
buildCmdExeCommandLine,
6+
resolvePathEnvKey,
7+
resolveWindowsCmdExePath,
8+
} from "./windows-cmd-helpers.mjs";
59

610
function resolveToolchainNpmRunner(params) {
711
const npmCliCandidates = [
@@ -48,7 +52,7 @@ export function resolveNpmRunner(params = {}) {
4852
const existsSync = params.existsSync ?? fs.existsSync;
4953
const env = params.env ?? process.env;
5054
const platform = params.platform ?? process.platform;
51-
const comSpec = params.comSpec ?? env.ComSpec ?? "cmd.exe";
55+
const comSpec = params.comSpec ?? (platform === "win32" ? resolveWindowsCmdExePath(env) : "");
5256
const pathImpl = platform === "win32" ? path.win32 : path.posix;
5357
const nodeDir = pathImpl.dirname(execPath);
5458
const npmToolchain = resolveToolchainNpmRunner({

0 commit comments

Comments
 (0)