Wrap Windows Python calls through cmd
This commit is contained in:
@@ -6,14 +6,52 @@ import process from "node:process";
|
|||||||
|
|
||||||
import type { AppConfig } from "./config.js";
|
import type { AppConfig } from "./config.js";
|
||||||
|
|
||||||
|
export interface PythonCommandSpec {
|
||||||
|
command: string;
|
||||||
|
args: string[];
|
||||||
|
viaCmdShell?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
function splitCommand(command: string): string[] {
|
function splitCommand(command: string): string[] {
|
||||||
const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
|
const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
|
||||||
return parts.map((part) => part.replace(/^"(.*)"$/, "$1"));
|
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) => {
|
return await new Promise<boolean>((resolve) => {
|
||||||
const child = spawn(command, [...args, "--version"], {
|
const child = spawn(invocation.command, invocation.args, {
|
||||||
stdio: ["ignore", "ignore", "ignore"],
|
stdio: ["ignore", "ignore", "ignore"],
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
});
|
});
|
||||||
@@ -90,14 +128,18 @@ export async function resolvePythonCommand(config: AppConfig): Promise<{ command
|
|||||||
return await resolveWorkerPythonCommand(config);
|
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();
|
const configured = config.LOCAL_AI_PYTHON?.trim();
|
||||||
if (configured) {
|
if (configured) {
|
||||||
const [command, ...args] = splitCommand(configured);
|
const [command, ...args] = splitCommand(configured);
|
||||||
if (!command) {
|
if (!command) {
|
||||||
throw new Error("LOCAL_AI_PYTHON 값이 비어 있습니다.");
|
throw new Error("LOCAL_AI_PYTHON 값이 비어 있습니다.");
|
||||||
}
|
}
|
||||||
return { command, args };
|
return {
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
viaCmdShell: process.platform === "win32" && !path.isAbsolute(command),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const venvPath = resolveVenvPythonPath(config);
|
const venvPath = resolveVenvPythonPath(config);
|
||||||
@@ -118,8 +160,8 @@ export async function resolveBasePythonCommand(config: AppConfig): Promise<{ com
|
|||||||
return { command: absolute, args: candidate.args };
|
return { command: absolute, args: candidate.args };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await canRun(candidate.command, candidate.args)) {
|
if (await canRun(candidate.command, candidate.args, true)) {
|
||||||
return candidate;
|
return { ...candidate, viaCmdShell: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline";
|
|||||||
|
|
||||||
import type { AppConfig } from "../config.js";
|
import type { AppConfig } from "../config.js";
|
||||||
import type { Logger } from "../logger.js";
|
import type { Logger } from "../logger.js";
|
||||||
import { resolveWorkerPythonCommand, resolveWorkerScript } from "../python-runtime.js";
|
import { buildPythonInvocation, resolveWorkerPythonCommand, resolveWorkerScript } from "../python-runtime.js";
|
||||||
|
|
||||||
interface RpcSuccess<T> {
|
interface RpcSuccess<T> {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -49,10 +49,11 @@ export class PythonJsonWorker {
|
|||||||
throw new Error(`${this.logPrefix} worker is shutting down`);
|
throw new Error(`${this.logPrefix} worker is shutting down`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { command, args } = await resolveWorkerPythonCommand(this.config);
|
const python = await resolveWorkerPythonCommand(this.config);
|
||||||
const scriptPath = resolveWorkerScript(this.scriptName);
|
const scriptPath = resolveWorkerScript(this.scriptName);
|
||||||
|
const invocation = buildPythonInvocation(python, [scriptPath]);
|
||||||
|
|
||||||
this.processRef = spawn(command, [...args, scriptPath], {
|
this.processRef = spawn(invocation.command, invocation.args, {
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import path from "node:path";
|
|||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
|
|
||||||
import { loadConfig } from "./config.js";
|
import { loadConfig } from "./config.js";
|
||||||
import { resolveBasePythonCommand, resolveVenvPythonPath } from "./python-runtime.js";
|
import { buildPythonInvocation, resolveBasePythonCommand, resolveVenvPythonPath } from "./python-runtime.js";
|
||||||
|
|
||||||
async function run(command: string, args: string[], cwd: string): Promise<void> {
|
async function run(command: string, args: string[], cwd: string): Promise<void> {
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const child = spawn(command, args, {
|
const child = spawn(command, args, {
|
||||||
cwd,
|
cwd,
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
|
windowsHide: true,
|
||||||
});
|
});
|
||||||
child.on("exit", (code) => {
|
child.on("exit", (code) => {
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
@@ -25,14 +26,15 @@ async function run(command: string, args: string[], cwd: string): Promise<void>
|
|||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const { command, args } = await resolveBasePythonCommand(config);
|
const python = await resolveBasePythonCommand(config);
|
||||||
const venvRoot = path.resolve(process.cwd(), config.LOCAL_AI_VENV_PATH);
|
const venvRoot = path.resolve(process.cwd(), config.LOCAL_AI_VENV_PATH);
|
||||||
const requirementsPath = path.resolve(process.cwd(), "python", "requirements.txt");
|
const requirementsPath = path.resolve(process.cwd(), "python", "requirements.txt");
|
||||||
|
|
||||||
await mkdir(path.dirname(venvRoot), { recursive: true });
|
await mkdir(path.dirname(venvRoot), { recursive: true });
|
||||||
|
|
||||||
console.log(`가상환경 생성: ${venvRoot}`);
|
console.log(`가상환경 생성: ${venvRoot}`);
|
||||||
await run(command, [...args, "-m", "venv", venvRoot], process.cwd());
|
const createVenv = buildPythonInvocation(python, ["-m", "venv", venvRoot]);
|
||||||
|
await run(createVenv.command, createVenv.args, process.cwd());
|
||||||
|
|
||||||
const venvPython = resolveVenvPythonPath(config);
|
const venvPython = resolveVenvPythonPath(config);
|
||||||
await run(venvPython, ["-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"], process.cwd());
|
await run(venvPython, ["-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"], process.cwd());
|
||||||
|
|||||||
Reference in New Issue
Block a user