mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
d5f82bb48b
commit
56403d21aa
3 changed files with 147 additions and 120 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
63
packages/codeflash/scripts/paths.js
Normal file
63
packages/codeflash/scripts/paths.js
Normal file
|
|
@ -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 };
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue