create-stackflow 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.
@@ -0,0 +1,70 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import { writeTemplate } from "../utils/template.js";
4
+
5
+ export async function createRootFiles(context) {
6
+ const frontendName = path.basename(context.frontendDir);
7
+ const backendName = path.basename(context.backendDir);
8
+
9
+ await writeTemplate(
10
+ path.join(context.projectDir, "package.json"),
11
+ `{
12
+ "name": "{{projectName}}",
13
+ "version": "1.0.0",
14
+ "private": true,
15
+ "scripts": {
16
+ "dev": "concurrently \\"npm run dev --workspace {{backendName}}\\" \\"npm run dev --workspace {{frontendName}}\\"",
17
+ "dev:frontend": "npm run dev --workspace {{frontendName}}",
18
+ "dev:backend": "npm run dev --workspace {{backendName}}"
19
+ },
20
+ "workspaces": [
21
+ "{{frontendName}}",
22
+ "{{backendName}}"
23
+ ],
24
+ "devDependencies": {
25
+ "concurrently": "latest"
26
+ }
27
+ }
28
+ `,
29
+ { ...context, frontendName, backendName }
30
+ );
31
+
32
+ await writeTemplate(
33
+ path.join(context.projectDir, "README.md"),
34
+ `# {{projectName}}
35
+
36
+ Generated with \`create-stackflow\`.
37
+
38
+ ## Development
39
+
40
+ \`\`\`bash
41
+ npm run dev
42
+ \`\`\`
43
+
44
+ Frontend runs on Vite/Next.js. Backend runs on Express at \`http://localhost:5000\`.
45
+
46
+ ## Environment
47
+
48
+ MongoDB local connection:
49
+
50
+ \`\`\`
51
+ mongodb://127.0.0.1:27017/{{databaseName}}
52
+ \`\`\`
53
+ `,
54
+ context
55
+ );
56
+
57
+ await fs.outputFile(
58
+ path.join(context.projectDir, ".gitignore"),
59
+ `node_modules
60
+ .env
61
+ .env.*
62
+ dist
63
+ build
64
+ .next
65
+ coverage
66
+ uploads/*
67
+ !uploads/.gitkeep
68
+ `
69
+ );
70
+ }
@@ -0,0 +1,15 @@
1
+ export function formatName(value) {
2
+ return String(value || "")
3
+ .trim()
4
+ .toLowerCase()
5
+ .replace(/\s+/g, "-")
6
+ .replace(/[^a-z0-9._-]/g, "-")
7
+ .replace(/-+/g, "-")
8
+ .replace(/^-|-$/g, "");
9
+ }
10
+
11
+ export function titleCase(value) {
12
+ return String(value || "")
13
+ .replace(/[-_]/g, " ")
14
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
15
+ }
@@ -0,0 +1,378 @@
1
+ import inquirer from "inquirer";
2
+
3
+ export async function askQuestions(projectName) {
4
+ return inquirer.prompt([
5
+ new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
6
+ new inquirer.Separator("BASIC PROJECT SETUP"),
7
+ new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
8
+ {
9
+ name: "projectName",
10
+ message: "Project name?",
11
+ default: projectName || "my-stackflow-app"
12
+ },
13
+ {
14
+ name: "frontendName",
15
+ message: "Frontend folder name?",
16
+ default: "frontend"
17
+ },
18
+ {
19
+ name: "backendName",
20
+ message: "Backend folder name?",
21
+ default: "backend"
22
+ },
23
+ new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
24
+ new inquirer.Separator("FRONTEND SETUP"),
25
+ new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
26
+ {
27
+ type: "list",
28
+ name: "frontend",
29
+ message: "Frontend framework?",
30
+ choices: [
31
+ { name: "React", value: "react" },
32
+ { name: "Next.js", value: "next" }
33
+ ],
34
+ default: "react"
35
+ },
36
+ {
37
+ type: "list",
38
+ name: "language",
39
+ message: "Language?",
40
+ choices: [
41
+ { name: "TypeScript", value: "typescript" },
42
+ { name: "JavaScript", value: "javascript" }
43
+ ],
44
+ default: "typescript"
45
+ },
46
+ {
47
+ type: "list",
48
+ name: "styling",
49
+ message: "Styling solution?",
50
+ choices: [
51
+ { name: "Tailwind CSS", value: "tailwind" },
52
+ { name: "SCSS", value: "scss" },
53
+ { name: "CSS Modules", value: "css-modules" },
54
+ { name: "Styled Components", value: "styled-components" }
55
+ ],
56
+ default: "tailwind"
57
+ },
58
+ {
59
+ type: "list",
60
+ name: "uiLibrary",
61
+ message: "Use UI library?",
62
+ choices: [
63
+ { name: "Shadcn UI", value: "shadcn" },
64
+ { name: "Material UI", value: "mui" },
65
+ { name: "Ant Design", value: "antd" },
66
+ { name: "Chakra UI", value: "chakra" },
67
+ { name: "None", value: "none" }
68
+ ],
69
+ default: "shadcn"
70
+ },
71
+ {
72
+ type: "list",
73
+ name: "iconLibrary",
74
+ message: "Icon library?",
75
+ choices: [
76
+ { name: "Lucide React", value: "lucide-react" },
77
+ { name: "React Icons", value: "react-icons" },
78
+ { name: "Heroicons", value: "heroicons" }
79
+ ],
80
+ default: "lucide-react"
81
+ },
82
+ {
83
+ type: "confirm",
84
+ name: "router",
85
+ message: "Use React Router DOM?",
86
+ default: true,
87
+ when: (answers) => answers.frontend === "react"
88
+ },
89
+ {
90
+ type: "list",
91
+ name: "state",
92
+ message: "State management?",
93
+ choices: [
94
+ { name: "Zustand", value: "zustand" },
95
+ { name: "Redux Toolkit", value: "redux-toolkit" },
96
+ { name: "Context API", value: "context-api" },
97
+ { name: "Jotai", value: "jotai" },
98
+ { name: "Recoil", value: "recoil" },
99
+ { name: "None", value: "none" }
100
+ ],
101
+ default: "zustand"
102
+ },
103
+ {
104
+ type: "list",
105
+ name: "dataFetching",
106
+ message: "Data fetching library?",
107
+ choices: [
108
+ { name: "Axios", value: "axios" },
109
+ { name: "Fetch API", value: "fetch" }
110
+ ],
111
+ default: "axios"
112
+ },
113
+ {
114
+ type: "confirm",
115
+ name: "tanstackQuery",
116
+ message: "Use TanStack Query?",
117
+ default: false
118
+ },
119
+ {
120
+ type: "list",
121
+ name: "form",
122
+ message: "Form handling library?",
123
+ choices: [
124
+ { name: "React Hook Form", value: "react-hook-form" },
125
+ { name: "Formik", value: "formik" },
126
+ { name: "None", value: "none" }
127
+ ],
128
+ default: "react-hook-form"
129
+ },
130
+ {
131
+ type: "list",
132
+ name: "validation",
133
+ message: "Validation library?",
134
+ choices: [
135
+ { name: "Zod", value: "zod" },
136
+ { name: "Yup", value: "yup" },
137
+ { name: "Joi", value: "joi" },
138
+ { name: "None", value: "none" }
139
+ ],
140
+ default: "zod"
141
+ },
142
+ {
143
+ type: "confirm",
144
+ name: "authPages",
145
+ message: "Generate authentication pages?",
146
+ default: true
147
+ },
148
+ {
149
+ type: "confirm",
150
+ name: "protectedRoutes",
151
+ message: "Generate protected routes?",
152
+ default: true
153
+ },
154
+ {
155
+ type: "confirm",
156
+ name: "darkMode",
157
+ message: "Add dark/light theme support?",
158
+ default: true
159
+ },
160
+ {
161
+ type: "list",
162
+ name: "animation",
163
+ message: "Animation library?",
164
+ choices: [
165
+ { name: "Framer Motion", value: "framer-motion" },
166
+ { name: "GSAP", value: "gsap" },
167
+ { name: "None", value: "none" }
168
+ ],
169
+ default: "none"
170
+ },
171
+ {
172
+ type: "list",
173
+ name: "toast",
174
+ message: "Toast notification library?",
175
+ choices: [
176
+ { name: "Sonner", value: "sonner" },
177
+ { name: "React Hot Toast", value: "react-hot-toast" },
178
+ { name: "Notistack", value: "notistack" },
179
+ { name: "None", value: "none" }
180
+ ],
181
+ default: "sonner"
182
+ },
183
+ {
184
+ type: "list",
185
+ name: "chart",
186
+ message: "Chart library?",
187
+ choices: [
188
+ { name: "Recharts", value: "recharts" },
189
+ { name: "Chart.js", value: "chartjs" },
190
+ { name: "ApexCharts", value: "apexcharts" },
191
+ { name: "None", value: "none" }
192
+ ],
193
+ default: "none"
194
+ },
195
+ {
196
+ type: "list",
197
+ name: "dragDropUpload",
198
+ message: "Add drag and drop upload?",
199
+ choices: [
200
+ { name: "React Dropzone", value: "react-dropzone" },
201
+ { name: "None", value: "none" }
202
+ ],
203
+ default: "none"
204
+ },
205
+ {
206
+ type: "confirm",
207
+ name: "eslint",
208
+ message: "Add ESLint?",
209
+ default: true
210
+ },
211
+ {
212
+ type: "confirm",
213
+ name: "prettier",
214
+ message: "Add Prettier?",
215
+ default: true
216
+ },
217
+ new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
218
+ new inquirer.Separator("BACKEND SETUP"),
219
+ new inquirer.Separator("━━━━━━━━━━━━━━━━━━━━"),
220
+ {
221
+ type: "list",
222
+ name: "backendFramework",
223
+ message: "Backend framework?",
224
+ choices: ["Express.js", "Fastify", "NestJS"],
225
+ default: "Express.js"
226
+ },
227
+ {
228
+ type: "list",
229
+ name: "backendLanguage",
230
+ message: "Backend language?",
231
+ choices: [
232
+ { name: "JavaScript", value: "javascript" },
233
+ { name: "TypeScript", value: "typescript" }
234
+ ],
235
+ default: "javascript"
236
+ },
237
+ {
238
+ type: "list",
239
+ name: "database",
240
+ message: "Database?",
241
+ choices: ["MongoDB", "PostgreSQL", "MySQL", "SQLite"],
242
+ default: "MongoDB"
243
+ },
244
+ {
245
+ type: "list",
246
+ name: "orm",
247
+ message: "ODM / ORM?",
248
+ choices: ["Mongoose", "Prisma", "Sequelize", "Drizzle", "TypeORM"],
249
+ default: "Mongoose"
250
+ },
251
+ {
252
+ type: "list",
253
+ name: "authStrategy",
254
+ message: "Authentication strategy?",
255
+ choices: ["JWT", "Session Auth", "Firebase Auth", "Clerk", "Supabase Auth", "None"],
256
+ default: "JWT"
257
+ },
258
+ {
259
+ type: "list",
260
+ name: "passwordHashing",
261
+ message: "Password hashing library?",
262
+ choices: ["bcryptjs", "argon2"],
263
+ default: "bcryptjs"
264
+ },
265
+ {
266
+ type: "list",
267
+ name: "fileUpload",
268
+ 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"
278
+ },
279
+ {
280
+ type: "checkbox",
281
+ name: "securityPackages",
282
+ message: "Add security packages?",
283
+ choices: [
284
+ { name: "helmet", value: "helmet", checked: true },
285
+ { 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
308
+ },
309
+ {
310
+ 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
+ ]
336
+ },
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
+ }));
378
+ }
@@ -0,0 +1,17 @@
1
+ import fs from "fs-extra";
2
+
3
+ export async function writeTemplate(file, content, context = {}) {
4
+ let output = content;
5
+ for (const [key, value] of Object.entries(context)) {
6
+ output = output.replaceAll(`{{${key}}}`, String(value));
7
+ }
8
+ await fs.outputFile(file, output);
9
+ }
10
+
11
+ export function ext(context) {
12
+ return context.language === "typescript" ? "ts" : "js";
13
+ }
14
+
15
+ export function jsxExt(context) {
16
+ return context.language === "typescript" ? "tsx" : "jsx";
17
+ }