259 lines
8.6 KiB
TypeScript
259 lines
8.6 KiB
TypeScript
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
|
|
const stdout = installResult.stdout;
|
|
const stderr = 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 });
|
|
}
|
|
} |