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,318 lines (1,148 loc) • 170 kB
import fs from 'fs-extra'; import path from 'path'; import chalk from 'chalk'; import prompts from 'prompts'; import { aiManager } from './aiProviders.js'; import { IntelligentProjectAnalyzer } from './intelligentAnalyzer.js'; export class ProfessionalConverter { constructor(nextjsPath = null, outputPath = null) { this.nextjsPath = nextjsPath; this.outputPath = outputPath; this.projectAnalysis = null; this.conversionPlan = null; this.fixingHistory = []; this.userPreferences = { autoFix: true, confirmBeforeMajorChanges: true, preferredStyling: 'nativewind' }; } async start() { console.log(chalk.cyan('šŸš€ NTRN Professional Converter')); console.log(chalk.cyan('Acting as Senior React Native + Next.js Developer\n')); // If paths not provided, ask user to select them if (!this.nextjsPath || !this.outputPath) { await this.selectProjectPaths(); } // Now start the conversion process return await this.convertProject(); } async selectProjectPaths() { console.log(chalk.cyan('šŸ“ Project Selection')); console.log(chalk.gray('Choose your Next.js project and output location\n')); const { nextjsPath, outputName } = await prompts([ { type: 'text', name: 'nextjsPath', message: 'Enter the path to your Next.js project:', initial: process.cwd(), validate: async (input) => { if (!input) return 'Path is required'; const fullPath = path.resolve(input); if (!await fs.exists(fullPath)) return 'Path does not exist'; // Check if it's actually a Next.js project const packageJsonPath = path.join(fullPath, 'package.json'); if (await fs.exists(packageJsonPath)) { try { const pkg = await fs.readJson(packageJsonPath); const deps = { ...pkg.dependencies, ...pkg.devDependencies }; if (!deps.next) { console.log(chalk.yellow('āš ļø This doesn\'t appear to be a Next.js project (no "next" dependency found)')); const proceed = await prompts({ type: 'toggle', name: 'continue', message: 'Continue anyway?', initial: false, active: 'yes', inactive: 'no' }); if (!proceed.continue) return 'Please provide a valid Next.js project path'; } } catch (error) { return 'Could not read package.json - please verify this is a valid Next.js project'; } } return true; } }, { type: 'text', name: 'outputName', message: 'Enter the name for your React Native project folder:', initial: 'converted-react-native', validate: (name) => { if (!name) return 'Project name is required'; if (!/^[a-zA-Z0-9-_]+$/.test(name)) { return 'Project name can only contain letters, numbers, hyphens, and underscores'; } return true; } } ]); if (!nextjsPath || !outputName) { console.log(chalk.red('āŒ Project selection cancelled.')); process.exit(1); } // Resolve full paths this.nextjsPath = path.resolve(nextjsPath); this.outputPath = path.resolve(outputName); // Check if output directory already exists if (await fs.exists(this.outputPath)) { const overwrite = await prompts({ type: 'toggle', name: 'overwrite', message: `Directory "${outputName}" already exists. Overwrite?`, initial: false, active: 'yes', inactive: 'no' }); if (!overwrite.overwrite) { console.log(chalk.red('āŒ Project creation cancelled.')); process.exit(1); } await fs.remove(this.outputPath); } console.log(chalk.green('\nāœ… Project paths configured:')); console.log(chalk.blue(`šŸ“‚ Input: ${this.nextjsPath}`)); console.log(chalk.blue(`šŸ“‚ Output: ${this.outputPath}\n`)); } async convertProject() { console.log(chalk.cyan('šŸš€ Starting Professional Conversion Process\n')); // Initialize AI providers await aiManager.initialize(); await aiManager.ensureApiKeys(); // Step 1: Intelligent Project Analysis console.log(chalk.yellow('Phase 1: Intelligent Project Analysis')); const analyzer = new IntelligentProjectAnalyzer(this.nextjsPath); this.projectAnalysis = await analyzer.analyzeProject(); // Step 2: Generate Conversion Plan console.log(chalk.yellow('Phase 2: Professional Conversion Planning')); this.conversionPlan = await this.generateConversionPlan(); // Step 3: Create React Native Project Structure console.log(chalk.yellow('Phase 3: Creating React Native Foundation')); await this.createReactNativeProject(); // Step 4: Professional Conversion console.log(chalk.yellow('Phase 4: Professional Code Conversion')); const results = await this.performProfessionalConversion(); // Show conversion summary this.showConversionSummary(results.filter(r => r.success).length, results.filter(r => !r.success && !r.skipped).length, results.filter(r => r.skipped).length); // Perform detailed analysis of results await this.performPostConversionAnalysis(results); return results; } async generateConversionPlan() { console.log(chalk.blue('🧠 Generating professional conversion plan...')); const planningPrompt = this.createPlanningPrompt(); const response = await aiManager.callAI(planningPrompt, { task: 'planning', temperature: 0.1, maxTokens: 4096 }); const plan = await this.parsePlanFromResponse(response.content); console.log(chalk.green('āœ… Conversion plan generated')); this.displayConversionPlan(plan); return plan; } createPlanningPrompt() { return `# INTELLIGENT REACT NATIVE APP CREATION PLAN You are a Senior Mobile App Developer with 10+ years of experience. Your task is to analyze this Next.js website and create a native mobile app that provides the same functionality and user experience. ## NEXT.JS PROJECT ANALYSIS: ${JSON.stringify(this.projectAnalysis, null, 2)} ## YOUR MISSION: Understand the website's PURPOSE, FUNCTIONALITY, and USER FLOW. Then design a mobile app that delivers the same value through native mobile patterns. ### 🧠 INTELLIGENT ANALYSIS REQUIRED: #### 1. **Website Understanding** - What is the main purpose of this website? - What are the key user journeys and flows? - What are the main features and functionality? - How should this translate to mobile app experience? #### 2. **Mobile App Architecture Design** - What navigation pattern fits best? (Tab Bar, Stack, Drawer) - Which screens are needed to recreate the website functionality? - How should the mobile user flow differ from web? - What mobile-specific features should be added? #### 3. **Screen Mapping Strategy** - DON'T just convert files 1:1 - ANALYZE content and functionality - CREATE appropriate mobile screens - DESIGN mobile-first user experience ### šŸ“± EXAMPLES OF INTELLIGENT CONVERSION: **Instead of converting files directly:** āŒ \`app/layout.tsx\` → \`layout.tsx\` (Wrong!) āŒ \`app/page.tsx\` → \`page.tsx\` (Wrong!) **Do intelligent analysis:** āœ… \`app/layout.tsx\` (navigation/header) → Design Tab Navigator āœ… \`app/page.tsx\` (homepage content) → HomeScreen.tsx āœ… \`app/login/page.tsx\` (login functionality) → LoginScreen.tsx āœ… \`app/dashboard/page.tsx\` (dashboard functionality) → DashboardScreen.tsx ### šŸŽÆ REACT NATIVE APP STRUCTURE TO CREATE: Based on your analysis, determine what the complete mobile app needs: { "appPurpose": "Brief description of what this website/app does", "userJourneys": ["Journey 1", "Journey 2", "Journey 3"], "mobileScreens": [ { "screenName": "HomeScreen", "purpose": "Landing/main functionality", "sourceAnalysis": "Based on app/page.tsx content", "mobileFeatures": ["Feature 1", "Feature 2"] }, { "screenName": "LoginScreen", "purpose": "User authentication", "sourceAnalysis": "Based on app/login/page.tsx", "mobileFeatures": ["Biometric login", "Remember me"] } ], "supportingFiles": [ { "category": "components", "files": ["Button.tsx", "Header.tsx", "LoadingSpinner.tsx"], "purpose": "Reusable UI components from components/ folder", "sourceFolder": "components/" }, { "category": "utils", "files": ["api.ts", "helpers.ts", "validation.ts"], "purpose": "Utility functions and helpers from lib/ or utils/", "sourceFolder": "lib/ or utils/" }, { "category": "api", "files": ["auth.ts", "users.ts", "products.ts"], "purpose": "API calls and data fetching logic", "sourceFolder": "api/ or lib/api/" }, { "category": "constants", "files": ["config.ts", "theme.ts", "urls.ts"], "purpose": "App configuration and constants", "sourceFolder": "constants/ or config/" }, { "category": "hooks", "files": ["useAuth.ts", "useApi.ts"], "purpose": "Custom React hooks", "sourceFolder": "hooks/" }, { "category": "types", "files": ["user.ts", "api.ts", "components.ts"], "purpose": "TypeScript type definitions", "sourceFolder": "types/ or @types/" } ], "architecture": { "navigation": "stack|tab|drawer", "reasoning": "Why this navigation pattern fits the app", "stateManagement": "native|redux|zustand", "styling": "stylesheet|nativewind" }, "conversionStrategy": { "phases": [ { "name": "Core Screens Creation", "priority": "high", "screens": ["HomeScreen", "LoginScreen"], "strategy": "Create main user flow screens", "estimatedTime": "10-15 minutes" } ] }, "mobileEnhancements": [ "Push notifications", "Offline functionality", "Native gestures", "Haptic feedback" ] } ## šŸš€ THINK LIKE A MOBILE APP DESIGNER: - How can we make this mobile experience BETTER than the website? - What mobile-specific features would enhance user experience? - How should navigation work on small screens? - What content should be prioritized on mobile? Be intelligent, creative, and mobile-first in your approach!`; } async parsePlanFromResponse(content) { try { // Extract JSON from the response const jsonMatch = content.match(/```(?:json)?\s*({[\s\S]*?})\s*```/) || content.match(/({[\s\S]*})/); if (jsonMatch) { const plan = JSON.parse(jsonMatch[1]); // Transform the new intelligent format to the expected format if (plan.mobileScreens && plan.conversionStrategy) { return { appPurpose: plan.appPurpose, userJourneys: plan.userJourneys, mobileScreens: plan.mobileScreens, supportingFiles: plan.supportingFiles || [], architecture: plan.architecture, phases: plan.conversionStrategy.phases, mobileEnhancements: plan.mobileEnhancements, criticalIssues: [], qualityChecks: [ 'Validate all imports are React Native compatible', 'Ensure all text is wrapped in Text components', 'Verify navigation structure works', 'Check for runtime errors' ] }; } return plan; } // Fallback plan if parsing fails return await this.createFallbackPlan(); } catch (error) { console.warn(chalk.yellow('āš ļø Could not parse AI plan, using fallback')); return await this.createFallbackPlan(); } } async createFallbackPlan() { // Scan for supporting files in the Next.js project const supportingFiles = await this.scanForSupportingFiles(); return { appPurpose: "Next.js application converted to React Native", userJourneys: ["Navigate between screens", "Use app functionality"], mobileScreens: this.projectAnalysis.patterns.routing.routes .filter(r => r.isPage) .map(r => ({ screenName: this.convertFileNameToScreenName(r.file), purpose: `Screen based on ${r.file}`, sourceAnalysis: `Based on ${r.file}`, mobileFeatures: ["Mobile navigation", "Touch interactions"] })), supportingFiles: supportingFiles, architecture: { navigation: 'stack', stateManagement: 'native', styling: this.projectAnalysis.patterns.styling.hasTailwind ? 'nativewind' : 'stylesheet' }, phases: [ { name: 'Core Components Conversion', priority: 'high', files: this.projectAnalysis.patterns.routing.routes.filter(r => r.isPage).map(r => r.file), strategy: 'Convert page components to React Native screens', estimatedTime: '10-15 minutes' } ], criticalIssues: this.projectAnalysis.recommendations.map(r => ({ issue: r.title, solution: r.description, priority: r.priority })), qualityChecks: [ 'Validate all imports are React Native compatible', 'Ensure all text is wrapped in Text components', 'Verify navigation structure works', 'Check for runtime errors' ] }; } async scanForSupportingFiles() { const supportingFiles = []; console.log(chalk.blue('šŸ” Intelligent auto-discovery of ALL project folders...')); // Phase 1: Scan for known patterns const knownCategories = [ { folder: 'components', category: 'components' }, { folder: 'lib', category: 'utils' }, { folder: 'utils', category: 'utils' }, { folder: 'api', category: 'api' }, { folder: 'constants', category: 'constants' }, { folder: 'hooks', category: 'hooks' }, { folder: 'types', category: 'types' }, { folder: 'services', category: 'services' }, { folder: 'helpers', category: 'utils' }, { folder: 'stores', category: 'stores' }, { folder: 'context', category: 'contexts' }, { folder: 'contexts', category: 'contexts' }, { folder: 'providers', category: 'contexts' }, // Nested src patterns { folder: 'src/components', category: 'components' }, { folder: 'src/lib', category: 'utils' }, { folder: 'src/utils', category: 'utils' }, { folder: 'src/api', category: 'api' }, { folder: 'src/constants', category: 'constants' }, { folder: 'src/hooks', category: 'hooks' }, { folder: 'src/types', category: 'types' }, { folder: 'src/services', category: 'services' }, { folder: 'src/helpers', category: 'utils' }, { folder: 'src/stores', category: 'stores' }, { folder: 'src/context', category: 'contexts' }, { folder: 'src/contexts', category: 'contexts' }, { folder: 'src/providers', category: 'contexts' }, // App router patterns { folder: 'app/(components)', category: 'components' }, { folder: 'app/components', category: 'components' }, { folder: 'app/lib', category: 'utils' }, { folder: 'app/utils', category: 'utils' }, { folder: 'app/api', category: 'api' }, // Common "noob developer" patterns { folder: 'my-components', category: 'components' }, { folder: 'mycomponents', category: 'components' }, { folder: 'ui', category: 'components' }, { folder: 'ui-components', category: 'components' }, { folder: 'shared', category: 'components' }, { folder: 'common', category: 'utils' }, { folder: 'helper', category: 'utils' }, { folder: 'helpers', category: 'utils' }, { folder: 'data', category: 'api' }, { folder: 'requests', category: 'api' }, { folder: 'fetch', category: 'api' }, { folder: 'apis', category: 'api' }, { folder: 'configs', category: 'constants' }, { folder: 'config', category: 'constants' }, { folder: 'settings', category: 'constants' }, { folder: 'consts', category: 'constants' }, { folder: 'custom-hooks', category: 'hooks' }, { folder: 'customhooks', category: 'hooks' }, { folder: 'my-hooks', category: 'hooks' }, { folder: 'typings', category: 'types' }, { folder: 'type-definitions', category: 'types' }, { folder: 'interfaces', category: 'types' }, { folder: 'models', category: 'types' }, { folder: 'business-logic', category: 'services' }, { folder: 'logic', category: 'services' }, { folder: 'core', category: 'services' }, { folder: 'providers', category: 'contexts' }, { folder: 'context-providers', category: 'contexts' }, { folder: 'state', category: 'contexts' }, { folder: 'store', category: 'stores' }, { folder: 'redux', category: 'stores' }, { folder: 'zustand', category: 'stores' }, { folder: 'external', category: 'lib' }, { folder: 'third-party', category: 'lib' }, { folder: 'integrations', category: 'lib' }, ]; // Phase 2: Discover unknown folders with intelligent analysis const discoveredFolders = await this.discoverUnknownFolders(); // Phase 3: Scan known folders for (const { folder, category } of knownCategories) { const fullPath = path.join(this.nextjsPath, folder); try { if (await fs.pathExists(fullPath)) { const files = await this.scanFolderForFiles(fullPath); if (files.length > 0) { console.log(chalk.green(`āœ… Found ${category}: ${folder}/ (${files.length} files)`)); supportingFiles.push({ category, files: files.map(f => path.basename(f)), purpose: this.getCategoryPurpose(category), sourceFolder: folder + '/', fullPath, discoveredFiles: files }); } } } catch (error) { // Folder doesn't exist or can't be accessed, skip silently } } // Phase 4: Process discovered unknown folders for (const discoveredFolder of discoveredFolders) { console.log(chalk.yellow(`🧠 Analyzing unknown folder: ${discoveredFolder.path}`)); const category = await this.intelligentlyCategorizeFolderWithAI(discoveredFolder); if (category && category !== 'ignore') { console.log(chalk.green(`āœ… Categorized as: ${category} (${discoveredFolder.files.length} files)`)); supportingFiles.push({ category, files: discoveredFolder.files.map(f => path.basename(f)), purpose: this.getCategoryPurpose(category), sourceFolder: path.relative(this.nextjsPath, discoveredFolder.fullPath) + '/', fullPath: discoveredFolder.fullPath, discoveredFiles: discoveredFolder.files, aiCategorized: true }); } else { console.log(chalk.gray(`ā­ļø Skipping folder: ${discoveredFolder.path} (not relevant for mobile)`)); } } console.log(chalk.green(`šŸŽÆ Auto-discovery complete: Found ${supportingFiles.length} categories`)); return supportingFiles; } async discoverUnknownFolders() { const discoveredFolders = []; const scannedPaths = new Set(); // Recursively scan the project for ANY folders with code files const scanDirectory = async (dirPath, depth = 0) => { if (depth > 4) return; // Prevent infinite recursion if (scannedPaths.has(dirPath)) return; scannedPaths.add(dirPath); try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { const fullPath = path.join(dirPath, entry.name); const relativePath = path.relative(this.nextjsPath, fullPath); // Skip common irrelevant folders if (this.shouldSkipFolder(entry.name, relativePath)) { continue; } // Check if this folder contains code files const files = await this.scanFolderForFiles(fullPath); if (files.length > 0) { // Check if it's not already in our known categories if (!this.isKnownCategory(relativePath)) { discoveredFolders.push({ name: entry.name, path: relativePath, fullPath, files, depth }); } } // Recursively scan subdirectories await scanDirectory(fullPath, depth + 1); } } } catch (error) { // Can't read directory, skip } }; await scanDirectory(this.nextjsPath); return discoveredFolders; } shouldSkipFolder(folderName, relativePath) { const skipPatterns = [ 'node_modules', '.next', '.git', 'dist', 'build', 'out', '.vscode', '.idea', 'coverage', '__tests__', '.storybook', 'public', 'static', 'assets', 'images', 'fonts', 'icons', '.env', '.env.local', '.env.development', '.env.production', 'styles', 'css', 'scss', 'sass', // Skip pure styling folders 'docs', 'documentation', 'readme', ]; return skipPatterns.some(pattern => folderName.toLowerCase().includes(pattern.toLowerCase()) || relativePath.toLowerCase().includes(pattern.toLowerCase()) ); } isKnownCategory(relativePath) { const knownPaths = [ 'components', 'lib', 'utils', 'api', 'constants', 'hooks', 'types', 'services', 'src/components', 'src/lib', 'src/utils', 'src/api', 'src/constants', 'src/hooks', 'src/types', 'src/services', 'app/components', 'app/lib', 'app/utils', 'app/api', 'pages/api' ]; return knownPaths.some(known => relativePath === known || relativePath.startsWith(known + '/')); } async scanFolderForFiles(folderPath) { const files = []; try { const entries = await fs.readdir(folderPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile()) { const filePath = path.join(folderPath, entry.name); // Only include TypeScript/JavaScript files if (this.isCodeFile(entry.name)) { files.push(filePath); } } else if (entry.isDirectory() && !this.shouldSkipFolder(entry.name, entry.name)) { // Recursively scan subdirectories (max 2 levels deep) const subFiles = await this.scanFolderForFiles(path.join(folderPath, entry.name)); files.push(...subFiles); } } } catch (error) { // Can't read folder, return empty array } return files; } isCodeFile(fileName) { const codeExtensions = ['.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte']; return codeExtensions.some(ext => fileName.endsWith(ext)); } async intelligentlyCategorizeFolderWithAI(discoveredFolder) { try { // Read a few sample files to understand the folder's purpose const sampleFiles = discoveredFolder.files.slice(0, 3); let sampleContent = ''; for (const filePath of sampleFiles) { try { const content = await fs.readFile(filePath, 'utf8'); sampleContent += `\n// File: ${path.basename(filePath)}\n${content.substring(0, 500)}...\n`; } catch (error) { // Skip if can't read file } } const categorizationPrompt = `# INTELLIGENT FOLDER CATEGORIZATION FOR ANY PROJECT STRUCTURE You are an expert developer analyzing a Next.js project with unknown folder structure. Your job is to intelligently categorize ANY folder, even if the developer used weird or non-standard naming. ## UNKNOWN FOLDER ANALYSIS: - **Folder Name**: ${discoveredFolder.name} - **Folder Path**: ${discoveredFolder.path} - **Files Found**: ${discoveredFolder.files.length} - **File Names**: ${discoveredFolder.files.map(f => path.basename(f)).join(', ')} ## SAMPLE CODE FROM FILES: \`\`\`typescript ${sampleContent} \`\`\` ## YOUR INTELLIGENT ANALYSIS TASK: Look at the folder name, file names, and code content to determine what this folder contains. Handle ANY naming convention, including non-standard ones from noob developers. **Available Categories:** - \`components\` - UI components, React components, reusable elements - \`utils\` - Helper functions, utilities, shared logic, formatters - \`api\` - API calls, data fetching, HTTP requests, backend communication - \`constants\` - Configuration, constants, config files, settings - \`hooks\` - Custom React hooks, state logic - \`types\` - TypeScript types, interfaces, type definitions - \`services\` - Business logic services, classes, core functionality - \`contexts\` - React context providers, state management - \`stores\` - State management (Redux, Zustand, etc.) - \`lib\` - Third-party integrations, external libraries - \`ignore\` - Not relevant for mobile app (skip this folder) **Pattern Recognition Examples:** - "ui", "shared", "common-components", "my-components" → \`components\` - "helper", "tools", "shared-utils", "common" → \`utils\` - "data", "requests", "fetch", "api-calls" → \`api\` - "config", "settings", "env", "constants" → \`constants\` - "custom-hooks", "hooks", "state-hooks" → \`hooks\` - "types", "interfaces", "models", "typings" → \`types\` - "logic", "business", "core", "services" → \`services\` - "context", "providers", "state" → \`contexts\` - "store", "redux", "zustand", "state-mgmt" → \`stores\` - "external", "third-party", "integrations" → \`lib\` - "server", "backend", "database", "docs" → \`ignore\` **Content Analysis:** - Files with JSX/TSX and return statements → \`components\` - Files with export function/const (utilities) → \`utils\` - Files with fetch/axios calls → \`api\` - Files with export const CONFIG → \`constants\` - Files with useEffect/useState → \`hooks\` - Files with interface/type definitions → \`types\` - Files with class definitions or business logic → \`services\` - Files with createContext/Provider → \`contexts\` - Files with store/dispatch/reducer → \`stores\` **BE INTELLIGENT:** Look beyond the folder name at the actual content and purpose. **Respond with ONLY the category name (no explanation):**`; const response = await aiManager.callAI(categorizationPrompt, { task: 'categorization', temperature: 0.1, maxTokens: 50 }); const category = response.content.trim().toLowerCase(); // Validate the category const validCategories = ['components', 'utils', 'api', 'constants', 'hooks', 'types', 'services', 'contexts', 'stores', 'lib', 'ignore']; return validCategories.includes(category) ? category : 'utils'; // Default to utils if uncertain } catch (error) { console.log(chalk.yellow(`āš ļø Could not categorize folder ${discoveredFolder.name}, defaulting to utils`)); return 'utils'; // Safe default } } getCategoryPurpose(category) { const purposes = { 'components': 'Reusable UI components', 'utils': 'Utility functions and helpers', 'api': 'API calls and data fetching logic', 'constants': 'App configuration and constants', 'hooks': 'Custom React hooks', 'types': 'TypeScript type definitions', 'services': 'Business logic and service classes', 'contexts': 'React context providers and state management', 'stores': 'State management (Redux, Zustand, etc.)', 'lib': 'Third-party integrations and external libraries' }; return purposes[category] || 'Supporting functionality'; } detectShadcnUsage(sourceContent) { if (!sourceContent) { return { hasShadcn: false, components: [], imports: [] }; } const shadcnImportRegex = /import\s*{\s*([^}]+)\s*}\s*from\s*["'`]@\/components\/ui\/([^"'`]+)["'`]/g; const shadcnComponents = [ 'Button', 'Input', 'Card', 'CardHeader', 'CardTitle', 'CardContent', 'CardFooter', 'Dialog', 'DialogContent', 'DialogHeader', 'DialogTitle', 'DialogTrigger', 'Sheet', 'SheetContent', 'SheetHeader', 'SheetTitle', 'SheetTrigger', 'Select', 'SelectContent', 'SelectItem', 'SelectTrigger', 'SelectValue', 'Checkbox', 'Switch', 'RadioGroup', 'RadioGroupItem', 'Tabs', 'TabsList', 'TabsTrigger', 'TabsContent', 'Badge', 'Avatar', 'AvatarImage', 'AvatarFallback', 'Progress', 'Slider', 'Textarea', 'Label', 'Alert', 'AlertDialog', 'AlertDialogAction', 'AlertDialogCancel', 'Toast', 'Toaster', 'useToast', 'Separator', 'Skeleton', 'ScrollArea', 'Command', 'CommandDialog', 'CommandInput', 'CommandList', 'Popover', 'PopoverContent', 'PopoverTrigger', 'HoverCard', 'HoverCardContent', 'HoverCardTrigger', 'DropdownMenu', 'DropdownMenuContent', 'DropdownMenuItem' ]; const detectedComponents = new Set(); const detectedImports = new Set(); // Check for import statements let match; while ((match = shadcnImportRegex.exec(sourceContent)) !== null) { const components = match[1].split(',').map(c => c.trim()); const importPath = match[2]; detectedImports.add(`@/components/ui/${importPath}`); components.forEach(comp => { if (shadcnComponents.includes(comp)) { detectedComponents.add(comp); } }); } // Check for component usage in JSX shadcnComponents.forEach(component => { const usageRegex = new RegExp(`<${component}\\b`, 'g'); if (usageRegex.test(sourceContent)) { detectedComponents.add(component); } }); return { hasShadcn: detectedComponents.size > 0 || detectedImports.size > 0, components: Array.from(detectedComponents), imports: Array.from(detectedImports) }; } getShadcnConversionMapping(component) { const mappings = { 'Button': '**Button** → TouchableOpacity + Text with proper styling and onPress handler', 'Input': '**Input** → TextInput with React Native styling, keyboardType, and onChangeText', 'Card': '**Card** → View with shadow/border styling', 'CardHeader': '**CardHeader** → View with header styling', 'CardTitle': '**CardTitle** → Text with title styling', 'CardContent': '**CardContent** → View with content padding', 'CardFooter': '**CardFooter** → View with footer styling', 'Dialog': '**Dialog** → Modal with overlay and proper animations', 'DialogContent': '**DialogContent** → View with modal content styling', 'DialogHeader': '**DialogHeader** → View with modal header', 'DialogTitle': '**DialogTitle** → Text with modal title styling', 'DialogTrigger': '**DialogTrigger** → TouchableOpacity to open modal', 'Sheet': '**Sheet** → Modal with slide-up animation', 'SheetContent': '**SheetContent** → View with bottom sheet styling', 'SheetHeader': '**SheetHeader** → View with sheet header', 'SheetTitle': '**SheetTitle** → Text with sheet title styling', 'SheetTrigger': '**SheetTrigger** → TouchableOpacity to open sheet', 'Select': '**Select** → Custom picker with TouchableOpacity + Modal + FlatList', 'SelectContent': '**SelectContent** → Modal with options list', 'SelectItem': '**SelectItem** → TouchableOpacity for each option', 'SelectTrigger': '**SelectTrigger** → TouchableOpacity to open picker', 'SelectValue': '**SelectValue** → Text showing selected value', 'Checkbox': '**Checkbox** → TouchableOpacity with custom checkbox styling', 'Switch': '**Switch** → React Native Switch component', 'RadioGroup': '**RadioGroup** → Custom radio button group with TouchableOpacity', 'RadioGroupItem': '**RadioGroupItem** → TouchableOpacity with radio styling', 'Tabs': '**Tabs** → Custom tab implementation with TouchableOpacity', 'TabsList': '**TabsList** → View with horizontal tab buttons', 'TabsTrigger': '**TabsTrigger** → TouchableOpacity for each tab', 'TabsContent': '**TabsContent** → View with tab content', 'Badge': '**Badge** → View with badge styling and Text', 'Avatar': '**Avatar** → Image with circular styling (expo-image)', 'AvatarImage': '**AvatarImage** → Image component', 'AvatarFallback': '**AvatarFallback** → Text with fallback styling', 'Progress': '**Progress** → Custom progress bar with View components', 'Slider': '**Slider** → @react-native-community/slider', 'Textarea': '**Textarea** → TextInput with multiline and proper styling', 'Label': '**Label** → Text with label styling', 'Alert': '**Alert** → View with alert styling and icon', 'AlertDialog': '**AlertDialog** → Modal with alert styling', 'AlertDialogAction': '**AlertDialogAction** → TouchableOpacity for alert actions', 'AlertDialogCancel': '**AlertDialogCancel** → TouchableOpacity for cancel action', 'Toast': '**Toast** → react-native-toast-message integration', 'Toaster': '**Toaster** → Toast message container', 'useToast': '**useToast** → Custom hook for toast notifications', 'Separator': '**Separator** → View with border styling', 'Skeleton': '**Skeleton** → View with loading animation', 'ScrollArea': '**ScrollArea** → ScrollView with proper styling', 'Command': '**Command** → Custom command palette with TextInput + FlatList', 'CommandDialog': '**CommandDialog** → Modal with command interface', 'CommandInput': '**CommandInput** → TextInput for command search', 'CommandList': '**CommandList** → FlatList for command results', 'Popover': '**Popover** → Modal with popover positioning', 'PopoverContent': '**PopoverContent** → View with popover content', 'PopoverTrigger': '**PopoverTrigger** → TouchableOpacity to open popover', 'HoverCard': '**HoverCard** → TouchableOpacity with long press (no hover in mobile)', 'HoverCardContent': '**HoverCardContent** → Modal or tooltip content', 'HoverCardTrigger': '**HoverCardTrigger** → TouchableOpacity with long press', 'DropdownMenu': '**DropdownMenu** → Modal with dropdown styling', 'DropdownMenuContent': '**DropdownMenuContent** → View with menu items', 'DropdownMenuItem': '**DropdownMenuItem** → TouchableOpacity for each menu item' }; return mappings[component] || `**${component}** → Create React Native equivalent using TouchableOpacity, View, Text, and proper styling`; } convertFileNameToScreenName(filename) { // Convert file path to screen name const baseName = path.basename(filename, path.extname(filename)); if (baseName === 'index' || baseName === 'page') { const dirName = path.dirname(filename); const folderName = path.basename(dirName); if (folderName === 'app' || folderName === '(app)') { return 'HomeScreen'; } return folderName.charAt(0).toUpperCase() + folderName.slice(1) + 'Screen'; } return baseName.charAt(0).toUpperCase() + baseName.slice(1) + 'Screen'; } displayConversionPlan(plan) { console.log(chalk.cyan('\nšŸ“± Intelligent Mobile App Creation Plan')); console.log(chalk.cyan('=' .repeat(60))); if (plan.appPurpose) { console.log(chalk.yellow('\nšŸŽÆ App Purpose:')); console.log(` ${plan.appPurpose}`); } if (plan.userJourneys) { console.log(chalk.yellow('\nšŸ›¤ļø Key User Journeys:')); plan.userJourneys.forEach((journey, index) => { console.log(` ${index + 1}. ${journey}`); }); } if (plan.mobileScreens) { console.log(chalk.yellow('\nšŸ“± Mobile Screens to Create:')); plan.mobileScreens.forEach((screen, index) => { console.log(` ${index + 1}. ${chalk.green(screen.screenName)}`); console.log(` Purpose: ${screen.purpose}`); console.log(` Based on: ${screen.sourceAnalysis}`); if (screen.mobileFeatures?.length > 0) { console.log(` Mobile Features: ${screen.mobileFeatures.join(', ')}`); } }); } if (plan.supportingFiles?.length > 0) { console.log(chalk.yellow('\nšŸ› ļø Supporting Files to Convert:')); plan.supportingFiles.forEach((category, index) => { console.log(` ${index + 1}. ${chalk.blue(category.category.toUpperCase())}`); console.log(` Files: ${category.files.join(', ')}`); console.log(` Purpose: ${category.purpose}`); console.log(` Source: ${category.sourceFolder}`); }); } if (plan.architecture) { console.log(chalk.yellow('\nšŸ—ļø Architecture Decisions:')); console.log(` Navigation: ${plan.architecture.navigation}`); if (plan.architecture.reasoning) { console.log(` Reasoning: ${plan.architecture.reasoning}`); } console.log(` State Management: ${plan.architecture.stateManagement}`); console.log(` Styling: ${plan.architecture.styling}`); } if (plan.phases) { console.log(chalk.yellow('\nšŸ“… Development Phases:')); plan.phases.forEach((phase, index) => { const priority = phase.priority === 'high' ? 'šŸ”“' : phase.priority === 'medium' ? '🟔' : '🟢'; console.log(` ${index + 1}. ${priority} ${phase.name} (${phase.estimatedTime})`); console.log(` Strategy: ${phase.strategy}`); if (phase.screens) { console.log(` Screens: ${phase.screens.join(', ')}`); } else if (phase.files) { console.log(` Files: ${phase.files.length} files`); } }); } if (plan.mobileEnhancements?.length > 0) { console.log(chalk.yellow('\nšŸš€ Mobile Enhancements:')); plan.mobileEnhancements.forEach(enhancement => { console.log(` ✨ ${enhancement}`); }); } if (plan.criticalIssues?.length > 0) { console.log(chalk.yellow('\nāš ļø Critical Issues to Address:')); plan.criticalIssues.slice(0, 3).forEach(issue => { const priority = issue.priority === 'high' ? 'šŸ”“' : '🟔'; console.log(` ${priority} ${issue.issue}`); }); } console.log(''); } async createReactNativeProject() { console.log(chalk.blue('šŸ—ļø Creating professional React Native project structure...')); // Create comprehensive directory structure const directories = [ 'src', 'src/screens', 'src/components', 'src/navigation', 'src/hooks', 'src/utils', 'src/services', 'src/api', 'src/contexts', 'src/types', 'src/constants', 'assets', 'assets/images', 'assets/fonts' ]; for (const dir of directories) { await fs.ensureDir(path.join(this.outputPath, dir)); } // Create essential files await this.createPackageJson(); await this.createConfigFiles(); await this.createTypeScriptConfig(); await this.createNavigationSetup(); await this.createContextProviders(); await this.createApiServices(); // Copy and process assets from public folder await this.copyAndProcessAssets(); console.log(chalk.green('āœ… React Native project structure created')); } async createPackageJson() { const packageJson = { name: path.basename(this.outputPath).toLowerCase().replace(/[^a-z0-9]/g, ''), version: '1.0.0', main: 'expo/AppEntry.js', scripts: { start: 'expo start', android: 'expo start --android', ios: 'expo start --ios', web: 'expo start --web', 'type-check': 'tsc --noEmit', eject: 'expo eject' }, dependencies: { expo: '~52.0.19', react: '18.3.1', 'react-native': '0.76.5', '@react-navigation/native': '^6.1.18', '@react-navigation/native-stack': '^6.11.0', '@react-navigation/bottom-tabs': '^6.6.1', 'react-native-screens': '3.34.0', 'react-native-safe-area-context': '4.12.0', '@react-native-async-storage/async-storage': '1.23.1', 'react-native-gesture-handler': '~2.20.0', 'expo-status-bar': '~2.0.0', 'expo-font': '~12.0.10', 'expo-splash-screen': '~0.27.8', 'expo-image': '~1.13.0', '@react-native-community/slider': '4.5.3', 'react-native-toast-message': '^2.2.1', 'react-native-reanimated': '~3.16.1', 'react-native-svg': '15.8.0' }, devDependencies: { '@babel/core': '^7.25.0', '@types/react': '~18.3.12', 'typescript': '~5.3.3' } }; await fs.writeJson(path.join(this.outputPath, 'package.json'), packageJson, { spaces: 2 }); } async createTypeScriptConfig() { const tsConfig = { extends: 'expo/tsconfig.base', compilerOptions: { strict: true, esModuleInterop: true, allowSyntheticDefaultImports: true, jsx: 'react-native', lib: ['dom', 'esnext'], moduleResolution: 'node', noEmit: true, skipLibCheck: true, resolveJsonModule: true, baseUrl: '.', paths: { '@/*': ['src/*'], '@/components/*': ['src/components/*'], '@/screens/*': ['src/screens/*'], '@/services/*': ['src/services/*'], '@/contexts/*': ['src/contexts/*'], '@/types/*': ['src/types/*'], '@/api/*': ['src/api/*'] } }, include: [ '**/*.ts', '**/*.tsx' ], exclude: [ 'node_modules' ] }; await fs.writeJson(path.join(this.outputPath, 'tsconfig.json'), tsConfig, { spaces: 2 }); } async createConfigFiles() { // Enhanced App.tsx with Context Providers const appContent = `import React from 'react'; import { StatusBar } from 'expo-status-bar'; import { NavigationContainer } from '@react-navigation/native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { AppNavigator } from './src/navigation/AppNavigator'; import { AppProviders } from './src/contexts/AppProviders'; export default function App(): JSX.Element { return ( <GestureHandlerRootView style={{ flex: 1 }}> <SafeAreaProvider> <AppProviders> <NavigationContainer> <AppNavigator /> <StatusBar style="auto" /> </NavigationContainer> </AppProviders> </SafeAreaProvider> </GestureHandlerRootView> ); }`; await fs.writeFile(path.join(this.outputPath, 'App.tsx'), appContent); // Fixed app.json - Official Expo SDK 53 React Native configuration const appJson = { expo: { name: path.basename(this.outputPath), slug: path.basename(this.outputPath).toLowerCase(), version: '1.0.0', orientation: 'portrait', icon: './assets/icon.png', userInterfaceStyle: 'light', newArchEnabled: true, // Enable New Architecture by default for SDK 52 splash: { image: './assets/splash.png', resizeMode: 'contain', backgroundColor: '#ffffff' }, assetBundlePatterns: ['**/*'], ios: { supportsTablet: true, bundleIdentifier: `com.${path.basename(this.outputPath).toLowerCase()}.app`, deploymentTarget: '15.1' // SDK 52 minimum iOS version }, android: { adaptiveIcon: { foregroundImage: './assets/adaptive-icon.png', backgroundColor: '#FFFFFF' }, package: `com.${path.basename(this.outputPath).toLowerCase()}.app`, compileSdkVersion: 34, // SDK 52 requirement targetSdkVersion: 34, minSdkVersion: 23 // SDK 52 requirement }, web: { favicon: './assets/favicon.png', bundler: 'metro' }, plugins: [ 'expo-splash-screen' // Use config plugin for SDK 52 ] } }; await fs.writeJson(path.join(this.outputPath, 'app.json'), appJson, { spaces: 2 }); // Create babel.config.js for proper Expo setup const babelConfig = `module.exports = function(api) { api.cache(true); return { presets: ['babel-preset-expo'], }; };`; await fs.writeFile(path.join(this.outputPath, 'babel.config.js'), babelConfig); // Create metro.config.js for TypeScript support const metroConfig = `const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); // Add TypeScript support config.resolver.sourceExts.push('tsx', 'ts'); module.exports = config;`; await fs.writeFile(path.join(this.outputPath, 'metro.config.js'), metroConfig); // Create .expo/README.md to mark as Expo project await fs.ensureDir(path.join(this.outputPath, '.expo')); const expoReadme = `# Expo Project This folder is created when an Expo project is started using "expo start" command. ## Required Files (generated automatically by Expo): - icon.png (1024x1024) - App icon - splash.png (1242x2436) - Splash screen image - adaptive-icon.png (1024x1024) - Android adaptive icon - favicon.png (48x48) - Web favicon ## Adding Custom Assets: 1. Place images, fonts, and other assets in this folder 2. Import them in your components: \`\`\`typescript import logo from '../assets/logo.png'; <Image source={logo} /> \`\`\` Expo will automatically generate the required app icons and splash screens when you build your app.`; await fs.writeFile(path.join(this.outputPath, '.expo/README.md'), expoReadme); // Create essential .gitignore for Expo project const gitignoreContent = `# OSX # .DS_Store # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate project.xcworkspace # Android/IntelliJ # build/ .idea .gradle local.properties *.iml *.hprof .cxx/ # node.js # node_modules/ npm-debug.log yarn-error.log # BUCK buck-out/ \\.buckd/ *.keystore !debug.keystore # Bundle artifacts *.jsbundle # CocoaPods /ios/Pods/ # Expo .expo/ dist/ web-build/ # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb # The following patterns were generated by expo-cli expo-env.d.ts # @end expo-cli`; await fs.writeFile(path.join(this.outputPath, '.gitignore'), gitignoreContent); // Create placeholder assets that app.json references await fs.ensureDir(path.join(this.outputPath, 'assets')); // Create a simple README for assets folder const assetsReadme = `# Assets This folder contains the static assets for your Expo React Native app. ## Required Files (generated automatically by Expo): - icon.png (1024x1024) - App icon - splash.png (1242x2436) - Splash screen image - adaptive-icon.png (1024x1024) - Android adaptive icon - favicon.png (48x48) - Web favicon ## Adding Custom Assets: 1. Place images, fonts, and other assets in this folder 2. Import them in your components: \`\`\`typescript import logo from '../assets/logo.png'; <Image source={logo} /> \`\`\` Expo will automatically generate the required app icons and splash screens when you build your app.`; await fs.writeFile(path.join(this.outputPath, 'assets/README.md'), assetsReadme); // Create expo-env.d.ts for TypeScript support const expoEnvTypes = `/// <reference types="expo/types" />`; await fs.writeFile(path.join(this.outputPath, 'expo-env.d.ts'), expoEnvTypes); } async createNavigationSetup() { // Enhanced navigation with TypeScript types const navigationTypesContent = `import type { NativeStackScreenProps } from '@react-navigation/native-stack'; export type RootStackParamList = { Home: undefined; // Add your screen types here // Profile: { userId: string }; // Settings: undefined; }; export type RootStackScreenProps<Screen extends keyof RootStackParamList> = NativeStackScreenProps<RootStackParamList, Screen>; declare global { namespace ReactNavigation { interface RootParamList extends RootStackParamList {} } }`; await fs.writeFile(path.join(this.outputPath, 'src/types/navigation.ts'), navigationTypesContent); // Enhanced Navigator with TypeScript const navigatorContent = `import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import type { R