import { NextRequest, NextResponse } from 'next/server'; declare global { var activeSandbox: any; var activeSandboxProvider: any; var sandboxData: any; } export async function POST(request: NextRequest) { try { const { packages } = await request.json(); // sandboxId not used - using global sandbox if (!packages || !Array.isArray(packages) || packages.length === 0) { return NextResponse.json({ success: false, error: 'Packages array is required' }, { status: 400 }); } // Validate and deduplicate package names const validPackages = [...new Set(packages)] .filter(pkg => pkg && typeof pkg === 'string' && pkg.trim() !== '') .map(pkg => pkg.trim()); if (validPackages.length === 0) { return NextResponse.json({ success: false, error: 'No valid package names provided' }, { status: 400 }); } // Log if duplicates were found if (packages.length !== validPackages.length) { console.log(`[install-packages] Cleaned packages: removed ${packages.length - validPackages.length} invalid/duplicate entries`); console.log(`[install-packages] Original:`, packages); console.log(`[install-packages] Cleaned:`, validPackages); } // Get active sandbox provider const provider = global.activeSandboxProvider; if (!provider) { return NextResponse.json({ success: false, error: 'No active sandbox provider available' }, { status: 400 }); } console.log('[install-packages] Installing packages:', validPackages); // Create a response stream for real-time updates const encoder = new TextEncoder(); const stream = new TransformStream(); const writer = stream.writable.getWriter(); // Function to send progress updates const sendProgress = async (data: any) => { const message = `data: ${JSON.stringify(data)}\n\n`; await writer.write(encoder.encode(message)); }; // Start installation in background (async (providerInstance) => { try { await sendProgress({ type: 'start', message: `Installing ${validPackages.length} package${validPackages.length > 1 ? 's' : ''}...`, packages: validPackages }); // Stop any existing development server first await sendProgress({ type: 'status', message: 'Stopping development server...' }); try { // Try to kill any running dev server processes await providerInstance.runCommand('pkill -f vite'); await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit } catch (killError) { // It's OK if no process is found console.debug('[install-packages] No existing dev server found:', killError); } // Check which packages are already installed await sendProgress({ type: 'status', message: 'Checking installed packages...' }); let packagesToInstall = validPackages; try { // Read package.json to check existing dependencies let packageJsonContent = ''; try { packageJsonContent = await providerInstance.readFile('package.json'); } catch (error) { console.log('[install-packages] Error reading package.json:', error); } if (packageJsonContent) { const packageJson = JSON.parse(packageJsonContent); const dependencies = packageJson.dependencies || {}; const devDependencies = packageJson.devDependencies || {}; const allDeps = { ...dependencies, ...devDependencies }; const alreadyInstalled = []; const needInstall = []; for (const pkg of validPackages) { // Handle scoped packages const pkgName = pkg.startsWith('@') ? pkg : pkg.split('@')[0]; if (allDeps[pkgName]) { alreadyInstalled.push(pkgName); } else { needInstall.push(pkg); } } packagesToInstall = needInstall; if (alreadyInstalled.length > 0) { await sendProgress({ type: 'info', message: `Already installed: ${alreadyInstalled.join(', ')}` }); } } } catch (error) { console.error('[install-packages] Error checking existing packages:', error); // If we can't check, just try to install all packages packagesToInstall = validPackages; } if (packagesToInstall.length === 0) { await sendProgress({ type: 'success', message: 'All packages are already installed', installedPackages: [], alreadyInstalled: validPackages }); // Restart dev server await sendProgress({ type: 'status', message: 'Restarting development server...' }); await providerInstance.restartViteServer(); await sendProgress({ type: 'complete', message: 'Dev server restarted!', installedPackages: [] }); return; } // Install only packages that aren't already installed await sendProgress({ type: 'info', message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}` }); // Install packages using provider method const installResult = await providerInstance.installPackages(packagesToInstall); // Get install output - ensure stdout/stderr are strings const stdout = String(installResult.stdout || ''); const stderr = String(installResult.stderr || ''); if (stdout) { const lines = stdout.split('\n').filter(line => line.trim()); for (const line of lines) { if (line.includes('npm WARN')) { await sendProgress({ type: 'warning', message: line }); } else if (line.trim()) { await sendProgress({ type: 'output', message: line }); } } } if (stderr) { const errorLines = stderr.split('\n').filter(line => line.trim()); for (const line of errorLines) { if (line.includes('ERESOLVE')) { await sendProgress({ type: 'warning', message: `Dependency conflict resolved with --legacy-peer-deps: ${line}` }); } else if (line.trim()) { await sendProgress({ type: 'error', message: line }); } } } if (installResult.exitCode === 0) { await sendProgress({ type: 'success', message: `Successfully installed: ${packagesToInstall.join(', ')}`, installedPackages: packagesToInstall }); } else { await sendProgress({ type: 'error', message: 'Package installation failed' }); } // Restart development server await sendProgress({ type: 'status', message: 'Restarting development server...' }); try { await providerInstance.restartViteServer(); // Wait a bit for the server to start await new Promise(resolve => setTimeout(resolve, 3000)); await sendProgress({ type: 'complete', message: 'Package installation complete and dev server restarted!', installedPackages: packagesToInstall }); } catch (error) { await sendProgress({ type: 'error', message: `Failed to restart dev server: ${(error as Error).message}` }); } } catch (error) { const errorMessage = (error as Error).message; if (errorMessage && errorMessage !== 'undefined') { await sendProgress({ type: 'error', message: errorMessage }); } } finally { await writer.close(); } })(provider); // Return the stream return new Response(stream.readable, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, }); } catch (error) { console.error('[install-packages] Error:', error); return NextResponse.json({ success: false, error: (error as Error).message }, { status: 500 }); } }