File size: 5,000 Bytes
75fefa7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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<BuildValidation> {
  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;
  }
}