Migrate to Vercel Sandbox using OIDC

This commit is contained in:
MFCo
2025-08-26 13:01:46 +02:00
parent cad43d5fca
commit 415179c234
20 changed files with 972 additions and 1621 deletions
+121 -219
View File
@@ -1,5 +1,4 @@
import { NextRequest, NextResponse } from 'next/server';
import { Sandbox } from '@e2b/code-interpreter';
declare global {
var activeSandbox: any;
@@ -36,23 +35,8 @@ export async function POST(request: NextRequest) {
console.log(`[install-packages] Cleaned:`, validPackages);
}
// Try to get sandbox - either from global or reconnect
let sandbox = global.activeSandbox;
if (!sandbox && sandboxId) {
console.log(`[install-packages] Reconnecting to sandbox ${sandboxId}...`);
try {
sandbox = await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY });
global.activeSandbox = sandbox;
console.log(`[install-packages] Successfully reconnected to sandbox ${sandboxId}`);
} catch (error) {
console.error(`[install-packages] Failed to reconnect to sandbox:`, error);
return NextResponse.json({
success: false,
error: `Failed to reconnect to sandbox: ${(error as Error).message}`
}, { status: 500 });
}
}
// Get active sandbox
const sandbox = global.activeSandbox;
if (!sandbox) {
return NextResponse.json({
@@ -61,7 +45,7 @@ export async function POST(request: NextRequest) {
}, { status: 400 });
}
console.log('[install-packages] Installing packages:', packages);
console.log('[install-packages] Installing packages:', validPackages);
// Create a response stream for real-time updates
const encoder = new TextEncoder();
@@ -83,23 +67,20 @@ export async function POST(request: NextRequest) {
packages: validPackages
});
// Kill any existing Vite process first
// Stop any existing development server first
await sendProgress({ type: 'status', message: 'Stopping development server...' });
await sandboxInstance.runCode(`
import subprocess
import os
import signal
# Try to kill any existing Vite process
try:
with open('/tmp/vite-process.pid', 'r') as f:
pid = int(f.read().strip())
os.kill(pid, signal.SIGTERM)
print("Stopped existing Vite process")
except:
print("No existing Vite process found")
`);
try {
// Try to kill any running dev server processes
await sandboxInstance.runCommand({
cmd: 'pkill',
args: ['-f', 'vite']
});
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit
} catch (error) {
// It's OK if no process is found
console.log('[install-packages] No existing dev server found');
}
// Check which packages are already installed
await sendProgress({
@@ -107,70 +88,51 @@ except:
message: 'Checking installed packages...'
});
const checkResult = await sandboxInstance.runCode(`
import os
import json
os.chdir('/home/user/app')
# Read package.json to check installed packages
try:
with open('package.json', 'r') as f:
package_json = json.load(f)
dependencies = package_json.get('dependencies', {})
dev_dependencies = package_json.get('devDependencies', {})
all_deps = {**dependencies, **dev_dependencies}
# Check which packages need to be installed
packages_to_check = ${JSON.stringify(validPackages)}
already_installed = []
need_install = []
for pkg in packages_to_check:
# Handle scoped packages
if pkg.startswith('@'):
pkg_name = pkg
else:
# Extract package name without version
pkg_name = pkg.split('@')[0]
if pkg_name in all_deps:
already_installed.append(pkg_name)
else:
need_install.append(pkg)
print(f"Already installed: {already_installed}")
print(f"Need to install: {need_install}")
print(f"NEED_INSTALL:{json.dumps(need_install)}")
except Exception as e:
print(f"Error checking packages: {e}")
print(f"NEED_INSTALL:{json.dumps(packages_to_check)}")
`);
// Parse packages that need installation
let packagesToInstall = validPackages;
// Check if checkResult has the expected structure
if (checkResult && checkResult.results && checkResult.results[0] && checkResult.results[0].text) {
const outputLines = checkResult.results[0].text.split('\n');
for (const line of outputLines) {
if (line.startsWith('NEED_INSTALL:')) {
try {
packagesToInstall = JSON.parse(line.substring('NEED_INSTALL:'.length));
} catch (e) {
console.error('Failed to parse packages to install:', e);
try {
// Read package.json to check existing dependencies
const catResult = await sandboxInstance.runCommand({
cmd: 'cat',
args: ['package.json']
});
if (catResult.exitCode === 0) {
const packageJsonContent = await catResult.stdout();
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(', ')}`
});
}
}
} else {
console.error('[install-packages] Invalid checkResult structure:', checkResult);
} 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',
@@ -178,164 +140,104 @@ except Exception as e:
installedPackages: [],
alreadyInstalled: validPackages
});
// Restart dev server
await sendProgress({ type: 'status', message: 'Restarting development server...' });
const devServerProcess = await sandboxInstance.runCommand({
cmd: 'npm',
args: ['run', 'dev'],
detached: true
});
await sendProgress({
type: 'complete',
message: 'Dev server restarted!',
installedPackages: []
});
return;
}
// Install only packages that aren't already installed
const packageList = packagesToInstall.join(' ');
// Only send the npm install command message if we're actually installing new packages
await sendProgress({
type: 'info',
message: `Installing ${packagesToInstall.length} new package(s): ${packagesToInstall.join(', ')}`
});
const installResult = await sandboxInstance.runCode(`
import subprocess
import os
os.chdir('/home/user/app')
# Run npm install with output capture
packages_to_install = ${JSON.stringify(packagesToInstall)}
cmd_args = ['npm', 'install', '--legacy-peer-deps'] + packages_to_install
print(f"Running command: {' '.join(cmd_args)}")
process = subprocess.Popen(
cmd_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Stream output
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())
# Get the return code
rc = process.poll()
# Capture any stderr
stderr = process.stderr.read()
if stderr:
print("STDERR:", stderr)
if 'ERESOLVE' in stderr:
print("ERESOLVE_ERROR: Dependency conflict detected - using --legacy-peer-deps flag")
print(f"\\nInstallation completed with code: {rc}")
# Verify packages were installed
import json
with open('/home/user/app/package.json', 'r') as f:
package_json = json.load(f)
installed = []
for pkg in ${JSON.stringify(packagesToInstall)}:
if pkg in package_json.get('dependencies', {}):
installed.append(pkg)
print(f"✓ Verified {pkg}")
else:
print(f"✗ Package {pkg} not found in dependencies")
// Run npm install
const installArgs = ['install', '--legacy-peer-deps', ...packagesToInstall];
const installResult = await sandboxInstance.runCommand({
cmd: 'npm',
args: installArgs
});
print(f"\\nVerified installed packages: {installed}")
`, { timeout: 60000 }); // 60 second timeout for npm install
// Get install output
const stdout = await installResult.stdout();
const stderr = await installResult.stderr();
// Send npm output
const output = installResult?.output || installResult?.logs?.stdout?.join('\n') || '';
const npmOutputLines = output.split('\n').filter((line: string) => line.trim());
for (const line of npmOutputLines) {
if (line.includes('STDERR:')) {
const errorMsg = line.replace('STDERR:', '').trim();
if (errorMsg && errorMsg !== 'undefined') {
await sendProgress({ type: 'error', message: errorMsg });
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 });
}
} else if (line.includes('ERESOLVE_ERROR:')) {
const msg = line.replace('ERESOLVE_ERROR:', '').trim();
await sendProgress({
type: 'warning',
message: `Dependency conflict resolved with --legacy-peer-deps: ${msg}`
});
} else if (line.includes('npm WARN')) {
await sendProgress({ type: 'warning', message: line });
} else if (line.trim() && !line.includes('undefined')) {
await sendProgress({ type: 'output', message: line });
}
}
// Check if installation was successful
const installedMatch = output.match(/Verified installed packages: \[(.*?)\]/);
let installedPackages: string[] = [];
if (installedMatch && installedMatch[1]) {
installedPackages = installedMatch[1]
.split(',')
.map((p: string) => p.trim().replace(/'/g, ''))
.filter((p: string) => p.length > 0);
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 (installedPackages.length > 0) {
if (installResult.exitCode === 0) {
await sendProgress({
type: 'success',
message: `Successfully installed: ${installedPackages.join(', ')}`,
installedPackages
message: `Successfully installed: ${packagesToInstall.join(', ')}`,
installedPackages: packagesToInstall
});
} else {
await sendProgress({
type: 'error',
message: 'Failed to verify package installation'
message: 'Package installation failed'
});
}
// Restart Vite dev server
// Restart development server
await sendProgress({ type: 'status', message: 'Restarting development server...' });
await sandboxInstance.runCode(`
import subprocess
import os
import time
os.chdir('/home/user/app')
# Kill any existing Vite processes
subprocess.run(['pkill', '-f', 'vite'], capture_output=True)
time.sleep(1)
# Start Vite dev server
env = os.environ.copy()
env['FORCE_COLOR'] = '0'
process = subprocess.Popen(
['npm', 'run', 'dev'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env
)
print(f'✓ Vite dev server restarted with PID: {process.pid}')
# Store process info for later
with open('/tmp/vite-process.pid', 'w') as f:
f.write(str(process.pid))
# Wait a bit for Vite to start up
time.sleep(3)
# Touch files to trigger Vite reload
subprocess.run(['touch', '/home/user/app/package.json'])
subprocess.run(['touch', '/home/user/app/vite.config.js'])
print("Vite restarted and should now recognize all packages")
`);
await sendProgress({
type: 'complete',
message: 'Package installation complete and dev server restarted!',
installedPackages
});
try {
const devServerProcess = await sandboxInstance.runCommand({
cmd: 'npm',
args: ['run', 'dev'],
detached: true
});
// 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;