UNPKG

ntrn

Version:

šŸš€ Professional AI-powered Next.js to React Native converter with intelligent screen creation, never-fail conversion system, comprehensive auto-fix, automatic runtime error fixing, AI assistant, Mistral AI problem-solving, intelligent Shadcn/ui conversion

1,230 lines (1,062 loc) • 130 kB
import fs from 'fs-extra'; import path from 'path'; import dotenv from 'dotenv'; import axios from 'axios'; import prompts from 'prompts'; import { fileURLToPath } from 'url'; import { RateLimiter } from './rateLimiter.js'; import { convertShadcnToReactNative, detectShadcnComponents } from './shadcnConverter.js'; import { generateUltraRobustPrompt, generateSuggestionPrompt, generateImprovementPrompt } from './perfectPrompts.js'; import chalk from 'chalk'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Absolute path to .env in the package root const envPath = path.resolve(__dirname, '../../.env'); // Global rate limiter instance let rateLimiter = null; // Initialize rate limiter function initializeRateLimiter(config = {}) { if (!rateLimiter) { const rateLimitConfig = { requestsPerMinute: config.requestsPerMinute || 10, // Conservative for free tier requestsPerHour: config.requestsPerHour || 200, maxRetries: config.maxRetries || 3, baseDelay: config.baseDelay || 3000, // 3 seconds maxDelay: config.maxDelay || 60000 // 1 minute }; rateLimiter = new RateLimiter(rateLimitConfig); } return rateLimiter; } // Check if .env exists; if not, ask and create it if (!fs.existsSync(envPath)) { const response = await prompts({ type: 'text', name: 'key', message: 'šŸ” Enter your Gemini API Key:', validate: value => value.trim().length > 10 || 'API key too short', }); if (!response.key) { console.error('āŒ No API key provided. Exiting.'); process.exit(1); } fs.writeFileSync(envPath, `GEMINI_API_KEY=${response.key.trim()}\n`); console.log('āœ… Gemini API Key saved successfully!\n'); } // Load the saved key from .env dotenv.config({ path: envPath }); const API_KEY = process.env.GEMINI_API_KEY; if (!API_KEY) { console.error('āŒ GEMINI_API_KEY not found in .env'); process.exit(1); } const GEMINI_ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'; // Specialized prompts for different file types const SPECIALIZED_PROMPTS = { page: ` ## SPECIALIZED INSTRUCTIONS FOR PAGE COMPONENT This is a Next.js page component that needs to become a React Native screen: ### PAGE-SPECIFIC TRANSFORMATIONS: 1. **Screen Structure**: Wrap entire component in SafeAreaView 2. **Navigation Props**: Add proper navigation and route props typing 3. **Data Fetching**: Convert getServerSideProps/getStaticProps to useEffect patterns 4. **Meta Data**: Remove all Next.js Head elements 5. **Dynamic Routes**: Convert [param] to route.params.param 6. **Loading States**: Add proper loading skeletons 7. **Error Boundaries**: Implement error handling 8. **Pull to Refresh**: Add refresh functionality if data is fetched ### MOBILE UX ENHANCEMENTS: - Add proper touch feedback for interactive elements - Implement keyboard-aware scrolling - Add loading indicators for async operations - Use FlatList for lists with many items - Add proper spacing for mobile touch targets (44pt minimum) `, component: ` ## SPECIALIZED INSTRUCTIONS FOR UI COMPONENT This is a reusable component that needs React Native compatibility: ### COMPONENT-SPECIFIC TRANSFORMATIONS: 1. **Prop Interface**: Ensure TypeScript props are mobile-appropriate 2. **Touch Interactions**: Add proper onPress handlers with feedback 3. **Styling**: Convert to StyleSheet or NativeWind patterns 4. **Animations**: Use React Native Reanimated for smooth animations 5. **Platform Specific**: Add Platform.OS checks where needed 6. **Accessibility**: Full a11y support with proper roles and labels ### PERFORMANCE OPTIMIZATIONS: - Use React.memo for expensive renders - Implement proper key props for lists - Optimize image loading and caching - Use proper flex layouts for responsive design `, layout: ` ## SPECIALIZED INSTRUCTIONS FOR LAYOUT COMPONENT This is a layout component that likely manages navigation structure: ### LAYOUT-SPECIFIC TRANSFORMATIONS: 1. **Navigation Container**: Convert to React Navigation structure 2. **Screen Management**: Implement proper screen routing 3. **Status Bar**: Add proper status bar configuration 4. **Safe Areas**: Implement SafeAreaProvider properly 5. **Theme Provider**: Adapt theme management for mobile 6. **Global State**: Setup Context or Redux providers ### NAVIGATION PATTERNS: - Determine if this should be Stack, Tab, or Drawer navigation - Implement proper screen transitions - Add navigation guards and deep linking support - Setup proper TypeScript navigation types ` }; // Enhanced prompt generation with file type detection function getSpecializedPrompt(fileName, sourceCode) { const isPage = fileName.includes('/page.') || fileName.includes('\\page.') || fileName.includes('/pages/') || fileName.includes('\\pages\\'); const isLayout = fileName.includes('layout') || fileName.includes('_app') || fileName.includes('RootLayout'); const isComponent = !isPage && !isLayout; if (isPage) return SPECIALIZED_PROMPTS.page; if (isLayout) return SPECIALIZED_PROMPTS.layout; if (isComponent) return SPECIALIZED_PROMPTS.component; return ''; } // Enhanced context analysis function generateContextualInsights(projectContext, sourceCode) { const { dependencies, hasStateManagement, hasApiRoutes } = projectContext; let insights = '\n## CONTEXTUAL INTELLIGENCE\n'; // Shadcn/ui detection const shadcnDetection = detectShadcnComponents(sourceCode, 'current-file'); if (shadcnDetection.hasComponents) { insights += `- **Shadcn/ui Components Detected**: ${shadcnDetection.components.map(c => c.name).join(', ')} - Convert to React Native equivalents\n`; } // Framework-specific insights if (dependencies['@tanstack/react-query']) { insights += '- **React Query Detected**: Preserve query patterns, convert to mobile-optimized caching\n'; } if (dependencies['framer-motion']) { insights += '- **Framer Motion Detected**: Convert to React Native Reanimated for better performance\n'; } if (dependencies['tailwindcss']) { insights += '- **Tailwind CSS Detected**: Convert to NativeWind syntax for mobile compatibility\n'; } if (hasStateManagement) { insights += '- **State Management Present**: Preserve Redux/Zustand patterns but adapt for mobile\n'; } // Code pattern analysis if (sourceCode.includes('useEffect')) { insights += '- **useEffect Detected**: Ensure mobile-appropriate lifecycle management\n'; } if (sourceCode.includes('useState')) { insights += '- **Local State Present**: Optimize for mobile re-renders and memory usage\n'; } if (sourceCode.includes('router.')) { insights += '- **Next.js Router Usage**: Critical conversion to React Navigation required\n'; } if (sourceCode.includes('Image')) { insights += '- **Image Components**: Convert to Expo Image with proper mobile optimization\n'; } if (sourceCode.includes('form') || sourceCode.includes('input')) { insights += '- **Form Elements**: Implement mobile-first form patterns with proper keyboard handling\n'; } return insights; } // Quality validation system for generated code function validateCodeQuality(code, fileName, dependencies) { const issues = []; const suggestions = []; // Critical validation checks const criticalChecks = { hasReactImport: code.includes('import React') || code.includes('from "react"'), hasReactNativeImports: code.includes('react-native') || code.includes('expo'), noWebElements: !code.match(/<(div|span|p|h[1-6]|img|button|input|form|a)\b/), properTextWrapping: !code.match(/>\s*[A-Za-z0-9]/m) || code.includes('<Text>'), hasTypeScript: fileName.endsWith('.tsx') ? code.includes('interface') || code.includes('type') : true }; // Web API detection for conversion suggestions (not errors) const webAPIPatterns = { localStorage: code.includes('localStorage'), sessionStorage: code.includes('sessionStorage'), windowLocation: code.includes('window.location'), navigator: code.includes('navigator.'), document: code.includes('document.'), windowHistory: code.includes('window.history'), geolocation: code.includes('navigator.geolocation'), clipboard: code.includes('navigator.clipboard'), notifications: code.includes('new Notification'), fileReader: code.includes('FileReader'), intersectionObserver: code.includes('IntersectionObserver') }; // Shadcn/ui detection for conversion validation const shadcnPatterns = { shadcnImports: code.includes('@/components/ui/'), hasButton: code.includes('from "@/components/ui/button"'), hasInput: code.includes('from "@/components/ui/input"'), hasCard: code.includes('from "@/components/ui/card"'), hasDialog: code.includes('from "@/components/ui/dialog"'), hasSelect: code.includes('from "@/components/ui/select"'), hasCheckbox: code.includes('from "@/components/ui/checkbox"'), // Check for unconverted Shadcn usage unconvertedShadcn: code.match(/<(Button|Input|Card|Dialog|Select|Checkbox)\b/) && !code.includes('TouchableOpacity') && !code.includes('TextInput') }; // Style and performance checks const styleChecks = { hasStyleSheet: code.includes('StyleSheet.create') || code.includes('style={{'), noInlineStyles: !code.match(/style=\{\{[^}]+fontSize:\s*\d+[^}]*\}\}/), // Complex inline styles properFlexbox: !code.includes('display: flex') || code.includes('flexDirection'), noWebCSS: !code.match(/\b(grid|float|position:\s*fixed)\b/) }; // React Native best practices const rnBestPractices = { usesTouchable: code.includes('TouchableOpacity') || code.includes('Pressable'), hasSafeArea: code.includes('SafeAreaView') || !fileName.includes('page'), properImageUsage: !code.includes('<img') || code.includes('<Image'), hasAccessibility: code.includes('accessibilityLabel') || code.includes('accessibilityRole') }; // Analyze critical issues Object.entries(criticalChecks).forEach(([check, passed]) => { if (!passed) { switch (check) { case 'hasReactImport': issues.push('āŒ Missing React import'); break; case 'hasReactNativeImports': issues.push('āŒ Missing React Native imports'); break; case 'noWebElements': issues.push('āŒ Contains HTML elements instead of React Native components'); break; case 'properTextWrapping': issues.push('āŒ Text content not properly wrapped in <Text> components'); break; case 'hasTypeScript': issues.push('āš ļø TypeScript interfaces/types might be missing'); break; } } }); // Analyze web API usage and suggest React Native alternatives Object.entries(webAPIPatterns).forEach(([api, detected]) => { if (detected) { switch (api) { case 'localStorage': suggestions.push('šŸ”„ localStorage detected → Convert to AsyncStorage from @react-native-async-storage/async-storage'); break; case 'sessionStorage': suggestions.push('šŸ”„ sessionStorage detected → Convert to AsyncStorage with expiration logic'); break; case 'windowLocation': suggestions.push('šŸ”„ window.location detected → Convert to React Navigation (navigation.navigate)'); break; case 'navigator': suggestions.push('šŸ”„ navigator API detected → Check for specific Expo equivalents (Location, Camera, etc.)'); break; case 'document': suggestions.push('šŸ”„ document API detected → Convert to React Native ref patterns or remove if DOM-specific'); break; case 'windowHistory': suggestions.push('šŸ”„ window.history detected → Convert to React Navigation history stack'); break; case 'geolocation': suggestions.push('šŸ”„ Geolocation detected → Convert to expo-location'); break; case 'clipboard': suggestions.push('šŸ”„ Clipboard API detected → Convert to expo-clipboard'); break; case 'notifications': suggestions.push('šŸ”„ Web Notifications detected → Convert to expo-notifications'); break; case 'fileReader': suggestions.push('šŸ”„ FileReader detected → Convert to expo-file-system'); break; case 'intersectionObserver': suggestions.push('šŸ”„ IntersectionObserver detected → Convert to onLayout/onScroll events'); break; } } }); // Analyze Shadcn/ui usage and conversion status Object.entries(shadcnPatterns).forEach(([pattern, detected]) => { if (detected) { switch (pattern) { case 'shadcnImports': suggestions.push('šŸŽØ Shadcn/ui imports detected → Convert to React Native components'); break; case 'hasButton': suggestions.push('šŸ”˜ Shadcn Button detected → Convert to TouchableOpacity with proper styling'); break; case 'hasInput': suggestions.push('šŸ“ Shadcn Input detected → Convert to TextInput with proper keyboard handling'); break; case 'hasCard': suggestions.push('šŸƒ Shadcn Card detected → Convert to View with card styling'); break; case 'hasDialog': suggestions.push('šŸ’¬ Shadcn Dialog detected → Convert to Modal with proper animations'); break; case 'hasSelect': suggestions.push('šŸ“‹ Shadcn Select detected → Convert to FlatList-based picker'); break; case 'hasCheckbox': suggestions.push('ā˜‘ļø Shadcn Checkbox detected → Convert to TouchableOpacity with checkbox styling'); break; case 'unconvertedShadcn': issues.push('āŒ Shadcn components found but not properly converted to React Native'); break; } } }); // Analyze style issues Object.entries(styleChecks).forEach(([check, passed]) => { if (!passed) { switch (check) { case 'hasStyleSheet': suggestions.push('šŸ’” Consider using StyleSheet.create for better performance'); break; case 'noInlineStyles': suggestions.push('šŸ’” Complex inline styles detected - consider extracting to StyleSheet'); break; case 'properFlexbox': suggestions.push('šŸ’” Web CSS flexbox detected - ensure React Native compatibility'); break; case 'noWebCSS': suggestions.push('šŸ’” Web-specific CSS properties detected'); break; } } }); // Analyze React Native best practices Object.entries(rnBestPractices).forEach(([check, passed]) => { if (!passed) { switch (check) { case 'usesTouchable': suggestions.push('šŸ’” Consider adding touchable components for better mobile UX'); break; case 'hasSafeArea': suggestions.push('šŸ’” Consider wrapping page components in SafeAreaView'); break; case 'properImageUsage': suggestions.push('šŸ’” HTML img tags detected - should use React Native Image'); break; case 'hasAccessibility': suggestions.push('šŸ’” Consider adding accessibility props for better UX'); break; } } }); // Calculate quality score const totalChecks = Object.keys(criticalChecks).length + Object.keys(styleChecks).length; const passedChecks = Object.values(criticalChecks).filter(Boolean).length + Object.values(styleChecks).filter(Boolean).length; const qualityScore = Math.round((passedChecks / totalChecks) * 100); return { qualityScore, issues, suggestions, isProductionReady: issues.length === 0 && qualityScore >= 80 }; } // Enhanced error recovery with intelligent fallbacks function generateFallbackCode(fileName, originalCode, error) { const isPage = fileName.includes('/page.') || fileName.includes('\\page.'); const isLayout = fileName.includes('layout'); if (isPage) { return `import React from 'react'; import { View, Text, SafeAreaView, StyleSheet } from 'react-native'; import { useNavigation } from '@react-navigation/native'; export default function ${fileName.split('/').pop().replace('.tsx', '').replace('.ts', '')}Screen() { const navigation = useNavigation(); return ( <SafeAreaView style={styles.container}> <View style={styles.content}> <Text style={styles.title}>Screen Converted</Text> <Text style={styles.subtitle}> This screen was automatically converted from Next.js. Manual review may be needed for complex functionality. </Text> </View> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', }, content: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 10, }, subtitle: { fontSize: 16, textAlign: 'center', color: '#666', }, });`; } if (isLayout) { return `import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { SafeAreaProvider } from 'react-native-safe-area-context'; const Stack = createStackNavigator(); export default function AppLayout({ children }: { children: React.ReactNode }) { return ( <SafeAreaProvider> <NavigationContainer> {children} </NavigationContainer> </SafeAreaProvider> ); }`; } // Generic component fallback return `import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; export default function ${fileName.split('/').pop().replace('.tsx', '').replace('.ts', '')}() { return ( <View style={styles.container}> <Text style={styles.text}>Component converted from Next.js</Text> </View> ); } const styles = StyleSheet.create({ container: { padding: 16, }, text: { fontSize: 16, }, });`; } export async function callGeminiAPI(sourceCode, fileName, projectContext = {}) { try { // Add Shadcn detection to project context const shadcnInfo = detectShadcnComponents(sourceCode, fileName); const enhancedContext = { ...projectContext, shadcnInfo }; // Generate ultra-robust prompt const prompt = generateUltraRobustPrompt(sourceCode, fileName, enhancedContext); console.log(`🧠 Using ultra-robust prompt for ${fileName} (${prompt.length} chars)`); // šŸš€ ROBUST CONVERSION WITH AUTO-IMPROVEMENT let convertedCode; let qualityResult; let attempt = 1; const maxAttempts = 5; while (attempt <= maxAttempts) { try { // API call with retry logic for "too short or empty" errors let response; let apiAttempt = 1; const maxApiAttempts = 3; while (apiAttempt <= maxApiAttempts) { try { response = await makeAPIRequest(prompt, 'conversion'); convertedCode = extractCodeFromResponse(response); // Handle "Generated code is too short or empty" error if (!convertedCode || convertedCode.length < 50) { if (apiAttempt < maxApiAttempts) { console.log(chalk.yellow(`āš ļø Generated code too short (attempt ${apiAttempt}), retrying with enhanced prompt...`)); // Enhance prompt for better results const enhancedPrompt = `${prompt} CRITICAL: The previous response was too short. Please ensure you provide: 1. Complete React Native component code 2. All necessary imports 3. Full component implementation 4. Proper export statement 5. At least 100 lines of meaningful code MINIMUM REQUIREMENTS: - Import React and React Native components - Complete functional component - Proper styling with StyleSheet - All props and state handling - Error boundaries if needed`; apiAttempt++; continue; } else { throw new Error('Generated code is too short or empty after multiple attempts'); } } break; // Success, exit API retry loop } catch (apiError) { if (apiAttempt < maxApiAttempts) { console.log(chalk.yellow(`āš ļø API attempt ${apiAttempt} failed, retrying...`)); apiAttempt++; await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds } else { throw apiError; } } } // Basic validation and fixing const hasReactImport = convertedCode.includes('import React') || convertedCode.includes('from \'react\''); const hasExport = convertedCode.includes('export default') || convertedCode.includes('export '); if (!hasReactImport || !hasExport) { console.log(chalk.yellow(`āš ļø Basic validation failed for ${fileName}, applying fixes...`)); convertedCode = ensureBasicReactNativeStructure(convertedCode, fileName); } // šŸŽÆ QUALITY VALIDATION AND AUTO-IMPROVEMENT qualityResult = validateCodeQuality(convertedCode, fileName, { '@react-navigation/native': '^6.1.9', '@react-navigation/native-stack': '^6.9.17', 'react-native-screens': '^3.27.0', 'react-native-safe-area-context': '^4.7.4', ...shadcnInfo.dependencies }); // Display current quality console.log(chalk.cyan(`šŸ“Š Quality Analysis for ${fileName} (Attempt ${attempt}):`)); console.log(chalk.green(` šŸŽÆ Quality Score: ${qualityResult.qualityScore}% ${qualityResult.isProductionReady ? 'āœ… Production Ready' : 'āš ļø Needs Review'}`)); // šŸ”§ AUTO-FIX CRITICAL ISSUES if (qualityResult.issues.length > 0) { console.log(chalk.red(` šŸ”§ Auto-fixing ${qualityResult.issues.length} issues...`)); convertedCode = await autoFixIssues(convertedCode, fileName, qualityResult.issues, enhancedContext); // Re-validate after fixes qualityResult = validateCodeQuality(convertedCode, fileName, { '@react-navigation/native': '^6.1.9', '@react-navigation/native-stack': '^6.9.17', 'react-native-screens': '^3.27.0', 'react-native-safe-area-context': '^4.7.4', ...shadcnInfo.dependencies }); } // šŸ’” AUTO-APPLY CRITICAL SUGGESTIONS if (qualityResult.suggestions.length > 0) { const criticalSuggestions = qualityResult.suggestions.filter(s => s.includes('localStorage') || s.includes('TypeScript') || s.includes('accessibility') || s.includes('HTML img') || s.includes('Shadcn') ); if (criticalSuggestions.length > 0) { console.log(chalk.yellow(` šŸ’” Auto-applying ${criticalSuggestions.length} critical suggestions...`)); convertedCode = await autoApplySuggestions(convertedCode, fileName, criticalSuggestions, enhancedContext); // Re-validate after applying suggestions qualityResult = validateCodeQuality(convertedCode, fileName, { '@react-navigation/native': '^6.1.9', '@react-navigation/native-stack': '^6.9.17', 'react-native-screens': '^3.27.0', 'react-native-safe-area-context': '^4.7.4', ...shadcnInfo.dependencies }); } } // šŸ” MANDATORY ERROR DETECTION AND FIXING (after reaching good quality) if (qualityResult.qualityScore >= 85) { console.log(chalk.cyan(`\nšŸ” Running mandatory error detection and fixing...`)); console.log(chalk.gray(` šŸ“‹ This step ensures no runtime errors remain in the final code`)); const errorDetectionResult = await detectAndFixErrors(convertedCode, fileName, enhancedContext); // Update code with error fixes convertedCode = errorDetectionResult.code; // STRICT ERROR CHECKING - Must be error-free to proceed if (errorDetectionResult.hasErrors) { console.log(chalk.red(`\n āŒ CRITICAL: ${errorDetectionResult.errors.length} runtime errors still remain!`)); console.log(chalk.yellow(` šŸ”„ Code quality will not be marked as complete until errors are resolved`)); // Force another iteration to fix remaining errors qualityResult.qualityScore = Math.min(qualityResult.qualityScore, 75); // Reduce score if errors remain // Re-validate after error fixes qualityResult = validateCodeQuality(convertedCode, fileName, { '@react-navigation/native': '^6.1.9', '@react-navigation/native-stack': '^6.9.17', 'react-native-screens': '^3.27.0', 'react-native-safe-area-context': '^4.7.4', ...shadcnInfo.dependencies }); console.log(chalk.cyan(` šŸ“Š Post-Error-Fix Quality: ${qualityResult.qualityScore}% (Error Score: ${errorDetectionResult.errorScore}%)`)); console.log(chalk.yellow(` šŸ”„ Will continue fixing until all runtime errors are resolved...`)); } else { console.log(chalk.green(`\n šŸŽ‰ SUCCESS: All runtime errors have been fixed!`)); console.log(chalk.cyan(` šŸ“Š Final Quality: ${qualityResult.qualityScore}% (Error Score: ${errorDetectionResult.errorScore}%)`)); } // ENHANCED COMPLETION CRITERIA - Must be error-free const overallScore = Math.min(qualityResult.qualityScore, errorDetectionResult.errorScore); if (overallScore >= 95 && !errorDetectionResult.hasErrors) { console.log(chalk.green(`\nšŸŽ‰ PERFECT QUALITY ACHIEVED for ${fileName}!`)); console.log(chalk.green(` āœ… Quality Score: ${qualityResult.qualityScore}%`)); console.log(chalk.green(` āœ… Error Score: ${errorDetectionResult.errorScore}%`)); console.log(chalk.green(` āœ… Production Ready: ${errorDetectionResult.isProductionReady}`)); break; } else if (!errorDetectionResult.hasErrors && qualityResult.qualityScore >= 85 && attempt === maxAttempts) { console.log(chalk.green(`\nāœ… GOOD QUALITY ACHIEVED for ${fileName}`)); console.log(chalk.green(` āœ… No Runtime Errors: ${!errorDetectionResult.hasErrors}`)); console.log(chalk.yellow(` šŸ“Š Overall Score: ${overallScore}% (acceptable for production)`)); break; } else if (errorDetectionResult.hasErrors && attempt === maxAttempts) { console.log(chalk.red(`\nāš ļø WARNING: Completed with remaining errors for ${fileName}`)); console.log(chalk.red(` 🚨 ${errorDetectionResult.errors.length} runtime errors still present`)); console.log(chalk.yellow(` šŸ”§ Manual review and fixing may be required`)); break; } } // Check if we've reached 100% quality or good enough if (qualityResult.qualityScore === 100 || (qualityResult.qualityScore >= 90 && qualityResult.issues.length === 0)) { console.log(chalk.green(`šŸŽ‰ Perfect quality achieved for ${fileName}!`)); break; } else if (qualityResult.qualityScore >= 85 && attempt === maxAttempts) { console.log(chalk.yellow(`āœ… Good quality achieved for ${fileName} (${qualityResult.qualityScore}%)`)); break; } else if (attempt < maxAttempts) { console.log(chalk.yellow(`šŸ”„ Quality: ${qualityResult.qualityScore}%, attempting improvement (${attempt}/${maxAttempts})...`)); // Generate improvement prompt for next iteration const improvementPrompt = generateTargetedImprovementPrompt(convertedCode, fileName, qualityResult, enhancedContext); // Update prompt for next iteration (this will be used with 'improvement' type) prompt = improvementPrompt; attempt++; // Small delay between attempts await new Promise(resolve => setTimeout(resolve, 1000)); } else { console.log(chalk.yellow(`āš ļø Reached max attempts for ${fileName}, using best version (${qualityResult.qualityScore}%)`)); break; } } catch (iterationError) { console.error(chalk.red(`āŒ Iteration ${attempt} failed for ${fileName}: ${iterationError.message}`)); if (attempt === maxAttempts) { // Use fallback for final attempt convertedCode = ensureBasicReactNativeStructure(generateFallbackCode(fileName, sourceCode, iterationError), fileName); qualityResult = validateCodeQuality(convertedCode, fileName, {}); break; } attempt++; await new Promise(resolve => setTimeout(resolve, 2000)); } } // Final quality display if (qualityResult.issues.length > 0) { qualityResult.issues.forEach(issue => console.log(chalk.red(` ${issue}`))); } if (qualityResult.suggestions.length > 0) { qualityResult.suggestions.slice(0, 3).forEach(suggestion => console.log(chalk.yellow(` ${suggestion}`))); if (qualityResult.suggestions.length > 3) { console.log(chalk.gray(` ... and ${qualityResult.suggestions.length - 3} more suggestions`)); } } console.log(chalk.green(`\nāœ… CONVERSION COMPLETE: ${fileName}`)); console.log(chalk.green(` šŸ“Š Final Quality Score: ${qualityResult.qualityScore}%`)); console.log(chalk.green(` šŸ“ Code Length: ${convertedCode.length} characters`)); console.log(chalk.green(` šŸ”„ Improvement Iterations: ${attempt}/${maxAttempts}`)); // Add error detection summary if available if (convertedCode.includes('// Error detection completed') || convertedCode.includes('Production Ready')) { console.log(chalk.green(` šŸ›”ļø Error Detection: COMPLETED`)); console.log(chalk.green(` šŸŽ‰ Status: PRODUCTION READY`)); } else { console.log(chalk.yellow(` šŸ” Error Detection: May need manual review`)); } // Final error detection if not already run let finalErrorDetection = null; if (qualityResult.qualityScore >= 85) { try { finalErrorDetection = await detectAndFixErrors(convertedCode, fileName, enhancedContext); convertedCode = finalErrorDetection.code; } catch (errorDetectionError) { console.log(chalk.yellow(`āš ļø Error detection failed: ${errorDetectionError.message}`)); } } return { code: convertedCode, originalCode: sourceCode, dependencies: { '@react-navigation/native': '^6.1.9', '@react-navigation/native-stack': '^6.9.17', 'react-native-screens': '^3.27.0', 'react-native-safe-area-context': '^4.7.4', ...shadcnInfo.dependencies }, shadcnInfo, qualityScore: qualityResult.qualityScore, qualityDetails: qualityResult, isProductionReady: qualityResult.isProductionReady, improvementAttempts: attempt - 1, errorDetection: finalErrorDetection, overallScore: finalErrorDetection ? Math.min(qualityResult.qualityScore, finalErrorDetection.errorScore) : qualityResult.qualityScore, hasRuntimeErrors: finalErrorDetection ? finalErrorDetection.hasErrors : false, errorSummary: finalErrorDetection ? { errors: finalErrorDetection.errors.length, warnings: finalErrorDetection.warnings.length, suggestions: finalErrorDetection.suggestions.length } : null }; } catch (error) { console.error(`āŒ Gemini API error for ${fileName}:`, error.message); throw new Error(`AI conversion failed: ${error.message}`); } } // Helper function to ensure basic React Native structure function ensureBasicReactNativeStructure(code, fileName) { let fixedCode = code; // Ensure React import if (!fixedCode.includes('import React')) { fixedCode = `import React from 'react';\n${fixedCode}`; } // Ensure React Native imports if (!fixedCode.includes('react-native')) { const imports = `import { View, Text, StyleSheet, ScrollView } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\n`; fixedCode = fixedCode.replace('import React from \'react\';', `import React from 'react';\n${imports}`); } // Ensure export if (!fixedCode.includes('export default')) { const componentName = getComponentName(fileName); fixedCode += `\n\nexport default ${componentName};`; } return fixedCode; } function createFallbackResponse(fileName, error) { const fallbackCode = generateFallbackCode(fileName, '', error); const quality = validateCodeQuality(fallbackCode, fileName, {}); return { code: fallbackCode, dependencies: { '@react-navigation/native': '^6.1.0', 'react-native-safe-area-context': '^4.8.0' }, quality: { ...quality, isFallback: true, originalError: error.message } }; } function getComponentName(fileName) { const baseName = path.basename(fileName, path.extname(fileName)); // Convert kebab-case or snake_case to PascalCase return baseName .split(/[-_]/) .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()) .join(''); } // Export rate limiter for external access export function getRateLimiter() { return rateLimiter; } // Shutdown function for graceful cleanup export async function shutdownGeminiClient() { if (rateLimiter) { await rateLimiter.shutdown(); } } // Missing API request function with token tracking async function makeAPIRequest(prompt, requestType = 'conversion') { try { // Initialize rate limiter if not already done if (!rateLimiter) { initializeRateLimiter(); } // Use rate limiter's addRequest method (not makeRequest) const response = await rateLimiter.addRequest(async () => { const res = await axios.post( `${GEMINI_ENDPOINT}?key=${API_KEY}`, { contents: [ { role: 'user', parts: [{ text: prompt }], }, ], generationConfig: { temperature: 0.1, topP: 0.8, topK: 40, maxOutputTokens: 8192, } }, { headers: { 'Content-Type': 'application/json', }, timeout: 60000, } ); return res.data; }, { fileName: 'API Request' }); // Track token usage from response if (response && response.usageMetadata) { trackTokenUsage(response, requestType); } const content = response?.candidates?.[0]?.content?.parts?.[0]?.text; if (!content) { throw new Error('No content returned from Gemini API'); } return content; } catch (error) { console.error('āŒ makeAPIRequest error:', error.message); throw error; } } // Missing code extraction function function extractCodeFromResponse(response) { if (!response || typeof response !== 'string') { throw new Error('Invalid response format'); } // Try to extract code from various markdown formats const codeBlockRegex = /```(?:tsx|ts|jsx|js)?\n([\s\S]*?)```/g; const matches = [...response.matchAll(codeBlockRegex)]; if (matches.length > 0) { // Get the largest code block (usually the main component) const codeBlocks = matches.map(match => match[1].trim()); const largestBlock = codeBlocks.reduce((a, b) => a.length > b.length ? a : b); return largestBlock; } // Fallback: try to find code between specific markers const markerRegex = /(?:CONVERTED_CODE|FINAL_CODE|COMPONENT_CODE):\s*```(?:tsx|ts|jsx|js)?\n([\s\S]*?)```/i; const markerMatch = response.match(markerRegex); if (markerMatch && markerMatch[1]) { return markerMatch[1].trim(); } // Last resort: return the response as-is if it looks like code if (response.includes('import ') || response.includes('export ') || response.includes('function ')) { return response.trim(); } throw new Error('Could not extract code from response'); } // šŸ”§ AUTO-FIX ISSUES FUNCTION WITH SURGICAL PRECISION AND VERIFICATION async function autoFixIssues(code, fileName, issues, projectContext) { let fixedCode = code; let appliedFixes = []; let failedFixes = []; let verificationResults = []; console.log(chalk.cyan(` šŸ”§ Starting surgical auto-fix with verification for ${issues.length} issues...`)); for (const issue of issues) { const originalLength = fixedCode.length; let fixApplied = false; let verificationPassed = false; let maxAttempts = 3; let attempt = 1; console.log(chalk.blue(` šŸŽÆ [${issues.indexOf(issue) + 1}/${issues.length}] Processing: ${issue.substring(0, 80)}...`)); while (!verificationPassed && attempt <= maxAttempts) { try { const beforeFixCode = fixedCode; if (issue.includes('Missing React import')) { console.log(` šŸ”§ Attempt ${attempt}: Adding React import...`); if (!fixedCode.includes('import React')) { fixedCode = `import React from 'react';\n${fixedCode}`; fixApplied = true; // Verify the fix if (fixedCode.includes('import React from \'react\'')) { verificationPassed = true; appliedFixes.push('Added React import'); verificationResults.push({ issue: 'Missing React import', status: 'success', verification: 'React import found in code', attempt: attempt }); console.log(chalk.green(` āœ… Verified: React import successfully added`)); } else { console.log(chalk.red(` āŒ Verification failed: React import not found after fix`)); } } else { verificationPassed = true; appliedFixes.push('React import already exists'); verificationResults.push({ issue: 'Missing React import', status: 'already_exists', verification: 'React import already present', attempt: attempt }); console.log(chalk.green(` āœ… Verified: React import already exists`)); } } else if (issue.includes('Missing React Native imports')) { console.log(` šŸ”§ Attempt ${attempt}: Adding React Native imports...`); const neededImports = []; const reactNativeComponents = ['View', 'Text', 'Image', 'ScrollView', 'TouchableOpacity', 'TextInput', 'StyleSheet', 'SafeAreaView', 'FlatList', 'SectionList']; reactNativeComponents.forEach(component => { if (fixedCode.includes(`<${component}`) && !fixedCode.includes(`${component},`) && !fixedCode.includes(`{ ${component} }`)) { neededImports.push(component); } }); if (neededImports.length > 0) { const beforeImportCount = (fixedCode.match(/from 'react-native'/g) || []).length; fixedCode = addMultipleImports(fixedCode, neededImports); fixApplied = true; // Verify the fix - check if all needed imports are now present const missingAfterFix = neededImports.filter(component => !fixedCode.includes(`${component},`) && !fixedCode.includes(`{ ${component} }`) ); if (missingAfterFix.length === 0) { verificationPassed = true; appliedFixes.push(`Added React Native imports: ${neededImports.join(', ')}`); verificationResults.push({ issue: 'Missing React Native imports', status: 'success', verification: `All imports verified: ${neededImports.join(', ')}`, attempt: attempt }); console.log(chalk.green(` āœ… Verified: All imports added - ${neededImports.join(', ')}`)); } else { console.log(chalk.red(` āŒ Verification failed: Still missing - ${missingAfterFix.join(', ')}`)); } } else { verificationPassed = true; appliedFixes.push('No missing React Native imports detected'); verificationResults.push({ issue: 'Missing React Native imports', status: 'not_needed', verification: 'No missing imports detected', attempt: attempt }); console.log(chalk.green(` āœ… Verified: No missing imports detected`)); } } else if (issue.includes('HTML elements')) { console.log(` šŸ”§ Attempt ${attempt}: Converting HTML elements...`); const htmlConversions = [ { from: /<div(\s[^>]*)?>/, to: '<View$1>', name: 'div → View', check: /<div\b/ }, { from: /<\/div>/g, to: '</View>', name: 'closing div', check: /<\/div>/ }, { from: /<span(\s[^>]*)?>/, to: '<Text$1>', name: 'span → Text', check: /<span\b/ }, { from: /<\/span>/g, to: '</Text>', name: 'closing span', check: /<\/span>/ }, { from: /<p(\s[^>]*)?>/, to: '<Text$1>', name: 'p → Text', check: /<p\b/ }, { from: /<\/p>/g, to: '</Text>', name: 'closing p', check: /<\/p>/ }, { from: /<h[1-6](\s[^>]*)?>/, to: '<Text$1>', name: 'heading → Text', check: /<h[1-6]\b/ }, { from: /<\/h[1-6]>/g, to: '</Text>', name: 'closing heading', check: /<\/h[1-6]>/ }, { from: /<img(\s[^>]*)?\/?>/, to: '<Image$1/>', name: 'img → Image', check: /<img\b/ }, { from: /<button(\s[^>]*)?>/, to: '<TouchableOpacity$1>', name: 'button → TouchableOpacity', check: /<button\b/ }, { from: /<\/button>/g, to: '</TouchableOpacity>', name: 'closing button', check: /<\/button>/ }, { from: /<input(\s[^>]*)?\/?>/, to: '<TextInput$1/>', name: 'input → TextInput', check: /<input\b/ } ]; let conversionsApplied = 0; const htmlElementsBefore = []; const htmlElementsAfter = []; // Track what HTML elements exist before conversion htmlConversions.forEach(conversion => { if (conversion.check.test(fixedCode)) { htmlElementsBefore.push(conversion.name.split(' → ')[0]); } }); htmlConversions.forEach(conversion => { const beforeMatch = fixedCode.match(conversion.from); if (beforeMatch) { fixedCode = fixedCode.replace(conversion.from, conversion.to); conversionsApplied++; console.log(chalk.blue(` āœ“ ${conversion.name}`)); } }); if (conversionsApplied > 0) { fixApplied = true; // Verify the conversion - check that HTML elements are gone htmlConversions.forEach(conversion => { if (conversion.check.test(fixedCode)) { htmlElementsAfter.push(conversion.name.split(' → ')[0]); } }); if (htmlElementsAfter.length === 0) { verificationPassed = true; appliedFixes.push(`Converted ${conversionsApplied} HTML elements`); verificationResults.push({ issue: 'HTML elements conversion', status: 'success', verification: `All HTML elements converted: ${htmlElementsBefore.join(', ')}`, attempt: attempt }); console.log(chalk.green(` āœ… Verified: All HTML elements converted (${conversionsApplied} elements)`)); // Ensure React Native components are imported const neededImports = []; if (fixedCode.includes('<View') && !fixedCode.includes('View,')) neededImports.push('View'); if (fixedCode.includes('<Text') && !fixedCode.includes('Text,')) neededImports.push('Text'); if (fixedCode.includes('<Image') && !fixedCode.includes('Image,')) neededImports.push('Image'); if (fixedCode.includes('<TouchableOpacity') && !fixedCode.includes('TouchableOpacity,')) neededImports.push('TouchableOpacity'); if (fixedCode.includes('<TextInput') && !fixedCode.includes('TextInput,')) neededImports.push('TextInput'); if (neededImports.length > 0) { fixedCode = addMultipleImports(fixedCode, neededImports); console.log(chalk.green(` āœ… Added imports: ${neededImports.join(', ')}`)); } } else { console.log(chalk.red(` āŒ Verification failed: HTML elements still present - ${htmlElementsAfter.join(', ')}`)); } } else { verificationPassed = true; appliedFixes.push('No HTML elements found to convert'); verificationResults.push({ issue: 'HTML elements conversion', status: 'not_needed', verification: 'No HTML elements found', attempt: attempt }); console.log(chalk.green(` āœ… Verified: No HTML elements found`)); } } else if (issue.includes('Text content not properly wrapped')) { console.log(` šŸ”§ Attempt ${attempt}: Fixing text wrapping...`); // Use AI for complex text wrapping issues const fixPrompt = `You are a React Native expert. Fix text wrapping issues in this code. CURRENT CODE: \`\`\`tsx ${fixedCode} \`\`\` CRITICAL RULES: 1. ALL text content MUST be wrapped in <Text> components 2. Find any bare text (text not inside <Text>...</Text>) 3. Wrap it properly: <Text>your text here</Text> 4. Maintain all styling and functionality 5. NO bare text should remain outside <Text> components Return the COMPLETE corrected code with ALL text properly wrapped.`; const fixResponse = await makeAPIRequest(fixPrompt, 'error-fix'); const textFix = extractCodeFromResponse(fixResponse); if (textFix && textFix.length > fixedCode.length * 0.7) { fixedCode = textFix; fixApplied = true; // Verify text wrapping - check for unwrapped text const unwrappedTextPattern = />[\s]*[A-Za-z0-9][^<>]*[A-Za-z0-9][\s]*</; const hasUnwrappedText = unwrappedTextPattern.test(fixedCode); if (!hasUnwrappedText || fixedCode.includes('<Text>')) { verificationPassed = true; appliedFixes.push('Fixed text wrapping'); verificationResults.push({ issue: 'Text wrapping', status: 'success', verification: 'All text properly wrapped in <Text> components', attempt: attempt }); console.log(chalk.green(` āœ… Verified: Text wrapping fixed`)); } else { console.log(chalk.red(` āŒ Verification failed: Unwrapped text still present`)); } } } // 🧠 INTELLIGENT ANALYSIS FOR NEW/UNKNOWN ISSUES else { console.log(` 🧠 Attempt ${attempt}: Analyzing unknown issue intelligently...`); const analysisResult = await analyzeAndFixUnknownIssue(fixedCode, issue, fileName, attempt); if (analysisResult.fixed) { fixedCode = analysisResult.code; fixApplied = true; // Verify the intelligent fix if (analysisResult.verification && analysisResult.verification(fixedCode)) { verificationPassed = true; appliedFixes.push(analysisResult.description); verificationResults.push({ issue: issue.substring(0, 50) + '...', status: 'success', verification: analysisResult.verificationMessage, attempt: attempt, method: 'intelligent_analysis' }); console.log(chalk.green(` āœ… Verified: ${analysisResult.verificationMessage}`)); } else { console.log(chalk.red(` āŒ Verification failed: ${analysisResult.verificationMessage || 'Fix not properly applied'}`)); } } else { console.log(chalk.yellow(` āš ļø Could not analyze/fix unknown issue: ${issue.substring(0, 50)}...`)); } } // If fix was applied but verification failed, try again if (fixApplied && !verificationPassed && attempt < maxAttempts) { console.log(chalk.yellow(` šŸ”„ Fix applied but verification failed, retrying (${attempt + 1}/${maxAttempts})...`)); attempt++; await new Promise(resolve => setTimeout(resolve, 1000)); // Wait before retry } else if (!fixApplied) { // If no fix was applied, break the loop break; } else { // Either verification passed or max attempts reached break; } } catch (fixError) { console.log(chalk.red(` āŒ Fix att