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

241 lines (209 loc) 8.56 kB
import fs from 'fs-extra'; import path from 'path'; import chalk from 'chalk'; export class RuntimeErrorFixer { constructor(projectPath) { this.projectPath = projectPath; } async fixProject() { console.log(chalk.cyan('🔧 Scanning for React Native runtime errors...')); try { const srcPath = path.join(this.projectPath, 'src'); if (await fs.exists(srcPath)) { await this.scanAndFixDirectory(srcPath); } // Also check App.tsx in root const appTsxPath = path.join(this.projectPath, 'App.tsx'); if (await fs.exists(appTsxPath)) { await this.fixFileRuntimeErrors(appTsxPath); } // Fix package.json dependencies await this.fixPackageJson(); console.log(chalk.green('✅ Runtime error fixes complete')); } catch (error) { console.log(chalk.red(`❌ Error during runtime error fix: ${error.message}`)); } } async scanAndFixDirectory(dirPath) { const files = await fs.readdir(dirPath); for (const file of files) { const filePath = path.join(dirPath, file); const stat = await fs.stat(filePath); if (stat.isDirectory() && !['node_modules', '.git', '.expo'].includes(file)) { await this.scanAndFixDirectory(filePath); } else if (stat.isFile() && /\.(tsx?|jsx?)$/.test(file)) { await this.fixFileRuntimeErrors(filePath); } } } async fixFileRuntimeErrors(filePath) { try { const content = await fs.readFile(filePath, 'utf-8'); let fixedContent = content; let hasChanges = false; // Fix 1: Remove nested Text components like <Text><Text><Text>Text</Text></Text></Text> const nestedTextRegex = /<Text[^>]*>(\s*<Text[^>]*>)*\s*([^<]+)\s*(<\/Text>\s*)*<\/Text>/g; const cleanTextContent = fixedContent.replace(nestedTextRegex, (match, innerOpening, textContent, innerClosing) => { if (textContent && textContent.trim()) { return `<Text>${textContent.trim()}</Text>`; } return match; }); if (cleanTextContent !== fixedContent) { fixedContent = cleanTextContent; hasChanges = true; } // Fix 2: Remove invalid asset imports const invalidImportRegex = /import .+ from ['"]\.\.\/.+\/assets\/.+['"]/g; if (fixedContent.match(invalidImportRegex)) { fixedContent = fixedContent.replace(invalidImportRegex, '// Asset import removed - file not found'); hasChanges = true; } // Fix 3: Missing React import if (!content.includes('import React') && content.includes('React.')) { fixedContent = `import React from 'react';\n${fixedContent}`; hasChanges = true; } // Fix 4: Missing React Native component imports const rnComponents = ['View', 'Text', 'StyleSheet', 'TouchableOpacity', 'TextInput', 'ScrollView', 'FlatList', 'Image']; for (const component of rnComponents) { if (content.includes(`<${component}`) && !content.includes(`import.*${component}`)) { // Add to existing RN import or create new one if (content.includes('from \'react-native\'')) { fixedContent = fixedContent.replace( /import\s*{([^}]+)}\s*from\s*'react-native'/, (match, imports) => { if (!imports.includes(component)) { return `import { ${imports.trim()}, ${component} } from 'react-native'`; } return match; } ); } else { fixedContent = `import { ${component} } from 'react-native';\n${fixedContent}`; } hasChanges = true; } } // Fix 5: HTML elements to React Native components const htmlToRn = { '<div': '<View', '</div>': '</View>', '<span': '<Text', '</span>': '</Text>', '<p': '<Text', '</p>': '</Text>', '<button': '<TouchableOpacity', '</button>': '</TouchableOpacity>', '<input': '<TextInput', 'onClick': 'onPress', 'className': 'style' }; for (const [html, rn] of Object.entries(htmlToRn)) { if (fixedContent.includes(html)) { fixedContent = fixedContent.replace(new RegExp(html, 'g'), rn); hasChanges = true; } } // Fix 6: Add StyleSheet if styles are used if (content.includes('style={') && !content.includes('StyleSheet')) { if (content.includes('from \'react-native\'')) { fixedContent = fixedContent.replace( /import\s*{([^}]+)}\s*from\s*'react-native'/, (match, imports) => { if (!imports.includes('StyleSheet')) { return `import { ${imports.trim()}, StyleSheet } from 'react-native'`; } return match; } ); } else { fixedContent = `import { StyleSheet } from 'react-native';\n${fixedContent}`; } hasChanges = true; } // Fix 7: Remove invalid dependency imports const invalidDeps = [ 'react-native-haptic-feedback', 'expo-local-authentication' ]; for (const dep of invalidDeps) { const importRegex = new RegExp(`import .+ from ['"]${dep}['"];?`, 'g'); if (fixedContent.match(importRegex)) { fixedContent = fixedContent.replace(importRegex, `// ${dep} import removed - not available`); hasChanges = true; } } // Save fixed content if (hasChanges) { // Create backup const backupPath = `${filePath}.backup`; await fs.copy(filePath, backupPath); await fs.writeFile(filePath, fixedContent, 'utf-8'); console.log(chalk.green(`🔧 Fixed: ${path.relative(this.projectPath, filePath)}`)); } } catch (error) { console.log(chalk.yellow(`⚠️ Could not fix ${filePath}: ${error.message}`)); } } async fixPackageJson() { try { const packageJsonPath = path.join(this.projectPath, 'package.json'); if (!await fs.exists(packageJsonPath)) { return; } const packageJson = await fs.readJson(packageJsonPath); // Update to proper Expo SDK 53 dependencies (official versions) const updatedDependencies = { "@babel/core": "^7.25.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/slider": "4.5.5", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "@types/react": "~19.0.10", "expo": "~53.0.12", "expo-font": "~13.0.4", "expo-image": "~2.0.7", "expo-splash-screen": "~0.29.14", "expo-status-bar": "~2.0.1", "react": "19.0.0", "react-native": "0.76.9", "react-native-gesture-handler": "~2.20.2", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", "react-native-svg": "15.8.0", "react-native-toast-message": "^2.2.1", "typescript": "~5.8.3" }; packageJson.dependencies = updatedDependencies; packageJson.devDependencies = { "@babel/preset-env": "^7.25.0", "@types/react-native": "^0.76.0" }; // Add scripts if missing if (!packageJson.scripts) { packageJson.scripts = {}; } packageJson.scripts = { ...packageJson.scripts, "start": "expo start", "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web" }; // Add main entry point packageJson.main = "node_modules/expo/AppEntry.js"; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); console.log(chalk.green('📦 Updated package.json with compatible dependencies')); } catch (error) { console.log(chalk.yellow(`⚠️ Could not fix package.json: ${error.message}`)); } } } // Export function to fix a specific project export async function fixProjectRuntimeErrors(projectPath) { const fixer = new RuntimeErrorFixer(projectPath); await fixer.fixProject(); }