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

406 lines (346 loc) 12.2 kB
// 🎯 Deterministic Next.js to React Native Converter // Handles 90% of conversions without AI - like v0.dev for mobile import fs from 'fs-extra'; import path from 'path'; import chalk from 'chalk'; export class DeterministicConverter { constructor() { this.conversionRules = new Map(); this.initializeRules(); } initializeRules() { // 🎨 UI Component Mappings (like v0.dev templates) this.conversionRules.set('SHADCN_COMPONENTS', { Button: this.createButtonTemplate, Input: this.createInputTemplate, Card: this.createCardTemplate, CardHeader: this.createCardHeaderTemplate, CardTitle: this.createCardTitleTemplate, CardContent: this.createCardContentTemplate, Dialog: this.createModalTemplate, Sheet: this.createModalTemplate, Avatar: this.createAvatarTemplate, Badge: this.createBadgeTemplate, Checkbox: this.createCheckboxTemplate, Switch: this.createSwitchTemplate, Select: this.createPickerTemplate, Textarea: this.createTextAreaTemplate, Progress: this.createProgressTemplate, Separator: this.createSeparatorTemplate, Skeleton: this.createSkeletonTemplate, Toast: this.createToastTemplate, Tabs: this.createTabsTemplate, Alert: this.createAlertTemplate }); // 🌐 Web API Mappings this.conversionRules.set('WEB_APIS', { localStorage: this.createAsyncStorageTemplate, sessionStorage: this.createAsyncStorageTemplate, fetch: this.createFetchTemplate, window: this.createWindowTemplate, document: this.createDocumentTemplate, navigator: this.createNavigatorTemplate, location: this.createLocationTemplate }); // 🧭 Navigation Mappings this.conversionRules.set('NAVIGATION', { 'router.push': this.createNavigationTemplate, 'router.back': this.createGoBackTemplate, 'router.replace': this.createReplaceTemplate, 'useRouter': this.createUseNavigationTemplate, 'Link': this.createTouchableLinkTemplate }); // 📱 React Native Element Mappings this.conversionRules.set('ELEMENTS', { div: 'View', span: 'Text', p: 'Text', h1: 'Text', h2: 'Text', h3: 'Text', h4: 'Text', h5: 'Text', h6: 'Text', button: 'TouchableOpacity', input: 'TextInput', img: 'Image', a: 'TouchableOpacity', section: 'View', article: 'View', main: 'ScrollView', header: 'View', footer: 'View', nav: 'View', ul: 'View', li: 'View', form: 'View' }); } // 🚀 Main conversion method - deterministic like v0.dev async convertFile(sourceCode, fileName) { try { console.log(chalk.gray(` 🎯 Attempting deterministic conversion...`)); // Phase 1: Simple pattern matching (no AST parsing for now) let convertedCode = this.applySimpleTransformations(sourceCode, fileName); // Phase 2: Generate React Native template convertedCode = this.generateReactNativeTemplate(convertedCode, fileName); // Phase 3: Add required imports convertedCode = this.addRequiredImports(convertedCode); // Phase 4: Generate styles convertedCode = this.generateStyles(convertedCode); // Phase 5: Final validation const isValid = this.validateReactNativeCode(convertedCode); if (!isValid.success) { console.log(chalk.yellow(` ⚠️ Deterministic conversion validation failed: ${isValid.errors.join(', ')}`)); return { success: false, reason: `Validation failed: ${isValid.errors.join(', ')}`, needsAI: true }; } console.log(chalk.green(` ✅ Deterministic conversion successful`)); return { success: true, code: convertedCode, tokensUsed: 0, // Deterministic = 0 tokens! patterns: 'deterministic' }; } catch (error) { console.log(chalk.yellow(` ⚠️ Deterministic conversion error: ${error.message}`)); return { success: false, reason: error.message, needsAI: true }; } } // 🔄 Simple pattern transformations (without AST for now) applySimpleTransformations(sourceCode, fileName) { let code = sourceCode; // Transform HTML elements to React Native components const elementMappings = this.conversionRules.get('ELEMENTS'); for (const [htmlElement, rnElement] of Object.entries(elementMappings)) { // Transform opening tags const openingTagRegex = new RegExp(`<${htmlElement}(\\s[^>]*)?(?<!/)>`, 'gi'); code = code.replace(openingTagRegex, `<${rnElement}$1>`); // Transform closing tags const closingTagRegex = new RegExp(`</${htmlElement}>`, 'gi'); code = code.replace(closingTagRegex, `</${rnElement}>`); // Transform self-closing tags const selfClosingRegex = new RegExp(`<${htmlElement}(\\s[^>]*)?/>`, 'gi'); code = code.replace(selfClosingRegex, `<${rnElement}$1/>`); } // Transform event handlers code = code.replace(/onClick=/g, 'onPress='); code = code.replace(/onChange=/g, 'onChangeText='); // Transform props code = code.replace(/className=/g, 'style='); code = code.replace(/src=/g, 'source='); // Transform common Shadcn components code = this.transformShadcnComponents(code); // Transform web APIs code = this.transformWebAPIs(code); return code; } transformShadcnComponents(code) { // Simple Button transformation code = code.replace( /<Button([^>]*?)>([^<]+)<\/Button>/gi, '<TouchableOpacity$1 activeOpacity={0.7}><Text>$2</Text></TouchableOpacity>' ); // Simple Input transformation code = code.replace( /<Input([^>]*?)\/>/gi, '<TextInput$1/>' ); // Simple Card transformation code = code.replace(/<Card([^>]*?)>/gi, '<View$1>'); code = code.replace(/<\/Card>/gi, '</View>'); return code; } transformWebAPIs(code) { // localStorage transformations code = code.replace(/localStorage\.setItem\(/g, 'AsyncStorage.setItem('); code = code.replace(/localStorage\.getItem\(/g, 'AsyncStorage.getItem('); code = code.replace(/localStorage\.removeItem\(/g, 'AsyncStorage.removeItem('); // Navigation transformations code = code.replace(/router\.push\(/g, 'navigation.navigate('); code = code.replace(/router\.back\(\)/g, 'navigation.goBack()'); return code; } generateReactNativeTemplate(code, fileName) { const isPage = fileName.includes('page.') || fileName.includes('/pages/'); const componentName = this.getComponentName(fileName); if (isPage) { return this.generateScreenTemplate(code, componentName); } else { return this.generateComponentTemplate(code, componentName); } } generateScreenTemplate(code, screenName) { // Extract the main JSX content const jsxMatch = code.match(/return\s*\(([\s\S]*?)\);?/); const jsxContent = jsxMatch ? jsxMatch[1] : '<View><Text>Screen Content</Text></View>'; return `import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity, TextInput, SafeAreaView } from 'react-native'; import { useNavigation, useRoute } from '@react-navigation/native'; import AsyncStorage from '@react-native-async-storage/async-storage'; export default function ${screenName}() { const navigation = useNavigation(); const route = useRoute(); return ( <SafeAreaView style={styles.container}> <ScrollView contentContainerStyle={styles.content}> ${jsxContent} </ScrollView> </SafeAreaView> ); } ${this.generateDefaultStyles('screen')}`; } generateComponentTemplate(code, componentName) { // Extract the main JSX content const jsxMatch = code.match(/return\s*\(([\s\S]*?)\);?/); const jsxContent = jsxMatch ? jsxMatch[1] : '<View><Text>Component Content</Text></View>'; return `import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; export default function ${componentName}(props) { return ( <View style={styles.container}> ${jsxContent} </View> ); } ${this.generateDefaultStyles('component')}`; } addRequiredImports(code) { // Basic implementation - could be enhanced return code; } generateStyles(code) { // Check if styles already exist if (code.includes('const styles = StyleSheet.create')) { return code; } // Add basic styles const additionalStyles = ` const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#ffffff', padding: 16, }, content: { flexGrow: 1, }, button: { backgroundColor: '#007AFF', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8, alignItems: 'center', marginVertical: 8, }, buttonText: { color: '#ffffff', fontSize: 16, fontWeight: '600', }, input: { borderWidth: 1, borderColor: '#E1E5E9', borderRadius: 8, paddingHorizontal: 16, paddingVertical: 12, fontSize: 16, marginVertical: 8, backgroundColor: '#ffffff', }, });`; return code + additionalStyles; } validateReactNativeCode(code) { const errors = []; // Check for common React Native issues if (code.includes('className=')) { errors.push('Found className usage - should be style prop'); } if (code.includes('onClick=')) { errors.push('Found onClick usage - should be onPress'); } // Check for required React Native imports if (!code.includes('from \'react-native\'')) { errors.push('Missing React Native imports'); } // Simple check for unwrapped text (basic implementation) const suspiciousTextMatches = code.match(/>\s*[a-zA-Z0-9\s]+\s*</g); if (suspiciousTextMatches) { // This is a simplified check - could have false positives const hasUnwrappedText = suspiciousTextMatches.some(match => !match.includes('<Text>') && !match.includes('{') && match.trim().length > 2 ); if (hasUnwrappedText) { errors.push('Possible unwrapped text - all text must be in <Text> components'); } } return { success: errors.length === 0, errors }; } getComponentName(fileName) { const baseName = path.basename(fileName, path.extname(fileName)); let componentName = baseName .replace(/[^a-zA-Z0-9]/g, '') .replace(/^./, str => str.toUpperCase()); // Handle common naming patterns if (componentName.toLowerCase() === 'page') { componentName = 'HomeScreen'; } else if (componentName.toLowerCase() === 'index') { componentName = 'MainScreen'; } return componentName; } generateDefaultStyles(type) { return ` const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#ffffff', padding: 16, }, });`; } // Placeholder template methods (simplified for now) createButtonTemplate(props, children) { return `<TouchableOpacity style={styles.button} onPress={${props.onClick}} activeOpacity={0.7}> <Text style={styles.buttonText}>${children}</Text> </TouchableOpacity>`; } createInputTemplate(props) { return `<TextInput style={styles.input} placeholder="${props.placeholder || ''}" value={${props.value}} onChangeText={${props.onChange}} ${props.secureTextEntry ? 'secureTextEntry={true}' : ''} />`; } createCardTemplate(props, children) { return `<View style={styles.card}>${children}</View>`; } } export default DeterministicConverter;