create-stackflow 1.0.3 → 1.0.5

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.
package/cli.js CHANGED
@@ -8,7 +8,7 @@ const program = new Command();
8
8
  program
9
9
  .name("create-stackflow")
10
10
  .description("Generate a production-minded MERN starter with frontend, backend, auth, CRUD, and dashboard UI.")
11
- .version("1.0.3")
11
+ .version("1.0.5")
12
12
  .argument("[project-name]", "project folder name")
13
13
  .option("--skip-install", "generate files without installing dependencies")
14
14
  .option("--yes", "use recommended defaults")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-stackflow",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Interactive CLI for generating modern full-stack MERN applications.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,9 +38,10 @@ export async function createStackFlow(options) {
38
38
  projectDir,
39
39
  frontendDir: path.join(projectDir, answers.frontendName || "frontend"),
40
40
  backendDir: path.join(projectDir, answers.backendName || "backend"),
41
- skipInstall: Boolean(options.skipInstall),
41
+ skipInstall: answers.runProject ? false : Boolean(options.skipInstall),
42
+ runProject: Boolean(answers.runProject),
42
43
  databaseName: projectName.replace(/[^a-zA-Z0-9_-]/g, ""),
43
- packageManager: "npm"
44
+ packageManager: "npm",
44
45
  };
45
46
 
46
47
  await fs.ensureDir(projectDir);
@@ -51,11 +52,21 @@ export async function createStackFlow(options) {
51
52
 
52
53
  if (!context.skipInstall) {
53
54
  await step("Installing root workspace dependencies", () =>
54
- execa("npm", ["install"], { cwd: projectDir, stdio: "ignore" })
55
+ execa("npm", ["install"], { cwd: projectDir, stdio: "ignore" }),
55
56
  );
56
57
  }
57
58
 
58
59
  printSuccess(context);
60
+
61
+ if (context.runProject) {
62
+ console.log(chalk.cyan("\nšŸš€ Starting the project..."));
63
+ try {
64
+ await execa("npm", ["run", "dev"], { cwd: projectDir, stdio: "inherit" });
65
+ } catch (error) {
66
+ console.error(chalk.red("\nFailed to start the project."));
67
+ console.error(chalk.dim(error.message));
68
+ }
69
+ }
59
70
  }
60
71
 
61
72
  function recommendedDefaults(projectName) {
@@ -109,8 +120,17 @@ function recommendedDefaults(projectName) {
109
120
  hpp: true,
110
121
  emailService: "None",
111
122
  crudModules: ["users", "products", "categories", "orders", "blogs"],
112
- authFeatures: ["login", "register", "forgot-password", "reset-password", "email-verification", "roles", "refresh-tokens"],
113
- adminDashboard: true
123
+ authFeatures: [
124
+ "login",
125
+ "register",
126
+ "forgot-password",
127
+ "reset-password",
128
+ "email-verification",
129
+ "roles",
130
+ "refresh-tokens",
131
+ ],
132
+ adminDashboard: true,
133
+ runProject: false,
114
134
  };
115
135
  }
116
136
 
