Wrap Windows Python calls through cmd

This commit is contained in:
2026-05-02 20:49:45 +09:00
parent 2667fc2632
commit dca5b2c9c4
3 changed files with 57 additions and 12 deletions

View File

@@ -6,14 +6,52 @@ import process from "node:process";
import type { AppConfig } from "./config.js";
export interface PythonCommandSpec {
command: string;
args: string[];
viaCmdShell?: boolean;
}
function splitCommand(command: string): string[] {
const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
return parts.map((part) => part.replace(/^"(.*)"$/, "$1"));
}
async function canRun(command: string, args: string[]): Promise<boolean> {
function quoteWindowsCmdArg(value: string): string {
return `"${value.replace(/"/g, '""')}"`;
}
function buildWindowsCommandLine(parts: string[]): string {
return parts.map((part) => quoteWindowsCmdArg(part)).join(" ");
}
export function buildPythonInvocation(spec: PythonCommandSpec, extraArgs: string[]): PythonCommandSpec {
if (process.platform === "win32" && spec.viaCmdShell) {
return {
command: "cmd.exe",
args: ["/d", "/s", "/c", buildWindowsCommandLine([spec.command, ...spec.args, ...extraArgs])],
};
}
return {
command: spec.command,
args: [...spec.args, ...extraArgs],
};
}
async function canRun(command: string, args: string[], viaCmdShell = false): Promise<boolean> {
const invocation = viaCmdShell
? {
command: "cmd.exe",
args: ["/d", "/s", "/c", buildWindowsCommandLine([command, ...args, "--version"])],
}
: {
command,
args: [...args, "--version"],
};
return await new Promise<boolean>((resolve) => {
const child = spawn(command, [...args, "--version"], {
const child = spawn(invocation.command, invocation.args, {
stdio: ["ignore", "ignore", "ignore"],
windowsHide: true,
});
@@ -90,14 +128,18 @@ export async function resolvePythonCommand(config: AppConfig): Promise<{ command
return await resolveWorkerPythonCommand(config);
}
export async function resolveBasePythonCommand(config: AppConfig): Promise<{ command: string; args: string[] }> {
export async function resolveBasePythonCommand(config: AppConfig): Promise<PythonCommandSpec> {
const configured = config.LOCAL_AI_PYTHON?.trim();
if (configured) {
const [command, ...args] = splitCommand(configured);
if (!command) {
throw new Error("LOCAL_AI_PYTHON 값이 비어 있습니다.");
}
return { command, args };
return {
command,
args,
viaCmdShell: process.platform === "win32" && !path.isAbsolute(command),
};
}
const venvPath = resolveVenvPythonPath(config);
@@ -118,8 +160,8 @@ export async function resolveBasePythonCommand(config: AppConfig): Promise<{ com
return { command: absolute, args: candidate.args };
}
if (await canRun(candidate.command, candidate.args)) {
return candidate;
if (await canRun(candidate.command, candidate.args, true)) {
return { ...candidate, viaCmdShell: true };
}
}