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