diff --git a/apps/frontend/src/main/python-detector.ts b/apps/frontend/src/main/python-detector.ts index 8b544767..c157b35b 100644 --- a/apps/frontend/src/main/python-detector.ts +++ b/apps/frontend/src/main/python-detector.ts @@ -1,4 +1,5 @@ import { execSync } from 'child_process'; +import { existsSync } from 'fs'; /** * Detect and return the best available Python command. @@ -48,12 +49,18 @@ export function getDefaultPythonCommand(): string { /** * Parse a Python command string into command and base arguments. - * Handles space-separated commands like "py -3". + * Handles space-separated commands like "py -3" and file paths with spaces. * - * @param pythonPath - The Python command string (e.g., "python3", "py -3") + * @param pythonPath - The Python command string (e.g., "python3", "py -3", "/path/with spaces/python") * @returns Tuple of [command, baseArgs] ready for use with spawn() */ export function parsePythonCommand(pythonPath: string): [string, string[]] { + // If the path points to an actual file, use it directly (handles paths with spaces) + if (existsSync(pythonPath)) { + return [pythonPath, []]; + } + + // Otherwise, split on spaces for commands like "py -3" const parts = pythonPath.split(' '); const command = parts[0]; const baseArgs = parts.slice(1); diff --git a/apps/frontend/src/main/python-env-manager.ts b/apps/frontend/src/main/python-env-manager.ts index 311dd585..5056b7cc 100644 --- a/apps/frontend/src/main/python-env-manager.ts +++ b/apps/frontend/src/main/python-env-manager.ts @@ -24,6 +24,7 @@ export class PythonEnvManager extends EventEmitter { private pythonPath: string | null = null; private isInitializing = false; private isReady = false; + private initializationPromise: Promise | null = null; /** * Get the path where the venv should be created. @@ -309,18 +310,42 @@ export class PythonEnvManager extends EventEmitter { /** * Initialize the Python environment. * Creates venv and installs deps if needed. + * + * If initialization is already in progress, this will wait for and return + * the existing initialization promise instead of starting a new one. */ async initialize(autoBuildSourcePath: string): Promise { - if (this.isInitializing) { + // If there's already an initialization in progress, wait for it + if (this.initializationPromise) { + console.warn('[PythonEnvManager] Initialization already in progress, waiting...'); + return this.initializationPromise; + } + + // If already ready and pointing to the same source, return cached status + if (this.isReady && this.autoBuildSourcePath === autoBuildSourcePath) { return { - ready: false, - pythonPath: null, - venvExists: false, - depsInstalled: false, - error: 'Already initializing' + ready: true, + pythonPath: this.pythonPath, + venvExists: true, + depsInstalled: true }; } + // Start new initialization and store the promise + this.initializationPromise = this._doInitialize(autoBuildSourcePath); + + try { + return await this.initializationPromise; + } finally { + this.initializationPromise = null; + } + } + + /** + * Internal initialization method that performs the actual setup. + * This is separated from initialize() to support the promise queue pattern. + */ + private async _doInitialize(autoBuildSourcePath: string): Promise { this.isInitializing = true; this.autoBuildSourcePath = autoBuildSourcePath;