From 56403d21aaba2af7929b63fde4551de5ce47fd8f Mon Sep 17 00:00:00 2001 From: ali Date: Thu, 26 Mar 2026 17:33:56 +0200 Subject: [PATCH] refactor: install Python CLI into isolated venv instead of uv tool Replace `uv tool install` (which installs globally into ~/.local/bin) with a dedicated venv at an OS-specific cache directory (~/.cache/codeflash/venv on Linux, ~/Library/Caches/codeflash/venv on macOS, %LOCALAPPDATA%\codeflash\venv on Windows). The CLI entry point now invokes the binary directly from the venv instead of via `uv tool run`. Also strips VIRTUAL_ENV/CONDA env vars from child processes to avoid interference from activated environments. Co-Authored-By: Claude Opus 4.6 --- packages/codeflash/bin/codeflash.js | 81 +++++--------- packages/codeflash/scripts/paths.js | 63 +++++++++++ packages/codeflash/scripts/postinstall.js | 123 +++++++++++----------- 3 files changed, 147 insertions(+), 120 deletions(-) create mode 100644 packages/codeflash/scripts/paths.js diff --git a/packages/codeflash/bin/codeflash.js b/packages/codeflash/bin/codeflash.js index 1cfe348bd..f0d769c39 100755 --- a/packages/codeflash/bin/codeflash.js +++ b/packages/codeflash/bin/codeflash.js @@ -4,69 +4,52 @@ * Codeflash CLI Entry Point * * This script is the main entry point for the codeflash CLI when installed via npm. - * It delegates to the Python codeflash CLI installed via uv. + * It delegates to the Python codeflash CLI installed in a dedicated venv. * * Usage: * npx codeflash --help * npx codeflash optimize --file src/utils.ts */ -const { spawn, spawnSync } = require('child_process'); -const os = require('os'); -const path = require('path'); +const { spawn } = require('child_process'); const fs = require('fs'); +const { getCodeflashBin } = require('../scripts/paths'); /** - * Find the uv binary + * Find the codeflash binary in the dedicated venv */ -function findUv() { - const homeDir = os.homedir(); - const platform = os.platform(); - - // Check the default uv installation location first - const uvPath = platform === 'win32' - ? path.join(homeDir, '.local', 'bin', 'uv.exe') - : path.join(homeDir, '.local', 'bin', 'uv'); - - if (fs.existsSync(uvPath)) { - return uvPath; +function findCodeflash() { + const codeflashBin = getCodeflashBin(); + if (fs.existsSync(codeflashBin)) { + return codeflashBin; } - - // Try to find uv in PATH by checking if it exists - try { - const uvInPath = spawnSync('uv', ['--version'], { - stdio: 'ignore', - }); - if (uvInPath.status === 0) { - return 'uv'; - } - } catch { - // uv not in PATH - } - return null; } /** - * Run the codeflash CLI via uv + * Run the codeflash CLI */ function runCodeflash(args) { - const uvBin = findUv(); + const codeflashBin = findCodeflash(); - if (!uvBin) { - console.error('\x1b[31mError:\x1b[0m uv not found.'); + if (!codeflashBin) { + console.error('\x1b[31mError:\x1b[0m codeflash Python CLI not found.'); console.error(''); console.error('Please run the setup script:'); console.error(' npx codeflash-setup'); console.error(''); - console.error('Or install uv manually:'); - console.error(' curl -LsSf https://astral.sh/uv/install.sh | sh'); process.exit(1); } - // Use uv tool run to execute codeflash - const child = spawn(uvBin, ['tool', 'run', 'codeflash', ...args], { + // Strip VIRTUAL_ENV so the venv's Python is used, not an activated one + const env = { ...process.env }; + delete env.VIRTUAL_ENV; + delete env.CONDA_PREFIX; + delete env.CONDA_DEFAULT_ENV; + + const child = spawn(codeflashBin, args, { stdio: 'inherit', + env, }); child.on('error', (error) => { @@ -87,19 +70,15 @@ function runCodeflash(args) { */ function showSetupHelp() { console.log(` -\x1b[36m╔════════════════════════════════════════════╗ -║ Codeflash CLI Setup Required ║ -╚════════════════════════════════════════════╝\x1b[0m +\x1b[36m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 +\u2551 Codeflash CLI Setup Required \u2551 +\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1b[0m The codeflash Python CLI is not installed. \x1b[33mTo complete setup, run:\x1b[0m npx codeflash-setup -\x1b[33mOr install manually:\x1b[0m - curl -LsSf https://astral.sh/uv/install.sh | sh - uv tool install codeflash - \x1b[36mDocumentation:\x1b[0m https://docs.codeflash.ai `); } @@ -111,18 +90,8 @@ const args = process.argv.slice(2); if (args[0] === 'setup' || args[0] === '--setup') { require('../scripts/postinstall.js'); } else { - // Check if codeflash is installed - const uvBin = findUv(); - if (uvBin) { - const check = spawnSync(uvBin, ['tool', 'run', 'codeflash', '--version'], { - stdio: 'ignore', - }); - - if (check.status !== 0 && args.length === 0) { - showSetupHelp(); - process.exit(1); - } - } else if (args.length === 0) { + const codeflashBin = findCodeflash(); + if (!codeflashBin && args.length === 0) { showSetupHelp(); process.exit(1); } diff --git a/packages/codeflash/scripts/paths.js b/packages/codeflash/scripts/paths.js new file mode 100644 index 000000000..efe6f6bb2 --- /dev/null +++ b/packages/codeflash/scripts/paths.js @@ -0,0 +1,63 @@ +/** + * Shared path utilities for codeflash installation. + * + * Provides OS-specific cache directory and binary paths used by + * both the postinstall script and the CLI entry point. + */ + +const os = require('os'); +const path = require('path'); + +/** + * Get the OS-appropriate cache directory for codeflash. + * + * - Linux: ~/.cache/codeflash + * - macOS: ~/Library/Caches/codeflash + * - Windows: %LOCALAPPDATA%\codeflash + */ +function getCacheDir() { + const platform = os.platform(); + const homeDir = os.homedir(); + + if (platform === 'win32') { + return path.join(process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'), 'codeflash'); + } + if (platform === 'darwin') { + return path.join(homeDir, 'Library', 'Caches', 'codeflash'); + } + // Linux / other Unix + return path.join(process.env.XDG_CACHE_HOME || path.join(homeDir, '.cache'), 'codeflash'); +} + +/** + * Get the path to the venv directory inside the cache. + */ +function getVenvDir() { + return path.join(getCacheDir(), 'venv'); +} + +/** + * Get the path to the codeflash binary inside the venv. + */ +function getCodeflashBin() { + const venvDir = getVenvDir(); + if (os.platform() === 'win32') { + return path.join(venvDir, 'Scripts', 'codeflash.exe'); + } + return path.join(venvDir, 'bin', 'codeflash'); +} + +/** + * Get the path to the uv binary. + */ +function getUvPath() { + const platform = os.platform(); + const homeDir = os.homedir(); + + if (platform === 'win32') { + return path.join(homeDir, '.local', 'bin', 'uv.exe'); + } + return path.join(homeDir, '.local', 'bin', 'uv'); +} + +module.exports = { getCacheDir, getVenvDir, getCodeflashBin, getUvPath }; diff --git a/packages/codeflash/scripts/postinstall.js b/packages/codeflash/scripts/postinstall.js index e3c5820f9..9c1cf218e 100644 --- a/packages/codeflash/scripts/postinstall.js +++ b/packages/codeflash/scripts/postinstall.js @@ -6,16 +6,27 @@ * This script runs after `npm install codeflash` and: * 1. Checks if uv (Python package manager) is installed * 2. If not, installs uv automatically - * 3. Uses uv to install the Python codeflash CLI + * 3. Creates a dedicated venv and installs the Python codeflash CLI into it * - * This approach follows the same pattern as aider and mistral-code, - * which use uv for Python distribution. + * The codeflash Python CLI is installed into an isolated venv at: + * - Linux: ~/.cache/codeflash/venv/ + * - macOS: ~/Library/Caches/codeflash/venv/ + * - Windows: %LOCALAPPDATA%\codeflash\venv\ */ const { execSync, spawnSync } = require('child_process'); const os = require('os'); -const path = require('path'); const fs = require('fs'); +const { getCacheDir, getVenvDir, getCodeflashBin, getUvPath } = require('./paths'); + +// Clean environment without VIRTUAL_ENV so uv doesn't target an activated venv +const cleanEnv = (() => { + const env = { ...process.env }; + delete env.VIRTUAL_ENV; + delete env.CONDA_PREFIX; + delete env.CONDA_DEFAULT_ENV; + return env; +})(); // ANSI color codes for pretty output const colors = { @@ -36,15 +47,15 @@ function logStep(step, message) { } function logSuccess(message) { - console.log(`${colors.green}✓${colors.reset} ${message}`); + console.log(`${colors.green}\u2713${colors.reset} ${message}`); } function logWarning(message) { - console.log(`${colors.yellow}⚠${colors.reset} ${message}`); + console.log(`${colors.yellow}\u26A0${colors.reset} ${message}`); } function logError(message) { - console.error(`${colors.red}✗${colors.reset} ${message}`); + console.error(`${colors.red}\u2717${colors.reset} ${message}`); } /** @@ -62,20 +73,6 @@ function commandExists(command) { } } -/** - * Get the uv binary path - * uv installs to ~/.local/bin on Unix or %USERPROFILE%\.local\bin on Windows - */ -function getUvPath() { - const platform = os.platform(); - const homeDir = os.homedir(); - - if (platform === 'win32') { - return path.join(homeDir, '.local', 'bin', 'uv.exe'); - } - return path.join(homeDir, '.local', 'bin', 'uv'); -} - /** * Install uv using the official installer */ @@ -86,13 +83,11 @@ function installUv() { try { if (platform === 'win32') { - // Windows: Use PowerShell execSync( 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', { stdio: 'inherit', shell: true } ); } else { - // macOS/Linux: Use curl execSync( 'curl -LsSf https://astral.sh/uv/install.sh | sh', { stdio: 'inherit', shell: true } @@ -122,26 +117,43 @@ function hasGit() { } /** - * Install codeflash Python CLI using uv tool + * Create the codeflash venv and install the Python CLI into it. * * Installation priority: * 1. GitHub main branch (if git available) - gets latest features * 2. PyPI (fallback) - stable release - * - * We prefer GitHub because it has the latest JS/TS support that may not - * be published to PyPI yet. uv handles cloning internally in its cache. */ function installCodeflash(uvBin) { logStep('2/3', 'Installing codeflash Python CLI...'); + const venvDir = getVenvDir(); + const cacheDir = getCacheDir(); + + // Ensure cache directory exists + fs.mkdirSync(cacheDir, { recursive: true }); + + // Create the venv (or reuse existing) + try { + execSync(`"${uvBin}" venv --python python3.12 --clear "${venvDir}"`, { + stdio: 'inherit', + shell: true, + env: cleanEnv, + }); + logSuccess(`venv created at ${venvDir}`); + } catch (error) { + logError(`Failed to create venv: ${error.message}`); + return false; + } + const GITHUB_REPO = 'git+https://github.com/codeflash-ai/codeflash.git'; // Priority 1: Install from GitHub (latest features, requires git) if (hasGit()) { try { - execSync(`"${uvBin}" tool install --force --python python3.12 "${GITHUB_REPO}"`, { + execSync(`"${uvBin}" pip install --python "${venvDir}" "${GITHUB_REPO}"`, { stdio: 'inherit', shell: true, + env: cleanEnv, }); logSuccess('codeflash CLI installed from GitHub (latest)'); return true; @@ -155,9 +167,10 @@ function installCodeflash(uvBin) { // Priority 2: Install from PyPI (stable release fallback) try { - execSync(`"${uvBin}" tool install --force --python python3.12 codeflash`, { + execSync(`"${uvBin}" pip install --python "${venvDir}" codeflash`, { stdio: 'inherit', shell: true, + env: cleanEnv, }); logSuccess('codeflash CLI installed from PyPI'); return true; @@ -167,34 +180,19 @@ function installCodeflash(uvBin) { } } -/** - * Update shell configuration to include uv tools in PATH - */ -function updateShellPath(uvBin) { - logStep('3/3', 'Updating shell configuration...'); - - try { - execSync(`"${uvBin}" tool update-shell`, { - stdio: 'inherit', - shell: true, - }); - logSuccess('Shell configuration updated'); - return true; - } catch (error) { - logWarning(`Could not update shell: ${error.message}`); - logWarning('You may need to add ~/.local/bin to your PATH manually'); - return true; // Non-fatal - } -} - /** * Verify the installation works */ -function verifyInstallation(uvBin) { +function verifyInstallation() { + const codeflashBin = getCodeflashBin(); try { - const result = spawnSync(uvBin, ['tool', 'run', 'codeflash', '--version'], { + if (!fs.existsSync(codeflashBin)) { + return false; + } + const result = spawnSync(codeflashBin, ['--version'], { encoding: 'utf8', shell: true, + env: cleanEnv, }); if (result.status === 0) { @@ -213,9 +211,9 @@ function verifyInstallation(uvBin) { */ async function main() { console.log(''); - log('╔════════════════════════════════════════════╗', 'cyan'); - log('║ Codeflash CLI Installation ║', 'cyan'); - log('╚════════════════════════════════════════════╝', 'cyan'); + log('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557', 'cyan'); + log('\u2551 Codeflash CLI Installation \u2551', 'cyan'); + log('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D', 'cyan'); console.log(''); // Check if running in CI or with --ignore-scripts @@ -230,7 +228,7 @@ async function main() { // Step 1: Check/install uv if (commandExists('uv')) { logSuccess('uv is already installed'); - uvBin = 'uv'; // Use the one in PATH + uvBin = 'uv'; } else if (fs.existsSync(uvBin)) { logSuccess('uv found at ' + uvBin); } else { @@ -240,7 +238,6 @@ async function main() { process.exit(1); } - // Check if uv is now available if (!fs.existsSync(uvBin) && !commandExists('uv')) { logError('uv installation completed but binary not found'); logError('Please restart your terminal and run: npx codeflash-setup'); @@ -253,25 +250,23 @@ async function main() { uvBin = 'uv'; } - // Step 2: Install codeflash Python CLI + // Step 2: Install codeflash Python CLI into dedicated venv if (!installCodeflash(uvBin)) { logError('Failed to install codeflash CLI'); - logError('You can try manually: uv tool install codeflash'); + logError('You can try manually: uv pip install codeflash'); process.exit(1); } - // Step 3: Update shell PATH - updateShellPath(uvBin); - // Verify installation console.log(''); - verifyInstallation(uvBin); + logStep('3/3', 'Verifying installation...'); + verifyInstallation(); // Print success message console.log(''); - log('════════════════════════════════════════════', 'green'); + log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550', 'green'); logSuccess('Codeflash installation complete!'); - log('════════════════════════════════════════════', 'green'); + log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550', 'green'); console.log(''); log('Get started:', 'cyan'); console.log(' npx codeflash --help');