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

902 lines (755 loc) β€’ 32.2 kB
// πŸ€– Interactive AI Assistant for React Native Projects // ChatGPT-like CLI interface for making project changes import fs from 'fs-extra'; import path from 'path'; import chalk from 'chalk'; import prompts from 'prompts'; import { aiManager } from './aiProviders.js'; import { ProjectAnalyzer } from './projectAnalyzer.js'; export class InteractivePrompt { constructor() { this.projectPath = process.cwd(); this.conversationHistory = []; this.projectContext = null; this.isActive = false; } async start() { console.log(chalk.cyan('πŸ€– NTRN AI Assistant Pro')); console.log(chalk.cyan('===========================')); console.log(chalk.green('Professional AI assistant for your React Native project')); console.log(chalk.gray('Powered by Mistral AI & Gemini 2.0 Flash\n')); // Initialize AI Manager await aiManager.initialize(); // Check project type and adapt accordingly const isRNProject = await this.verifyReactNativeProject(); const isNextJSProject = await this.verifyNextJSProject(); if (!isRNProject && !isNextJSProject) { console.log(chalk.yellow('⚠️ This doesn\'t appear to be a React Native or Next.js project.')); console.log(chalk.gray('πŸ’‘ You can still use the AI assistant for general React Native questions!\n')); this.projectContext = { totalFiles: 0, componentFiles: [], pageFiles: [], dependencies: {}, projectMetadata: { framework: 'General' } }; } else if (isNextJSProject && !isRNProject) { console.log(chalk.blue('πŸ“¦ Next.js project detected!')); console.log(chalk.yellow('πŸ’‘ Use `ntrn` (without flags) to convert this to React Native first.\n')); this.projectContext = { totalFiles: 0, componentFiles: [], pageFiles: [], dependencies: {}, projectMetadata: { framework: 'Next.js' } }; } // Check for failed conversion files and offer to fix them await this.checkForFailedConversions(); // Analyze the current project console.log(chalk.gray('πŸ“Š Analyzing your React Native project...')); await this.analyzeProject(); console.log(chalk.green('βœ… Project analyzed! Ready for your requests.\n')); // Show available commands this.showCommands(); // Start interactive session this.isActive = true; await this.startInteractiveSession(); } async verifyReactNativeProject() { try { const packageJsonPath = path.join(this.projectPath, 'package.json'); if (!await fs.exists(packageJsonPath)) { return false; } const packageJson = await fs.readJson(packageJsonPath); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for React Native indicators return !!( dependencies['react-native'] || dependencies['expo'] || dependencies['@react-native/metro-config'] || packageJson.main === 'expo/AppEntry.js' || await fs.exists(path.join(this.projectPath, 'app.json')) ); } catch (error) { return false; } } async verifyNextJSProject() { try { const packageJsonPath = path.join(this.projectPath, 'package.json'); if (!await fs.exists(packageJsonPath)) { return false; } const packageJson = await fs.readJson(packageJsonPath); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for Next.js indicators return !!( dependencies['next'] || await fs.exists(path.join(this.projectPath, 'next.config.js')) || await fs.exists(path.join(this.projectPath, 'next.config.mjs')) || await fs.exists(path.join(this.projectPath, 'pages')) || await fs.exists(path.join(this.projectPath, 'app')) ); } catch (error) { return false; } } async analyzeProject() { try { const analyzer = new ProjectAnalyzer(this.projectPath); this.projectContext = await analyzer.analyzeProject(); console.log(chalk.blue(`πŸ“± Project Type: ${this.projectContext.projectMetadata.framework}`)); console.log(chalk.blue(`πŸ“ Total Files: ${this.projectContext.totalFiles}`)); console.log(chalk.blue(`🧩 Components: ${this.projectContext.componentFiles.length}`)); console.log(chalk.blue(`πŸ“„ Screens: ${this.projectContext.pageFiles.length}`)); } catch (error) { console.log(chalk.yellow('⚠️ Could not fully analyze project, but continuing...')); this.projectContext = { totalFiles: 0, componentFiles: [], pageFiles: [], dependencies: {}, projectMetadata: { framework: 'React Native' } }; } } showCommands() { console.log(chalk.cyan('πŸ’¬ What can you ask me?')); console.log(chalk.gray('Examples:')); console.log(chalk.white(' β€’ "Add a login screen with TypeScript"')); console.log(chalk.white(' β€’ "Create a user profile component"')); console.log(chalk.white(' β€’ "Fix the navigation styling"')); console.log(chalk.white(' β€’ "Add dark mode support with context"')); console.log(chalk.white(' β€’ "Update button styles to be more modern"')); console.log(chalk.white(' β€’ "Add pull-to-refresh functionality"')); console.log(chalk.white(' β€’ "Convert this component to TypeScript"')); console.log(chalk.white(' β€’ "Add API service for user management"')); console.log(''); console.log(chalk.cyan('πŸ”§ Quick Commands:')); console.log(chalk.white(' β€’ type "fix" to automatically fix conversion issues')); console.log(chalk.white(' β€’ type "errors" to fix runtime errors (Cannot read property, etc.)')); console.log(chalk.white(' β€’ type "analyze" to get a detailed project analysis')); console.log(chalk.white(' β€’ type "clear" to clear conversation history')); console.log(chalk.white(' β€’ type "status" to see project info')); console.log(chalk.white(' β€’ type "provider" to switch AI provider')); console.log(chalk.white(' β€’ type "help" to see this again')); console.log(chalk.white(' β€’ type "exit" or "quit" to leave')); console.log(''); } async startInteractiveSession() { while (this.isActive) { try { const response = await prompts({ type: 'text', name: 'userPrompt', message: chalk.cyan('πŸ€– You:'), validate: value => value.trim().length > 0 || 'Please enter a request' }); if (!response.userPrompt) { console.log(chalk.yellow('πŸ‘‹ Goodbye!')); break; } const userInput = response.userPrompt.trim(); // Handle special commands if (await this.handleSpecialCommands(userInput)) { continue; } // Process AI request await this.processUserRequest(userInput); } catch (error) { if (error.message === 'cancelled') { console.log(chalk.yellow('\nπŸ‘‹ Goodbye!')); break; } console.error(chalk.red(`❌ Error: ${error.message}`)); } } } async handleSpecialCommands(input) { const command = input.toLowerCase(); switch (command) { case 'exit': case 'quit': case 'q': console.log(chalk.yellow('πŸ‘‹ Goodbye!')); this.isActive = false; return true; case 'clear': this.conversationHistory = []; console.log(chalk.green('πŸ—‘οΈ Conversation history cleared!')); return true; case 'help': this.showCommands(); return true; case 'status': this.showProjectStatus(); return true; case 'provider': await this.switchAIProvider(); return true; case 'fix': case 'fix failed': case 'fix conversion': await this.quickFixFailedFiles(); return true; case 'analyze': case 'analyze project': await this.quickAnalyzeProject(); return true; case 'errors': case 'runtime': case 'debug': case 'fix errors': case 'fix runtime': await this.fixRuntimeErrors(); return true; default: return false; } } async switchAIProvider() { try { console.log(chalk.cyan('\nπŸ”„ Current AI Provider: ') + chalk.white(aiManager.currentProvider?.name || 'None')); const response = await prompts({ type: 'select', name: 'provider', message: 'Choose AI provider:', choices: [ { title: '🧠 Mistral AI (Recommended)', value: 'mistral' }, { title: 'πŸ€– Gemini 2.0 Flash', value: 'gemini' }, { title: '❌ Cancel', value: 'cancel' } ] }); if (response.provider && response.provider !== 'cancel') { await aiManager.switchProvider(response.provider); console.log(chalk.green(`βœ… Switched to ${response.provider === 'mistral' ? 'Mistral AI' : 'Gemini 2.0 Flash'}`)); } } catch (error) { console.log(chalk.red(`❌ Error switching provider: ${error.message}`)); } } showProjectStatus() { console.log(chalk.cyan('\nπŸ“Š Project Status:')); console.log(` πŸ“ Directory: ${path.basename(this.projectPath)}`); console.log(` πŸ“± Framework: ${this.projectContext.projectMetadata.framework}`); console.log(` πŸ“„ Components: ${this.projectContext.componentFiles.length}`); console.log(` 🧩 Screens: ${this.projectContext.pageFiles.length}`); console.log(` πŸ’¬ Conversation: ${this.conversationHistory.length} messages`); console.log(` πŸ€– AI Provider: ${aiManager.currentProvider?.name || 'None'}\n`); } async processUserRequest(userInput) { console.log(chalk.gray('πŸ€– Processing your request...')); try { // Add to conversation history this.conversationHistory.push({ role: 'user', content: userInput, timestamp: new Date().toISOString() }); // Generate AI response with project context and retry logic const aiResponse = await this.generateAIResponseWithRetry(userInput); // Add AI response to history this.conversationHistory.push({ role: 'assistant', content: aiResponse.content, timestamp: new Date().toISOString() }); // Display AI response console.log(chalk.cyan('\nπŸ€– AI Assistant:')); console.log(chalk.white(aiResponse.content)); // Execute any actions if the AI provided them if (aiResponse.actions && aiResponse.actions.length > 0) { await this.executeActions(aiResponse.actions); } } catch (error) { console.error(chalk.red(`❌ Error: ${error.message}`)); if (error.message.includes('rate limit') || error.message.includes('429')) { console.log(chalk.yellow('\n⏱️ Rate limit reached. Suggestions:')); console.log(chalk.white('1. Wait a moment and try again')); console.log(chalk.white('2. Switch AI provider: type "provider"')); console.log(chalk.white('3. Break your request into smaller parts')); } } console.log(''); // Add spacing } async generateAIResponseWithRetry(userInput, maxRetries = 3) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // Show retry attempt if not the first if (attempt > 1) { console.log(chalk.yellow(`πŸ”„ Retry attempt ${attempt}/${maxRetries}...`)); // Wait before retry with exponential backoff const waitTime = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, waitTime)); } const aiResponse = await this.generateAIResponse(userInput); return aiResponse; } catch (error) { lastError = error; // If it's a rate limit error, wait longer if (error.message.includes('rate limit') || error.message.includes('429')) { if (attempt < maxRetries) { const waitTime = 5000 * attempt; // 5s, 10s, 15s console.log(chalk.yellow(`⏱️ Rate limit hit. Waiting ${waitTime/1000}s before retry...`)); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } } // If it's the last attempt or non-rate-limit error, break if (attempt === maxRetries || !error.message.includes('rate limit')) { break; } } } // If all retries failed, throw the last error throw lastError; } async generateAIResponse(userInput) { const prompt = this.buildContextualPrompt(userInput); try { const response = await aiManager.callAI(prompt, { task: 'interactive-assistance', temperature: 0.3, maxTokens: 4096 }); return this.parseAIResponse(response.content); } catch (error) { // Enhanced error handling if (error.message.includes('rate limit') || error.message.includes('429')) { throw new Error(`Rate limit exceeded. Please wait a moment before trying again. Current provider: ${aiManager.currentProvider?.name || 'Unknown'}`); } throw new Error(`AI generation failed: ${error.message}`); } } buildContextualPrompt(userInput) { return `# πŸ€– NTRN AI Assistant Pro You are a Senior React Native Developer helping modify an existing React Native project through natural language commands. ## πŸ“± PROJECT CONTEXT - **Project Type**: ${this.projectContext.projectMetadata.framework} - **Components**: ${this.projectContext.componentFiles.length} files - **Screens**: ${this.projectContext.pageFiles.length} files - **Dependencies**: ${Object.keys(this.projectContext.dependencies || {}).join(', ')} ## πŸ“œ CONVERSATION HISTORY ${this.conversationHistory.slice(-3).map(msg => `${msg.role}: ${msg.content}`).join('\n')} ## 🎯 USER REQUEST "${userInput}" ## πŸ“‹ RESPONSE INSTRUCTIONS You must respond in this JSON format: \`\`\`json { "content": "Your conversational response to the user", "actions": [ { "type": "create_file" | "modify_file" | "delete_file", "path": "relative/path/to/file.tsx", "content": "file content here (for create/modify)", "description": "What this action does" } ] } \`\`\` ## 🎨 PROFESSIONAL GUIDELINES ### 1. **TypeScript-First Development** - All files must be .tsx/.ts (NO .jsx/.js) - Strict TypeScript types for all props, state, functions - Proper interface definitions and generic types - Import types with \`import type\` ### 2. **React Native Best Practices** - **ALL text must be in <Text> components** - Use proper React Native components (View, TouchableOpacity, etc.) - Mobile-optimized styling with StyleSheet.create() - Touch targets minimum 44pt for accessibility - Handle keyboard interactions properly ### 3. **Professional Architecture** - Follow established project structure: - Screens: \`src/screens/ScreenName.tsx\` - Components: \`src/components/ComponentName.tsx\` - Services: \`src/services/ServiceName.ts\` - Contexts: \`src/contexts/ContextName.tsx\` - Types: \`src/types/TypeName.ts\` ### 4. **Navigation & State Management** - Use proper TypeScript navigation types - Context providers for global state - Proper screen lifecycle management - Update navigation configuration when adding screens ### 5. **Code Quality Standards** - Production-ready, maintainable code - Proper error handling and validation - Performance optimizations (memoization, FlatList) - Accessibility considerations ## πŸš€ ENHANCED CAPABILITIES: ### "Add a login screen with TypeScript" - Create LoginScreen.tsx with proper TypeScript types - Include form validation with proper error states - Add to navigation with type safety - Modern mobile UX with loading states ### "Create API service for users" - Build UserService.ts with TypeScript interfaces - Proper API client integration - Error handling and response types - Authentication token management ### "Add dark mode with context" - Create ThemeContext.tsx with TypeScript - Light/dark theme definitions - Provider integration in App.tsx - Update existing components to use theme ### "Convert component to TypeScript" - Analyze existing JavaScript component - Add proper TypeScript types and interfaces - Convert styling to mobile-optimized patterns - Ensure React Native best practices ### "Fix navigation and add new screen" - Update navigation types in src/types/navigation.ts - Add screen component with proper props - Update AppNavigator.tsx with new route - Test navigation flow ### "Fix runtime errors" - Scan for common React Native conversion issues - Fix missing React Native component imports - Convert HTML elements to RN components (divβ†’View, spanβ†’Text) - Fix event handlers (onClickβ†’onPress) - Add missing StyleSheet imports - Use action type "fix_runtime_errors" to trigger automatic fixes ## 🚨 COMMON RUNTIME ERROR FIXES: - **"Cannot read property 'default' of undefined"** β†’ Missing or broken imports - **"Cannot read property 'S' of undefined"** β†’ Missing StyleSheet import - **Element type is invalid** β†’ HTML elements not converted to RN - **Text strings must be rendered within a <Text> component** β†’ Wrap loose text Now respond professionally to: "${userInput}"`; } parseAIResponse(response) { try { // Extract JSON from response const jsonMatch = response.match(/```json\n([\s\S]*?)\n```/); if (jsonMatch) { return JSON.parse(jsonMatch[1]); } // Try to parse entire response as JSON try { return JSON.parse(response); } catch { // Fallback: treat entire response as content return { content: response, actions: [] }; } } catch (error) { return { content: response, actions: [] }; } } async executeActions(actions) { console.log(chalk.cyan(`πŸ”§ Executing ${actions.length} action(s)...`)); for (const action of actions) { try { await this.executeAction(action); console.log(chalk.green(`βœ… ${action.description}`)); } catch (error) { console.log(chalk.red(`❌ Failed: ${action.description} - ${error.message}`)); } } } async executeAction(action) { const fullPath = path.join(this.projectPath, action.path); switch (action.type) { case 'create_file': await fs.ensureDir(path.dirname(fullPath)); await fs.writeFile(fullPath, action.content, 'utf-8'); console.log(chalk.green(`πŸ“„ Created: ${action.path}`)); break; case 'modify_file': if (await fs.exists(fullPath)) { // Create backup before modifying const backupPath = `${fullPath}.backup`; await fs.copy(fullPath, backupPath); await fs.writeFile(fullPath, action.content, 'utf-8'); console.log(chalk.green(`πŸ“ Modified: ${action.path} (backup created)`)); } else { // Create if doesn't exist await fs.ensureDir(path.dirname(fullPath)); await fs.writeFile(fullPath, action.content, 'utf-8'); console.log(chalk.green(`πŸ“„ Created: ${action.path}`)); } break; case 'delete_file': if (await fs.exists(fullPath)) { await fs.remove(fullPath); console.log(chalk.green(`πŸ—‘οΈ Deleted: ${action.path}`)); } break; case 'fix_runtime_errors': await this.fixRuntimeErrors(); console.log(chalk.green(`πŸ”§ Fixed runtime errors`)); break; default: console.log(chalk.yellow(`⚠️ Unknown action type: ${action.type}`)); } } async checkForFailedConversions() { try { // Look for common signs of incomplete conversion const packageJsonPath = path.join(this.projectPath, 'package.json'); const srcPath = path.join(this.projectPath, 'src'); if (!await fs.exists(srcPath)) { console.log(chalk.yellow('⚠️ No src/ directory found. This might be an incomplete conversion.')); const response = await this.askToFixConversion('Looks like the conversion might be incomplete. Would you like me to analyze and fix it?'); if (response) { await this.offerConversionFix(); return; } } // Check for missing navigation setup const navigationPath = path.join(this.projectPath, 'src', 'navigation'); const screensPath = path.join(this.projectPath, 'src', 'screens'); if (!await fs.exists(navigationPath) || !await fs.exists(screensPath)) { console.log(chalk.yellow('⚠️ Missing navigation or screens directory.')); const response = await this.askToFixConversion('Would you like me to set up missing React Native navigation and screens?'); if (response) { await this.offerNavigationFix(); return; } } // Check for remaining Next.js files that weren't converted const possibleNextJsFiles = [ 'pages', 'app', 'next.config.js', 'next.config.mjs' ]; let hasUnconvertedFiles = false; for (const file of possibleNextJsFiles) { const filePath = path.join(this.projectPath, file); if (await fs.exists(filePath)) { hasUnconvertedFiles = true; break; } } if (hasUnconvertedFiles) { console.log(chalk.yellow('⚠️ Found Next.js files that might need conversion.')); const response = await this.askToFixConversion('Would you like me to analyze and convert any remaining Next.js files?'); if (response) { await this.offerRemainingFilesConversion(); return; } } } catch (error) { // Silently continue if can't check for failed conversions } } async askToFixConversion(message) { try { const prompts = await import('prompts'); const response = await prompts.default({ type: 'confirm', name: 'fix', message: message, initial: true }); return response.fix; } catch (error) { return false; } } async offerConversionFix() { console.log(chalk.cyan('\nπŸ”§ Analyzing conversion issues...')); const analysisPrompt = `Analyze this React Native project directory structure and identify what's missing or incorrectly converted from Next.js: Project directory: ${this.projectPath} Please provide a comprehensive analysis and list specific files/folders that need to be created or fixed for a proper React Native Expo project structure.`; try { const response = await aiManager.callAI(analysisPrompt, { task: 'project-analysis', temperature: 0.2, maxTokens: 2048 }); console.log(chalk.cyan('\nπŸ€– Analysis Results:')); console.log(chalk.white(response.content)); console.log(chalk.yellow('\nπŸ’‘ I can help you fix these issues automatically!')); console.log(chalk.gray('Just ask me things like:')); console.log(chalk.white('β€’ "Fix the project structure"')); console.log(chalk.white('β€’ "Create missing navigation setup"')); console.log(chalk.white('β€’ "Add missing screens and components"')); } catch (error) { console.log(chalk.red('❌ Error analyzing project: ' + error.message)); console.log(chalk.yellow('You can still ask me to fix specific issues manually.')); } } async offerNavigationFix() { console.log(chalk.cyan('\n🧭 I can set up React Navigation for you automatically!')); const message = `Create a complete React Navigation setup for this React Native project. Include: 1. AppNavigator.tsx with TypeScript 2. Navigation types in src/types/navigation.ts 3. Update App.tsx to use navigation 4. Create at least a HomeScreen if none exists 5. Ensure all imports and dependencies are correct`; await this.processUserRequest(message); } async offerRemainingFilesConversion() { console.log(chalk.cyan('\nπŸ“„ I can convert remaining Next.js files to React Native!')); const message = `Analyze this directory for any Next.js files (pages/, components/, etc.) that haven't been converted to React Native yet, and convert them to proper React Native TypeScript files with: 1. Pages β†’ Screens in src/screens/ 2. Components β†’ Components in src/components/ 3. API routes β†’ Services in src/services/ 4. Contexts β†’ Contexts in src/contexts/ 5. Update navigation to include new screens`; await this.processUserRequest(message); } async quickFixFailedFiles() { console.log(chalk.cyan('\nπŸ”§ Quick Fix Mode: Analyzing and fixing conversion issues...')); const fixMessage = `Please analyze this React Native project and fix any conversion issues: 1. Check for missing navigation setup (AppNavigator.tsx, navigation types) 2. Look for Next.js files that weren't converted (pages/, api/, etc.) 3. Ensure proper TypeScript project structure 4. Fix any broken imports or missing dependencies 5. Create missing screens, components, or services 6. Update App.tsx if needed Automatically fix what you can and tell me what was fixed.`; await this.processUserRequest(fixMessage); } async quickAnalyzeProject() { console.log(chalk.cyan('\nπŸ“Š Quick Analysis Mode: Examining project structure...')); const analysisMessage = `Please analyze this React Native project and provide a detailed report: 1. Project structure completeness 2. Navigation setup status 3. TypeScript configuration 4. Missing or broken files 5. Conversion quality assessment 6. Specific recommendations for improvement Give me a comprehensive status report.`; await this.processUserRequest(analysisMessage); } async fixRuntimeErrors() { console.log(chalk.cyan('\nπŸ”§ Scanning for common React Native runtime errors...')); try { const srcPath = path.join(this.projectPath, 'src'); if (!await fs.exists(srcPath)) { console.log(chalk.yellow('⚠️ No src directory found, scanning root directory...')); await this.scanAndFixDirectory(this.projectPath); } else { 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); } console.log(chalk.green('βœ… Runtime error scan 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: Missing React import if (!content.includes('import React') && content.includes('React.')) { fixedContent = `import React from 'react';\n${fixedContent}`; hasChanges = true; } // Fix 2: 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 3: 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 4: 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 5: Wrap loose text in Text components const lines = fixedContent.split('\n'); const fixedLines = lines.map(line => { // Look for JSX with loose text (simplified regex) if (line.includes('>') && line.includes('<') && />[^<>]+</.test(line)) { // This is a simplified fix - in practice, you'd need more sophisticated parsing return line.replace(/>([\w\s]+)</g, (match, text) => { if (text.trim() && !text.includes('<Text>')) { return `><Text>${text}</Text><`; } return match; }); } return line; }); if (fixedLines.join('\n') !== fixedContent) { fixedContent = fixedLines.join('\n'); hasChanges = true; } // Save fixed content if (hasChanges) { 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}`)); } } } export default InteractivePrompt;