Leon4gr45's picture
Deploy to clean space
75fefa7 verified
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 });
}
}