create-stackflow 1.0.2 → 1.0.4

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.2")
11
+ .version("1.0.4")
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,18 +1,19 @@
1
1
  {
2
2
  "name": "create-stackflow",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Interactive CLI for generating modern full-stack MERN applications.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-stackflow": "./cli.js"
8
8
  },
9
9
  "files": [
10
- "src"
10
+ "src",
11
+ "cli.js"
11
12
  ],
12
13
  "scripts": {
13
- "start": "node ./src/cli.js",
14
- "dev": "node ./src/cli.js",
15
- "smoke": "node ./src/cli.js --help"
14
+ "start": "node ./cli.js",
15
+ "dev": "node ./cli.js",
16
+ "smoke": "node ./cli.js --help"
16
17
  },
17
18
  "keywords": [
18
19
  "create",
@@ -39,4 +40,4 @@
39
40
  "engines": {
40
41
  "node": ">=18.18.0"
41
42
  }
42
- }
43
+ }
@@ -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
  }
@@ -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
  }