11#!/usr/bin/env node
2-
32import inquirer from "inquirer" ;
4- import { execSync } from "child_process" ;
53import path from "path" ;
6- import fs from "fs" ;
7-
8- const run = ( cmd , cwd = process . cwd ( ) ) => {
9- console . log ( `\n📦 Running: ${ cmd } ` ) ;
10- execSync ( cmd , { stdio : "inherit" , cwd } ) ;
11- } ;
4+ import { run , createFolder , deleteFile } from './lib/utils.js' ;
5+ import { initializePWA } from './lib/pwa.js' ;
6+ import { setupCSSFramework } from './lib/css-frameworks.js' ;
7+ import { createAxiosSetup , createAppComponent , setupRouterMain , createPWAReadme } from './lib/templates.js' ;
128
139( async ( ) => {
14- // 1. Ask project name
15- const { projectName } = await inquirer . prompt ( [
16- { type : "input" , name : "projectName" , message : "Enter project name:" }
17- ] ) ;
18-
19- // 2. Ask for CSS framework
20- const { cssFramework } = await inquirer . prompt ( [
10+ // 1. Collect user inputs
11+ const answers = await inquirer . prompt ( [
12+ {
13+ type : "input" ,
14+ name : "projectName" ,
15+ message : "Enter project name:"
16+ } ,
2117 {
2218 type : "list" ,
2319 name : "cssFramework" ,
2420 message : "Choose a CSS framework:" ,
25- choices : [
26- "Tailwind" ,
27- "Bootstrap (CDN)" ,
28- "React Bootstrap" ,
29- "MUI"
30- ]
31- }
32- ] ) ;
33-
34- // 3. Ask optional packages
35- const { packages } = await inquirer . prompt ( [
21+ choices : [ "Tailwind" , "Bootstrap (CDN)" , "React Bootstrap" , "MUI" ]
22+ } ,
23+ {
24+ type : "confirm" ,
25+ name : "isPWA" ,
26+ message : "Do you want to make this a Progressive Web App (PWA)?" ,
27+ default : false
28+ } ,
3629 {
3730 type : "checkbox" ,
3831 name : "packages" ,
@@ -48,214 +41,62 @@ const run = (cmd, cwd = process.cwd()) => {
4841 }
4942 ] ) ;
5043
51- // 4. Create Vite + React project
52- run ( `npm create vite@latest ${ projectName } -- --template react` ) ;
44+ const { projectName, cssFramework, isPWA, packages } = answers ;
5345 const projectPath = path . join ( process . cwd ( ) , projectName ) ;
5446
55- // 5. Install chosen CSS framework
56- if ( cssFramework === "Tailwind" ) {
57- run ( `npm install tailwindcss @tailwindcss/vite` , projectPath ) ;
58-
59- const viteConfigPath = path . join ( projectPath , "vite.config.js" ) ;
60- let viteConfig = fs . readFileSync ( viteConfigPath , "utf-8" ) ;
61- viteConfig = `import tailwindcss from '@tailwindcss/vite'\n` + viteConfig ;
62- viteConfig = viteConfig . replace ( / p l u g i n s : \s * \[ / , "plugins: [\n tailwindcss()," ) ;
63- fs . writeFileSync ( viteConfigPath , viteConfig ) ;
64-
65- fs . writeFileSync ( path . join ( projectPath , "src" , "index.css" ) , `@import "tailwindcss";\n` ) ;
66-
67- const mainFile = fs . existsSync ( path . join ( projectPath , "src/main.jsx" ) )
68- ? "src/main.jsx"
69- : "src/main.tsx" ;
70- const mainPath = path . join ( projectPath , mainFile ) ;
71- let mainContent = fs . readFileSync ( mainPath , "utf-8" ) ;
72- mainContent = mainContent . replace ( / i m p o r t \s + [ ' " ] \. \/ i n d e x \. c s s [ ' " ] ; ? / g, "" ) ;
73- if ( ! mainContent . includes ( `import './index.css'` ) ) {
74- mainContent = `import './index.css';\n` + mainContent ;
75- }
76- fs . writeFileSync ( mainPath , mainContent ) ;
77-
78- } else if ( cssFramework === "Bootstrap (CDN)" ) {
79- const indexHtmlPath = path . join ( projectPath , "index.html" ) ;
80- let indexHtml = fs . readFileSync ( indexHtmlPath , "utf-8" ) ;
81- indexHtml = indexHtml . replace (
82- / < h e a d > / ,
83- `<head>\n <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">\n <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>`
84- ) ;
85- fs . writeFileSync ( indexHtmlPath , indexHtml ) ;
47+ console . log ( `\n🚀 Creating ${ projectName } ${ isPWA ? ' with PWA capabilities' : '' } ...` ) ;
8648
87- } else if ( cssFramework === "React Bootstrap" ) {
88- run ( `npm install react-bootstrap bootstrap` , projectPath ) ;
89- const mainFile = fs . existsSync ( path . join ( projectPath , "src/main.jsx" ) )
90- ? "src/main.jsx"
91- : "src/main.tsx" ;
92- const mainPath = path . join ( projectPath , mainFile ) ;
93- let mainContent = fs . readFileSync ( mainPath , "utf-8" ) ;
94- mainContent = mainContent
95- . replace ( / i m p o r t \s + [ ' " ] \. \/ i n d e x \. c s s [ ' " ] ; ? / g, "" )
96- . replace ( / i m p o r t \s + [ ' " ] \. \/ A p p \. c s s [ ' " ] ; ? / g, "" ) ;
97- mainContent = `import 'bootstrap/dist/css/bootstrap.min.css';\n` + mainContent ;
98- fs . writeFileSync ( mainPath , mainContent ) ;
49+ // 2. Create Vite project
50+ run ( `npm create vite@latest ${ projectName } -- --template react` ) ;
9951
100- } else if ( cssFramework === "MUI" ) {
101- run ( `npm install @mui/material @emotion/react @emotion/styled` , projectPath ) ;
102- const mainFile = fs . existsSync ( path . join ( projectPath , "src/main.jsx" ) )
103- ? "src/main.jsx"
104- : "src/main.tsx" ;
105- const mainPath = path . join ( projectPath , mainFile ) ;
106- let mainContent = fs . readFileSync ( mainPath , "utf-8" ) ;
107- mainContent = mainContent
108- . replace ( / i m p o r t \s + [ ' " ] \. \/ i n d e x \. c s s [ ' " ] ; ? / g, "" )
109- . replace ( / i m p o r t \s + [ ' " ] \. \/ A p p \. c s s [ ' " ] ; ? / g, "" ) ;
110- fs . writeFileSync ( mainPath , mainContent ) ;
111- }
52+ // 3. Create all necessary folder structure first
53+ const folders = [ "components" , "pages" , "hooks" , "store" , "utils" , "assets" ] ;
54+ folders . forEach ( ( folder ) => {
55+ createFolder ( path . join ( projectPath , "src" , folder ) ) ;
56+ } ) ;
11257
113- // 6 . Install default + optional packages
58+ // 4 . Install packages
11459 const defaultPackages = [ "react-router-dom" ] ;
11560 const allPackages = [ ...defaultPackages , ...packages ] ;
11661 if ( allPackages . length > 0 ) {
11762 run ( `npm install ${ allPackages . join ( " " ) } ` , projectPath ) ;
11863 }
11964
120- // 7. Create folder structure
121- const folders = [ "components" , "pages" , "hooks" , "store" , "utils" , "assets" ] ;
122- folders . forEach ( ( folder ) => {
123- fs . mkdirSync ( path . join ( projectPath , "src" , folder ) , { recursive : true } ) ;
124- } ) ;
125-
126- // 8. Axios setup if chosen
127- if ( packages . includes ( "axios" ) ) {
128- const axiosContent = `import axios from "axios";
129-
130- export const api = axios.create({
131- baseURL: import.meta.env.VITE_API_URL || "http://localhost:5000",
132- headers: { "Content-Type": "application/json" },
133- timeout: 10000
134- });
135-
136- // ✅ Request Interceptor
137- api.interceptors.request.use(
138- (config) => {
139- // Example: Add token if available
140- const token = localStorage.getItem("token");
141- if (token) {
142- config.headers.Authorization = \`Bearer \${token}\`;
143- }
144- return config;
145- },
146- (error) => {
147- return Promise.reject(error);
148- }
149- );
150-
151- // ✅ Response Interceptor
152- api.interceptors.response.use(
153- (response) => {
154- return response.data; // Return only data for convenience
155- },
156- (error) => {
157- if (error.response) {
158- console.error("API Error:", error.response.data?.message || error.message);
159- // Example: Handle unauthorized
160- if (error.response.status === 401) {
161- // Optionally redirect to login
162- window.location.href = "/login";
163- }
164- } else if (error.request) {
165- console.error("No response received from server.");
166- } else {
167- console.error("Request setup error:", error.message);
168- }
169- return Promise.reject(error);
65+ // 5. Setup PWA if selected (after folder structure is created)
66+ if ( isPWA ) {
67+ initializePWA ( projectPath , projectName ) ;
17068 }
171- );
172- ` ;
17369
174- fs . writeFileSync ( path . join ( projectPath , "src" , "utils" , "axiosInstance.js" ) , axiosContent ) ;
175- }
176-
177- // 9. Clean up default CSS files (centralized)
178- const appCssPath = path . join ( projectPath , "src" , "App.css" ) ;
179- if ( fs . existsSync ( appCssPath ) ) fs . unlinkSync ( appCssPath ) ;
70+ // 6. Setup CSS framework
71+ setupCSSFramework ( cssFramework , projectPath ) ;
18072
181- const indexCssPath = path . join ( projectPath , "src" , "index.css" ) ;
182- if ( cssFramework !== "Tailwind" && fs . existsSync ( indexCssPath ) ) {
183- fs . unlinkSync ( indexCssPath ) ;
73+ // 7. Setup Axios if selected
74+ if ( packages . includes ( "axios" ) ) {
75+ createAxiosSetup ( projectPath ) ;
18476 }
18577
186- // 10. Replace App.jsx content
187- const appFile = fs . existsSync ( path . join ( projectPath , "src/App.jsx" ) )
188- ? path . join ( projectPath , "src/App.jsx" )
189- : path . join ( projectPath , "src/App.tsx" ) ;
190-
191- let appContent = `export default function App() {
192- return (
193- <div
194- style={{
195- display: "flex",
196- flexDirection: "column",
197- justifyContent: "center",
198- alignItems: "center",
199- height: "100vh",
200- fontFamily: "sans-serif",
201- background: "#f9fafb",
202- color: "#111",
203- textAlign: "center",
204- }}
205- >
206- <h1
207- style={{
208- fontSize: "2.5rem",
209- marginBottom: "0.5rem",
210- fontWeight: 600,
211- }}
212- >
213- Welcome to{" "}
214- <span style={{ color: "#2563eb" }}>${ projectName } </span> 🚀
215- </h1>
216- <p style={{ fontSize: "1.1rem", color: "#555" }}>
217- Your project is ready. Start building amazing things!
218- </p>
219- </div>
220- );
221- }` ;
222- fs . writeFileSync ( appFile , appContent ) ;
223-
224- // 11. Default Router setup in main.jsx
225- const mainFile = fs . existsSync ( path . join ( projectPath , "src/main.jsx" ) )
226- ? "src/main.jsx"
227- : "src/main.tsx" ;
228- const mainPath = path . join ( projectPath , mainFile ) ;
229-
230- let cssImports = "" ;
231- if ( cssFramework === "React Bootstrap" ) {
232- cssImports = `import 'bootstrap/dist/css/bootstrap.min.css';\n` ;
233- } else if ( cssFramework === "Tailwind" ) {
234- cssImports = `import './index.css';\n` ;
235- } else if ( cssFramework === "Bootstrap (CDN)" ) {
236- cssImports = "" ; // CDN already added in index.html
237- } else if ( cssFramework === "MUI" ) {
238- cssImports = "" ; // no CSS import needed
78+ // 8. Clean up default boilerplate files
79+ deleteFile ( path . join ( projectPath , "src" , "App.css" ) ) ;
80+ if ( cssFramework !== "Tailwind" ) {
81+ deleteFile ( path . join ( projectPath , "src" , "index.css" ) ) ;
23982 }
24083
241- const routerSetup = `${ cssImports } import React from 'react';
242- import ReactDOM from 'react-dom/client';
243- import { BrowserRouter, Routes, Route } from 'react-router-dom';
244- import App from './App';
245-
246- ReactDOM.createRoot(document.getElementById('root')).render(
247- <React.StrictMode>
248- <BrowserRouter>
249- <Routes>
250- <Route path="/" element={<App />} />
251- </Routes>
252- </BrowserRouter>
253- </React.StrictMode>
254- );` ;
255-
256- fs . writeFileSync ( mainPath , routerSetup ) ;
257-
84+ // 9. Generate clean templates
85+ createAppComponent ( projectPath , projectName , isPWA ) ;
86+ setupRouterMain ( projectPath , cssFramework ) ;
87+
88+ // 10. Create comprehensive README
89+ createPWAReadme ( projectPath , projectName , cssFramework , packages , isPWA ) ;
25890
91+ // 11. Success message
25992 console . log ( "\n✅ Setup complete!" ) ;
260- console . log ( `\nNext steps:\n cd ${ projectName } \n npm run dev` ) ;
93+ if ( isPWA ) {
94+ console . log ( "📱 PWA features enabled - your app can be installed on mobile devices!" ) ;
95+ console . log ( "⚠️ Important: Replace placeholder SVG icons with proper PNG icons for production" ) ;
96+ }
97+ console . log ( `\nNext steps:\n cd ${ projectName } \n npm install\n npm run dev` ) ;
98+
99+ if ( isPWA ) {
100+ console . log ( `\n📱 To test PWA:\n npm run build\n npm run preview\n Open http://localhost:4173 and test install/offline features` ) ;
101+ }
261102} ) ( ) ;
0 commit comments