export interface BuildValidation { success: boolean; errors: string[]; isRendering: boolean; warnings?: string[]; } /** * Validates that the sandbox build was successful * Checks compilation status and verifies app is rendering */ export async function validateBuild(sandboxUrl: string, sandboxId: string): Promise { try { // Step 1: Wait for Vite to process files (give it time to compile) await new Promise(resolve => setTimeout(resolve, 3000)); // Step 2: Check if the sandbox is actually serving content const response = await fetch(sandboxUrl, { headers: { 'User-Agent': 'OpenLovable-Validator', 'Cache-Control': 'no-cache' } }); if (!response.ok) { return { success: false, errors: [`Sandbox returned ${response.status}`], isRendering: false }; } const html = await response.text(); // Step 3: Check if it's the default page or actual app const isDefaultPage = html.includes('Vercel Sandbox Ready') || html.includes('Start building your React app with Vite') || html.includes('Vite + React') || !html.includes('id="root"'); if (isDefaultPage) { return { success: false, errors: ['Sandbox showing default page, app not rendered'], isRendering: false }; } // Step 4: Check for Vite error overlay in HTML const hasViteError = html.includes('vite-error-overlay'); if (hasViteError) { // Try to extract error message const errorMatch = html.match(/Failed to resolve import "([^"]+)"/); const error = errorMatch ? `Missing package: ${errorMatch[1]}` : 'Vite compilation error detected'; return { success: false, errors: [error], isRendering: false }; } // Success! App is rendering return { success: true, errors: [], isRendering: true }; } catch (error) { console.error('[validateBuild] Error during validation:', error); return { success: false, errors: [error instanceof Error ? error.message : 'Validation failed'], isRendering: false }; } } /** * Extracts missing package names from error messages */ export function extractMissingPackages(error: any): string[] { const message = error?.message || String(error); const packages: string[] = []; // Pattern 1: "Failed to resolve import 'package-name'" const importMatches = message.matchAll(/Failed to resolve import ["']([^"']+)["']/g); for (const match of importMatches) { packages.push(match[1]); } // Pattern 2: "Cannot find module 'package-name'" const moduleMatches = message.matchAll(/Cannot find module ["']([^"']+)["']/g); for (const match of moduleMatches) { packages.push(match[1]); } // Pattern 3: "Package 'package-name' not found" const packageMatches = message.matchAll(/Package ["']([^"']+)["'] not found/g); for (const match of packageMatches) { packages.push(match[1]); } return [...new Set(packages)]; // Remove duplicates } /** * Classifies error type for targeted recovery */ export type ErrorType = 'missing-package' | 'syntax-error' | 'sandbox-timeout' | 'not-rendered' | 'vite-error' | 'unknown'; export function classifyError(error: any): ErrorType { const message = (error?.message || String(error)).toLowerCase(); if (message.includes('failed to resolve import') || message.includes('cannot find module') || message.includes('missing package')) { return 'missing-package'; } if (message.includes('syntax error') || message.includes('unexpected token') || message.includes('parsing error')) { return 'syntax-error'; } if (message.includes('timeout') || message.includes('not responding') || message.includes('timed out')) { return 'sandbox-timeout'; } if (message.includes('not rendered') || message.includes('sandbox ready') || message.includes('default page')) { return 'not-rendered'; } if (message.includes('vite') || message.includes('compilation')) { return 'vite-error'; } return 'unknown'; } /** * Calculates retry delay based on attempt number and error type */ export function calculateRetryDelay(attempt: number, errorType: ErrorType): number { const baseDelay = 2000; // 2 seconds // Different strategies for different errors switch (errorType) { case 'missing-package': // Packages need time to install return baseDelay * 2 * attempt; // 4s, 8s, 12s case 'not-rendered': // Vite needs time to compile return baseDelay * 3 * attempt; // 6s, 12s, 18s case 'vite-error': // Vite restart needed return baseDelay * 2 * attempt; case 'sandbox-timeout': // Sandbox might be slow return baseDelay * 4 * attempt; // 8s, 16s, 24s default: // Standard exponential backoff return baseDelay * attempt; } }