create-vite-pp
Version:
CLI to scaffold a Vite + React + Tailwind project with JS/TS option
380 lines (307 loc) • 9.41 kB
JavaScript
import prompts from "prompts";
import shell from "shelljs";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Simple console functions
const log = {
info: (msg) => console.log(`ℹ ${msg}`),
success: (msg) => console.log(`✓ ${msg}`),
error: (msg) => console.log(`✗ ${msg}`),
progress: (msg) => console.log(`⏳ ${msg}`),
};
// Check if command exists
function commandExists(command) {
return shell.which(command) !== null;
}
// Execute command with error handling
function executeCommand(command, options = {}) {
const { cwd = null } = options;
const execOptions = { silent: true };
if (cwd) execOptions.cwd = cwd;
const result = shell.exec(command, execOptions);
if (result.code !== 0) {
log.error(`Command failed: ${command}`);
if (result.stderr) log.error(`Error: ${result.stderr}`);
return false;
}
return true;
}
// Check prerequisites
function checkPrerequisites() {
log.info("Checking prerequisites...");
if (!commandExists("node")) {
log.error("Node.js is not installed. Please install Node.js first.");
process.exit(1);
}
if (!commandExists("npm")) {
log.error("npm is not installed. Please install npm first.");
process.exit(1);
}
log.success("Prerequisites check passed");
}
// Get project configuration
async function getProjectConfig() {
console.log("\n" + "=".repeat(50));
console.log(" 🚀 React + Vite + Tailwind Setup");
console.log("=".repeat(50) + "\n");
const questions = [
{
type: "text",
name: "projectName",
message: "Enter your project name:",
initial: "my-react-app",
validate: (value) => {
if (!value || value.trim() === "") {
return "Project name cannot be empty!";
}
const trimmed = value.trim();
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(trimmed)) {
return "Invalid project name format!";
}
if (fs.existsSync(trimmed)) {
return `Directory '${trimmed}' already exists!`;
}
return true;
},
},
{
type: "select",
name: "language",
message: "Select language:",
choices: [
{ title: "JavaScript", value: "javascript" },
{ title: "TypeScript", value: "typescript" },
],
initial: 0,
},
];
const response = await prompts(questions, {
onCancel: () => {
log.error("Setup cancelled");
process.exit(1);
},
});
return {
projectName: response.projectName.trim(),
language: response.language,
template: response.language === "typescript" ? "react-ts" : "react",
};
}
// Create Vite project
function createViteProject(config) {
log.progress(`Creating Vite project...`);
const command = `npm create vite@latest ${config.projectName} -- --template ${config.template}`;
if (!executeCommand(command)) {
log.error("Failed to create Vite project");
process.exit(1);
}
log.success("Vite project created successfully");
return path.resolve(config.projectName);
}
// Install dependencies
function installDependencies(projectPath) {
log.progress("Installing dependencies...");
// Install base dependencies
if (!executeCommand("npm install", { cwd: projectPath })) {
log.error("Failed to install base dependencies");
process.exit(1);
}
// Install Tailwind CSS v3 with the new PostCSS plugin
const tailwindCommand =
"npm install -D tailwindcss@^3.4.0 @tailwindcss/postcss autoprefixer";
if (!executeCommand(tailwindCommand, { cwd: projectPath })) {
log.error("Failed to install Tailwind CSS and PostCSS plugin");
process.exit(1);
}
log.success("Dependencies installed successfully");
}
// Setup Tailwind CSS
function setupTailwind(projectPath, language) {
log.progress("Setting up Tailwind CSS...");
// Initialize Tailwind config
if (!executeCommand("npx tailwindcss init", { cwd: projectPath })) {
log.error("Failed to initialize Tailwind config");
process.exit(1);
}
// Update tailwind.config.js with proper content paths
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}`;
fs.writeFileSync(
path.join(projectPath, "tailwind.config.js"),
tailwindConfig
);
// Create postcss.config.js with the new PostCSS plugin
const postcssConfig = `export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}`;
fs.writeFileSync(path.join(projectPath, "postcss.config.js"), postcssConfig);
// Update src/index.css
const indexCSS = `@tailwind base;
@tailwind components;
@tailwind utilities;`;
fs.writeFileSync(path.join(projectPath, "src", "index.css"), indexCSS);
log.success("Tailwind CSS setup completed");
}
// Update App component with simple example
function updateAppComponent(projectPath, language) {
log.progress("Updating App component...");
const extension = language === "typescript" ? "tsx" : "jsx";
const appComponent = `function App() {
return (
<div className="min-h-screen relative bg-black">
{/* Attribution */}
<div className="fixed bottom-4 right-4 text-xs text-gray-400 ">
— Pritam Paul —
</div>
</div>
);
}
export default App;`;
const appPath = path.join(projectPath, "src", `App.${extension}`);
fs.writeFileSync(appPath, appComponent);
log.success("App component updated");
}
// Update App.css to remove conflicts
function updateAppCSS(projectPath) {
log.progress("Updating App.css...");
const appCSS = `#root {
max-width: full-width;
margin: 0 auto;
padding: 0;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
.logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}`;
fs.writeFileSync(path.join(projectPath, "src", "App.css"), appCSS);
log.success("App.css updated");
}
// Create simple README
function createReadme(projectPath, config) {
log.progress("Creating README...");
const readme = `# ${config.projectName}
A React application built with Vite and Tailwind CSS.
## Getting Started
\`\`\`bash
npm install
npm run dev
\`\`\`
## Scripts
- \`npm run dev\` - Start development server
- \`npm run build\` - Build for production
- \`npm run preview\` - Preview production build
- \`npm run lint\` - Run ESLint
## Tech Stack
- React 18
- Vite
- Tailwind CSS v3.4+
- ${config.language === "typescript" ? "TypeScript" : "JavaScript"}
## Features
- ⚡ Lightning fast with Vite
- 🎨 Tailwind CSS for styling
- 🔥 Hot Module Replacement
- 📱 Responsive design
- ${config.language === "typescript" ? "🔧 TypeScript support" : "🚀 Modern JavaScript"}
## Project Structure
\`\`\`
${config.projectName}/
├── public/
├── src/
│ ├── assets/
│ ├── App.${config.language === "typescript" ? "tsx" : "jsx"}
│ ├── App.css
│ ├── index.css
│ └── main.${config.language === "typescript" ? "ts" : "js"}
├── index.html
├── package.json
├── tailwind.config.js
├── postcss.config.js
└── vite.config.${config.language === "typescript" ? "ts" : "js"}
\`\`\`
`;
fs.writeFileSync(path.join(projectPath, "README.md"), readme);
log.success("README created");
}
// Main function
async function main() {
try {
checkPrerequisites();
const config = await getProjectConfig();
console.log("\n");
log.info("Starting project setup...");
// Create project
const projectPath = createViteProject(config);
// Setup everything
installDependencies(projectPath);
setupTailwind(projectPath, config.language);
updateAppComponent(projectPath, config.language);
updateAppCSS(projectPath);
createReadme(projectPath, config);
// Success message
console.log("\n" + "=".repeat(50));
console.log(" 🎉 Setup Complete!");
console.log("=".repeat(50));
console.log(`\nNext steps:`);
console.log(` 1. cd ${config.projectName}`);
console.log(` 2. npm run dev`);
console.log(` 3. Open http://localhost:5173\n`);
log.success("Your React + Vite + Tailwind project is ready!");
} catch (error) {
log.error("Setup failed:");
console.error(error.message);
process.exit(1);
}
}
// Handle interruption
process.on("SIGINT", () => {
log.error("Setup interrupted");
process.exit(1);
});
// Run the script
main().catch((error) => {
log.error("Unexpected error:");
console.error(error);
process.exit(1);
});