@@ -128,7 +148,11 @@ async function step(text, task) {
128
148
  function printBanner() {
129
149
  console.log();
130
150
  console.log(chalk.cyan.bold("create-stackflow"));
131
- console.log(chalk.dim("Modern MERN app generator with auth, CRUD, dashboard, and production-ready structure."));
151
+ console.log(
152
+ chalk.dim(
153
+ "Modern MERN app generator with auth, CRUD, dashboard, and production-ready structure.",
154
+ ),
155
+ );
132
156
  console.log();
133
157
  }
134
158
 
@@ -144,6 +168,8 @@ function printSuccess(context) {
144
168
  console.log();
145
169
  console.log(chalk.dim(`Frontend: ${frontend}`));
146
170
  console.log(chalk.dim(`Backend: ${backend}`));
147
- console.log(chalk.dim("MongoDB: mongodb://127.0.0.1:27017/" + context.databaseName));
171
+ console.log(
172
+ chalk.dim("MongoDB: mongodb://127.0.0.1:27017/" + context.databaseName),
173
+ );
148
174
  console.log();
149
175
  }
@@ -125,6 +125,13 @@ COOKIE_NAME=stackflow_token
125
125
  if (context.multer) await fs.outputFile(path.join(context.backendDir, "uploads", ".gitkeep"), "");
126
126
 
127
127
  const files = backendTemplates({ ...context, language: backendContext.language, clientUrl }, e);
128
+
129
+ // Filter out unused configuration files
130
+ if (!context.cloudinary) delete files[`src/config/cloudinary.${e}`];
131
+ if (!context.multer) delete files[`src/services/uploadService.${e}`];
132
+ if (!context.winston) delete files[`src/utils/logger.${e}`];
133
+ if (!context.swagger) delete files[`src/config/swagger.${e}`];
134
+
128
135
  await Promise.all(Object.entries(files).map(([file, content]) =>
129
136
  writeTemplate(path.join(context.backendDir, file), content, context)
130
137
  ));
@@ -188,9 +195,9 @@ ${context.rateLimit === false ? "" : "import rateLimit from \"express-rate-limit
188
195
  ${context.hpp ? "import hpp from \"hpp\";" : ""}
189
196
  ${imports.morgan}
190
197
  ${imports.swagger}
191
- import apiRoutes from "./routes/index.routes.js";
192
- import { errorHandler } from "./middleware/error.middleware.js";
193
- import { notFound } from "./middleware/not-found.middleware.js";
198
+ import apiRoutes from "./routes/mainRoute.js";
199
+ import { errorHandler } from "./middleware/errorMiddleware.js";
200
+ import { notFound } from "./middleware/notFoundMiddleware.js";
194
201
 
195
202
  const app = express();
196
203
 
@@ -228,7 +235,7 @@ export async function connectDB() {
228
235
  ${context.winston ? "logger.info(\"MongoDB connected\");" : "console.log(\"MongoDB connected\");"}
229
236
  }
230
237
  `,
231
- [`src/models/user.model.${e}`]: `import mongoose from "mongoose";
238
+ [`src/models/userModel.${e}`]: `import mongoose from "mongoose";
232
239
  import bcrypt from "bcryptjs";
233
240
 
234
241
  const userSchema = new mongoose.Schema({
@@ -248,9 +255,9 @@ userSchema.methods.comparePassword = function(candidatePassword) {
248
255
 
249
256
  export const User = mongoose.model("User", userSchema);
250
257
  `,
251
- [`src/models/task.model.${e}`]: `import mongoose from "mongoose";
258
+ [`src/models/todoModel.${e}`]: `import mongoose from "mongoose";
252
259
 
253
- const taskSchema = new mongoose.Schema({
260
+ const todoSchema = new mongoose.Schema({
254
261
  title: { type: String, required: true, trim: true },
255
262
  description: { type: String, default: "" },
256
263
  imageUrl: { type: String, default: "" },
@@ -258,7 +265,7 @@ const taskSchema = new mongoose.Schema({
258
265
  owner: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
259
266
  }, { timestamps: true });
260
267
 
261
- export const Task = mongoose.model("Task", taskSchema);
268
+ export const Todo = mongoose.model("Todo", todoSchema);
262
269
  `,
263
270
  [`src/utils/jwt.${e}`]: `import jwt from "jsonwebtoken";
264
271
 
@@ -277,8 +284,8 @@ export function setAuthCookie(res, token) {
277
284
  });
278
285
  }
279
286
  `,
280
- [`src/middleware/auth.middleware.${e}`]: `${reqRes}import jwt from "jsonwebtoken";
281
- import { User } from "../models/user.model.js";
287
+ [`src/middleware/authMiddleware.${e}`]: `${reqRes}import jwt from "jsonwebtoken";
288
+ import { User } from "../models/userModel.js";
282
289
 
283
290
  export async function protect(req${type("Request & { user?: unknown }")}, res${type("Response")}, next${type("NextFunction")}) {
284
291
  try {
@@ -301,7 +308,7 @@ export async function protect(req${type("Request & { user?: unknown }")}, res${t
301
308
  }
302
309
  }
303
310
  `,
304
- [`src/middleware/error.middleware.${e}`]: `${reqRes}export function errorHandler(error${type("Error & { statusCode?: number }")}, _req${type("Request")}, res${type("Response")}, _next${type("NextFunction")}) {
311
+ [`src/middleware/errorMiddleware.${e}`]: `${reqRes}export function errorHandler(error${type("Error & { statusCode?: number }")}, _req${type("Request")}, res${type("Response")}, _next${type("NextFunction")}) {
305
312
  const status = error.statusCode || 500;
306
313
  res.status(status).json({
307
314
  message: error.message || "Server error",
@@ -309,11 +316,11 @@ export async function protect(req${type("Request & { user?: unknown }")}, res${t
309
316
  });
310
317
  }
311
318
  `,
312
- [`src/middleware/not-found.middleware.${e}`]: `${reqRes}export function notFound(req${type("Request")}, res${type("Response")}) {
319
+ [`src/middleware/notFoundMiddleware.${e}`]: `${reqRes}export function notFound(req${type("Request")}, res${type("Response")}) {
313
320
  res.status(404).json({ message: \`Route not found: \${req.originalUrl}\` });
314
321
  }
315
322
  `,
316
- [`src/controllers/auth.controller.${e}`]: `import { User } from "../models/user.model.js";
323
+ [`src/controllers/authController.${e}`]: `import { User } from "../models/userModel.js";
317
324
  import { signToken, setAuthCookie } from "../utils/jwt.js";
318
325
 
319
326
  function publicUser(user) {
@@ -358,56 +365,93 @@ export async function logout(_req, res) {
358
365
  res.json({ message: "Logged out" });
359
366
  }
360
367
  `,
361
- [`src/controllers/task.controller.${e}`]: `import { Task } from "../models/task.model.js";
368
+ [`src/controllers/todoController.${e}`]: `import { Todo } from "../models/todoModel.js";
362
369
 
363
- export async function listTasks(req, res, next) {
364
- try {
365
- const tasks = await Task.find({ owner: req.user._id }).sort({ createdAt: -1 });
366
- res.json({ tasks });
367
- } catch (error) {
368
- next(error);
369
- }
370
- }
370
+ const todoController = {
371
+ // Get All Todos
372
+ getTodos: async (req, res) => {
373
+ try {
374
+ const todos = await Todo.find({ owner: req.user._id }).sort({ createdAt: -1 });
375
+ res.status(200).json({
376
+ success: true,
377
+ message: "Todos fetched successfully",
378
+ data: todos
379
+ });
380
+ } catch (error) {
381
+ res.status(500).json({
382
+ success: false,
383
+ message: error.message
384
+ });
385
+ }
386
+ },
371
387
 
372
- export async function createTask(req, res, next) {
373
- try {
374
- const imageUrl = req.file ? \`/uploads/\${req.file.filename}\` : "";
375
- const task = await Task.create({ ...req.body, imageUrl, owner: req.user._id });
376
- res.status(201).json({ task });
377
- } catch (error) {
378
- next(error);
379
- }
380
- }
388
+ // Create Todo
389
+ createTodo: async (req, res) => {
390
+ try {
391
+ const imageUrl = req.file ? \`/uploads/\${req.file.filename}\` : "";
392
+ const newTodo = await Todo.create({ ...req.body, imageUrl, owner: req.user._id });
393
+ res.status(201).json({
394
+ success: true,
395
+ message: "Todo created successfully",
396
+ data: newTodo
397
+ });
398
+ } catch (error) {
399
+ res.status(500).json({
400
+ success: false,
401
+ message: error.message
402
+ });
403
+ }
404
+ },
381
405
 
382
- export async function updateTask(req, res, next) {
383
- try {
384
- const updates = { ...req.body };
385
- if (req.file) updates.imageUrl = \`/uploads/\${req.file.filename}\`;
386
- const task = await Task.findOneAndUpdate(
387
- { _id: req.params.id, owner: req.user._id },
388
- updates,
389
- { new: true, runValidators: true }
390
- );
391
- if (!task) return res.status(404).json({ message: "Task not found" });
392
- res.json({ task });
393
- } catch (error) {
394
- next(error);
395
- }
396
- }
406
+ // Update Todo
407
+ updateTodo: async (req, res) => {
408
+ try {
409
+ const { id } = req.params;
410
+ const updates = { ...req.body };
411
+ if (req.file) updates.imageUrl = \`/uploads/\${req.file.filename}\`;
412
+ const todo = await Todo.findOneAndUpdate(
413
+ { _id: id, owner: req.user._id },
414
+ updates,
415
+ { new: true, runValidators: true }
416
+ );
417
+ if (!todo) return res.status(404).json({ success: false, message: "Todo not found" });
418
+ res.status(200).json({
419
+ success: true,
420
+ message: \`Todo \${id} updated successfully\`,
421
+ data: todo
422
+ });
423
+ } catch (error) {
424
+ res.status(500).json({
425
+ success: false,
426
+ message: error.message
427
+ });
428
+ }
429
+ },
397
430
 
398
- export async function deleteTask(req, res, next) {
399
- try {
400
- const task = await Task.findOneAndDelete({ _id: req.params.id, owner: req.user._id });
401
- if (!task) return res.status(404).json({ message: "Task not found" });
402
- res.json({ message: "Task deleted" });
403
- } catch (error) {
404
- next(error);
431
+ // Delete Todo
432
+ deleteTodo: async (req, res) => {
433
+ try {
434
+ const { id } = req.params;
435
+ const todo = await Todo.findOneAndDelete({ _id: id, owner: req.user._id });
436
+ if (!todo) return res.status(404).json({ success: false, message: "Todo not found" });
437
+ res.status(200).json({
438
+ success: true,
439
+ message: \`Todo \${id} deleted successfully\`
440
+ });
441
+ } catch (error) {
442
+ res.status(500).json({
443
+ success: false,
444
+ message: error.message
445
+ });
446
+ }
405
447
  }
406
- }
448
+ };
449
+
450
+ export default todoController;
407
451
  `,
408
- [`src/routes/auth.routes.${e}`]: `import { Router } from "express";
409
- import { login, logout, me, register } from "../controllers/auth.controller.js";
410
- import { protect } from "../middleware/auth.middleware.js";
452
+ [`src/routes/authRoute.${e}`]: `import { Router } from "express";
453
+ import { login, logout, me, register } from "../controllers/authController.js";
454
+ import { protect } from "../middleware/authMiddleware.js";
411
455
 
412
456
  const router = Router();
413
457
 
@@ -418,33 +462,34 @@ router.post("/logout", logout);
418
462
 
419
463
  export default router;
420
464
  `,
421
- [`src/routes/index.routes.${e}`]: `import { Router } from "express";
422
- import authRoutes from "./auth.routes.js";
423
- import taskRoutes from "./task.routes.js";
465
+ [`src/routes/mainRoute.${e}`]: `import { Router } from "express";
466
+ import authRoutes from "./authRoute.js";
467
+ import todoRoutes from "./todoRoute.js";
424
468
 
425
469
  const router = Router();
426
470
 
427
471
  router.use("/auth", authRoutes);
428
- router.use("/tasks", taskRoutes);
472
+ router.use("/todos", todoRoutes);
429
473
 
430
474
  export default router;
431
475
  `,
432
- [`src/routes/task.routes.${e}`]: `import { Router } from "express";
433
- import { createTask, deleteTask, listTasks, updateTask } from "../controllers/task.controller.js";
434
- import { protect } from "../middleware/auth.middleware.js";
435
- ${context.multer ? "import { upload } from \"../services/upload.service.js\";" : ""}
476
+ [`src/routes/todoRoute.${e}`]: `import { Router } from "express";
477
+ import todoController from "../controllers/todoController.js";
478
+ import { protect } from "../middleware/authMiddleware.js";
479
+ ${context.multer ? "import { upload } from \"../services/uploadService.js\";" : ""}
436
480
 
437
481
  const router = Router();
438
482
 
439
483
  router.use(protect);
440
- router.get("/", listTasks);
441
- router.post("/", ${context.multer ? "upload.single(\"image\"), " : ""}createTask);
442
- router.patch("/:id", ${context.multer ? "upload.single(\"image\"), " : ""}updateTask);
443
- router.delete("/:id", deleteTask);
484
+
485
+ router.get("/", todoController.getTodos);
486
+ router.post("/", ${context.multer ? "upload.single(\"image\"), " : ""}todoController.createTodo);
487
+ router.put("/:id", ${context.multer ? "upload.single(\"image\"), " : ""}todoController.updateTodo);
488
+ router.delete("/:id", todoController.deleteTodo);
444
489
 
445
490
  export default router;
446
491
  `,
447
- [`src/services/upload.service.${e}`]: context.multer ? `import multer from "multer";
492
+ [`src/services/uploadService.${e}`]: context.multer ? `import multer from "multer";
448
493
 
449
494
  const storage = multer.diskStorage({
450
495
  destination: "uploads/",
@@ -456,7 +501,7 @@ const storage = multer.diskStorage({
456
501
  export const upload = multer({ storage });
457
502
  ` : `export {};
458
503
  `,
459
- [`src/validations/auth.validation.${e}`]: `export const authValidation = {
504
+ [`src/validations/authValidation.${e}`]: `export const authValidation = {
460
505
  register: ["name", "email", "password"],
461
506
  login: ["email", "password"]
462
507
  };
@@ -122,11 +122,11 @@ async function writeReactOverlay(context) {
122
122
  const src = path.join(context.frontendDir, "src");
123
123
  await fs.ensureDir(path.join(src, "components", "ui"));
124
124
  await fs.ensureDir(path.join(src, "features", "auth"));
125
- await fs.ensureDir(path.join(src, "features", "tasks"));
125
+ await fs.ensureDir(path.join(src, "features", "todos"));
126
126
  await fs.ensureDir(path.join(src, "lib"));
127
127
  await fs.ensureDir(path.join(src, "pages"));
128
128
  await fs.ensureDir(path.join(src, "routes"));
129
- await fs.ensureDir(path.join(src, "store"));
129
+ if (context.state !== "none") await fs.ensureDir(path.join(src, "store"));
130
130
 
131
131
  await writeTemplate(path.join(context.frontendDir, ".env"), "VITE_API_URL=http://localhost:5000/api\n");
132
132
  await relaxTypescriptConfig(context, "react");
@@ -145,6 +145,9 @@ export default defineConfig({
145
145
  }
146
146
 
147
147
  const files = reactTemplates(context, x);
148
+ if (context.state === "none") delete files[`store/auth.${x}`];
149
+ if (context.validation === "none") delete files[`lib/validation.${context.language === "typescript" ? "ts" : "js"}`];
150
+
148
151
  await Promise.all(Object.entries(files).map(([file, content]) => writeTemplate(path.join(src, file), content, context)));
149
152
  if (context.language === "typescript") {
150
153
  await writeTemplate(path.join(src, "vite-env.d.ts"), `/// <reference types="vite/client" />
@@ -160,14 +163,17 @@ async function writeNextOverlay(context) {
160
163
  await fs.ensureDir(path.join(src, "app", "register"));
161
164
  await fs.ensureDir(path.join(src, "components", "ui"));
162
165
  await fs.ensureDir(path.join(src, "features", "auth"));
163
- await fs.ensureDir(path.join(src, "features", "tasks"));
166
+ await fs.ensureDir(path.join(src, "features", "todos"));
164
167
  await fs.ensureDir(path.join(src, "lib"));
165
- await fs.ensureDir(path.join(src, "store"));
168
+ if (context.state !== "none") await fs.ensureDir(path.join(src, "store"));
166
169
 
167
170
  await removeNextGeneratedDuplicates(context, x);
168
171
  await writeTemplate(path.join(context.frontendDir, ".env.local"), "NEXT_PUBLIC_API_URL=http://localhost:5000/api\n");
169
172
  await relaxTypescriptConfig(context, "next");
170
173
  const files = nextTemplates(context, x);
174
+ if (context.state === "none") delete files[`store/auth.${x}`];
175
+ if (context.validation === "none") delete files[`lib/validation.${context.language === "typescript" ? "ts" : "js"}`];
176
+
171
177
  await Promise.all(Object.entries(files).map(([file, content]) => writeTemplate(path.join(src, file), content, context)));
172
178
  }
173
179
 
@@ -241,11 +247,11 @@ async function relaxTypescriptConfig(context, framework) {
241
247
  function commonTemplates(context, x) {
242
248
  const isTs = context.language === "typescript";
243
249
  const typeBlock = isTs ? `export type User = { id: string; name: string; email: string };
244
- export type Task = { _id: string; title: string; description?: string; imageUrl?: string; status: "todo" | "in-progress" | "done" };
250
+ export type Todo = { _id: string; title: string; description?: string; imageUrl?: string; status: "todo" | "in-progress" | "done" };
245
251
  ` : "";
246
252
  const schema = context.validation === "none" || context.validation === "joi"
247
253
  ? `export const authSchema = null;
248
- export const taskSchema = null;
254
+ export const todoSchema = null;
249
255
  `
250
256
  : context.validation === "zod"
251
257
  ? `import { z } from "zod";
@@ -256,7 +262,7 @@ export const authSchema = z.object({
256
262
  password: z.string().min(6)
257
263
  });
258
264
 
259
- export const taskSchema = z.object({
265
+ export const todoSchema = z.object({
260
266
  title: z.string().min(2),
261
267
  description: z.string().optional(),
262
268
  status: z.enum(["todo", "in-progress", "done"]).default("todo")
@@ -270,7 +276,7 @@ export const authSchema = yup.object({
270
276
  password: yup.string().min(6).required()
271
277
  });
272
278
 
273
- export const taskSchema = yup.object({
279
+ export const todoSchema = yup.object({
274
280
  title: yup.string().min(2).required(),
275
281
  description: yup.string(),
276
282
  status: yup.string().oneOf(["todo", "in-progress", "done"]).default("todo")
@@ -354,7 +360,7 @@ api.interceptors.request.use((config) => {
354
360
  `,
355
361
  [`lib/validation.${isTs ? "ts" : "js"}`]: schema,
356
362
  [`store/auth.${x}`]: authStore,
357
- [`features/auth/auth.service.${isTs ? "ts" : "js"}`]: `import { api } from "../../lib/api";
363
+ [`features/auth/authService.${isTs ? "ts" : "js"}`]: `import { api } from "../../lib/api";
358
364
 
359
365
  export const authService = {
360
366
  register: (payload) => api.post("/auth/register", payload).then((res) => res.data),
@@ -363,13 +369,13 @@ export const authService = {
363
369
  logout: () => api.post("/auth/logout").then((res) => res.data)
364
370
  };
365
371
  `,
366
- [`features/tasks/task.service.${isTs ? "ts" : "js"}`]: `import { api } from "../../lib/api";
372
+ [`features/todos/todoService.${isTs ? "ts" : "js"}`]: `import { api } from "../../lib/api";
367
373
 
368
- export const taskService = {
369
- list: () => api.get("/tasks").then((res) => res.data.tasks),
370
- create: (payload) => api.post("/tasks", payload, multipartConfig(payload)).then((res) => res.data.task),
371
- update: (id, payload) => api.patch(\`/tasks/\${id}\`, payload, multipartConfig(payload)).then((res) => res.data.task),
372
- remove: (id) => api.delete(\`/tasks/\${id}\`).then((res) => res.data)
374
+ export const todoService = {
375
+ list: () => api.get("/todos").then((res) => res.data.data),
376
+ create: (payload) => api.post("/todos", payload, multipartConfig(payload)).then((res) => res.data.data),
377
+ update: (id, payload) => api.put(\`/todos/\${id}\`, payload, multipartConfig(payload)).then((res) => res.data.data),
378
+ remove: (id) => api.delete(\`/todos/\${id}\`).then((res) => res.data)
373
379
  };
374
380
 
375
381
  function multipartConfig(payload) {
@@ -457,13 +463,10 @@ export default function App() {
457
463
  }
458
464
  `}`,
459
465
  [`routes/ProtectedRoute.${x}`]: `import { Navigate } from "react-router-dom";
460
- ${context.state === "zustand" ? "import { useAuthStore } from \"../store/auth\";" : ""}
461
- ${context.state === "context-api" ? "import { useAuthContext } from \"../store/auth\";" : ""}
462
- ${context.state === "redux-toolkit" ? "import { useSelector } from \"react-redux\";" : ""}
463
466
 
464
467
  export function ProtectedRoute({ children }) {
465
- const token = ${context.state === "zustand" ? "useAuthStore((state) => state.token)" : context.state === "context-api" ? "useAuthContext()?.token" : context.state === "redux-toolkit" ? "useSelector((state) => state.auth.token)" : "localStorage.getItem(\"stackflow_token\")"};
466
- return token ? children : <Navigate to="/login" replace />;
468
+ const isAuthenticated = Boolean(localStorage.getItem("stackflow_token"));
469
+ return isAuthenticated ? children : <Navigate to="/login" replace />;
467
470
  }
468
471
  `,
469
472
  [`pages/Login.${x}`]: authPage("login", context),
@@ -530,12 +533,20 @@ ${navImport}
530
533
  import { toast } from "sonner";
531
534
  import { Button } from "${next ? "../../components/ui/button" : "../components/ui/button"}";
532
535
  import { Input } from "${next ? "../../components/ui/input" : "../components/ui/input"}";
533
- import { authService } from "${next ? "../../features/auth/auth.service" : "../features/auth/auth.service"}";
536
+ import { authService } from "${next ? "../../features/auth/authService" : "../features/auth/authService"}";
534
537
  ${context.state === "zustand" ? `import { useAuthStore } from "${next ? "../../store/auth" : "../store/auth"}";` : ""}
538
+ ${context.state === "context-api" ? `import { useAuthContext } from "${next ? "../../store/auth" : "../store/auth"}";` : ""}
539
+ ${context.state === "redux-toolkit" ? `import { useDispatch } from "react-redux";\nimport { setSession as setReduxSession } from "${next ? "../../store/auth" : "../store/auth"}";` : ""}
535
540
 
536
541
  export function ${isRegister ? "Register" : "Login"}() {
537
542
  ${navHook}
538
- const setSession = ${context.state === "zustand" ? "useAuthStore((state) => state.setSession)" : "({ user, token }) => localStorage.setItem(\"stackflow_token\", token)"};
543
+ ${context.state === "redux-toolkit" ? "const dispatch = useDispatch();" : ""}
544
+ const setSession = ${
545
+ context.state === "zustand" ? "useAuthStore((state) => state.setSession)" :
546
+ context.state === "context-api" ? "useAuthContext().setSession" :
547
+ context.state === "redux-toolkit" ? "(data) => dispatch(setReduxSession(data))" :
548
+ "({ user, token }) => localStorage.setItem(\"stackflow_token\", token)"
549
+ };
539
550
  const [loading, setLoading] = useState(false);
540
551
  const [form, setForm] = useState({ name: "", email: "", password: "" });
541
552
 
@@ -583,15 +594,23 @@ import { Edit3, ImagePlus, LogOut, Moon, Plus, Save, Sun, Trash2, X } from "luci
583
594
  import { toast } from "sonner";
584
595
  import { Button } from "${next ? "../../components/ui/button" : "../components/ui/button"}";
585
596
  import { Input } from "${next ? "../../components/ui/input" : "../components/ui/input"}";
586
- import { taskService } from "${next ? "../../features/tasks/task.service" : "../features/tasks/task.service"}";
597
+ import { todoService } from "${next ? "../../features/todos/todoService" : "../features/todos/todoService"}";
587
598
  ${context.state === "zustand" ? `import { useAuthStore } from "${next ? "../../store/auth" : "../store/auth"}";` : ""}
599
+ ${context.state === "context-api" ? `import { useAuthContext } from "${next ? "../../store/auth" : "../store/auth"}";` : ""}
600
+ ${context.state === "redux-toolkit" ? `import { useDispatch } from "react-redux";\nimport { logout as reduxLogout } from "${next ? "../../store/auth" : "../store/auth"}";` : ""}
588
601
 
589
602
  ${componentName} {
590
603
  const ${next ? "router" : "navigate"} = ${next ? "useRouter()" : "useNavigate()"};
591
- const logoutStore = ${context.state === "zustand" ? "useAuthStore((state) => state.logout)" : "() => localStorage.removeItem(\"stackflow_token\")"};
604
+ ${context.state === "redux-toolkit" ? "const dispatch = useDispatch();" : ""}
605
+ const logoutStore = ${
606
+ context.state === "zustand" ? "useAuthStore((state) => state.logout)" :
607
+ context.state === "context-api" ? "useAuthContext().logout" :
608
+ context.state === "redux-toolkit" ? "() => dispatch(reduxLogout())" :
609
+ "() => localStorage.removeItem(\"stackflow_token\")"
610
+ };
592
611
  const [dark, setDark] = useState(() => typeof window !== "undefined" && localStorage.getItem("stackflow_theme") === "dark");
593
612
  const [loading, setLoading] = useState(true);
594
- const [tasks, setTasks] = useState([]);
613
+ const [todos, setTodos] = useState([]);
595
614
  const [form, setForm] = useState({ title: "", description: "", status: "todo", image: null });
596
615
  const [editingId, setEditingId] = useState(null);
597
616
 
@@ -601,30 +620,30 @@ ${componentName} {
601
620
  }, [dark]);
602
621
 
603
622
  useEffect(() => {
604
- taskService.list()
605
- .then(setTasks)
606
- .catch(() => toast.error("Could not load tasks"))
623
+ todoService.list()
624
+ .then(setTodos)
625
+ .catch(() => toast.error("Could not load todos"))
607
626
  .finally(() => setLoading(false));
608
627
  }, []);
609
628
 
610
- async function createTask(event) {
629
+ async function createTodo(event) {
611
630
  event.preventDefault();
612
631
  if (!form.title.trim()) return;
613
632
  const payload = toPayload(form);
614
633
  const saved = editingId
615
- ? await taskService.update(editingId, payload)
616
- : await taskService.create(payload);
617
- setTasks(editingId ? tasks.map((task) => task._id === editingId ? saved : task) : [saved, ...tasks]);
634
+ ? await todoService.update(editingId, payload)
635
+ : await todoService.create(payload);
636
+ setTodos(editingId ? todos.map((todo) => todo._id === editingId ? saved : todo) : [saved, ...todos]);
618
637
  resetForm();
619
- toast.success(editingId ? "Task updated" : "Task created");
638
+ toast.success(editingId ? "Todo updated" : "Todo created");
620
639
  }
621
640
 
622
- function startEdit(task) {
623
- setEditingId(task._id);
641
+ function startEdit(todo) {
642
+ setEditingId(todo._id);
624
643
  setForm({
625
- title: task.title || "",
626
- description: task.description || "",
627
- status: task.status || "todo",
644
+ title: todo.title || "",
645
+ description: todo.description || "",
646
+ status: todo.status || "todo",
628
647
  image: null
629
648
  });
630
649
  }
@@ -634,10 +653,10 @@ ${componentName} {
634
653
  setForm({ title: "", description: "", status: "todo", image: null });
635
654
  }
636
655
 
637
- async function removeTask(id) {
638
- await taskService.remove(id);
639
- setTasks(tasks.filter((task) => task._id !== id));
640
- toast.success("Task deleted");
656
+ async function removeTodo(id) {
657
+ await todoService.remove(id);
658
+ setTodos(todos.filter((todo) => todo._id !== id));
659
+ toast.success("Todo deleted");
641
660
  }
642
661
 
643
662
  function logout() {
@@ -675,37 +694,37 @@ ${componentName} {
675
694
  <section className="mx-auto grid max-w-6xl gap-6 px-4 py-8 md:grid-cols-[1fr_320px]">
676
695
  <div className="rounded-lg border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900">
677
696
  <div className="mb-4 flex items-center justify-between">
678
- <h2 className="text-lg font-semibold">Tasks</h2>
679
- <span className="text-sm text-slate-500">{tasks.length} total</span>
697
+ <h2 className="text-lg font-semibold">Todos</h2>
698
+ <span className="text-sm text-slate-500">{todos.length} total</span>
680
699
  </div>
681
- {loading ? <p className="text-sm text-slate-500">Loading tasks...</p> : (
700
+ {loading ? <p className="text-sm text-slate-500">Loading todos...</p> : (
682
701
  <div className="space-y-3">
683
- {tasks.map((task) => (
684
- <div key={task._id} className="grid gap-3 rounded-md border border-slate-200 p-3 dark:border-slate-800 sm:grid-cols-[96px_1fr_auto]">
702
+ {todos.map((todo) => (
703
+ <div key={todo._id} className="grid gap-3 rounded-md border border-slate-200 p-3 dark:border-slate-800 sm:grid-cols-[96px_1fr_auto]">
685
704
  <div className="h-24 overflow-hidden rounded-md bg-slate-100 dark:bg-slate-800">
686
- {task.imageUrl ? <img className="h-full w-full object-cover" src={\`http://localhost:5000\${task.imageUrl}\`} alt={task.title} /> : <div className="grid h-full place-items-center text-slate-400"><ImagePlus size={20} /></div>}
705
+ {todo.imageUrl ? <img className="h-full w-full object-cover" src={\`http://localhost:5000\${todo.imageUrl}\`} alt={todo.title} /> : <div className="grid h-full place-items-center text-slate-400"><ImagePlus size={20} /></div>}
687
706
  </div>
688
707
  <div>
689
- <p className="font-medium">{task.title}</p>
690
- <p className="mt-1 text-sm text-slate-500">{task.description || "No description"}</p>
691
- <p className="mt-2 text-xs uppercase tracking-wide text-slate-500">{task.status}</p>
708
+ <p className="font-medium">{todo.title}</p>
709
+ <p className="mt-1 text-sm text-slate-500">{todo.description || "No description"}</p>
710
+ <p className="mt-2 text-xs uppercase tracking-wide text-slate-500">{todo.status}</p>
692
711
  </div>
693
712
  <div className="flex items-start gap-2">
694
- <Button variant="ghost" onClick={() => startEdit(task)} aria-label="Edit task"><Edit3 size={16} /></Button>
695
- <Button variant="ghost" onClick={() => removeTask(task._id)} aria-label="Delete task"><Trash2 size={16} /></Button>
713
+ <Button variant="ghost" onClick={() => startEdit(todo)} aria-label="Edit todo"><Edit3 size={16} /></Button>
714
+ <Button variant="ghost" onClick={() => removeTodo(todo._id)} aria-label="Delete todo"><Trash2 size={16} /></Button>
696
715
  </div>
697
716
  </div>
698
717
  ))}
699
- {!tasks.length && <p className="text-sm text-slate-500">No tasks yet.</p>}
718
+ {!todos.length && <p className="text-sm text-slate-500">No todos yet.</p>}
700
719
  </div>
701
720
  )}
702
721
  </div>
703
- <form onSubmit={createTask} className="h-fit space-y-3 rounded-lg border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900">
722
+ <form onSubmit={createTodo} className="h-fit space-y-3 rounded-lg border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900">
704
723
  <div className="flex items-center justify-between">
705
- <h2 className="text-lg font-semibold">{editingId ? "Edit task" : "Create task"}</h2>
724
+ <h2 className="text-lg font-semibold">{editingId ? "Edit todo" : "Create todo"}</h2>
706
725
  {editingId && <Button type="button" variant="ghost" onClick={resetForm} aria-label="Cancel edit"><X size={18} /></Button>}
707
726
  </div>
708
- <Input value={form.title} onChange={(event) => setForm({ ...form, title: event.target.value })} placeholder="Task title" />
727
+ <Input value={form.title} onChange={(event) => setForm({ ...form, title: event.target.value })} placeholder="Todo title" />
709
728
  <Input value={form.description} onChange={(event) => setForm({ ...form, description: event.target.value })} placeholder="Description" />
710
729
  <select className="h-10 w-full rounded-md border border-slate-300 bg-white px-3 text-sm dark:border-slate-700 dark:bg-slate-900" value={form.status} onChange={(event) => setForm({ ...form, status: event.target.value })}>
711
730
  <option value="todo">Todo</option>
@@ -713,7 +732,7 @@ ${componentName} {
713
732
  <option value="done">Done</option>
714
733
  </select>
715
734
  ${context.multer ? `<Input type="file" accept="image/*" onChange={(event) => setForm({ ...form, image: event.target.files?.[0] || null })} />` : ""}
716
- <Button className="w-full" aria-label={editingId ? "Save task" : "Add task"}>{editingId ? <Save size={18} /> : <Plus size={18} />}<span className="ml-2">{editingId ? "Save changes" : "Add task"}</span></Button>
735
+ <Button className="w-full" aria-label={editingId ? "Save todo" : "Add todo"}>{editingId ? <Save size={18} /> : <Plus size={18} />}<span className="ml-2">{editingId ? "Save changes" : "Add todo"}</span></Button>
717
736
  </form>
718
737
  </section>
719
738
  </main>
@@ -1,37 +1,46 @@
1
1
  import inquirer from "inquirer";
2
+ import chalk from "chalk";
2
3
 
3
4
  export async function askQuestions(projectName) {
4
- return inquirer.prompt([
5
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
6
- new inquirer.Separator("BASIC PROJECT SETUP"),
7
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
5
+ console.log(chalk.cyan("\n━━━━━━━━━━━━━━━━━━━━"));
6
+ console.log(chalk.cyan("BASIC PROJECT SETUP"));
7
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━\n"));
8
+
9
+ const basicAnswers = await inquirer.prompt([
8
10
  {
11
+ type: "input",
9
12
  name: "projectName",
10
13
  message: "Project name?",
11
- default: projectName || "my-stackflow-app"
14
+ default: projectName || "my-stackflow-app",
12
15
  },
13
16
  {
17
+ type: "input",
14
18
  name: "frontendName",
15
19
  message: "Frontend folder name?",
16
- default: "frontend"
20
+ default: "frontend",
17
21
  },
18
22
  {
23
+ type: "input",
19
24
  name: "backendName",
20
25
  message: "Backend folder name?",
21
- default: "backend"
26
+ default: "backend",
22
27
  },
23
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
24
- new inquirer.Separator("FRONTEND SETUP"),
25
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
28
+ ]);
29
+
30
+ console.log(chalk.cyan("\n━━━━━━━━━━━━━━━━━━━━"));
31
+ console.log(chalk.cyan("FRONTEND SETUP"));
32
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━\n"));
33
+
34
+ const frontendAnswers = await inquirer.prompt([
26
35
  {
27
36
  type: "list",
28
37
  name: "frontend",
29
38
  message: "Frontend framework?",
30
39
  choices: [
31
40
  { name: "React", value: "react" },
32
- { name: "Next.js", value: "next" }
41
+ { name: "Next.js", value: "next" },
33
42
  ],
34
- default: "react"
43
+ default: "react",
35
44
  },
36
45
  {
37
46
  type: "list",
@@ -39,9 +48,9 @@ export async function askQuestions(projectName) {
39
48
  message: "Language?",
40
49
  choices: [
41
50
  { name: "TypeScript", value: "typescript" },
42
- { name: "JavaScript", value: "javascript" }
51
+ { name: "JavaScript", value: "javascript" },
43
52
  ],
44
- default: "typescript"
53
+ default: "typescript",
45
54
  },
46
55
  {
47
56
  type: "list",
@@ -51,9 +60,9 @@ export async function askQuestions(projectName) {
51
60
  { name: "Tailwind CSS", value: "tailwind" },
52
61
  { name: "SCSS", value: "scss" },
53
62
  { name: "CSS Modules", value: "css-modules" },
54
- { name: "Styled Components", value: "styled-components" }
63
+ { name: "Styled Components", value: "styled-components" },
55
64
  ],
56
- default: "tailwind"
65
+ default: "tailwind",
57
66
  },
58
67
  {
59
68
  type: "list",
@@ -64,9 +73,9 @@ export async function askQuestions(projectName) {
64
73
  { name: "Material UI", value: "mui" },
65
74
  { name: "Ant Design", value: "antd" },
66
75
  { name: "Chakra UI", value: "chakra" },
67
- { name: "None", value: "none" }
76
+ { name: "None", value: "none" },
68
77
  ],
69
- default: "shadcn"
78
+ default: "shadcn",
70
79
  },
71
80
  {
72
81
  type: "list",
@@ -75,16 +84,16 @@ export async function askQuestions(projectName) {
75
84
  choices: [
76
85
  { name: "Lucide React", value: "lucide-react" },
77
86
  { name: "React Icons", value: "react-icons" },
78
- { name: "Heroicons", value: "heroicons" }
87
+ { name: "Heroicons", value: "heroicons" },
79
88
  ],
80
- default: "lucide-react"
89
+ default: "lucide-react",
81
90
  },
82
91
  {
83
92
  type: "confirm",
84
93
  name: "router",
85
94
  message: "Use React Router DOM?",
86
95
  default: true,
87
- when: (answers) => answers.frontend === "react"
96
+ when: (answers) => answers.frontend === "react",
88
97
  },
89
98
  {
90
99
  type: "list",
@@ -96,9 +105,9 @@ export async function askQuestions(projectName) {
96
105
  { name: "Context API", value: "context-api" },
97
106
  { name: "Jotai", value: "jotai" },
98
107
  { name: "Recoil", value: "recoil" },
99
- { name: "None", value: "none" }
108
+ { name: "None", value: "none" },
100
109
  ],
101
- default: "zustand"
110
+ default: "zustand",
102
111
  },
103
112
  {
104
113
  type: "list",
@@ -106,26 +115,26 @@ export async function askQuestions(projectName) {
106
115
  message: "Data fetching library?",
107
116
  choices: [
108
117
  { name: "Axios", value: "axios" },
109
- { name: "Fetch API", value: "fetch" }
118
+ { name: "Fetch API", value: "fetch" },
110
119
  ],
111
- default: "axios"
120
+ default: "axios",
112
121
  },
113
122
  {
114
123
  type: "confirm",
115
124
  name: "tanstackQuery",
116
125
  message: "Use TanStack Query?",
117
- default: false
126
+ default: false,
118
127
  },
119
128
  {
120
129
  type: "list",
121
- name: "form",
130
+ name: "formLibrary",
122
131
  message: "Form handling library?",
123
132
  choices: [
124
133
  { name: "React Hook Form", value: "react-hook-form" },
125
134
  { name: "Formik", value: "formik" },
126
- { name: "None", value: "none" }
135
+ { name: "None", value: "none" },
127
136
  ],
128
- default: "react-hook-form"
137
+ default: "react-hook-form",
129
138
  },
130
139
  {
131
140
  type: "list",
@@ -135,27 +144,27 @@ export async function askQuestions(projectName) {
135
144
  { name: "Zod", value: "zod" },
136
145
  { name: "Yup", value: "yup" },
137
146
  { name: "Joi", value: "joi" },
138
- { name: "None", value: "none" }
147
+ { name: "None", value: "none" },
139
148
  ],
140
- default: "zod"
149
+ default: "zod",
141
150
  },
142
151
  {
143
152
  type: "confirm",
144
153
  name: "authPages",
145
154
  message: "Generate authentication pages?",
146
- default: true
155
+ default: true,
147
156
  },
148
157
  {
149
158
  type: "confirm",
150
159
  name: "protectedRoutes",
151
160
  message: "Generate protected routes?",
152
- default: true
161
+ default: true,
153
162
  },
154
163
  {
155
164
  type: "confirm",
156
165
  name: "darkMode",
157
166
  message: "Add dark/light theme support?",
158
- default: true
167
+ default: true,
159
168
  },
160
169
  {
161
170
  type: "list",
@@ -164,9 +173,9 @@ export async function askQuestions(projectName) {
164
173
  choices: [
165
174
  { name: "Framer Motion", value: "framer-motion" },
166
175
  { name: "GSAP", value: "gsap" },
167
- { name: "None", value: "none" }
176
+ { name: "None", value: "none" },
168
177
  ],
169
- default: "none"
178
+ default: "none",
170
179
  },
171
180
  {
172
181
  type: "list",
@@ -176,9 +185,9 @@ export async function askQuestions(projectName) {
176
185
  { name: "Sonner", value: "sonner" },
177
186
  { name: "React Hot Toast", value: "react-hot-toast" },
178
187
  { name: "Notistack", value: "notistack" },
179
- { name: "None", value: "none" }
188
+ { name: "None", value: "none" },
180
189
  ],
181
- default: "sonner"
190
+ default: "sonner",
182
191
  },
183
192
  {
184
193
  type: "list",
@@ -188,9 +197,9 @@ export async function askQuestions(projectName) {
188
197
  { name: "Recharts", value: "recharts" },
189
198
  { name: "Chart.js", value: "chartjs" },
190
199
  { name: "ApexCharts", value: "apexcharts" },
191
- { name: "None", value: "none" }
200
+ { name: "None", value: "none" },
192
201
  ],
193
- default: "none"
202
+ default: "none",
194
203
  },
195
204
  {
196
205
  type: "list",
@@ -198,31 +207,35 @@ export async function askQuestions(projectName) {
198
207
  message: "Add drag and drop upload?",
199
208
  choices: [
200
209
  { name: "React Dropzone", value: "react-dropzone" },
201
- { name: "None", value: "none" }
210
+ { name: "None", value: "none" },
202
211
  ],
203
- default: "none"
212
+ default: "none",
204
213
  },
205
214
  {
206
215
  type: "confirm",
207
216
  name: "eslint",
208
217
  message: "Add ESLint?",
209
- default: true
218
+ default: true,
210
219
  },
211
220
  {
212
221
  type: "confirm",
213
222
  name: "prettier",
214
223
  message: "Add Prettier?",
215
- default: true
224
+ default: true,
216
225
  },
217
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
218
- new inquirer.Separator("BACKEND SETUP"),
219
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
226
+ ]);
227
+
228
+ console.log(chalk.cyan("\n━━━━━━━━━━━━━━━━━━━━"));
229
+ console.log(chalk.cyan("BACKEND SETUP"));
230
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━\n"));
231
+
232
+ const backendAnswers = await inquirer.prompt([
220
233
  {
221
234
  type: "list",
222
235
  name: "backendFramework",
223
236
  message: "Backend framework?",
224
237
  choices: ["Express.js", "Fastify", "NestJS"],
225
- default: "Express.js"
238
+ default: "Express.js",
226
239
  },
227
240
  {
228
241
  type: "list",
@@ -230,149 +243,99 @@ export async function askQuestions(projectName) {
230
243
  message: "Backend language?",
231
244
  choices: [
232
245
  { name: "JavaScript", value: "javascript" },
233
- { name: "TypeScript", value: "typescript" }
246
+ { name: "TypeScript", value: "typescript" },
234
247
  ],
235
- default: "javascript"
248
+ default: "javascript",
236
249
  },
237
250
  {
238
251
  type: "list",
239
252
  name: "database",
240
253
  message: "Database?",
241
254
  choices: ["MongoDB", "PostgreSQL", "MySQL", "SQLite"],
242
- default: "MongoDB"
255
+ default: "MongoDB",
243
256
  },
244
257
  {
245
258
  type: "list",
246
259
  name: "orm",
247
260
  message: "ODM / ORM?",
248
261
  choices: ["Mongoose", "Prisma", "Sequelize", "Drizzle", "TypeORM"],
249
- default: "Mongoose"
262
+ default: "Mongoose",
250
263
  },
251
264
  {
252
265
  type: "list",
253
266
  name: "authStrategy",
254
267
  message: "Authentication strategy?",
255
- choices: ["JWT", "Session Auth", "Firebase Auth", "Clerk", "Supabase Auth", "None"],
256
- default: "JWT"
268
+ choices: [
269
+ "JWT",
270
+ "Session Auth",
271
+ "Firebase Auth",
272
+ "Clerk",
273
+ "Supabase Auth",
274
+ "None",
275
+ ],
276
+ default: "JWT",
257
277
  },
258
278
  {
259
279
  type: "list",
260
280
  name: "passwordHashing",
261
281
  message: "Password hashing library?",
262
282
  choices: ["bcryptjs", "argon2"],
263
- default: "bcryptjs"
283
+ default: "bcryptjs",
264
284
  },
265
285
  {
266
286
  type: "list",
267
287
  name: "fileUpload",
268
288
  message: "File upload solution?",
269
- choices: ["Multer", "Cloudinary", "Cloudflare R2", "AWS S3", "UploadThing", "None"],
270
- default: "Multer"
271
- },
272
- {
273
- type: "list",
274
- name: "backendValidation",
275
- message: "Backend validation library?",
276
- choices: ["Zod", "Joi", "express-validator", "None"],
277
- default: "None"
289
+ choices: [
290
+ "Multer",
291
+ "Cloudinary",
292
+ "Cloudflare R2",
293
+ "AWS S3",
294
+ "UploadThing",
295
+ "None",
296
+ ],
297
+ default: "Multer",
278
298
  },
279
299
  {
280
300
  type: "checkbox",
281
301
  name: "securityPackages",
282
302
  message: "Add security packages?",
283
303
  choices: [
284
- { name: "helmet", value: "helmet", checked: true },
304
+ { name: "helmet", value: "helmet" },
285
305
  { name: "cors", value: "cors", checked: true },
286
- { name: "express-rate-limit", value: "express-rate-limit", checked: true },
287
- { name: "hpp", value: "hpp", checked: true }
288
- ]
289
- },
290
- {
291
- type: "confirm",
292
- name: "cookieParser",
293
- message: "Use cookie-parser?",
294
- default: true
295
- },
296
- {
297
- type: "list",
298
- name: "logging",
299
- message: "Logging library?",
300
- choices: ["Morgan", "Winston", "Pino", "None"],
301
- default: "Morgan"
302
- },
303
- {
304
- type: "confirm",
305
- name: "swagger",
306
- message: "Add Swagger/OpenAPI docs?",
307
- default: true
306
+ {
307
+ name: "express-rate-limit",
308
+ value: "express-rate-limit",
309
+ checked: true,
310
+ },
311
+ { name: "hpp", value: "hpp" },
312
+ ],
308
313
  },
309
314
  {
310
315
  type: "confirm",
311
- name: "socketio",
312
- message: "Add Socket.IO?",
313
- default: false
314
- },
315
- {
316
- type: "list",
317
- name: "emailService",
318
- message: "Email service?",
319
- choices: ["Nodemailer", "Resend", "AWS SES", "SendGrid", "Mailgun", "Postmark", "None"],
320
- default: "None"
321
- },
322
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
323
- new inquirer.Separator("CRUD GENERATION"),
324
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
325
- {
326
- type: "checkbox",
327
- name: "crudModules",
328
- message: "Generate CRUD modules?",
329
- choices: [
330
- { name: "Users", value: "users", checked: true },
331
- { name: "Products", value: "products", checked: true },
332
- { name: "Categories", value: "categories", checked: true },
333
- { name: "Orders", value: "orders", checked: true },
334
- { name: "Blogs", value: "blogs", checked: true }
335
- ]
316
+ name: "runProject",
317
+ message: "Run the project immediately?",
318
+ default: true,
336
319
  },
337
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
338
- new inquirer.Separator("AUTH FEATURES"),
339
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
340
- {
341
- type: "checkbox",
342
- name: "authFeatures",
343
- message: "Add auth features?",
344
- choices: [
345
- { name: "Login", value: "login", checked: true },
346
- { name: "Register", value: "register", checked: true },
347
- { name: "Forgot Password", value: "forgot-password", checked: true },
348
- { name: "Reset Password", value: "reset-password", checked: true },
349
- { name: "Email Verification", value: "email-verification", checked: true },
350
- { name: "Role-based Access", value: "roles", checked: true },
351
- { name: "Refresh Tokens", value: "refresh-tokens", checked: true }
352
- ]
353
- },
354
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
355
- new inquirer.Separator("ADMIN PANEL"),
356
- new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
357
- {
358
- type: "confirm",
359
- name: "adminDashboard",
360
- message: "Generate admin dashboard?",
361
- default: true
362
- }
363
- ]).then((answers) => ({
364
- ...answers,
365
- tailwind: answers.styling === "tailwind",
366
- shadcn: answers.uiLibrary === "shadcn",
367
- axios: answers.dataFetching === "axios",
368
- form: answers.form !== "none",
369
- winston: answers.logging === "Winston",
370
- morgan: answers.logging === "Morgan",
371
- cloudinary: answers.fileUpload === "Cloudinary",
372
- multer: answers.fileUpload === "Multer",
373
- hpp: answers.securityPackages.includes("hpp"),
374
- helmet: answers.securityPackages.includes("helmet"),
375
- cors: answers.securityPackages.includes("cors"),
376
- rateLimit: answers.securityPackages.includes("express-rate-limit")
377
- }));
320
+ ]);
321
+
322
+ return {
323
+ ...basicAnswers,
324
+ ...frontendAnswers,
325
+ ...backendAnswers,
326
+
327
+ tailwind: frontendAnswers.styling === "tailwind",
328
+ shadcn: frontendAnswers.uiLibrary === "shadcn",
329
+ axios: frontendAnswers.dataFetching === "axios",
330
+ reactHookForm: frontendAnswers.formLibrary === "react-hook-form",
331
+
332
+ helmet: backendAnswers.securityPackages.includes("helmet"),
333
+ cors: backendAnswers.securityPackages.includes("cors"),
334
+ rateLimit: backendAnswers.securityPackages.includes("express-rate-limit"),
335
+ hpp: backendAnswers.securityPackages.includes("hpp"),
336
+
337
+ multer: backendAnswers.fileUpload === "Multer",
338
+ cloudinary: backendAnswers.fileUpload === "Cloudinary",
339
+ runProject: backendAnswers.runProject,
340
+ };
378
341
  }