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.
- package/README.md +142 -0
- package/package.json +42 -0
- package/src/cli.js +25 -0
- package/src/commands/create.js +149 -0
- package/src/generators/backend.js +501 -0
- package/src/generators/frontend.js +723 -0
- package/src/generators/root.js +70 -0
- package/src/utils/names.js +15 -0
- package/src/utils/prompts.js +378 -0
- package/src/utils/template.js +17 -0
|
@@ -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
|
+
}
|