Skip to content

Commit 8fafe20

Browse files
committed
Refactor project setup to modularize utilities, PWA configuration, and CSS framework handling; enhance user input collection and streamline project initialization process.
1 parent 414c97d commit 8fafe20

File tree

5 files changed

+696
-216
lines changed

5 files changed

+696
-216
lines changed

index.js

Lines changed: 57 additions & 216 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,31 @@
11
#!/usr/bin/env node
2-
32
import inquirer from "inquirer";
4-
import { execSync } from "child_process";
53
import 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(/plugins:\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(/import\s+['"]\.\/index\.css['"];?/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-
/<head>/,
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(/import\s+['"]\.\/index\.css['"];?/g, "")
96-
.replace(/import\s+['"]\.\/App\.css['"];?/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(/import\s+['"]\.\/index\.css['"];?/g, "")
109-
.replace(/import\s+['"]\.\/App\.css['"];?/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
})();

lib/css-frameworks.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { run, writeFile, readFile, fileExists } from './utils.js';
2+
import path from 'path';
3+
4+
export const setupTailwind = (projectPath) => {
5+
run(`npm install tailwindcss @tailwindcss/vite`, projectPath);
6+
7+
const viteConfigPath = path.join(projectPath, "vite.config.js");
8+
let viteConfig = readFile(viteConfigPath);
9+
viteConfig = `import tailwindcss from '@tailwindcss/vite'\n` + viteConfig;
10+
viteConfig = viteConfig.replace(/plugins:\s*\[/, "plugins: [\n tailwindcss(),");
11+
writeFile(viteConfigPath, viteConfig);
12+
13+
writeFile(path.join(projectPath, "src", "index.css"), `@import "tailwindcss";\n`);
14+
15+
const mainFile = fileExists(path.join(projectPath, "src/main.jsx")) ? "src/main.jsx" : "src/main.tsx";
16+
const mainPath = path.join(projectPath, mainFile);
17+
let mainContent = readFile(mainPath);
18+
mainContent = mainContent.replace(/import\s+['"]\.\/index\.css['"];?/g, "");
19+
if (!mainContent.includes(`import './index.css'`)) {
20+
mainContent = `import './index.css';\n` + mainContent;
21+
}
22+
writeFile(mainPath, mainContent);
23+
};
24+
25+
export const setupBootstrapCDN = (projectPath) => {
26+
const indexHtmlPath = path.join(projectPath, "index.html");
27+
let indexHtml = readFile(indexHtmlPath);
28+
indexHtml = indexHtml.replace(
29+
/<head>/,
30+
`<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>`
31+
);
32+
writeFile(indexHtmlPath, indexHtml);
33+
};
34+
35+
export const setupReactBootstrap = (projectPath) => {
36+
run(`npm install react-bootstrap bootstrap`, projectPath);
37+
const mainFile = fileExists(path.join(projectPath, "src/main.jsx")) ? "src/main.jsx" : "src/main.tsx";
38+
const mainPath = path.join(projectPath, mainFile);
39+
let mainContent = readFile(mainPath);
40+
mainContent = mainContent
41+
.replace(/import\s+['"]\.\/index\.css['"];?/g, "")
42+
.replace(/import\s+['"]\.\/App\.css['"];?/g, "");
43+
mainContent = `import 'bootstrap/dist/css/bootstrap.min.css';\n` + mainContent;
44+
writeFile(mainPath, mainContent);
45+
};
46+
47+
export const setupMUI = (projectPath) => {
48+
run(`npm install @mui/material @emotion/react @emotion/styled`, projectPath);
49+
const mainFile = fileExists(path.join(projectPath, "src/main.jsx")) ? "src/main.jsx" : "src/main.tsx";
50+
const mainPath = path.join(projectPath, mainFile);
51+
let mainContent = readFile(mainPath);
52+
mainContent = mainContent
53+
.replace(/import\s+['"]\.\/index\.css['"];?/g, "")
54+
.replace(/import\s+['"]\.\/App\.css['"];?/g, "");
55+
writeFile(mainPath, mainContent);
56+
};
57+
58+
export const setupCSSFramework = (cssFramework, projectPath) => {
59+
const frameworkMap = {
60+
"Tailwind": () => setupTailwind(projectPath),
61+
"Bootstrap (CDN)": () => setupBootstrapCDN(projectPath),
62+
"React Bootstrap": () => setupReactBootstrap(projectPath),
63+
"MUI": () => setupMUI(projectPath)
64+
};
65+
66+
const setupFunction = frameworkMap[cssFramework];
67+
if (setupFunction) {
68+
setupFunction();
69+
}
70+
};

0 commit comments

Comments
 (0)