zura-stack-native
Version:
A comprehensive React Native CLI project generator with production-ready setup
1,030 lines (905 loc) โข 35.8 kB
JavaScript
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 -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 = ` base;
components;
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 };