import { Sandbox } from '@vercel/sandbox'; import { SandboxProvider, SandboxInfo, CommandResult } from '../types'; // SandboxProviderConfig available through parent class export class VercelProvider extends SandboxProvider { private existingFiles: Set = new Set(); async createSandbox(): Promise { try { console.log('[VercelProvider] Creating sandbox...'); // Kill existing sandbox if any if (this.sandbox) { console.log('[VercelProvider] Stopping existing sandbox...'); try { await this.sandbox.stop(); } catch (e) { console.error('Failed to stop existing sandbox:', e); } this.sandbox = null; } // Clear existing files tracking this.existingFiles.clear(); // Create Vercel sandbox console.log('[VercelProvider] Creating Vercel sandbox...'); const sandboxConfig: any = { timeout: 300000, // 5 minutes in ms runtime: 'node22', // Use node22 runtime for Vercel sandboxes ports: [5173] // Vite port }; // Add authentication based on environment variables if (process.env.VERCEL_TOKEN && process.env.VERCEL_TEAM_ID && process.env.VERCEL_PROJECT_ID) { console.log('[VercelProvider] Using personal access token authentication'); console.log('[VercelProvider] Team ID:', process.env.VERCEL_TEAM_ID); console.log('[VercelProvider] Project ID:', process.env.VERCEL_PROJECT_ID); console.log('[VercelProvider] Token present:', !!process.env.VERCEL_TOKEN); sandboxConfig.teamId = process.env.VERCEL_TEAM_ID; sandboxConfig.projectId = process.env.VERCEL_PROJECT_ID; sandboxConfig.token = process.env.VERCEL_TOKEN; } else if (process.env.VERCEL_OIDC_TOKEN) { console.log('[VercelProvider] Using OIDC token authentication'); } else { console.log('[VercelProvider] No authentication found - relying on default Vercel authentication'); console.log('[VercelProvider] Available env vars:', Object.keys(process.env).filter(k => k.startsWith('VERCEL'))); } console.log('[VercelProvider] Creating sandbox with config:', { runtime: sandboxConfig.runtime, timeout: sandboxConfig.timeout, ports: sandboxConfig.ports, hasTeamId: !!sandboxConfig.teamId, hasProjectId: !!sandboxConfig.projectId, hasToken: !!sandboxConfig.token }); this.sandbox = await Sandbox.create(sandboxConfig); const sandboxId = this.sandbox.sandboxId; console.log(`[VercelProvider] Sandbox created successfully:`, { sandboxId: sandboxId, status: this.sandbox.status }); // Get the sandbox URL using the correct Vercel Sandbox API const sandboxUrl = this.sandbox.domain(5173); console.log(`[VercelProvider] Sandbox URL: ${sandboxUrl}`); this.sandboxInfo = { sandboxId, url: sandboxUrl, provider: 'vercel', createdAt: new Date() }; return this.sandboxInfo; } catch (error) { console.error('[VercelProvider] Error creating sandbox:', error); throw error; } } async runCommand(command: string): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } console.log(`[VercelProvider] Executing: ${command}`); try { // Parse command into cmd and args (matching PR syntax) const parts = command.split(' '); const cmd = parts[0]; const args = parts.slice(1); // Vercel uses runCommand with cmd and args object (based on PR) const result = await this.sandbox.runCommand({ cmd: cmd, args: args, cwd: '/vercel/sandbox', env: {} }); return { stdout: result.stdout || '', stderr: result.stderr || '', exitCode: result.exitCode || 0, success: result.exitCode === 0 }; } catch (error: any) { return { stdout: '', stderr: error.message || 'Command failed', exitCode: 1, success: false }; } } async writeFile(path: string, content: string): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } // Vercel sandbox default working directory is /vercel/sandbox const fullPath = path.startsWith('/') ? path : `/vercel/sandbox/${path}`; console.log(`[VercelProvider] writeFile called:`, { originalPath: path, fullPath: fullPath, contentLength: content.length, contentPreview: content.substring(0, 100) + (content.length > 100 ? '...' : ''), sandboxId: this.sandbox.sandboxId, sandboxStatus: this.sandbox.status }); // Based on Vercel SDK docs, writeFiles expects path and Buffer content try { const buffer = Buffer.from(content, 'utf-8'); console.log(`[VercelProvider] Calling sandbox.writeFiles with:`, { path: fullPath, bufferLength: buffer.length, isBuffer: Buffer.isBuffer(buffer) }); await this.sandbox.writeFiles([{ path: fullPath, content: buffer }]); console.log(`[VercelProvider] Successfully written: ${fullPath}`); this.existingFiles.add(path); } catch (writeError: any) { // Log detailed error information console.error(`[VercelProvider] writeFiles failed for ${fullPath}:`, { error: writeError, message: writeError?.message, response: writeError?.response, statusCode: writeError?.response?.status, responseData: writeError?.response?.data }); // Fallback to command-based approach if writeFiles fails console.log(`[VercelProvider] Attempting command fallback for ${fullPath}`); // Ensure directory exists const dir = fullPath.substring(0, fullPath.lastIndexOf('/')); if (dir) { console.log(`[VercelProvider] Creating directory: ${dir}`); const mkdirResult = await this.sandbox.runCommand({ cmd: 'mkdir', args: ['-p', dir] }); console.log(`[VercelProvider] mkdir result:`, { exitCode: mkdirResult.exitCode, stdout: mkdirResult.stdout, stderr: mkdirResult.stderr }); } // Write file using echo and redirection const escapedContent = content .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\$/g, '\\$') .replace(/`/g, '\\`') .replace(/\n/g, '\\n'); console.log(`[VercelProvider] Writing file via echo command to: ${fullPath}`); const writeResult = await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', `echo "${escapedContent}" > "${fullPath}"`] }); console.log(`[VercelProvider] Write command result:`, { exitCode: writeResult.exitCode, stdout: writeResult.stdout, stderr: writeResult.stderr }); if (writeResult.exitCode === 0) { console.log(`[VercelProvider] Successfully written via command: ${fullPath}`); this.existingFiles.add(path); } else { throw new Error(`Failed to write file via command: ${writeResult.stderr}`); } } } async readFile(path: string): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } // Vercel sandbox default working directory is /vercel/sandbox const fullPath = path.startsWith('/') ? path : `/vercel/sandbox/${path}`; const result = await this.sandbox.runCommand({ cmd: 'cat', args: [fullPath] }); if (result.exitCode !== 0) { throw new Error(`Failed to read file: ${result.stderr}`); } return result.stdout || ''; } async listFiles(directory: string = '/vercel/sandbox'): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } const result = await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', `find ${directory} -type f -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/.next/*" -not -path "*/dist/*" -not -path "*/build/*" | sed "s|^${directory}/||"`], cwd: '/' }); if (result.exitCode !== 0) { return []; } return (result.stdout || '').split('\n').filter((line: string) => line.trim() !== ''); } async installPackages(packages: string[]): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } const flags = process.env.NPM_FLAGS || ''; console.log(`[VercelProvider] Installing packages: ${packages.join(' ')}`); // Build args array const args = ['install']; if (flags) { args.push(...flags.split(' ')); } args.push(...packages); const result = await this.sandbox.runCommand({ cmd: 'npm', args: args, cwd: '/vercel/sandbox' }); // Restart Vite if configured and successful if (result.exitCode === 0 && process.env.AUTO_RESTART_VITE === 'true') { await this.restartViteServer(); } return { stdout: result.stdout || '', stderr: result.stderr || '', exitCode: result.exitCode || 0, success: result.exitCode === 0 }; } async setupViteApp(): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } console.log('[VercelProvider] Setting up Vite React app...'); console.log('[VercelProvider] Sandbox details:', { sandboxId: this.sandbox.sandboxId, status: this.sandbox.status }); // Create directory structure console.log('[VercelProvider] Creating directory structure...'); const mkdirResult = await this.sandbox.runCommand({ cmd: 'mkdir', args: ['-p', '/vercel/sandbox/src'] }); console.log('[VercelProvider] mkdir /vercel/sandbox/src result:', { exitCode: mkdirResult.exitCode, stdout: mkdirResult.stdout, stderr: mkdirResult.stderr }); // Create package.json const packageJson = { 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" } }; console.log('[VercelProvider] Writing package.json...'); await this.writeFile('package.json', JSON.stringify(packageJson, null, 2)); // Create vite.config.js const viteConfig = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { host: '0.0.0.0', port: 5173, strictPort: true, allowedHosts: [ '.vercel.run', // Allow all Vercel sandbox domains '.e2b.dev', // Allow all E2B sandbox domains 'localhost' ], hmr: { clientPort: 443, protocol: 'wss' } } })`; await this.writeFile('vite.config.js', viteConfig); // Create tailwind.config.js const tailwindConfig = `/** @type {import('tailwindcss').Config} */ export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], }`; await this.writeFile('tailwind.config.js', tailwindConfig); // Create postcss.config.js const postcssConfig = `export default { plugins: { tailwindcss: {}, autoprefixer: {}, }, }`; await this.writeFile('postcss.config.js', postcssConfig); // Create index.html const indexHtml = ` Sandbox App
`; await this.writeFile('index.html', indexHtml); // Create src/main.jsx const mainJsx = `import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' import './index.css' ReactDOM.createRoot(document.getElementById('root')).render( , )`; await this.writeFile('src/main.jsx', mainJsx); // Create src/App.jsx const appJsx = `function App() { return (

