boil-gen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +758 -0
  2. package/package.json +31 -0
package/index.js ADDED
@@ -0,0 +1,758 @@
1
+ #!/usr/bin/env node
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { execSync } from "child_process";
5
+ import { fileURLToPath } from "url";
6
+
7
+ import dotenv from "dotenv";
8
+
9
+ dotenv.config();
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+
15
+
16
+ function extractJSON(text) {
17
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
18
+ if (jsonMatch) {
19
+ try {
20
+ return JSON.parse(jsonMatch[0]);
21
+ } catch (e) {
22
+ return null;
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function sanitizePath(p) {
29
+ return p.replace(/[<>:"|?*\x00-\x1f]/g, "").trim();
30
+ }
31
+
32
+ function deduplicate(arr) {
33
+ return [...new Set(arr)];
34
+ }
35
+
36
+ function detectProjectType(prompt) {
37
+ const lowerPrompt = prompt.toLowerCase();
38
+
39
+ const frontendOnlyKeywords = [
40
+ "frontend only", "frontend-only", "only frontend",
41
+ "client side", "client-side", "client side only",
42
+ "static site", "static website", "landing page",
43
+ "portfolio site", "portfolio website", "personal website",
44
+ "react app", "vue app", "svelte app", "next.js app",
45
+ "spa", "single page application", "ui only", "ui-only"
46
+ ];
47
+
48
+ const backendOnlyKeywords = [
49
+ "backend only", "backend-only", "only backend",
50
+ "server only", "server-only", "api only", "api-only",
51
+ "rest api", "graphql api", "express api", "fastify api",
52
+ "microservice", "serverless", "lambda function"
53
+ ];
54
+
55
+ const fullstackKeywords = [
56
+ "fullstack", "full stack", "full-stack",
57
+ "mern", "mean", "mevn", "full stack app",
58
+ "web application", "web app", "complete app",
59
+ "with database", "with api", "with backend"
60
+ ];
61
+
62
+ for (const keyword of frontendOnlyKeywords) {
63
+ if (lowerPrompt.includes(keyword)) {
64
+ return "frontend";
65
+ }
66
+ }
67
+
68
+ for (const keyword of backendOnlyKeywords) {
69
+ if (lowerPrompt.includes(keyword)) {
70
+ return "backend";
71
+ }
72
+ }
73
+
74
+ for (const keyword of fullstackKeywords) {
75
+ if (lowerPrompt.includes(keyword)) {
76
+ return "fullstack";
77
+ }
78
+ }
79
+
80
+ if (lowerPrompt.includes("frontend") && !lowerPrompt.includes("backend") && !lowerPrompt.includes("api")) {
81
+ return "frontend";
82
+ }
83
+
84
+ if ((lowerPrompt.includes("backend") || lowerPrompt.includes("api") || lowerPrompt.includes("server")) &&
85
+ !lowerPrompt.includes("frontend") && !lowerPrompt.includes("client")) {
86
+ return "backend";
87
+ }
88
+
89
+ return "fullstack";
90
+ }
91
+
92
+ function detectLanguageFromPrompt(prompt) {
93
+ const lowerPrompt = prompt.toLowerCase();
94
+ if (lowerPrompt.includes("typescript") || lowerPrompt.includes("ts")) {
95
+ return "typescript";
96
+ }
97
+ return "javascript";
98
+ }
99
+
100
+ function generateFileContent(filePath, arch, projectName, language = "javascript") {
101
+ const ext = path.extname(filePath).toLowerCase();
102
+ const name = path.basename(filePath, ext);
103
+ const dir = path.dirname(filePath);
104
+
105
+ if (ext === ".json") {
106
+ if (name === "package") {
107
+ // Determine if this is backend or frontend
108
+ const isBackend = filePath.includes("backend");
109
+
110
+ if (isBackend) {
111
+ // Backend package.json
112
+ return JSON.stringify({
113
+ name: projectName.toLowerCase().replace(/\s+/g, "-"),
114
+ version: "1.0.0",
115
+ type: "module",
116
+ scripts: {
117
+ start: language === "typescript" ? "node dist/index.js" : "node index.js",
118
+ dev: language === "typescript" ? "nodemon --exec tsx index.ts" : "nodemon index.js"
119
+ },
120
+ dependencies: {
121
+ "cors": "^2.8.5",
122
+ "dotenv": "^17.2.3",
123
+ "express": "^5.2.1",
124
+ "helmet": "^8.1.0",
125
+ "mongoose": "^9.1.5",
126
+ "morgan": "^1.10.1"
127
+ },
128
+ devDependencies: language === "typescript" ? {
129
+ "@types/cors": "^2.8.19",
130
+ "@types/express": "^5.0.6",
131
+ "@types/morgan": "^1.9.10",
132
+ "@types/node": "^25.0.9",
133
+ "nodemon": "^3.1.11",
134
+ "tsx": "^4.10.0",
135
+ "typescript": "^5.9.3"
136
+ } : {
137
+ "nodemon": "^3.1.11"
138
+ }
139
+ }, null, 2);
140
+ } else {
141
+ // Frontend package.json
142
+ return JSON.stringify({
143
+ name: projectName.toLowerCase().replace(/\s+/g, "-"),
144
+ version: "1.0.0",
145
+ type: "module",
146
+ scripts: {
147
+ start: "node index.js",
148
+ dev: "node --watch index.js"
149
+ },
150
+ dependencies: {},
151
+ devDependencies: {}
152
+ }, null, 2);
153
+ }
154
+ }
155
+ if (name === "tsconfig") {
156
+ return JSON.stringify({
157
+ compilerOptions: {
158
+ target: "ES2020",
159
+ module: "ESNext",
160
+ lib: ["ES2020"],
161
+ strict: true,
162
+ esModuleInterop: true,
163
+ skipLibCheck: true,
164
+ forceConsistentCasingInFileNames: true,
165
+ moduleResolution: "node",
166
+ resolveJsonModule: true,
167
+ isolatedModules: true,
168
+ noEmit: true
169
+ },
170
+ include: ["src"],
171
+ exclude: ["node_modules"]
172
+ }, null, 2);
173
+ }
174
+ return "{}";
175
+ }
176
+
177
+ if (ext === ".js" || ext === ".jsx" || ext === ".ts" || ext === ".tsx") {
178
+ if (name === "index" || name === "main" || name === "app" || name === "server") {
179
+ if (dir.includes("backend") || dir.includes("server")) {
180
+ return `${language === "typescript" ? "import express from 'express';" : "import express from 'express';"}
181
+ import cors from 'cors';
182
+ import dotenv from 'dotenv';
183
+
184
+ dotenv.config();
185
+
186
+ const app = express();
187
+ const PORT = process.env.PORT || 5001;
188
+
189
+ app.use(cors());
190
+ app.use(express.json());
191
+
192
+ app.get('/', (req, res) => {
193
+ res.json({ message: 'API is running', project: '${projectName}' });
194
+ });
195
+
196
+ app.listen(PORT, () => {
197
+ console.log(\`Server running on http://localhost:\${PORT}\`);
198
+ });
199
+
200
+ export default app;`;
201
+ }
202
+ if (dir.includes("frontend") || dir.includes("client")) {
203
+ return `import React from "react";
204
+ import ReactDOM from "react-dom/client";
205
+ import "./index.css";
206
+
207
+ function App() {
208
+ return (
209
+ <div className="app">
210
+ <h1>${projectName}</h1>
211
+ <p>Welcome to your new project!</p>
212
+ </div>
213
+ );
214
+ }
215
+
216
+ const root = ReactDOM.createRoot(document.getElementById("root"));
217
+ root.render(<App />);`;
218
+ }
219
+ }
220
+ return `${language === "typescript" ? "// " : "// "}${name}${ext}\nexport default function ${name}() {\n return null;\n}`;
221
+ }
222
+
223
+ if (ext === ".css") {
224
+ return `* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n font-family: system-ui, sans-serif;\n}`;
225
+ }
226
+
227
+ if (ext === ".html") {
228
+ return `<!DOCTYPE html>
229
+ <html lang="en">
230
+ <head>
231
+ <meta charset="UTF-8">
232
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
233
+ <title>${projectName}</title>
234
+ </head>
235
+ <body>
236
+ <div id="root"></div>
237
+ </body>
238
+ </html>`;
239
+ }
240
+
241
+ if (ext === ".env" || name === ".env") {
242
+ return `PORT=5000\nNODE_ENV=development`;
243
+ }
244
+
245
+ if (ext === ".md") {
246
+ return `# ${projectName}\n\nGenerated by mern-gen\n`;
247
+ }
248
+
249
+ if (ext === ".gitignore") {
250
+ return `node_modules/\n.env\n.DS_Store\ndist/\nbuild/\n*.log`;
251
+ }
252
+
253
+ return "";
254
+ }
255
+
256
+ async function callAI(prompt, projectType, retries = 3) {
257
+
258
+ const API_URL = process.env.MERN_GEN_PROXY_URL || "http://localhost:5001/generate";
259
+
260
+ console.log(`Connecting to Server ....`);
261
+
262
+ for (let i = 0; i < retries; i++) {
263
+ try {
264
+ const response = await fetch(API_URL, {
265
+ method: "POST",
266
+ headers: {
267
+ "Content-Type": "application/json",
268
+ },
269
+ body: JSON.stringify({ prompt }),
270
+ });
271
+
272
+ if (!response.ok) {
273
+ throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
274
+ }
275
+
276
+ const data = await response.json();
277
+
278
+ if (!data.success) {
279
+ throw new Error(data.error || "Unknown error from server");
280
+ }
281
+
282
+ return data.data;
283
+
284
+ } catch (error) {
285
+ if (i === retries - 1) throw error;
286
+ console.warn(`Connection failed (attempt ${i + 1}/${retries}). Retrying...`);
287
+ await new Promise(r => setTimeout(r, 1000 * (i + 1)));
288
+ }
289
+ }
290
+ throw new Error("AI call failed after retries");
291
+ }
292
+
293
+ function createDirectory(dirPath) {
294
+ try {
295
+ if (!fs.existsSync(dirPath)) {
296
+ fs.mkdirSync(dirPath, { recursive: true });
297
+ }
298
+ } catch (error) {
299
+ console.warn(`Could not create directory ${dirPath}: ${error.message}`);
300
+ }
301
+ }
302
+
303
+ function createFile(filePath, content) {
304
+ try {
305
+ const dir = path.dirname(filePath);
306
+ createDirectory(dir);
307
+ fs.writeFileSync(filePath, content, "utf-8");
308
+ } catch (error) {
309
+ console.warn(`Could not create file ${filePath}: ${error.message}`);
310
+ }
311
+ }
312
+
313
+ function installDependencies(basePath, deps, devDeps = []) {
314
+ if (deps.length === 0 && devDeps.length === 0) return;
315
+
316
+ try {
317
+ if (!fs.existsSync(path.join(basePath, "package.json"))) {
318
+ execSync("npm init -y", { cwd: basePath, stdio: "pipe" });
319
+ }
320
+
321
+ if (deps.length > 0) {
322
+ execSync(`npm install ${deps.join(" ")}`, {
323
+ cwd: basePath,
324
+ stdio: "inherit"
325
+ });
326
+ }
327
+
328
+ if (devDeps.length > 0) {
329
+ execSync(`npm install -D ${devDeps.join(" ")}`, {
330
+ cwd: basePath,
331
+ stdio: "inherit"
332
+ });
333
+ }
334
+ } catch (error) {
335
+ console.warn(`Dependency installation had issues: ${error.message}`);
336
+ }
337
+ }
338
+
339
+ function setupTailwindCSS(frontendPath) {
340
+ try {
341
+ console.log("Setting up Tailwind CSS...");
342
+
343
+ // Step 1: Install Tailwind CSS and Vite plugin
344
+ console.log("Installing tailwindcss and @tailwindcss/vite...");
345
+ execSync("npm install tailwindcss @tailwindcss/vite", {
346
+ cwd: frontendPath,
347
+ stdio: "inherit"
348
+ });
349
+
350
+ // Step 2: Configure the Vite plugin - handle both vite.config.ts and vite.config.js
351
+ let viteConfigPath = path.join(frontendPath, "vite.config.ts");
352
+ let isTypeScript = true;
353
+
354
+ if (!fs.existsSync(viteConfigPath)) {
355
+ viteConfigPath = path.join(frontendPath, "vite.config.js");
356
+ isTypeScript = false;
357
+ }
358
+
359
+ let viteConfigContent = "";
360
+
361
+ if (fs.existsSync(viteConfigPath)) {
362
+ // Update existing vite.config
363
+ viteConfigContent = fs.readFileSync(viteConfigPath, "utf-8");
364
+ if (!viteConfigContent.includes("@tailwindcss/vite")) {
365
+ // Add import if not present
366
+ if (!viteConfigContent.includes("import tailwindcss from '@tailwindcss/vite'")) {
367
+ if (viteConfigContent.includes("import react from '@vitejs/plugin-react'")) {
368
+ viteConfigContent = viteConfigContent.replace(
369
+ "import react from '@vitejs/plugin-react'",
370
+ "import react from '@vitejs/plugin-react'\nimport tailwindcss from '@tailwindcss/vite'"
371
+ );
372
+ } else {
373
+ viteConfigContent = viteConfigContent.replace(
374
+ /import { defineConfig } from ['"]vite['"]/,
375
+ "import { defineConfig } from 'vite'\nimport tailwindcss from '@tailwindcss/vite'"
376
+ );
377
+ }
378
+ }
379
+ // Add plugin to plugins array - handle different formats
380
+ if (viteConfigContent.includes("plugins: [react()]")) {
381
+ viteConfigContent = viteConfigContent.replace(
382
+ "plugins: [react()]",
383
+ "plugins: [tailwindcss(), react()]"
384
+ );
385
+ } else if (viteConfigContent.includes("plugins: [")) {
386
+ viteConfigContent = viteConfigContent.replace(
387
+ /plugins:\s*\[\s*react\(\)/,
388
+ "plugins: [\n tailwindcss(),\n react()"
389
+ );
390
+ }
391
+ fs.writeFileSync(viteConfigPath, viteConfigContent);
392
+ console.log(`Updated ${isTypeScript ? 'vite.config.ts' : 'vite.config.js'} with Tailwind plugin`);
393
+ } else {
394
+ console.log(`${isTypeScript ? 'vite.config.ts' : 'vite.config.js'} already has Tailwind plugin`);
395
+ }
396
+ } else {
397
+ // Create new vite.config.ts with React and Tailwind
398
+ viteConfigPath = path.join(frontendPath, "vite.config.ts");
399
+ viteConfigContent = `import { defineConfig } from 'vite'
400
+ import react from '@vitejs/plugin-react'
401
+ import tailwindcss from '@tailwindcss/vite'
402
+
403
+ export default defineConfig({
404
+ plugins: [
405
+ tailwindcss(),
406
+ react(),
407
+ ],
408
+ })`;
409
+ createFile(viteConfigPath, viteConfigContent);
410
+ console.log("Created vite.config.ts with Tailwind plugin");
411
+ }
412
+
413
+ // Step 3: Add @import to CSS file
414
+ const cssFiles = ["src/index.css", "src/main.css", "src/App.css", "index.css", "main.css"];
415
+ let cssFileFound = false;
416
+
417
+ for (const cssFile of cssFiles) {
418
+ const fullCssPath = path.join(frontendPath, cssFile);
419
+ if (fs.existsSync(fullCssPath)) {
420
+ let cssContent = fs.readFileSync(fullCssPath, "utf-8");
421
+ if (!cssContent.includes('@import "tailwindcss"')) {
422
+ cssContent = `@import "tailwindcss";\n\n${cssContent}`;
423
+ fs.writeFileSync(fullCssPath, cssContent);
424
+ cssFileFound = true;
425
+ console.log(`Added Tailwind import to ${cssFile}`);
426
+ break;
427
+ } else {
428
+ cssFileFound = true;
429
+ console.log(`Tailwind import already present in ${cssFile}`);
430
+ break;
431
+ }
432
+ }
433
+ }
434
+
435
+ // If no CSS file found, create one
436
+ if (!cssFileFound) {
437
+ const newCssPath = path.join(frontendPath, "src", "index.css");
438
+ createDirectory(path.dirname(newCssPath));
439
+ createFile(newCssPath, `@import "tailwindcss";\n`);
440
+ console.log("Created new src/index.css with Tailwind import");
441
+ }
442
+
443
+ // Ensure postcss.config.js is removed if it was created
444
+ const postcssPath = path.join(frontendPath, "postcss.config.js");
445
+ if (fs.existsSync(postcssPath)) {
446
+ fs.unlinkSync(postcssPath);
447
+ console.log("Removed auto-generated postcss.config.js");
448
+ }
449
+
450
+ console.log("Tailwind CSS configured successfully!");
451
+ } catch (error) {
452
+ console.warn(`Tailwind setup had issues: ${error.message}`);
453
+ }
454
+ }
455
+
456
+ function copyRecursive(src, dest) {
457
+ const stat = fs.statSync(src);
458
+ if (stat.isDirectory()) {
459
+ if (!fs.existsSync(dest)) {
460
+ fs.mkdirSync(dest, { recursive: true });
461
+ }
462
+ const files = fs.readdirSync(src);
463
+ for (const file of files) {
464
+ copyRecursive(path.join(src, file), path.join(dest, file));
465
+ }
466
+ } else {
467
+ fs.copyFileSync(src, dest);
468
+ }
469
+ }
470
+
471
+ function removeRecursive(dirPath) {
472
+ if (fs.existsSync(dirPath)) {
473
+ const files = fs.readdirSync(dirPath);
474
+ for (const file of files) {
475
+ const filePath = path.join(dirPath, file);
476
+ if (fs.statSync(filePath).isDirectory()) {
477
+ removeRecursive(filePath);
478
+ } else {
479
+ fs.unlinkSync(filePath);
480
+ }
481
+ }
482
+ fs.rmdirSync(dirPath);
483
+ }
484
+ }
485
+
486
+ function fixNestedFolders(basePath) {
487
+ try {
488
+ if (!fs.existsSync(basePath)) return;
489
+
490
+ const files = fs.readdirSync(basePath);
491
+ for (const file of files) {
492
+ const filePath = path.join(basePath, file);
493
+ if (fs.statSync(filePath).isDirectory() && (file === "frontend" || file === "backend")) {
494
+ const nestedPath = filePath;
495
+ const nestedFiles = fs.readdirSync(nestedPath);
496
+ for (const nestedFile of nestedFiles) {
497
+ if (nestedFile === "node_modules" || nestedFile === ".git") continue;
498
+ const srcPath = path.join(nestedPath, nestedFile);
499
+ const destPath = path.join(basePath, nestedFile);
500
+ if (fs.existsSync(destPath) && fs.statSync(srcPath).isDirectory()) {
501
+ copyRecursive(srcPath, destPath);
502
+ } else if (!fs.existsSync(destPath)) {
503
+ copyRecursive(srcPath, destPath);
504
+ }
505
+ }
506
+ removeRecursive(nestedPath);
507
+ fixNestedFolders(basePath);
508
+ }
509
+ }
510
+ } catch (error) {
511
+ console.warn(`Error fixing nested folders: ${error.message}`);
512
+ }
513
+ }
514
+
515
+ function setupFramework(basePath, framework, projectName, language) {
516
+ try {
517
+ if (framework === "react" || framework === "vite") {
518
+ const template = language === "typescript" ? "react-ts" : "react";
519
+ execSync(`npm create vite@latest . -- --template ${template}`, {
520
+ cwd: basePath,
521
+ env: { ...process.env, CI: "true" },
522
+ stdio: "pipe"
523
+ });
524
+ fixNestedFolders(basePath);
525
+ } else if (framework === "next") {
526
+ const tsFlag = language === "typescript" ? "--typescript" : "--typescript=false";
527
+ execSync(`npx create-next-app@latest . --yes ${tsFlag} --eslint=false --tailwind=false --app=false`, {
528
+ cwd: basePath,
529
+ stdio: "pipe"
530
+ });
531
+ fixNestedFolders(basePath);
532
+ } else if (framework === "vue") {
533
+ execSync("npm create vue@latest . -- --default", {
534
+ cwd: basePath,
535
+ env: { ...process.env, CI: "true" },
536
+ stdio: "pipe"
537
+ });
538
+ fixNestedFolders(basePath);
539
+ }
540
+ } catch (error) {
541
+ console.warn(`Framework setup had issues: ${error.message}`);
542
+ }
543
+ }
544
+
545
+ async function generateProject() {
546
+ const userPrompt = process.argv.slice(2).join(" ").trim();
547
+
548
+ if (!userPrompt) {
549
+ console.error("Please provide a project description");
550
+ console.log("Usage: mern-gen \"build a fullstack app for X\"");
551
+ process.exit(1);
552
+ }
553
+
554
+ const projectType = detectProjectType(userPrompt);
555
+ console.log(`Analyzing your request... (Detected: ${projectType === "frontend" ? "Frontend-only" : projectType === "backend" ? "Backend-only" : "Fullstack"})`);
556
+
557
+
558
+ let arch;
559
+ try {
560
+ arch = await callAI(userPrompt, projectType);
561
+ } catch (error) {
562
+ console.error(`Failed to generate architecture: ${error.message}`);
563
+ process.exit(1);
564
+ }
565
+
566
+ // Infer language from the generated architecture
567
+ let language = "javascript";
568
+ const allDevDeps = [...(arch.frontend.devDependencies || []), ...(arch.backend.devDependencies || [])];
569
+ const allFiles = [...(arch.frontend.files || []), ...(arch.backend.files || [])];
570
+
571
+ if (allDevDeps.some(d => d.includes("typescript")) || allFiles.some(f => f.endsWith(".ts") || f.endsWith(".tsx"))) {
572
+ language = "typescript";
573
+ }
574
+
575
+ console.log(`Architecture generated. Using ${language === "typescript" ? "TypeScript" : "JavaScript"} based on AI decision.`);
576
+
577
+ const projectName = sanitizePath(arch.projectName || "my-project");
578
+ const projectPath = path.join(process.cwd(), projectName);
579
+
580
+ if (fs.existsSync(projectPath)) {
581
+ console.error(`Directory "${projectName}" already exists`);
582
+ process.exit(1);
583
+ }
584
+
585
+ console.log(`Creating project: ${projectName}`);
586
+ createDirectory(projectPath);
587
+
588
+ console.log("Setting up project structure...");
589
+
590
+ const rootFiles = arch.rootFiles || [];
591
+ rootFiles.push("README.md", ".gitignore");
592
+
593
+ for (const file of deduplicate(rootFiles)) {
594
+ const filePath = path.join(projectPath, sanitizePath(file));
595
+ const content = generateFileContent(filePath, arch, projectName, language);
596
+ createFile(filePath, content);
597
+ }
598
+
599
+ const hasFrontend = arch.frontend && (arch.frontend.files?.length > 0 || arch.frontend.folders?.length > 0 || arch.frontend.framework);
600
+ const hasBackend = arch.backend && (arch.backend.files?.length > 0 || arch.backend.folders?.length > 0 || arch.backend.framework);
601
+
602
+ if (hasFrontend) {
603
+ console.log("Setting up frontend...");
604
+ const frontendPath = path.join(projectPath, "frontend");
605
+ createDirectory(frontendPath);
606
+
607
+ if (arch.frontend.framework) {
608
+ setupFramework(frontendPath, arch.frontend.framework, projectName, language);
609
+ }
610
+
611
+ if (!fs.existsSync(path.join(frontendPath, "package.json"))) {
612
+ execSync("npm init -y", { cwd: frontendPath, stdio: "pipe" });
613
+ }
614
+
615
+ for (const folder of arch.frontend.folders || []) {
616
+ const folderPath = sanitizePath(folder).replace(/^frontend[\\\/]/, "").replace(/^[\\\/]/, "");
617
+ if (folderPath) {
618
+ createDirectory(path.join(frontendPath, folderPath));
619
+ }
620
+ }
621
+
622
+ for (const file of arch.frontend.files || []) {
623
+ const filePath = sanitizePath(file).replace(/^frontend[\\\/]/, "").replace(/^[\\\/]/, "");
624
+ if (filePath && !fs.existsSync(path.join(frontendPath, filePath))) {
625
+ const fullPath = path.join(frontendPath, filePath);
626
+ const content = generateFileContent(fullPath, arch, projectName);
627
+ createFile(fullPath, content);
628
+ }
629
+ }
630
+
631
+ if (arch.frontend.dependencies?.length > 0 || arch.frontend.devDependencies?.length > 0) {
632
+ console.log("Installing frontend dependencies...");
633
+ installDependencies(
634
+ frontendPath,
635
+ arch.frontend.dependencies || [],
636
+ arch.frontend.devDependencies || []
637
+ );
638
+ }
639
+
640
+ setupTailwindCSS(frontendPath);
641
+ }
642
+
643
+ if (hasBackend) {
644
+ console.log("Setting up backend...");
645
+ const backendPath = path.join(projectPath, "backend");
646
+ createDirectory(backendPath);
647
+
648
+ if (!fs.existsSync(path.join(backendPath, "package.json"))) {
649
+ execSync("npm init -y", { cwd: backendPath, stdio: "pipe" });
650
+ }
651
+
652
+ for (const folder of arch.backend.folders || []) {
653
+ const folderPath = sanitizePath(folder).replace(/^backend[\\\/]/, "").replace(/^[\\\/]/, "");
654
+ if (folderPath) {
655
+ createDirectory(path.join(backendPath, folderPath));
656
+ }
657
+ }
658
+
659
+ for (const file of arch.backend.files || []) {
660
+ const filePath = sanitizePath(file).replace(/^backend[\\\/]/, "").replace(/^[\\\/]/, "");
661
+ if (filePath && !fs.existsSync(path.join(backendPath, filePath))) {
662
+ const fullPath = path.join(backendPath, filePath);
663
+ const content = generateFileContent(fullPath, arch, projectName, language);
664
+ createFile(fullPath, content);
665
+ }
666
+ }
667
+
668
+ // Ensure TypeScript dependencies are present if language is TypeScript
669
+ if (language === "typescript") {
670
+ if (!arch.backend.devDependencies) arch.backend.devDependencies = [];
671
+ const requiredDevDeps = ["typescript", "tsx", "@types/node"];
672
+
673
+ // Add type definitions for common packages if they are being used
674
+ const allBackendDeps = [...(arch.backend.dependencies || []), ...(arch.backend.devDependencies || [])];
675
+ if (allBackendDeps.some(d => d.includes("express"))) requiredDevDeps.push("@types/express");
676
+ if (allBackendDeps.some(d => d.includes("cors"))) requiredDevDeps.push("@types/cors");
677
+ if (allBackendDeps.some(d => d.includes("mongoose"))) requiredDevDeps.push("@types/mongoose");
678
+ if (allBackendDeps.some(d => d.includes("morgan"))) requiredDevDeps.push("@types/morgan");
679
+ if (allBackendDeps.some(d => d.includes("jsonwebtoken"))) requiredDevDeps.push("@types/jsonwebtoken");
680
+ if (allBackendDeps.some(d => d.includes("bcrypt"))) requiredDevDeps.push("@types/bcryptjs");
681
+
682
+ requiredDevDeps.forEach(dep => {
683
+ // Check if dependency is already included (checking by name, ignoring version)
684
+ const isIncluded = arch.backend.devDependencies.some(d => {
685
+ const depName = d.split("@")[0] || d; // Handle versioned deps like "dep@1.0.0" (simple check)
686
+ return d.includes(dep);
687
+ });
688
+
689
+ if (!isIncluded) {
690
+ arch.backend.devDependencies.push(dep);
691
+ }
692
+ });
693
+
694
+ // Also ensure start/dev scripts are correct in the architecture so they don't get overwritten incorrectly later?
695
+ // Actually we handle scripts in the final package.json replacement, but adding them to arch helps if we used arch.scripts
696
+ }
697
+
698
+ if (arch.backend.dependencies?.length > 0 || arch.backend.devDependencies?.length > 0) {
699
+ console.log("Installing backend dependencies...");
700
+ installDependencies(
701
+ backendPath,
702
+ arch.backend.dependencies || [],
703
+ arch.backend.devDependencies || []
704
+ );
705
+ }
706
+
707
+ // Override package.json with our template that has proper scripts
708
+ const backendPackageJsonPath = path.join(backendPath, "package.json");
709
+ if (fs.existsSync(backendPackageJsonPath)) {
710
+ const existingPackage = JSON.parse(fs.readFileSync(backendPackageJsonPath, "utf-8"));
711
+ const updatedPackage = {
712
+ name: existingPackage.name || projectName.toLowerCase().replace(/\s+/g, "-"),
713
+ version: existingPackage.version || "1.0.0",
714
+ description: existingPackage.description || "",
715
+ type: "module",
716
+ main: existingPackage.main || "index.js",
717
+ scripts: {
718
+ start: language === "typescript" ? "node dist/index.js" : "node index.js",
719
+ dev: language === "typescript" ? "nodemon --exec tsx index.ts" : "nodemon index.js"
720
+ },
721
+ keywords: existingPackage.keywords || [],
722
+ author: existingPackage.author || "",
723
+ license: existingPackage.license || "ISC",
724
+ dependencies: existingPackage.dependencies || {},
725
+ devDependencies: {
726
+ ...(existingPackage.devDependencies || {}),
727
+ ...(language === "typescript" ? {
728
+ "nodemon": "^3.1.11",
729
+ "tsx": "^4.10.0"
730
+ } : {
731
+ "nodemon": "^3.1.11"
732
+ })
733
+ }
734
+ };
735
+
736
+ fs.writeFileSync(backendPackageJsonPath, JSON.stringify(updatedPackage, null, 2));
737
+ console.log("Backend package.json configured with proper scripts");
738
+ }
739
+ }
740
+
741
+ console.log("Project generated successfully!");
742
+ console.log(`\nProject location: ${projectPath}`);
743
+ console.log("\nNext steps:");
744
+ if (hasFrontend) {
745
+ console.log(` cd ${projectName}/frontend \n npm run dev`);
746
+ }
747
+ if (hasBackend) {
748
+ console.log(` cd ${projectName}/backend \n npm start`);
749
+ }
750
+ if (!hasFrontend && !hasBackend) {
751
+ console.log(` cd ${projectName} \n npm install`);
752
+ }
753
+ }
754
+
755
+ generateProject().catch(error => {
756
+ console.error("Fatal error:", error.message);
757
+ process.exit(1);
758
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "boil-gen",
3
+ "version": "1.0.0",
4
+ "description": "CLI to scaffold a standard MERN backend boilerplate",
5
+ "bin": {
6
+ "boil-gen": "./index.js"
7
+ },
8
+ "type": "module",
9
+ "main": "index.js",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [
14
+ "mern",
15
+ "cli",
16
+ "express",
17
+ "mongodb",
18
+ "boilerplate"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Rohanbhandari9703/mern-gen.git"
23
+ },
24
+ "author": "Rohan Bhandari",
25
+ "license": "ISC",
26
+ "dependencies": {
27
+ "@google/genai": "^1.37.0",
28
+ "dotenv": "^17.2.3",
29
+ "env": "^0.0.2"
30
+ }
31
+ }