import { existsSync } from 'node:fs'; import { dirname, isAbsolute, join, resolve } from 'node:path'; export type PythonRuntimeSource = 'configured' | 'bundled' | 'system'; export type PythonRuntime = { executable: string; binDir: string; source: PythonRuntimeSource; }; export type UvRuntime = { executable: string; binDir: string; source: 'bundled' | 'system'; }; export type PythonRuntimeOptions = { isPackaged: boolean; resourcesPath: string; appPath: string; platform: NodeJS.Platform; arch: string; configuredPath?: string; }; export type UvRuntimeOptions = Omit; function configuredRuntime(configuredPath: string | undefined, appPath: string): PythonRuntime | null { const trimmed = configuredPath?.trim(); if (!trimmed) return null; const executable = isAbsolute(trimmed) ? trimmed : resolve(appPath, trimmed); if (!existsSync(executable)) return null; return { executable, binDir: dirname(executable), source: 'configured' }; } function bundledExecutable(resourcesPath: string, platform: NodeJS.Platform): string { return platform === 'win32' ? join(resourcesPath, 'python', 'python.exe') : join(resourcesPath, 'python', 'bin', 'python3'); } export function resolvePythonRuntime(options: PythonRuntimeOptions): PythonRuntime { const configured = options.isPackaged ? null : configuredRuntime(options.configuredPath, options.appPath); if (configured) return configured; const executable = bundledExecutable(options.resourcesPath, options.platform); if (existsSync(executable)) { return { executable, binDir: dirname(executable), source: 'bundled' }; } if (options.isPackaged) { throw new Error(`Bundled Python runtime is missing: ${executable}`); } return { executable: options.platform === 'win32' ? 'python' : 'python3', binDir: '', source: 'system', }; } function uvExecutableName(platform: NodeJS.Platform): string { return platform === 'win32' ? 'uv.exe' : 'uv'; } export function resolveUvRuntime(options: UvRuntimeOptions): UvRuntime { const executable = options.isPackaged ? join(options.resourcesPath, 'bin', uvExecutableName(options.platform)) : join( options.appPath, 'resources', 'bin', `${options.platform}-${options.arch}`, uvExecutableName(options.platform), ); if (existsSync(executable)) { return { executable, binDir: dirname(executable), source: 'bundled' }; } if (options.isPackaged) { throw new Error(`Bundled uv runtime is missing: ${executable}`); } return { executable: 'uv', binDir: '', source: 'system' }; } function prependRuntimeBin( environment: Record, binDir: string, platform: NodeJS.Platform, ): Record { if (!binDir) return environment; const result = { ...environment }; const existingKey = Object.keys(result).find((key) => key.toLowerCase() === 'path'); const pathKey = platform === 'win32' && existingKey ? existingKey : 'PATH'; const existingPath = result[pathKey]; const separator = platform === 'win32' ? ';' : ':'; result[pathKey] = existingPath ? `${binDir}${separator}${existingPath}` : binDir; return result; } export function prependPythonToPath( environment: Record, runtime: PythonRuntime, platform: NodeJS.Platform = process.platform, ): Record { return prependRuntimeBin( { ...environment, NIANCODE_PYTHON_PATH: runtime.executable }, runtime.binDir, platform, ); } export function prependManagedRuntimesToPath( environment: Record, pythonRuntime?: PythonRuntime, uvRuntime?: UvRuntime, platform: NodeJS.Platform = process.platform, ): Record { const withPython = pythonRuntime ? prependPythonToPath(environment, pythonRuntime, platform) : { ...environment }; if (!uvRuntime) return withPython; return prependRuntimeBin( { ...withPython, NIANCODE_UV_PATH: uvRuntime.executable }, uvRuntime.binDir, platform, ); }