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
JavaScript
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