UNPKG

zura-stack-native

Version:

A comprehensive React Native CLI project generator with production-ready setup

1,030 lines (905 loc) โ€ข 35.8 kB
const chalk = require('chalk'); const ora = require('ora').default; const fs = require('fs-extra'); const path = require('path'); const { execSync } = require('child_process'); const { DependencyInstaller } = require('./DependencyInstaller'); const { FileGenerator } = require('./FileGenerator'); const { ConfigGenerator } = require('./ConfigGenerator'); class ProjectGenerator { constructor(config) { this.config = config; this.spinner = null; // Don't instantiate these until we need them, after the project is created this.installer = null; this.fileGenerator = null; this.configGenerator = null; } async generate() { try { console.log(chalk.blue(`\n๐ŸŽฏ Creating project: ${this.config.projectName}`)); console.log(chalk.gray(`๐Ÿ“ Location: ${this.config.projectPath}\n`)); // Step 1: Create project directory await this.createProjectDirectory(); // Step 2: Initialize React Native project await this.initializeReactNativeProject(); // Step 3: Fix React version compatibility issues immediately await this.fixReactVersionCompatibility(); // Step 4: Install Zustand await this.installZustand(); // Step 5: Create industry-level folder structure await this.createFolderStructure(); // Step 6: Install NativeWind await this.installNativeWind(); // Step 7: Install Gluestack UI (interactive) await this.installGluestackUI(); // Step 8: Install Lucide icons await this.installLucideIcons(); // Step 9: Install React Navigation and set up navigation await this.installReactNavigation(); await this.setupNavigationFiles(); // Step 10: Fix SVG compatibility issue await this.fixSVGCompatibility(); // Step 11: Final compatibility check and cleanup await this.performFinalCompatibilityCheck(); // Success message this.showSuccessMessage(); } catch (error) { this.stopSpinner(); console.error(chalk.red('\nโŒ Error generating project:'), error.message); throw error; } } async createProjectDirectory() { this.startSpinner('Creating project directory...'); await fs.ensureDir(this.config.projectPath); this.stopSpinner(); } async initializeReactNativeProject() { this.startSpinner('Initializing React Native project...'); const command = `npx @react-native-community/cli@latest init ${this.config.projectName}`; try { execSync(command, { cwd: path.dirname(this.config.projectPath), stdio: this.config.verbose ? 'inherit' : 'pipe' }); // Update the project path to point to the created React Native project this.config.projectPath = path.resolve(this.config.projectName); } catch (error) { throw new Error(`Failed to initialize React Native project: ${error.message}`); } this.stopSpinner(); } /** * Fix React version compatibility issues immediately after project creation */ async fixReactVersionCompatibility() { this.startSpinner('Fixing React version compatibility...'); try { // Step 1: Check current React versions const packageJsonPath = path.join(this.config.projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); console.log(chalk.yellow('๐Ÿ” Checking React version compatibility...')); // Step 2: Ensure all React packages have the same version const reactVersion = '19.1.0'; // Use the stable version that matches react-native-renderer // Update package.json with exact versions packageJson.dependencies = { ...packageJson.dependencies, 'react': reactVersion, 'react-dom': reactVersion, }; // Remove any conflicting peer dependencies if (packageJson.peerDependencies) { delete packageJson.peerDependencies.react; delete packageJson.peerDependencies['react-dom']; } await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); // Step 3: Clean install with exact versions console.log(chalk.yellow('๐Ÿ“ฆ Installing exact React versions...')); execSync('npm install', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath }); // Step 4: Verify versions are correct const verifyCommand = 'npm list react react-dom react-native'; const output = execSync(verifyCommand, { encoding: 'utf8', cwd: this.config.projectPath }); console.log(chalk.green('โœ… React version compatibility verified:')); console.log(chalk.gray(output)); } catch (error) { console.log(chalk.yellow('โš ๏ธ React version fix failed, trying alternative approach...')); // Alternative: Force install with legacy peer deps try { execSync('npm install react@19.1.0 react-dom@19.1.0 --legacy-peer-deps --force', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath }); } catch (retryError) { throw new Error(`Failed to fix React version compatibility: ${retryError.message}`); } } this.stopSpinner(); } async installNativeWind() { this.startSpinner('Installing NativeWind...'); try { // Step 1: Uninstall prettier first (only prettier, not the plugin) execSync('npm uninstall prettier', { stdio: 'pipe', cwd: this.config.projectPath }); } catch (error) { // Ignore errors if prettier is not installed } // Step 2: Install NativeWind and its dependencies with exact versions const installCommands = [ 'npm install nativewind@^2.0.11 react-native-reanimated@3.18.0 react-native-safe-area-context@5.4.0', 'npm install --dev tailwindcss@^3.4.17 prettier-plugin-tailwindcss@^0.5.11' ]; for (const command of installCommands) { try { execSync(command, { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath }); } catch (error) { // Try with legacy peer deps if normal install fails console.log(chalk.yellow(`โš ๏ธ Retrying with legacy peer deps: ${command}`)); execSync(`${command} --legacy-peer-deps`, { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath }); } } // Step 3: Run pod install for react-native-reanimated try { console.log(chalk.yellow('๐Ÿ“ฆ Running pod install for react-native-reanimated...')); execSync('npx pod-install', { stdio: 'inherit', cwd: this.config.projectPath }); console.log(chalk.green('โœ… Pod install completed successfully')); } catch (error) { console.log(chalk.yellow('โš ๏ธ Pod install failed (this is normal if you\'re not on macOS)')); console.log(chalk.gray('Error details:'), error.message); } // Step 4: Initialize Tailwind CSS execSync('npx tailwindcss init', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath }); // Step 5: Generate configuration files await this.generateNativeWindConfig(); this.stopSpinner(); } async generateNativeWindConfig() { // Generate tailwind.config.js with proper content paths const tailwindConfig = `/** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./App.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}" ], presets: [require("nativewind/preset")], theme: { extend: {}, }, plugins: [], }`; await fs.writeFile(path.join(this.config.projectPath, 'tailwind.config.js'), tailwindConfig); // Generate global.css const globalCSS = `@tailwind base; @tailwind components; @tailwind utilities;`; await fs.writeFile(path.join(this.config.projectPath, 'global.css'), globalCSS); // Update babel.config.js const babelConfigPath = path.join(this.config.projectPath, 'babel.config.js'); let babelConfig = await fs.readFile(babelConfigPath, 'utf8'); babelConfig = babelConfig.replace( "presets: ['module:@react-native/babel-preset'],", "presets: ['module:@react-native/babel-preset', 'nativewind/babel']," ); // Add react-native-reanimated plugin babelConfig = babelConfig.replace( "plugins: [],", "plugins: ['react-native-reanimated/plugin']," ); await fs.writeFile(babelConfigPath, babelConfig); // Update metro.config.js const metroConfig = `const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config"); const { withNativeWind } = require("nativewind/metro"); const config = mergeConfig(getDefaultConfig(__dirname), { resolver: { alias: { 'react-native-reanimated': require.resolve('react-native-reanimated'), }, }, }); module.exports = withNativeWind(config, { input: "./global.css" });`; await fs.writeFile(path.join(this.config.projectPath, 'metro.config.js'), metroConfig); // Update App.tsx with NativeWind styles and CSS import const appContent = `import "./global.css" /** * Sample React Native App with NativeWind * https://github.com/facebook/react-native * * @format */ import { StatusBar, useColorScheme, View, Text } from 'react-native'; function App() { const isDarkMode = useColorScheme() === 'dark'; return ( <View className="flex-1 items-center justify-center bg-white dark:bg-gray-900"> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} /> <Text className="text-2xl font-bold text-blue-600 dark:text-blue-400"> Welcome to NativeWind! </Text> <Text className="text-lg text-gray-600 dark:text-gray-300 mt-4"> Your React Native app is ready with Tailwind CSS </Text> </View> ); } export default App;`; await fs.writeFile(path.join(this.config.projectPath, 'App.tsx'), appContent); // Create TypeScript declaration file const tsDeclaration = `/// <reference types="nativewind/types" />`; await fs.writeFile(path.join(this.config.projectPath, 'nativewind-env.d.ts'), tsDeclaration); } async installGluestackUI() { this.stopSpinner(); console.log(chalk.blue('\n๐Ÿ”ง Installing Gluestack UI...')); console.log(chalk.gray('This step requires interactive input. Please follow the prompts below.\n')); try { // Step 1: Ensure React versions are compatible before Gluestack installation console.log(chalk.yellow('๐Ÿ“ฆ Ensuring React version compatibility...')); execSync('npm install react@19.1.0 react-dom@19.1.0 --legacy-peer-deps', { stdio: 'inherit', cwd: this.config.projectPath }); // Step 2: Initialize Gluestack UI (interactive) console.log(chalk.yellow('\n๐Ÿš€ Initializing Gluestack UI...')); console.log(chalk.gray('Please answer the prompts below:')); execSync('npx gluestack-ui init', { stdio: 'inherit', cwd: this.config.projectPath }); // Step 3: Install all Gluestack UI components (interactive) console.log(chalk.yellow('\n๐Ÿ“ฆ Installing all Gluestack UI components...')); execSync('npx gluestack-ui add --all', { stdio: 'inherit', cwd: this.config.projectPath }); // Step 4: Generate Gluestack UI configuration await this.generateGluestackUIConfig(); } catch (error) { // If the above fails, try with legacy peer deps console.log(chalk.yellow('\nโš ๏ธ Retrying with legacy peer deps...')); try { execSync('npm install react@19.1.0 react-dom@19.1.0 --legacy-peer-deps', { stdio: 'inherit', cwd: this.config.projectPath }); console.log(chalk.yellow('\n๐Ÿš€ Initializing Gluestack UI...')); execSync('npx gluestack-ui init', { stdio: 'inherit', cwd: this.config.projectPath }); console.log(chalk.yellow('\n๐Ÿ“ฆ Installing all Gluestack UI components...')); execSync('npx gluestack-ui add --all', { stdio: 'inherit', cwd: this.config.projectPath }); await this.generateGluestackUIConfig(); } catch (retryError) { throw new Error(`Failed to install Gluestack UI: ${retryError.message}`); } } } async generateGluestackUIConfig() { // Update App.tsx to include Gluestack UI provider and example components const appContent = `import "./global.css" /** * Sample React Native App with NativeWind and Gluestack UI * https://github.com/facebook/react-native * * @format */ import { StatusBar, useColorScheme, View, Text } from 'react-native'; import { GluestackUIProvider } from './components/ui/gluestack-ui-provider'; import { Button, ButtonText } from './components/ui/button'; import { VStack } from './components/ui/vstack'; import { HStack } from './components/ui/hstack'; import { Heading } from './components/ui/heading'; import { Icon, CheckCircleIcon, StarIcon, ArrowRightIcon } from './components/ui/icon'; function App() { const isDarkMode = useColorScheme() === 'dark'; return ( <GluestackUIProvider mode={isDarkMode ? 'dark' : 'light'}> <View className="flex-1 items-center justify-center bg-white dark:bg-gray-900"> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} /> <VStack space="xl" className="px-6 items-center"> <VStack space="md" className="items-center"> <Icon as={StarIcon} size="xl" className="text-yellow-500" /> <Heading size="2xl" className="text-blue-600 dark:text-blue-400 text-center"> ZuraStackNative </Heading> <Text className="text-lg text-gray-600 dark:text-gray-300 text-center"> React Native + NativeWind + Gluestack UI </Text> </VStack> <VStack space="md" className="w-full max-w-sm"> <HStack space="sm" className="bg-green-50 dark:bg-green-900/20 p-3 rounded-lg items-center"> <Icon as={CheckCircleIcon} size="sm" className="text-green-600" /> <Text className="text-green-800 dark:text-green-200 flex-1"> NativeWind CSS-in-JS working </Text> </HStack> <HStack space="sm" className="bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg items-center"> <Icon as={CheckCircleIcon} size="sm" className="text-blue-600" /> <Text className="text-blue-800 dark:text-blue-200 flex-1"> Gluestack UI components ready </Text> </HStack> </VStack> <VStack space="md" className="w-full max-w-sm"> <Button size="lg" variant="solid" action="primary" className="bg-blue-600 hover:bg-blue-700" > <ButtonText className="font-semibold">๐Ÿš€ Launch App</ButtonText> <Icon as={ArrowRightIcon} size="sm" className="ml-2" /> </Button> <Button size="md" variant="outline" action="secondary" className="border-gray-300 dark:border-gray-600" > <ButtonText className="text-gray-700 dark:text-gray-300"> View Documentation </ButtonText> </Button> </VStack> </VStack> </View> </GluestackUIProvider> ); } export default App;`; await fs.writeFile(path.join(this.config.projectPath, 'App.tsx'), appContent); } async installDependencies() { this.installer = new DependencyInstaller(this.config); await this.installer.installAll(); } async generateConfigFiles() { this.configGenerator = new ConfigGenerator(this.config); await this.configGenerator.generateAll(); } async generateSourceCode() { this.fileGenerator = new FileGenerator(this.config); await this.fileGenerator.generateAll(); } async postInstallationSetup() { this.startSpinner('Running post-installation setup...'); // iOS setup try { execSync('cd ios && pod install', { cwd: this.config.projectPath, stdio: this.config.verbose ? 'inherit' : 'pipe' }); } catch (error) { console.log(chalk.yellow('โš ๏ธ iOS pod install failed (this is normal if you\'re not on macOS)')); } // Git initialization try { execSync('git init', { cwd: this.config.projectPath, stdio: this.config.verbose ? 'inherit' : 'pipe' }); execSync('git add .', { cwd: this.config.projectPath, stdio: this.config.verbose ? 'inherit' : 'pipe' }); execSync('git commit -m "Initial commit: React Native project setup"', { cwd: this.config.projectPath, stdio: this.config.verbose ? 'inherit' : 'pipe' }); } catch (error) { console.log(chalk.yellow('โš ๏ธ Git initialization failed (this is normal if git is not configured)')); } this.stopSpinner(); } showSuccessMessage() { console.log(chalk.green('\n๐ŸŽ‰ React Native project created successfully!')); console.log(chalk.blue('\n๐Ÿ“‹ Next steps:')); console.log(chalk.gray(` cd ${this.config.projectName}`)); console.log(chalk.gray(' npm start')); console.log(chalk.gray(' npm run ios # For iOS')); console.log(chalk.gray(' npm run android # For Android')); console.log(chalk.blue('\n๐Ÿ“š Documentation:')); console.log(chalk.gray(' โ€ข React Native: https://reactnative.dev/')); } startSpinner(text) { if (!this.config.verbose) { this.spinner = ora(text).start(); } else { console.log(chalk.blue(`๐Ÿ”„ ${text}`)); } } stopSpinner() { if (this.spinner) { this.spinner.succeed(); this.spinner = null; } } async fixSVGCompatibility() { this.stopSpinner(); console.log(chalk.yellow('\n๐Ÿ”ง Fixing SVG compatibility for React Native 0.80.1...')); try { // Update react-native-svg to latest version for React Native 0.80.1 compatibility execSync('npm install react-native-svg@latest', { stdio: 'inherit', cwd: this.config.projectPath }); console.log(chalk.green('โœ… SVG compatibility fixed!')); } catch (error) { console.log(chalk.yellow('โš ๏ธ SVG update failed, but this is not critical:'), error.message); } } /** * Install Zustand for state management */ async installZustand() { this.startSpinner('Installing Zustand...'); try { const execSync = require('child_process').execSync; execSync('npm install zustand@^5.0.6', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); this.stopSpinner(); } catch (error) { this.stopSpinner(); // Try with legacy peer deps if normal install fails try { console.log(chalk.yellow('โš ๏ธ Retrying Zustand installation with legacy peer deps...')); execSync('npm install zustand@^5.0.6 --legacy-peer-deps', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); } catch (retryError) { throw new Error('Failed to install Zustand: ' + retryError.message); } } } /** * Create an industry-level folder structure with placeholder files, including a sample Zustand store */ async createFolderStructure() { const fs = require('fs-extra'); const path = require('path'); const base = this.config.projectPath; const src = p => path.join(base, 'src', p); const folders = [ 'assets/fonts', 'assets/images', 'assets/icons', 'src/api', 'src/components/ui', 'src/components/common', 'src/constants', 'src/features/auth', 'src/features/home', 'src/hooks', 'src/navigation', 'src/redux', 'src/screens', 'src/services', 'src/store', 'src/theme', 'src/types', 'src/utils', ]; for (const folder of folders) { await fs.ensureDir(path.join(base, folder)); } // Placeholders for key files const placeholders = [ { file: src('store/useAppStore.ts'), text: `// Example Zustand store\nimport { create } from 'zustand';\n\ntype AppState = {\n count: number;\n increment: () => void;\n decrement: () => void;\n};\n\nexport const useAppStore = create<AppState>((set) => ({\n count: 0,\n increment: () => set((state) => ({ count: state.count + 1 })),\n decrement: () => set((state) => ({ count: state.count - 1 })),\n}));\n` }, { file: src('api/apiClient.ts'), text: '// API client setup (e.g., axios instance)\n' }, { file: src('api/endpoints.ts'), text: '// API endpoints\n' }, { file: src('components/common/README.md'), text: '# Common reusable components\n' }, { file: src('constants/colors.ts'), text: '// Color constants\n' }, { file: src('constants/strings.ts'), text: '// String constants\n' }, { file: src('constants/config.ts'), text: '// App config\n' }, { file: src('features/auth/AuthScreen.tsx'), text: '// Auth screen\n' }, { file: src('features/auth/authSlice.ts'), text: '// Auth Redux slice\n' }, { file: src('features/auth/authApi.ts'), text: '// Auth API\n' }, { file: src('features/home/HomeScreen.tsx'), text: '// Home screen\n' }, { file: src('features/home/homeSlice.ts'), text: '// Home Redux slice\n' }, { file: src('features/home/homeApi.ts'), text: '// Home API\n' }, { file: src('hooks/useAuth.ts'), text: '// useAuth custom hook\n' }, { file: src('hooks/useTheme.ts'), text: '// useTheme custom hook\n' }, { file: src('navigation/AppNavigator.tsx'), text: '// App navigation setup\n' }, { file: src('navigation/RootStack.tsx'), text: '// Root stack navigator\n' }, { file: src('redux/store.ts'), text: '// Redux store setup\n' }, { file: src('redux/rootReducer.ts'), text: '// Root reducer\n' }, { file: src('screens/SplashScreen.tsx'), text: '// Splash screen\n' }, { file: src('screens/NotFoundScreen.tsx'), text: '// Not found screen\n' }, { file: src('services/analytics.ts'), text: '// Analytics service\n' }, { file: src('services/crashlytics.ts'), text: '// Crashlytics service\n' }, { file: src('theme/index.ts'), text: '// Theme entry\n' }, { file: src('theme/colors.ts'), text: '// Theme colors\n' }, { file: src('theme/typography.ts'), text: '// Typography\n' }, { file: src('types/navigation.d.ts'), text: '// Navigation types\n' }, { file: src('types/api.d.ts'), text: '// API types\n' }, { file: src('utils/helpers.ts'), text: '// Helper functions\n' }, { file: src('utils/validators.ts'), text: '// Validation functions\n' }, ]; for (const { file, text } of placeholders) { await fs.ensureFile(file); await fs.writeFile(file, text); } // Add README to assets await fs.writeFile(path.join(base, 'assets/README.md'), '# Place your images, fonts, and icons here.\n'); } /** * Install React Navigation and its dependencies */ async installReactNavigation() { this.startSpinner('Installing React Navigation...'); try { const execSync = require('child_process').execSync; // Core navigation with exact versions execSync('npm install @react-navigation/native@^7.1.14 @react-navigation/native-stack@^7.3.21', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); // Peer dependencies with exact versions execSync('npm install react-native-screens@^4.13.1 react-native-safe-area-context@^5.5.2', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); this.stopSpinner(); } catch (error) { this.stopSpinner(); // Try with legacy peer deps if normal install fails try { console.log(chalk.yellow('โš ๏ธ Retrying React Navigation installation with legacy peer deps...')); execSync('npm install @react-navigation/native@^7.1.14 @react-navigation/native-stack@^7.3.21 --legacy-peer-deps', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); execSync('npm install react-native-screens@^4.13.1 react-native-safe-area-context@^5.5.2 --legacy-peer-deps', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); } catch (retryError) { throw new Error('Failed to install React Navigation: ' + retryError.message); } } } /** * Install Lucide icons for use in UI */ async installLucideIcons() { this.startSpinner('Installing Lucide icons...'); try { const execSync = require('child_process').execSync; execSync('npm install lucide-react-native@^0.525.0', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); this.stopSpinner(); } catch (error) { this.stopSpinner(); // Try with legacy peer deps if normal install fails try { console.log(chalk.yellow('โš ๏ธ Retrying Lucide icons installation with legacy peer deps...')); execSync('npm install lucide-react-native@^0.525.0 --legacy-peer-deps', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath, }); } catch (retryError) { throw new Error('Failed to install Lucide icons: ' + retryError.message); } } } /** * Set up navigation files, move App.tsx to src, update index.js, and create Home/Profile screens */ async setupNavigationFiles() { const fs = require('fs-extra'); const path = require('path'); const base = this.config.projectPath; const srcDir = path.join(base, 'src'); await fs.ensureDir(srcDir); // App.tsx with correct imports const appContent = `import "../global.css"; import React from "react"; import { NavigationContainer } from '@react-navigation/native'; import { GluestackUIProvider } from "../components/ui/gluestack-ui-provider"; import RootNavigator from "./navigation/RootNavigator"; import { useColorScheme } from "react-native"; export default function App() { const isDarkMode = useColorScheme() === 'dark'; return ( <GluestackUIProvider mode={isDarkMode ? 'dark' : 'light'}> <NavigationContainer> <RootNavigator /> </NavigationContainer> </GluestackUIProvider> ); } `; await fs.writeFile(path.join(srcDir, 'App.tsx'), appContent); // index.js with correct import const indexContent = `import { AppRegistry } from 'react-native'; import App from './src/App'; import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => App); `; await fs.writeFile(path.join(base, 'index.js'), indexContent); // navigation/RootNavigator.tsx with Lucide icons and correct imports const navDir = path.join(srcDir, 'navigation'); await fs.ensureDir(navDir); const rootNavContent = `import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import HomeScreen from '../screens/HomeScreen'; import ProfileScreen from '../screens/ProfileScreen'; export type RootStackParamList = { Home: undefined; Profile: undefined; }; const Stack = createNativeStackNavigator<RootStackParamList>(); export default function RootNavigator() { return ( <Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: '#0f172a', // dark background }, headerTintColor: '#ffffff', // white text headerTitleStyle: { fontWeight: 'bold', }, }} > <Stack.Screen name="Home" component={HomeScreen} options={{ headerTitle: 'ZuraStackNative', headerRight: () => <ProfileIcon />, }} /> <Stack.Screen name="Profile" component={ProfileScreen} options={{ headerTitle: 'Profile', }} /> </Stack.Navigator> ); } // Profile icon button for header import { TouchableOpacity } from 'react-native'; import { User } from 'lucide-react-native'; import { useNavigation } from '@react-navigation/native'; function ProfileIcon() { const navigation = useNavigation(); return ( <TouchableOpacity onPress={() => navigation.navigate('Profile' as never)} style={{ marginRight: 8 }} > <User color="#ffffff" size={24} /> </TouchableOpacity> ); } `; await fs.writeFile(path.join(navDir, 'RootNavigator.tsx'), rootNavContent); // screens/HomeScreen.tsx with Lucide icons and correct imports const screensDir = path.join(srcDir, 'screens'); await fs.ensureDir(screensDir); const homeScreenContent = `import React from 'react'; import { View, Text, ScrollView } from 'react-native'; import { VStack } from '../../components/ui/vstack'; import { Heading } from '../../components/ui/heading'; import { Button, ButtonText } from '../../components/ui/button'; import { Box, Waves, Folder, Compass, Brackets, ArrowRight, Package, Squirrel, } from 'lucide-react-native'; export default function HomeScreen() { return ( <ScrollView className="flex-1 bg-gray-950" contentContainerStyle={{ flexGrow: 1 }} > <VStack space="xl" className="flex-1 items-center justify-center py-12"> <Box color="#ef4444" size={40} /> <Heading size="3xl" className="text-white font-bold text-center mt-2"> ZuraStack-Native </Heading> <Text className="text-lg text-gray-300 text-center max-w-xl mt-2"> React Native CLI, NativeWind, GlueStack, Zustand, Class Name Helper, and industry-grade folder structure with Navigator and Lucide Icons setup. </Text> <Button size="lg" variant="solid" action="primary" className="bg-gray-800 hover:bg-gray-700 mt-6 w-48 flex-row items-center justify-center" > <ButtonText className="font-semibold text-white"> Get Started </ButtonText> <ArrowRight color="#fff" size={20} style={{ marginLeft: 8 }} /> </Button> {/* Feature Grid */} <View className="w-full max-w-2xl mt-10"> {/* Row 1: 2 cards, 6 cols each */} <View className="flex-row gap-4"> <FeatureCard icon={<Package color="#fff" size={32} />} label="React Native CLI" col={6} /> <FeatureCard icon={<Waves color="#fff" size={32} />} label="NativeWind" col={6} /> </View> {/* Row 2: 3 cards, 4 cols each */} <View className="flex-row gap-4 mt-4"> <FeatureCard icon={<Squirrel color="#fff" size={32} />} label="Zustand" col={4} /> <FeatureCard icon={<Brackets color="#fff" size={32} />} label="Class Name Helper" col={4} /> <FeatureCard icon={<Folder color="#fff" size={32} />} label="Folder Structure" col={4} /> </View> {/* Row 3: 1 card, 12 cols */} <View className="flex-row mt-4"> <FeatureCard icon={<Compass color="#fff" size={32} />} label="Navigator and Lucide Icons" col={12} /> </View> </View> </VStack> </ScrollView> ); } // Helper to calculate width based on 12-column grid function getColWidth(col: number) { return (col / 12) * 100; } function FeatureCard({ icon, label, col, }: { icon: React.ReactNode; label: string; col: number; }) { return ( <View className="bg-gray-900 rounded-xl p-6 items-center justify-center" style={{ flex: 1, maxWidth: \`\${getColWidth(col)}%\`, minWidth: 100, }} > {icon} <Text className="text-white text-base font-medium text-center mt-4"> {label} </Text> </View> ); } `; await fs.writeFile(path.join(screensDir, 'HomeScreen.tsx'), homeScreenContent); // screens/ProfileScreen.tsx with Lucide icons and correct imports const profileScreenContent = `import React from 'react'; import { View, Text } from 'react-native'; import { VStack } from '../../components/ui/vstack'; import { Heading } from '../../components/ui/heading'; import { User } from 'lucide-react-native'; export default function ProfileScreen() { return ( <View className="flex-1 items-center justify-center bg-gray-950"> <VStack space="xl" className="items-center px-6"> <User color="#ef4444" size={64} /> <Heading size="3xl" className="text-white font-bold text-center"> Profile </Heading> <Text className="text-lg text-gray-300 text-center max-w-md mt-4"> Welcome to your profile! This is where you can manage your account settings and preferences. </Text> <View className="bg-gray-900 rounded-xl p-6 w-full max-w-sm mt-8"> <Text className="text-white text-base font-medium text-center"> Profile features coming soon... </Text> </View> </VStack> </View> ); } `; await fs.writeFile(path.join(screensDir, 'ProfileScreen.tsx'), profileScreenContent); } /** * Perform final compatibility check and cleanup */ async performFinalCompatibilityCheck() { this.startSpinner('Performing final compatibility check...'); try { // Step 1: Verify all React versions are aligned console.log(chalk.yellow('๐Ÿ” Final React version verification...')); const packageJsonPath = path.join(this.config.projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); // Ensure all React packages have the same version const targetVersion = '19.1.0'; packageJson.dependencies = { ...packageJson.dependencies, 'react': targetVersion, 'react-dom': targetVersion, }; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); // Step 2: Clean install to ensure consistency console.log(chalk.yellow('๐Ÿ“ฆ Final dependency cleanup...')); execSync('npm install --legacy-peer-deps', { stdio: this.config.verbose ? 'inherit' : 'pipe', cwd: this.config.projectPath }); // Step 3: Clear Metro cache console.log(chalk.yellow('๐Ÿงน Clearing Metro cache...')); try { execSync('npx react-native start --reset-cache', { stdio: 'pipe', cwd: this.config.projectPath, timeout: 10000 // 10 second timeout }); } catch (error) { // This is expected to timeout, just clearing cache console.log(chalk.gray('Metro cache cleared')); } console.log(chalk.green('โœ… Final compatibility check completed!')); } catch (error) { console.log(chalk.yellow('โš ๏ธ Final compatibility check failed, but project should still work:'), error.message); } this.stopSpinner(); } } module.exports = { ProjectGenerator };