Vercel Sandbox Ready
Start building your React app with Vite and Tailwind CSS!

) } export default App`; await this.writeFile('src/App.jsx', appJsx); // Create src/index.css const indexCss = `@tailwind base; @tailwind components; @tailwind utilities; body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background-color: rgb(17 24 39); }`; await this.writeFile('src/index.css', indexCss); console.log('[VercelProvider] All files created successfully!'); // Install dependencies console.log('[VercelProvider] Installing dependencies...'); console.log('[VercelProvider] Running npm install in /vercel/sandbox'); try { const installResult = await this.sandbox.runCommand({ cmd: 'npm', args: ['install'], cwd: '/vercel/sandbox' }); console.log('[VercelProvider] npm install result:', { exitCode: installResult.exitCode, stdout: typeof installResult.stdout === 'function' ? 'function' : installResult.stdout, stderr: typeof installResult.stderr === 'function' ? 'function' : installResult.stderr }); if (installResult.exitCode === 0) { console.log('[VercelProvider] Dependencies installed successfully'); } else { console.warn('[VercelProvider] npm install had issues:', installResult.stderr); } } catch (error: any) { console.error('[VercelProvider] npm install error:', { message: error?.message, response: error?.response?.status, responseText: error?.text }); // Try alternative approach - run as shell command console.log('[VercelProvider] Trying alternative npm install approach...'); try { const altResult = await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', 'cd /vercel/sandbox && npm install'], cwd: '/vercel/sandbox' }); if (altResult.exitCode === 0) { console.log('[VercelProvider] Dependencies installed successfully (alternative method)'); } else { console.warn('[VercelProvider] Alternative npm install also had issues:', altResult.stderr); } } catch (altError) { console.error('[VercelProvider] Alternative npm install also failed:', altError); console.warn('[VercelProvider] Continuing without npm install - packages may need to be installed manually'); } } // Start Vite dev server console.log('[VercelProvider] Starting Vite dev server...'); // Kill any existing Vite processes await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', 'pkill -f vite || true'], cwd: '/' }); // Start Vite in background await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', 'nohup npm run dev > /tmp/vite.log 2>&1 &'], cwd: '/vercel/sandbox' }); console.log('[VercelProvider] Vite dev server started'); // Wait for Vite to be ready await new Promise(resolve => setTimeout(resolve, 7000)); // Track initial files this.existingFiles.add('src/App.jsx'); this.existingFiles.add('src/main.jsx'); this.existingFiles.add('src/index.css'); this.existingFiles.add('index.html'); this.existingFiles.add('package.json'); this.existingFiles.add('vite.config.js'); this.existingFiles.add('tailwind.config.js'); this.existingFiles.add('postcss.config.js'); } async restartViteServer(): Promise { if (!this.sandbox) { throw new Error('No active sandbox'); } console.log('[VercelProvider] Restarting Vite server...'); // Kill existing Vite process await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', 'pkill -f vite || true'], cwd: '/' }); // Wait a moment await new Promise(resolve => setTimeout(resolve, 2000)); // Start Vite in background await this.sandbox.runCommand({ cmd: 'sh', args: ['-c', 'nohup npm run dev > /tmp/vite.log 2>&1 &'], cwd: '/vercel/sandbox' }); console.log('[VercelProvider] Vite restarted'); // Wait for Vite to be ready await new Promise(resolve => setTimeout(resolve, 7000)); } getSandboxUrl(): string | null { return this.sandboxInfo?.url || null; } getSandboxInfo(): SandboxInfo | null { return this.sandboxInfo; } async terminate(): Promise { if (this.sandbox) { console.log('[VercelProvider] Terminating sandbox...'); try { await this.sandbox.stop(); } catch (e) { console.error('Failed to terminate sandbox:', e); } this.sandbox = null; this.sandboxInfo = null; } } isAlive(): boolean { return !!this.sandbox; } }