git-bro
Version:
CLI tool that lets you download specific folders from GitHub repositories without cloning the entire repo.
1,679 lines (1,412 loc) • 42.8 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const inquirer = require('inquirer');
const chalk = require('chalk');
const ora = require('ora');
const { execSync } = require('child_process');
/**
* Creates a file with content
* @param {string} filePath - Path to the file
* @param {string} content - Content of the file
*/
const createFileBackend = (filePath, content = '') => {
fs.ensureFileSync(filePath);
fs.writeFileSync(filePath, content);
};
/**
* Creates a directory
* @param {string} dirPath - Path to the directory
*/
const createBackendDirectory = (dirPath) => {
fs.ensureDirSync(dirPath);
};
/**
* Creates a backend project structure
* @param {string} baseDir - Base directory for the project
* @returns {Promise<void>}
*/
const createBackendStructure = async (baseDir) => {
const spinner = ora('Creating backend project structure').start();
try {
// Create main directories
createBackendDirectory(path.join(baseDir, 'src'));
createBackendDirectory(path.join(baseDir, 'src/config'));
createBackendDirectory(path.join(baseDir, 'src/controllers'));
createBackendDirectory(path.join(baseDir, 'src/models'));
createBackendDirectory(path.join(baseDir, 'src/routes'));
createBackendDirectory(path.join(baseDir, 'src/middlewares'));
createBackendDirectory(path.join(baseDir, 'src/services'));
createBackendDirectory(path.join(baseDir, 'src/utils'));
createBackendDirectory(path.join(baseDir, 'src/database'));
createBackendDirectory(path.join(baseDir, 'tests'));
createBackendDirectory(path.join(baseDir, 'public'));
createBackendDirectory(path.join(baseDir, 'logs'));
createBackendDirectory(path.join(baseDir, 'uploads'));
// Create config files
createFileBackend(path.join(baseDir, 'src/config/db.js'), `
// Database configuration
module.exports = {
url: process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp',
options: {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
}
};
`);
createFile(path.join(baseDir, 'src/config/env.js'), `
// Environment configuration
module.exports = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '1d'
};
`);
// Create controller files
createFile(path.join(baseDir, 'src/controllers/userController.js'), `
const User = require('../models/userModel');
const userService = require('../services/userService');
const { successResponse, errorResponse } = require('../utils/responseUtil');
// Get all users
exports.getUsers = async (req, res) => {
try {
const users = await userService.getAllUsers();
return successResponse(res, 200, 'Users retrieved successfully', users);
} catch (error) {
return errorResponse(res, 500, 'Error retrieving users', error);
}
};
// Get user by ID
exports.getUserById = async (req, res) => {
try {
const user = await userService.getUserById(req.params.id);
if (!user) {
return errorResponse(res, 404, 'User not found');
}
return successResponse(res, 200, 'User retrieved successfully', user);
} catch (error) {
return errorResponse(res, 500, 'Error retrieving user', error);
}
};
// Create new user
exports.createUser = async (req, res) => {
try {
const user = await userService.createUser(req.body);
return successResponse(res, 201, 'User created successfully', user);
} catch (error) {
return errorResponse(res, 500, 'Error creating user', error);
}
};
// Update user
exports.updateUser = async (req, res) => {
try {
const user = await userService.updateUser(req.params.id, req.body);
if (!user) {
return errorResponse(res, 404, 'User not found');
}
return successResponse(res, 200, 'User updated successfully', user);
} catch (error) {
return errorResponse(res, 500, 'Error updating user', error);
}
};
// Delete user
exports.deleteUser = async (req, res) => {
try {
const user = await userService.deleteUser(req.params.id);
if (!user) {
return errorResponse(res, 404, 'User not found');
}
return successResponse(res, 200, 'User deleted successfully');
} catch (error) {
return errorResponse(res, 500, 'Error deleting user', error);
}
};
`);
// Create model files
createFile(path.join(baseDir, 'src/models/userModel.js'), `
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
lowercase: true,
trim: true
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: 6,
select: false
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
active: {
type: Boolean,
default: true
}
}, {
timestamps: true
});
// Hash password before saving
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// Compare password method
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
const User = mongoose.model('User', userSchema);
module.exports = User;
`);
// Create route files
createFile(path.join(baseDir, 'src/routes/userRoutes.js'), `
const express = require('express');
const userController = require('../controllers/userController');
const { authenticate, authorize } = require('../middlewares/authMiddleware');
const router = express.Router();
router.get('/', authenticate, authorize('admin'), userController.getUsers);
router.get('/:id', authenticate, userController.getUserById);
router.post('/', userController.createUser);
router.put('/:id', authenticate, userController.updateUser);
router.delete('/:id', authenticate, authorize('admin'), userController.deleteUser);
module.exports = router;
`);
// Create middleware files
createFile(path.join(baseDir, 'src/middlewares/authMiddleware.js'), `
const jwt = require('jsonwebtoken');
const { jwtSecret } = require('../config/env');
const User = require('../models/userModel');
const { errorResponse } = require('../utils/responseUtil');
// Authenticate user
exports.authenticate = async (req, res, next) => {
try {
// Get token from header
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return errorResponse(res, 401, 'Authentication required');
}
// Verify token
const decoded = jwt.verify(token, jwtSecret);
// Find user
const user = await User.findById(decoded.id);
if (!user || !user.active) {
return errorResponse(res, 401, 'User not found or inactive');
}
// Attach user to request
req.user = user;
next();
} catch (error) {
return errorResponse(res, 401, 'Invalid token', error);
}
};
// Authorize user roles
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return errorResponse(res, 403, 'Not authorized to access this resource');
}
next();
};
};
`);
createFile(path.join(baseDir, 'src/middlewares/errorMiddleware.js'), `
const { errorResponse } = require('../utils/responseUtil');
// Not found middleware
exports.notFound = (req, res, next) => {
errorResponse(res, 404, \`Resource not found - \${req.originalUrl}\`);
};
// Error handler middleware
exports.errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
// Log error
console.error(err.stack);
errorResponse(
res,
statusCode,
err.message || 'Internal Server Error',
process.env.NODE_ENV === 'production' ? null : err.stack
);
};
`);
// Create service files
createFile(path.join(baseDir, 'src/services/userService.js'), `
const User = require('../models/userModel');
// Get all users
exports.getAllUsers = async () => {
return await User.find({ active: true }).select('-password');
};
// Get user by ID
exports.getUserById = async (id) => {
return await User.findById(id).select('-password');
};
// Create new user
exports.createUser = async (userData) => {
return await User.create(userData);
};
// Update user
exports.updateUser = async (id, userData) => {
return await User.findByIdAndUpdate(
id,
userData,
{ new: true, runValidators: true }
).select('-password');
};
// Delete user
exports.deleteUser = async (id) => {
return await User.findByIdAndUpdate(
id,
{ active: false },
{ new: true }
);
};
`);
// Create utility files
createFile(path.join(baseDir, 'src/utils/responseUtil.js'), `
// Success response
exports.successResponse = (res, statusCode = 200, message = 'Success', data = null) => {
const response = {
success: true,
message
};
if (data !== null) {
response.data = data;
}
return res.status(statusCode).json(response);
};
// Error response
exports.errorResponse = (res, statusCode = 500, message = 'Error', error = null) => {
const response = {
success: false,
message
};
if (error !== null && process.env.NODE_ENV !== 'production') {
response.error = error.toString();
}
return res.status(statusCode).json(response);
};
`);
// Create database files
createFile(path.join(baseDir, 'src/database/index.js'), `
const mongoose = require('mongoose');
const config = require('../config/db');
// Connect to MongoDB
const connectDB = async () => {
try {
const conn = await mongoose.connect(config.url, config.options);
console.log(\`MongoDB Connected: \${conn.connection.host}\`);
return conn;
} catch (error) {
console.error(\`Error connecting to MongoDB: \${error.message}\`);
process.exit(1);
}
};
module.exports = connectDB;
`);
// Create main application files
createFile(path.join(baseDir, 'src/app.js'), `
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const helmet = require('helmet');
const compression = require('compression');
const { notFound, errorHandler } = require('./middlewares/errorMiddleware');
const userRoutes = require('./routes/userRoutes');
// Initialize express app
const app = express();
// Middleware
app.use(helmet()); // Security headers
app.use(compression()); // Compress responses
app.use(cors()); // Enable CORS
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
app.use(morgan('dev')); // HTTP request logger
// Static files
app.use(express.static('public'));
// Routes
app.use('/api/users', userRoutes);
// Welcome route
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the API' });
});
// Error handling middleware
app.use(notFound);
app.use(errorHandler);
module.exports = app;
`);
createFile(path.join(baseDir, 'src/server.js'), `
const app = require('./app');
const connectDB = require('./database');
const { port, nodeEnv } = require('./config/env');
// Connect to database
connectDB();
// Start server
const server = app.listen(port, () => {
console.log(\`Server running in \${nodeEnv} mode on port \${port}\`);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
console.error(\`Unhandled Rejection: \${err.message}\`);
// Close server & exit process
server.close(() => process.exit(1));
});
module.exports = server;
`);
// Create test files
createFile(path.join(baseDir, 'tests/user.test.js'), `
const request = require('supertest');
const mongoose = require('mongoose');
const app = require('../src/app');
const User = require('../src/models/userModel');
describe('User API', () => {
beforeAll(async () => {
// Connect to test database
await mongoose.connect(process.env.MONGODB_URI_TEST || 'mongodb://localhost:27017/testdb');
});
afterAll(async () => {
// Disconnect from test database
await mongoose.connection.close();
});
beforeEach(async () => {
// Clear users collection before each test
await User.deleteMany({});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'Test User',
email: 'test@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('_id');
expect(response.body.data.name).toBe(userData.name);
expect(response.body.data.email).toBe(userData.email);
});
});
});
`);
// Create configuration files
createFile(path.join(baseDir, '.env'), `
# Server Configuration
PORT=3000
NODE_ENV=development
# Database Configuration
MONGODB_URI=mongodb://localhost:27017/myapp
# JWT Configuration
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=1d
# Logging
LOG_LEVEL=info
`);
createFile(path.join(baseDir, '.gitignore'), `
# Dependencies
node_modules/
npm-debug.log
yarn-error.log
yarn-debug.log
package-lock.json
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build files
dist/
build/
# Logs
logs/
*.log
# Testing
coverage/
# OS files
.DS_Store
Thumbs.db
# IDE files
.idea/
.vscode/
*.sublime-project
*.sublime-workspace
`);
createFile(path.join(baseDir, 'package.json'), `
{
"name": "backend-app",
"version": "1.0.0",
"description": "Backend application",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"test": "jest --detectOpenHandles",
"lint": "eslint ."
},
"dependencies": {
"bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"helmet": "^6.0.1",
"jsonwebtoken": "^9.0.0",
"mongoose": "^7.0.3",
"morgan": "^1.10.0"
},
"devDependencies": {
"eslint": "^8.36.0",
"jest": "^29.5.0",
"nodemon": "^2.0.22",
"supertest": "^6.3.3"
},
"engines": {
"node": ">=14.0.0"
}
}
`);
createFile(path.join(baseDir, 'nodemon.json'), `
{
"watch": ["src"],
"ext": "js,json",
"ignore": ["node_modules", "logs"],
"env": {
"NODE_ENV": "development"
}
}
`);
createFile(path.join(baseDir, 'README.md'), `
# Backend Application
A Node.js backend application with Express and MongoDB.
## Features
- RESTful API architecture
- User authentication and authorization
- MongoDB database integration
- Error handling middleware
- Request validation
- Logging
- Testing with Jest
## Getting Started
### Prerequisites
- Node.js (v14 or higher)
- MongoDB
### Installation
1. Clone the repository
2. Install dependencies:
\`\`\`
npm install
\`\`\`
3. Create a \`.env\` file based on the provided example
4. Start the development server:
\`\`\`
npm run dev
\`\`\`
## API Endpoints
### Users
- \`GET /api/users\` - Get all users (admin only)
- \`GET /api/users/:id\` - Get user by ID
- \`POST /api/users\` - Create a new user
- \`PUT /api/users/:id\` - Update user
- \`DELETE /api/users/:id\` - Delete user (admin only)
## Testing
Run tests with:
\`\`\`
npm test
\`\`\`
## License
MIT
`);
createFile(path.join(baseDir, 'public/README.md'), `
# Public Directory
This directory contains static files that are served by the Express application.
You can place HTML, CSS, JavaScript, images, and other static assets here.
`);
createFile(path.join(baseDir, 'logs/error.log'), '');
spinner.succeed('Backend project structure created successfully');
} catch (error) {
spinner.fail(`Failed to create backend project structure: ${error.message}`);
throw error;
}
};
/**
* Creates a frontend project structure
* @param {string} baseDir - Base directory for the project
* @returns {Promise<void>}
*/
// Helper function to create directory if it doesn't exist
const createDirectory = (dir) => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
};
// Helper function to create file with content
const createFile = (filePath, content) => {
fs.writeFileSync(filePath, content.trim());
};
// Main function to create frontend structure using Vite
const createFrontendStructure = async (baseDir, projectName = 'frontend') => {
const spinner = ora('Creating frontend project with Vite').start();
try {
// Create the project directory if it doesn't exist
createDirectory(baseDir);
const fullPath = path.join(process.cwd(), baseDir);
// Initialize a new Vite project
spinner.text = 'Initializing Vite project...';
try {
execSync(`npm create vite@latest ${projectName} -- --template react`, {
cwd: process.cwd(),
stdio: 'ignore'
});
// Change to the newly created project directory
process.chdir(path.join(process.cwd(), projectName));
} catch (error) {
spinner.fail(`Failed to initialize Vite project: ${error.message}`);
throw error;
}
// Path to the new project
const projectPath = path.join(process.cwd());
// Create main directories
spinner.text = 'Creating project structure...';
createDirectory(path.join(projectPath, 'src/api'));
createDirectory(path.join(projectPath, 'src/assets/images'));
createDirectory(path.join(projectPath, 'src/assets/icons'));
createDirectory(path.join(projectPath, 'src/components'));
createDirectory(path.join(projectPath, 'src/context'));
createDirectory(path.join(projectPath, 'src/hooks'));
createDirectory(path.join(projectPath, 'src/layouts'));
createDirectory(path.join(projectPath, 'src/pages'));
createDirectory(path.join(projectPath, 'src/routes'));
createDirectory(path.join(projectPath, 'src/styles'));
createDirectory(path.join(projectPath, 'src/utils'));
// Create API files
createFile(path.join(projectPath, 'src/api/axiosClient.js'), `
import axios from 'axios';
const baseURL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
const axiosClient = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json'
}
});
// Request interceptor
axiosClient.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = \`Bearer \${token}\`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor
axiosClient.interceptors.response.use(
(response) => response,
(error) => {
// Handle unauthorized errors
if (error.response && error.response.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default axiosClient;
`);
createFile(path.join(projectPath, 'src/api/userApi.js'), `
import axiosClient from './axiosClient';
const userApi = {
// Get all users
getAll: () => {
return axiosClient.get('/users');
},
// Get user by ID
getById: (id) => {
return axiosClient.get(\`/users/\${id}\`);
},
// Create new user
create: (data) => {
return axiosClient.post('/users', data);
},
// Update user
update: (id, data) => {
return axiosClient.put(\`/users/\${id}\`, data);
},
// Delete user
delete: (id) => {
return axiosClient.delete(\`/users/\${id}\`);
},
// Login
login: (credentials) => {
return axiosClient.post('/auth/login', credentials);
},
// Register
register: (userData) => {
return axiosClient.post('/auth/register', userData);
}
};
export default userApi;
`);
// Create component files
createFile(path.join(projectPath, 'src/components/Navbar.jsx'), `
import { Link } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
const Navbar = () => {
const { user, logout } = useAuth();
return (
<nav className="bg-gray-800 text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<Link to="/" className="text-xl font-bold">
My App
</Link>
<div className="flex space-x-4">
<Link to="/" className="hover:text-gray-300">
Home
</Link>
{user ? (
<>
<Link to="/profile" className="hover:text-gray-300">
Profile
</Link>
<button
onClick={logout}
className="hover:text-gray-300"
>
Logout
</button>
</>
) : (
<>
<Link to="/login" className="hover:text-gray-300">
Login
</Link>
<Link to="/register" className="hover:text-gray-300">
Register
</Link>
</>
)}
</div>
</div>
</nav>
);
};
export default Navbar;
`);
createFile(path.join(projectPath, 'src/components/Footer.jsx'), `
const Footer = () => {
return (
<footer className="bg-gray-800 text-white p-4 mt-auto">
<div className="container mx-auto text-center">
<p>© {new Date().getFullYear()} My App. All rights reserved.</p>
</div>
</footer>
);
};
export default Footer;
`);
createFile(path.join(projectPath, 'src/components/Button.jsx'), `
const Button = ({
children,
type = 'button',
variant = 'primary',
size = 'md',
className = '',
disabled = false,
onClick,
...props
}) => {
const baseClasses = 'font-medium rounded focus:outline-none transition-colors';
const variantClasses = {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-600 hover:bg-gray-700 text-white',
success: 'bg-green-600 hover:bg-green-700 text-white',
danger: 'bg-red-600 hover:bg-red-700 text-white',
outline: 'bg-transparent border border-blue-600 text-blue-600 hover:bg-blue-50'
};
const sizeClasses = {
sm: 'py-1 px-2 text-sm',
md: 'py-2 px-4',
lg: 'py-3 px-6 text-lg'
};
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer';
const buttonClasses = \`\${baseClasses} \${variantClasses[variant]} \${sizeClasses[size]} \${disabledClasses} \${className}\`;
return (
<button
type={type}
className={buttonClasses}
disabled={disabled}
onClick={onClick}
{...props}
>
{children}
</button>
);
};
export default Button;
`);
// Create context files
createFile(path.join(projectPath, 'src/context/AuthContext.jsx'), `
import { createContext, useState, useEffect } from 'react';
import userApi from '../api/userApi';
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Check if user is logged in
const token = localStorage.getItem('token');
const storedUser = localStorage.getItem('user');
if (token && storedUser) {
setUser(JSON.parse(storedUser));
}
setLoading(false);
}, []);
const login = async (email, password) => {
try {
setLoading(true);
setError(null);
const response = await userApi.login({ email, password });
const { token, user } = response.data.data;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
setUser(user);
return user;
} catch (error) {
setError(error.response?.data?.message || 'Login failed');
throw error;
} finally {
setLoading(false);
}
};
const register = async (userData) => {
try {
setLoading(true);
setError(null);
const response = await userApi.register(userData);
const { token, user } = response.data.data;
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));
setUser(user);
return user;
} catch (error) {
setError(error.response?.data?.message || 'Registration failed');
throw error;
} finally {
setLoading(false);
}
};
const logout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
setUser(null);
};
const updateUser = (updatedUser) => {
localStorage.setItem('user', JSON.stringify(updatedUser));
setUser(updatedUser);
};
return (
<AuthContext.Provider
value={{
user,
loading,
error,
login,
register,
logout,
updateUser
}}
>
{children}
</AuthContext.Provider>
);
};
`);
// Create hook files
createFile(path.join(projectPath, 'src/hooks/useAuth.js'), `
import { useContext } from 'react';
import { AuthContext } from '../context/AuthContext';
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
`);
// Create layout files
createFile(path.join(projectPath, 'src/layouts/MainLayout.jsx'), `
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
const MainLayout = ({ children }) => {
return (
<div className="flex flex-col min-h-screen">
<Navbar />
<main className="flex-grow container mx-auto px-4 py-8">
{children}
</main>
<Footer />
</div>
);
};
export default MainLayout;
`);
// Create page files
createFile(path.join(projectPath, 'src/pages/Home.jsx'), `
import MainLayout from '../layouts/MainLayout';
import { useAuth } from '../hooks/useAuth';
const Home = () => {
const { user } = useAuth();
return (
<MainLayout>
<div className="text-center">
<h1 className="text-3xl font-bold mb-4">Welcome to My App</h1>
{user ? (
<p className="mb-4">Hello, {user.name}! You are logged in.</p>
) : (
<p className="mb-4">Please log in to access all features.</p>
)}
<div className="max-w-2xl mx-auto">
<p className="mb-4">
This is a Vite React application with authentication, routing, and API integration.
</p>
<p>
Explore the features and functionality of this application.
</p>
</div>
</div>
</MainLayout>
);
};
export default Home;
`);
createFile(path.join(projectPath, 'src/pages/Login.jsx'), `
import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import MainLayout from '../layouts/MainLayout';
import Button from '../components/Button';
import { useAuth } from '../hooks/useAuth';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
if (!email || !password) {
setError('Please enter both email and password');
return;
}
try {
setLoading(true);
setError('');
await login(email, password);
navigate('/');
} catch (error) {
setError(error.response?.data?.message || 'Login failed');
} finally {
setLoading(false);
}
};
return (
<MainLayout>
<div className="max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-6 text-center">Login</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block mb-1">
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label htmlFor="password" className="block mb-1">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<Button
type="submit"
variant="primary"
className="w-full"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</Button>
</form>
<p className="mt-4 text-center">
Don't have an account?{' '}
<Link to="/register" className="text-blue-600 hover:underline">
Register
</Link>
</p>
</div>
</MainLayout>
);
};
export default Login;
`);
createFile(path.join(projectPath, 'src/pages/Register.jsx'), `
import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import MainLayout from '../layouts/MainLayout';
import Button from '../components/Button';
import { useAuth } from '../hooks/useAuth';
const Register = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const { register } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
if (!name || !email || !password || !confirmPassword) {
setError('Please fill in all fields');
return;
}
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
try {
setLoading(true);
setError('');
await register({ name, email, password });
navigate('/');
} catch (error) {
setError(error.response?.data?.message || 'Registration failed');
} finally {
setLoading(false);
}
};
return (
<MainLayout>
<div className="max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-6 text-center">Register</h1>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block mb-1">
Name
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label htmlFor="email" className="block mb-1">
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label htmlFor="password" className="block mb-1">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block mb-1">
Confirm Password
</label>
<input
type="password"
id="confirmPassword"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<Button
type="submit"
variant="primary"
className="w-full"
disabled={loading}
>
{loading ? 'Registering...' : 'Register'}
</Button>
</form>
<p className="mt-4 text-center">
Already have an account?{' '}
<Link to="/login" className="text-blue-600 hover:underline">
Login
</Link>
</p>
</div>
</MainLayout>
);
};
export default Register;
`);
// Create route files
createFile(path.join(projectPath, 'src/routes/AppRoutes.jsx'), `
import { Routes, Route, Navigate } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
// Pages
import Home from '../pages/Home';
import Login from '../pages/Login';
import Register from '../pages/Register';
// Protected route component
const ProtectedRoute = ({ children }) => {
const { user, loading } = useAuth();
if (loading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" />;
}
return children;
};
const AppRoutes = () => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* Protected routes */}
<Route
path="/profile"
element={
<ProtectedRoute>
<div>Profile Page</div>
</ProtectedRoute>
}
/>
{/* Catch all route */}
<Route path="*" element={<div>Page not found</div>} />
</Routes>
);
};
export default AppRoutes;
`);
// Create utility files
createFile(path.join(projectPath, 'src/utils/helpers.js'), `
/**
* Format date to a readable string
* @param {string|Date} date - Date to format
* @returns {string} - Formatted date string
*/
export const formatDate = (date) => {
const d = new Date(date);
return d.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
/**
* Truncate text to a specified length
* @param {string} text - Text to truncate
* @param {number} length - Maximum length
* @returns {string} - Truncated text
*/
export const truncateText = (text, length = 100) => {
if (!text) return '';
if (text.length <= length) return text;
return text.substring(0, length) + '...';
};
/**
* Format currency
* @param {number} amount - Amount to format
* @param {string} currency - Currency code
* @returns {string} - Formatted currency string
*/
export const formatCurrency = (amount, currency = 'USD') => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(amount);
};
/**
* Debounce function
* @param {Function} func - Function to debounce
* @param {number} wait - Wait time in milliseconds
* @returns {Function} - Debounced function
*/
export const debounce = (func, wait = 300) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
`);
// Create main application files
createFile(path.join(projectPath, 'src/App.jsx'), `
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import AppRoutes from './routes/AppRoutes';
import './styles/index.css';
function App() {
return (
<BrowserRouter>
<AuthProvider>
<AppRoutes />
</AuthProvider>
</BrowserRouter>
);
}
export default App;
`);
createFile(path.join(projectPath, 'src/main.jsx'), `
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './styles/index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
`);
// Create CSS file to include tailwind
createFile(path.join(projectPath, 'src/styles/index.css'), `
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
`);
// Create Tailwind configuration
createFile(path.join(projectPath, 'tailwind.config.js'), `
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
`);
createFile(path.join(projectPath, 'postcss.config.js'), `
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
`);
// Create .env file
createFile(path.join(projectPath, '.env'), `
VITE_API_URL=http://localhost:3000/api
`);
// Update the package.json to include additional dependencies
const packageJsonPath = path.join(projectPath, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
// Add additional dependencies
packageJson.dependencies = {
...packageJson.dependencies,
'axios': '^1.6.2',
'react-router-dom': '^6.20.0'
};
// Add Tailwind CSS as dev dependencies
packageJson.devDependencies = {
...packageJson.devDependencies,
'autoprefixer': '^10.4.16',
'postcss': '^8.4.31',
'tailwindcss': '^3.3.5'
};
// Write the updated package.json
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
// Update the README.md
createFile(path.join(projectPath, 'README.md'), `
# Vite React Frontend
A modern React frontend application built with Vite, featuring routing, authentication, and API integration.
## Features
- Fast development with Vite
- React Router for navigation
- Context API for state management
- Authentication with JWT
- Responsive design with Tailwind CSS
- Reusable components
- API integration with Axios
## Getting Started
### Prerequisites
- Node.js (v14 or higher)
### Installation
1. Clone the repository
2. Install dependencies:
\`\`\`
npm install
\`\`\`
3. Create a \`.env\` file based on the provided example
4. Start the development server:
\`\`\`
npm run dev
\`\`\`
## Available Scripts
- \`npm run dev\` - Start the development server
- \`npm run build\` - Build the app for production
- \`npm run lint\` - Run ESLint
- \`npm run preview\` - Preview the production build locally
## Folder Structure
- \`src/api\` - API clients and services
- \`src/assets\` - Static assets like images and icons
- \`src/components\` - Reusable UI components
- \`src/context\` - React Context providers
- \`src/hooks\` - Custom React hooks
- \`src/layouts\` - Page layout components
- \`src/pages\` - Page components
- \`src/routes\` - Routing configuration
- \`src/styles\` - Global styles and CSS
- \`src/utils\` - Utility functions
## License
MIT
`);
spinner.succeed('Frontend project structure created successfully');
// Go back to original directory
process.chdir('..');
return projectPath;
} catch (error) {
spinner.fail(`Failed to create frontend project structure: ${error.message}`);
throw error;
}
};
// Command handler for the generate command
const generateCommand = async (options) => {
try {
const projectName = options.name || 'my-app';
const projectPath = await createFrontendStructure('.', projectName);
console.log(`\n✅ Project created at: ${projectPath}`);
console.log('\nNext steps:');
console.log(`1. cd ${projectName}`);
console.log('2. npm install');
console.log('3. npm run dev');
} catch (error) {
console.error('Error generating project:', error);
process.exit(1);
}
};
module.exports = {
createFrontendStructure,
generateCommand
};
/**
* Creates a project structure based on user selection
* @returns {Promise<void>}
*/
const createProjectStructure = async () => {
try {
// Ask user for project type
const { projectType } = await inquirer.prompt([
{
type: 'list',
name: 'projectType',
message: 'What type of project structure do you want to create?',
choices: [
{ name: 'Backend (Node.js, Express, MongoDB)', value: 'backend' },
{ name: 'Frontend (React, React Router, Tailwind CSS)', value: 'frontend' }
]
}
]);
// Ask for output directory
const { outputDir } = await inquirer.prompt([
{
type: 'input',
name: 'outputDir',
message: 'Enter the output directory:',
default: projectType === 'backend' ? './backend' : './frontend'
}
]);
// Create the project structure
if (projectType === 'backend') {
await createBackendStructure(outputDir);
} else {
await createFrontendStructure(outputDir);
}
console.log(chalk.green.bold(`\n✅ Project structure created successfully in ${outputDir}`));
console.log(chalk.blue(`\nNext steps:`));
console.log(chalk.blue(`1. cd ${outputDir}`));
console.log(chalk.blue(`2. npm install`));
console.log(chalk.blue(`3. npm run dev (for backend as well frontend)`));
} catch (error) {
console.error(chalk.red(`\n❌ Error creating project structure: ${error.message}`));
throw error;
}
};
module.exports = {
createProjectStructure
};