(pythonExe: string, venvDir: string, win: BrowserWindow)
| 187 | // ─── Setup steps ───────────────────────────────────────────────────────────── |
| 188 | |
| 189 | function createVenv(pythonExe: string, venvDir: string, win: BrowserWindow): Promise<void> { |
| 190 | return new Promise((resolve, reject) => { |
| 191 | win.webContents.send('setup:progress', { step: 'venv', percent: 5 }) |
| 192 | console.log('[PythonSetup] Creating venv at', venvDir) |
| 193 | const proc = spawn(pythonExe, ['-m', 'venv', '--clear', venvDir], { |
| 194 | stdio: ['ignore', 'pipe', 'pipe'], |
| 195 | env: cleanPythonEnv(), |
| 196 | }) |
| 197 | let stderrOut = '' |
| 198 | proc.stdout?.on('data', (d: Buffer) => console.log('[venv]', d.toString().trim())) |
| 199 | proc.stderr?.on('data', (d: Buffer) => { |
| 200 | const text = d.toString().trim() |
| 201 | if (text) { console.error('[venv]', text); stderrOut += text + '\n' } |
| 202 | }) |
| 203 | proc.on('close', (code) => { |
| 204 | if (code === 0) { |
| 205 | win.webContents.send('setup:progress', { step: 'venv', percent: 20 }) |
| 206 | resolve() |
| 207 | } else { |
| 208 | let msg = `python -m venv exited with code ${code}` |
| 209 | if (stderrOut) msg += `\n\n${stderrOut.trim()}` |
| 210 | if (process.platform === 'win32') { |
| 211 | msg += |
| 212 | '\n\nYour antivirus may be blocking the Python runtime.' + |
| 213 | `\nTry adding an exclusion for:\n ${pythonExe}` + |
| 214 | '\nOr temporarily pause real-time protection, then click Retry.' |
| 215 | } |
| 216 | reject(new Error(msg)) |
| 217 | } |
| 218 | }) |
| 219 | }) |
| 220 | } |
| 221 | |
| 222 | function installRequirements( |
| 223 | pythonExe: string, |
no test coverage detected