Migrate to Vercel Sandbox using OIDC
This commit is contained in:
+135
-189
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { Sandbox } from '@e2b/code-interpreter';
|
||||
import { Sandbox } from '@vercel/sandbox';
|
||||
import type { SandboxState } from '@/types/sandbox';
|
||||
import { appConfig } from '@/config/app.config';
|
||||
|
||||
@@ -15,15 +15,15 @@ export async function POST() {
|
||||
let sandbox: any = null;
|
||||
|
||||
try {
|
||||
console.log('[create-ai-sandbox] Creating base sandbox...');
|
||||
console.log('[create-ai-sandbox] Creating Vercel sandbox...');
|
||||
|
||||
// Kill existing sandbox if any
|
||||
if (global.activeSandbox) {
|
||||
console.log('[create-ai-sandbox] Killing existing sandbox...');
|
||||
console.log('[create-ai-sandbox] Stopping existing sandbox...');
|
||||
try {
|
||||
await global.activeSandbox.kill();
|
||||
await global.activeSandbox.stop();
|
||||
} catch (e) {
|
||||
console.error('Failed to close existing sandbox:', e);
|
||||
console.error('Failed to stop existing sandbox:', e);
|
||||
}
|
||||
global.activeSandbox = null;
|
||||
}
|
||||
@@ -35,81 +35,86 @@ export async function POST() {
|
||||
global.existingFiles = new Set<string>();
|
||||
}
|
||||
|
||||
// Create base sandbox - we'll set up Vite ourselves for full control
|
||||
console.log(`[create-ai-sandbox] Creating base E2B sandbox with ${appConfig.e2b.timeoutMinutes} minute timeout...`);
|
||||
sandbox = await Sandbox.create({
|
||||
apiKey: process.env.E2B_API_KEY,
|
||||
timeoutMs: appConfig.e2b.timeoutMs
|
||||
// Create Vercel sandbox
|
||||
console.log(`[create-ai-sandbox] Creating Vercel sandbox with ${appConfig.vercelSandbox.timeoutMinutes} minute timeout...`);
|
||||
sandbox = await Sandbox.create({
|
||||
timeout: appConfig.vercelSandbox.timeoutMs,
|
||||
runtime: appConfig.vercelSandbox.runtime,
|
||||
ports: [appConfig.vercelSandbox.devPort]
|
||||
});
|
||||
|
||||
const sandboxId = (sandbox as any).sandboxId || Date.now().toString();
|
||||
const host = (sandbox as any).getHost(appConfig.e2b.vitePort);
|
||||
|
||||
const sandboxId = sandbox.sandboxId;
|
||||
console.log(`[create-ai-sandbox] Sandbox created: ${sandboxId}`);
|
||||
console.log(`[create-ai-sandbox] Sandbox host: ${host}`);
|
||||
|
||||
// Set up a basic Vite React app using Python to write files
|
||||
// Set up a basic Vite React app
|
||||
console.log('[create-ai-sandbox] Setting up Vite React app...');
|
||||
|
||||
// Write all files in a single Python script to avoid multiple executions
|
||||
const setupScript = `
|
||||
import os
|
||||
import json
|
||||
// First, change to the working directory
|
||||
await sandbox.runCommand('pwd');
|
||||
const workDir = appConfig.vercelSandbox.workingDirectory;
|
||||
|
||||
// Get the sandbox URL using the correct Vercel Sandbox API
|
||||
const sandboxUrl = sandbox.domain(appConfig.vercelSandbox.devPort);
|
||||
|
||||
// Extract the hostname from the sandbox URL for Vite config
|
||||
const sandboxHostname = new URL(sandboxUrl).hostname;
|
||||
console.log(`[create-ai-sandbox] Sandbox hostname: ${sandboxHostname}`);
|
||||
|
||||
print('Setting up React app with Vite and Tailwind...')
|
||||
|
||||
# Create directory structure
|
||||
os.makedirs('/home/user/app/src', exist_ok=True)
|
||||
|
||||
# Package.json
|
||||
package_json = {
|
||||
"name": "sandbox-app",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"vite": "^4.3.9",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"postcss": "^8.4.31",
|
||||
"autoprefixer": "^10.4.16"
|
||||
}
|
||||
}
|
||||
|
||||
with open('/home/user/app/package.json', 'w') as f:
|
||||
json.dump(package_json, f, indent=2)
|
||||
print('✓ package.json')
|
||||
|
||||
# Vite config for E2B - with allowedHosts
|
||||
vite_config = """import { defineConfig } from 'vite'
|
||||
// Create the Vite config content with the proper hostname (using string concatenation)
|
||||
const viteConfigContent = `import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// E2B-compatible Vite configuration
|
||||
// Vercel Sandbox compatible Vite configuration
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
port: ${appConfig.vercelSandbox.devPort},
|
||||
strictPort: true,
|
||||
hmr: false,
|
||||
allowedHosts: ['.e2b.app', 'localhost', '127.0.0.1']
|
||||
hmr: true,
|
||||
allowedHosts: [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
'` + sandboxHostname + `', // Allow the Vercel Sandbox domain
|
||||
'.vercel.run', // Allow all Vercel sandbox domains
|
||||
'.vercel-sandbox.dev' // Fallback pattern
|
||||
]
|
||||
}
|
||||
})"""
|
||||
})`;
|
||||
|
||||
with open('/home/user/app/vite.config.js', 'w') as f:
|
||||
f.write(vite_config)
|
||||
print('✓ vite.config.js')
|
||||
|
||||
# Tailwind config - standard without custom design tokens
|
||||
tailwind_config = """/** @type {import('tailwindcss').Config} */
|
||||
// Create the project files (now we have the sandbox hostname)
|
||||
const projectFiles = [
|
||||
{
|
||||
path: 'package.json',
|
||||
content: Buffer.from(JSON.stringify({
|
||||
"name": "sandbox-app",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host --port 3000",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"vite": "^4.3.9",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"postcss": "^8.4.31",
|
||||
"autoprefixer": "^10.4.16"
|
||||
}
|
||||
}, null, 2))
|
||||
},
|
||||
{
|
||||
path: 'vite.config.js',
|
||||
content: Buffer.from(viteConfigContent)
|
||||
},
|
||||
{
|
||||
path: 'tailwind.config.js',
|
||||
content: Buffer.from(`/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
@@ -119,26 +124,20 @@ export default {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}"""
|
||||
|
||||
with open('/home/user/app/tailwind.config.js', 'w') as f:
|
||||
f.write(tailwind_config)
|
||||
print('✓ tailwind.config.js')
|
||||
|
||||
# PostCSS config
|
||||
postcss_config = """export default {
|
||||
}`)
|
||||
},
|
||||
{
|
||||
path: 'postcss.config.js',
|
||||
content: Buffer.from(`export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}"""
|
||||
|
||||
with open('/home/user/app/postcss.config.js', 'w') as f:
|
||||
f.write(postcss_config)
|
||||
print('✓ postcss.config.js')
|
||||
|
||||
# Index.html
|
||||
index_html = """<!DOCTYPE html>
|
||||
}`)
|
||||
},
|
||||
{
|
||||
path: 'index.html',
|
||||
content: Buffer.from(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -149,14 +148,11 @@ index_html = """<!DOCTYPE html>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
with open('/home/user/app/index.html', 'w') as f:
|
||||
f.write(index_html)
|
||||
print('✓ index.html')
|
||||
|
||||
# Main.jsx
|
||||
main_jsx = """import React from 'react'
|
||||
</html>`)
|
||||
},
|
||||
{
|
||||
path: 'src/main.jsx',
|
||||
content: Buffer.from(`import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
@@ -165,19 +161,18 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)"""
|
||||
|
||||
with open('/home/user/app/src/main.jsx', 'w') as f:
|
||||
f.write(main_jsx)
|
||||
print('✓ src/main.jsx')
|
||||
|
||||
# App.jsx with explicit Tailwind test
|
||||
app_jsx = """function App() {
|
||||
)`)
|
||||
},
|
||||
{
|
||||
path: 'src/App.jsx',
|
||||
content: Buffer.from(`function App() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center p-4">
|
||||
<div className="text-center max-w-2xl">
|
||||
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent">
|
||||
Sandbox Ready
|
||||
</h1>
|
||||
<p className="text-lg text-gray-400">
|
||||
Sandbox Ready<br/>
|
||||
Start building your React app with Vite and Tailwind CSS!
|
||||
</p>
|
||||
</div>
|
||||
@@ -185,14 +180,11 @@ app_jsx = """function App() {
|
||||
)
|
||||
}
|
||||
|
||||
export default App"""
|
||||
|
||||
with open('/home/user/app/src/App.jsx', 'w') as f:
|
||||
f.write(app_jsx)
|
||||
print('✓ src/App.jsx')
|
||||
|
||||
# Index.css with explicit Tailwind directives
|
||||
index_css = """@tailwind base;
|
||||
export default App`)
|
||||
},
|
||||
{
|
||||
path: 'src/index.css',
|
||||
content: Buffer.from(`@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@@ -216,99 +208,53 @@ index_css = """@tailwind base;
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background-color: rgb(17 24 39);
|
||||
}"""
|
||||
}`)
|
||||
}
|
||||
];
|
||||
|
||||
with open('/home/user/app/src/index.css', 'w') as f:
|
||||
f.write(index_css)
|
||||
print('✓ src/index.css')
|
||||
|
||||
print('\\nAll files created successfully!')
|
||||
`;
|
||||
|
||||
// Execute the setup script
|
||||
await sandbox.runCode(setupScript);
|
||||
// Create directory structure first
|
||||
await sandbox.runCommand({
|
||||
cmd: 'mkdir',
|
||||
args: ['-p', 'src']
|
||||
});
|
||||
|
||||
// Write all files
|
||||
await sandbox.writeFiles(projectFiles);
|
||||
console.log('[create-ai-sandbox] ✓ Project files created');
|
||||
|
||||
// Install dependencies
|
||||
console.log('[create-ai-sandbox] Installing dependencies...');
|
||||
await sandbox.runCode(`
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
print('Installing npm packages...')
|
||||
result = subprocess.run(
|
||||
['npm', 'install'],
|
||||
cwd='/home/user/app',
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print('✓ Dependencies installed successfully')
|
||||
else:
|
||||
print(f'⚠ Warning: npm install had issues: {result.stderr}')
|
||||
# Continue anyway as it might still work
|
||||
`);
|
||||
const installResult = await sandbox.runCommand({
|
||||
cmd: 'npm',
|
||||
args: ['install', '--loglevel', 'info']
|
||||
});
|
||||
if (installResult.exitCode === 0) {
|
||||
console.log('[create-ai-sandbox] ✓ Dependencies installed successfully');
|
||||
} else {
|
||||
console.log('[create-ai-sandbox] ⚠ Warning: npm install had issues but continuing...');
|
||||
}
|
||||
|
||||
// Start Vite dev server
|
||||
// Start Vite dev server in detached mode
|
||||
console.log('[create-ai-sandbox] Starting Vite dev server...');
|
||||
await sandbox.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 started with PID: {process.pid}')
|
||||
print('Waiting for server to be ready...')
|
||||
`);
|
||||
const viteProcess = await sandbox.runCommand({
|
||||
cmd: 'npm',
|
||||
args: ['run', 'dev'],
|
||||
detached: true
|
||||
});
|
||||
|
||||
console.log('[create-ai-sandbox] ✓ Vite dev server started');
|
||||
|
||||
// Wait for Vite to be fully ready
|
||||
await new Promise(resolve => setTimeout(resolve, appConfig.e2b.viteStartupDelay));
|
||||
|
||||
// Force Tailwind CSS to rebuild by touching the CSS file
|
||||
await sandbox.runCode(`
|
||||
import os
|
||||
import time
|
||||
|
||||
# Touch the CSS file to trigger rebuild
|
||||
css_file = '/home/user/app/src/index.css'
|
||||
if os.path.exists(css_file):
|
||||
os.utime(css_file, None)
|
||||
print('✓ Triggered CSS rebuild')
|
||||
|
||||
# Also ensure PostCSS processes it
|
||||
time.sleep(2)
|
||||
print('✓ Tailwind CSS should be loaded')
|
||||
`);
|
||||
await new Promise(resolve => setTimeout(resolve, appConfig.vercelSandbox.devServerStartupDelay));
|
||||
|
||||
// Store sandbox globally
|
||||
global.activeSandbox = sandbox;
|
||||
global.sandboxData = {
|
||||
sandboxId,
|
||||
url: `https://${host}`
|
||||
url: sandboxUrl,
|
||||
viteProcess
|
||||
};
|
||||
|
||||
// Set extended timeout on the sandbox instance if method available
|
||||
if (typeof sandbox.setTimeout === 'function') {
|
||||
sandbox.setTimeout(appConfig.e2b.timeoutMs);
|
||||
console.log(`[create-ai-sandbox] Set sandbox timeout to ${appConfig.e2b.timeoutMinutes} minutes`);
|
||||
}
|
||||
|
||||
// Initialize sandbox state
|
||||
global.sandboxState = {
|
||||
fileCache: {
|
||||
@@ -319,7 +265,7 @@ print('✓ Tailwind CSS should be loaded')
|
||||
sandbox,
|
||||
sandboxData: {
|
||||
sandboxId,
|
||||
url: `https://${host}`
|
||||
url: sandboxUrl
|
||||
}
|
||||
};
|
||||
|
||||
@@ -333,13 +279,13 @@ print('✓ Tailwind CSS should be loaded')
|
||||
global.existingFiles.add('tailwind.config.js');
|
||||
global.existingFiles.add('postcss.config.js');
|
||||
|
||||
console.log('[create-ai-sandbox] Sandbox ready at:', `https://${host}`);
|
||||
console.log('[create-ai-sandbox] Sandbox ready at:', sandboxUrl);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
sandboxId,
|
||||
url: `https://${host}`,
|
||||
message: 'Sandbox created and Vite React app initialized'
|
||||
url: sandboxUrl,
|
||||
message: 'Vercel sandbox created and Vite React app initialized'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
@@ -348,9 +294,9 @@ print('✓ Tailwind CSS should be loaded')
|
||||
// Clean up on error
|
||||
if (sandbox) {
|
||||
try {
|
||||
await sandbox.kill();
|
||||
await sandbox.stop();
|
||||
} catch (e) {
|
||||
console.error('Failed to close sandbox on error:', e);
|
||||
console.error('Failed to stop sandbox on error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user