create-flex-stack 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/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/index.js +330 -0
- package/dist/templates/cleanTemplate.js +697 -0
- package/dist/templates/frontendTemplate.js +855 -0
- package/dist/templates/hexagonalTemplate.js +855 -0
- package/dist/templates/layeredTemplate.js +745 -0
- package/dist/templates/modularTemplate.js +691 -0
- package/dist/templates/sharedTemplate.js +654 -0
- package/dist/templates/vmcTemplate.js +26 -0
- package/dist/types.js +1 -0
- package/dist/utils/generator.js +1120 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# create-flex-stack
|
|
2
|
+
|
|
3
|
+
Production-ready TypeScript project generator for Express APIs and optional React frontends.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm create flex-stack@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run the binary directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx create-flex-stack@latest
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The CLI will ask for the project name and stack options, then generate the project and install dependencies.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Express + TypeScript backend
|
|
22
|
+
- Optional React + Vite frontend
|
|
23
|
+
- Architecture choices: Layered/MVC, VMC, Modular, Clean Architecture, Hexagonal Architecture
|
|
24
|
+
- Database choices: PostgreSQL, MySQL, SQLite, MongoDB
|
|
25
|
+
- ORM choices: Prisma, TypeORM, Mongoose
|
|
26
|
+
- Optional JWT auth, RBAC, Swagger, rate limiting, file uploads, Docker, Redis and Jest
|
|
27
|
+
- Generated README with the commands needed for the selected stack
|
|
28
|
+
|
|
29
|
+
## Generated Project
|
|
30
|
+
|
|
31
|
+
After generation, enter the project folder and follow its generated README. For Prisma projects, the most common commands are:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm run db:generate
|
|
35
|
+
npm run db:push
|
|
36
|
+
npm run dev
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For Docker deployments with Prisma:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run db:generate
|
|
43
|
+
npm run db:migrate:deploy
|
|
44
|
+
docker compose up --build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Development
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install
|
|
51
|
+
npm run build
|
|
52
|
+
npm start
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { intro, outro, text, select, confirm, isCancel, cancel, spinner } from '@clack/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { generateProject } from './utils/generator.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
intro(pc.bold(pc.cyan('CREATE-FLEX-STACK: Ultimate Full-Stack Boilerplate Generator')));
|
|
7
|
+
// 1. Project Name
|
|
8
|
+
const projectName = await text({
|
|
9
|
+
message: 'Enter your project name:',
|
|
10
|
+
placeholder: 'my-ultimate-stack',
|
|
11
|
+
initialValue: 'my-ultimate-stack',
|
|
12
|
+
validate(value) {
|
|
13
|
+
if (value.length === 0)
|
|
14
|
+
return 'Project name is required';
|
|
15
|
+
if (/[<>:"/\\|?*]/.test(value))
|
|
16
|
+
return 'Project name contains invalid characters';
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (isCancel(projectName)) {
|
|
20
|
+
cancel('Generation cancelled.');
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
// 2. Choose Architecture
|
|
24
|
+
const architecture = await select({
|
|
25
|
+
message: 'Choose your backend architecture:',
|
|
26
|
+
options: [
|
|
27
|
+
{ value: 'layered', label: 'Traditional Layered (MVC)', hint: 'controllers, services, repositories, models, routes' },
|
|
28
|
+
{ value: 'vmc', label: 'VMC (View, Model, Controller)', hint: 'view DTOs, model/business logic, controllers, routes' },
|
|
29
|
+
{ value: 'modular', label: 'Modular / Feature-Based', hint: 'organized by feature folder: users, auth, products' },
|
|
30
|
+
{ value: 'clean', label: 'Clean Architecture', hint: 'presentation, application, domain, infrastructure, shared' },
|
|
31
|
+
{ value: 'hexagonal', label: 'Hexagonal Architecture (Ports & Adapters)', hint: 'domain, application, ports, adapters' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
if (isCancel(architecture)) {
|
|
35
|
+
cancel('Generation cancelled.');
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
// 3. Backend Framework
|
|
39
|
+
const frameworkChoice = await select({
|
|
40
|
+
message: 'Choose your backend framework:',
|
|
41
|
+
options: [
|
|
42
|
+
{ value: 'express', label: 'ExpressJS (TypeScript) [Fully Implemented]' },
|
|
43
|
+
{ value: 'nestjs', label: pc.yellow('NestJS (Coming Soon) [Fallback to Express]') },
|
|
44
|
+
{ value: 'fastify', label: pc.yellow('Fastify (Coming Soon) [Fallback to Express]') },
|
|
45
|
+
{ value: 'hono', label: pc.yellow('Hono (Coming Soon) [Fallback to Express]') },
|
|
46
|
+
{ value: 'koa', label: pc.yellow('Koa (Coming Soon) [Fallback to Express]') },
|
|
47
|
+
{ value: 'adonis', label: pc.yellow('AdonisJS (Coming Soon) [Fallback to Express]') },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
if (isCancel(frameworkChoice)) {
|
|
51
|
+
cancel('Generation cancelled.');
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
if (frameworkChoice !== 'express') {
|
|
55
|
+
console.log(pc.yellow(`\nā Framework "${frameworkChoice}" is coming soon. Defaulting backend to ExpressJS.`));
|
|
56
|
+
}
|
|
57
|
+
const framework = 'express';
|
|
58
|
+
// 4. Database Selection
|
|
59
|
+
const databaseChoice = await select({
|
|
60
|
+
message: 'Choose your database:',
|
|
61
|
+
options: [
|
|
62
|
+
{ value: 'postgres', label: 'PostgreSQL' },
|
|
63
|
+
{ value: 'mysql', label: 'MySQL' },
|
|
64
|
+
{ value: 'sqlite', label: 'SQLite' },
|
|
65
|
+
{ value: 'mongodb', label: 'MongoDB' },
|
|
66
|
+
{ value: 'mariadb', label: pc.yellow('MariaDB (Coming Soon) [Fallback to MySQL]') },
|
|
67
|
+
{ value: 'firestore', label: pc.yellow('Firestore (Coming Soon) [Fallback to MongoDB]') },
|
|
68
|
+
{ value: 'dynamodb', label: pc.yellow('DynamoDB (Coming Soon) [Fallback to MongoDB]') },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
if (isCancel(databaseChoice)) {
|
|
72
|
+
cancel('Generation cancelled.');
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
let database = 'postgres';
|
|
76
|
+
if (databaseChoice === 'postgres' || databaseChoice === 'mysql' || databaseChoice === 'sqlite' || databaseChoice === 'mongodb') {
|
|
77
|
+
database = databaseChoice;
|
|
78
|
+
}
|
|
79
|
+
else if (databaseChoice === 'mariadb') {
|
|
80
|
+
console.log(pc.yellow('\nā MariaDB is coming soon. Defaulting to MySQL configuration.'));
|
|
81
|
+
database = 'mysql';
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(pc.yellow(`\nā DB "${databaseChoice}" is coming soon. Defaulting to MongoDB configuration.`));
|
|
85
|
+
database = 'mongodb';
|
|
86
|
+
}
|
|
87
|
+
// 5. ORM Selection based on database
|
|
88
|
+
let ormOptions = [];
|
|
89
|
+
if (database === 'postgres' || database === 'mysql') {
|
|
90
|
+
ormOptions = [
|
|
91
|
+
{ value: 'prisma', label: 'Prisma ORM [Default]' },
|
|
92
|
+
{ value: 'typeorm', label: 'TypeORM' },
|
|
93
|
+
{ value: 'sequelize', label: pc.yellow('Sequelize (Coming Soon) [Fallback to Prisma]') },
|
|
94
|
+
{ value: 'drizzle', label: pc.yellow('Drizzle ORM (Coming Soon) [Fallback to Prisma]') },
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
else if (database === 'sqlite') {
|
|
98
|
+
ormOptions = [
|
|
99
|
+
{ value: 'prisma', label: 'Prisma ORM [Default]' },
|
|
100
|
+
{ value: 'typeorm', label: 'TypeORM' },
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// mongodb
|
|
105
|
+
ormOptions = [
|
|
106
|
+
{ value: 'mongoose', label: 'Mongoose ODM [Default]' },
|
|
107
|
+
{ value: 'prisma', label: 'Prisma ORM' },
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
const ormChoice = await select({
|
|
111
|
+
message: 'Choose your ORM/ODM:',
|
|
112
|
+
options: ormOptions,
|
|
113
|
+
});
|
|
114
|
+
if (isCancel(ormChoice)) {
|
|
115
|
+
cancel('Generation cancelled.');
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
let orm = 'prisma';
|
|
119
|
+
if (ormChoice === 'prisma' || ormChoice === 'typeorm' || ormChoice === 'mongoose') {
|
|
120
|
+
orm = ormChoice;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(pc.yellow(`\nā ORM "${ormChoice}" is coming soon. Defaulting to Prisma ORM.`));
|
|
124
|
+
orm = 'prisma';
|
|
125
|
+
}
|
|
126
|
+
// 6. Frontend Framework Choice
|
|
127
|
+
const frontendChoice = await select({
|
|
128
|
+
message: 'Choose frontend framework:',
|
|
129
|
+
options: [
|
|
130
|
+
{ value: 'none', label: 'None (Backend API only)' },
|
|
131
|
+
{ value: 'react', label: 'React (Vite + TypeScript) [Fully Implemented]' },
|
|
132
|
+
{ value: 'nextjs', label: pc.yellow('Next.js (Coming Soon) [Fallback to React]') },
|
|
133
|
+
{ value: 'vue', label: pc.yellow('Vue 3 (Coming Soon) [Fallback to React]') },
|
|
134
|
+
{ value: 'angular', label: pc.yellow('Angular (Coming Soon) [Fallback to React]') },
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
if (isCancel(frontendChoice)) {
|
|
138
|
+
cancel('Generation cancelled.');
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
let frontend = false;
|
|
142
|
+
if (frontendChoice === 'react') {
|
|
143
|
+
frontend = true;
|
|
144
|
+
}
|
|
145
|
+
else if (frontendChoice !== 'none') {
|
|
146
|
+
console.log(pc.yellow(`\nā Frontend "${frontendChoice}" is coming soon. Defaulting to React client.`));
|
|
147
|
+
frontend = true;
|
|
148
|
+
}
|
|
149
|
+
// If frontend is React, show UI and State prompts
|
|
150
|
+
if (frontend) {
|
|
151
|
+
const uiChoice = await select({
|
|
152
|
+
message: 'Choose UI library:',
|
|
153
|
+
options: [
|
|
154
|
+
{ value: 'tailwind', label: 'TailwindCSS + shadcn/ui [Fully Implemented]' },
|
|
155
|
+
{ value: 'mui', label: pc.yellow('Material UI (Coming Soon) [Fallback to TailwindCSS]') },
|
|
156
|
+
{ value: 'chakra', label: pc.yellow('Chakra UI (Coming Soon) [Fallback to TailwindCSS]') },
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
if (isCancel(uiChoice)) {
|
|
160
|
+
cancel('Generation cancelled.');
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
const stateChoice = await select({
|
|
164
|
+
message: 'Choose State management:',
|
|
165
|
+
options: [
|
|
166
|
+
{ value: 'zustand', label: 'Zustand [Fully Implemented]' },
|
|
167
|
+
{ value: 'redux', label: pc.yellow('Redux Toolkit (Coming Soon) [Fallback to Zustand]') },
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
if (isCancel(stateChoice)) {
|
|
171
|
+
cancel('Generation cancelled.');
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 7. Validation Library
|
|
176
|
+
const validationChoice = await select({
|
|
177
|
+
message: 'Choose validation library:',
|
|
178
|
+
options: [
|
|
179
|
+
{ value: 'zod', label: 'Zod' },
|
|
180
|
+
{ value: 'joi', label: 'Joi' },
|
|
181
|
+
{ value: 'yup', label: pc.yellow('Yup (Coming Soon) [Fallback to Zod]') },
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
if (isCancel(validationChoice)) {
|
|
185
|
+
cancel('Generation cancelled.');
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
let validation = 'zod';
|
|
189
|
+
if (validationChoice === 'zod' || validationChoice === 'joi') {
|
|
190
|
+
validation = validationChoice;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(pc.yellow(`\nā Validation library "${validationChoice}" is coming soon. Defaulting to Zod.`));
|
|
194
|
+
validation = 'zod';
|
|
195
|
+
}
|
|
196
|
+
// 8. Auth Module
|
|
197
|
+
const authChoice = await confirm({
|
|
198
|
+
message: 'Generate User & Auth module? (JWT, Login, Signup, Refresh Tokens)',
|
|
199
|
+
initialValue: true,
|
|
200
|
+
});
|
|
201
|
+
if (isCancel(authChoice)) {
|
|
202
|
+
cancel('Generation cancelled.');
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
const auth = Boolean(authChoice);
|
|
206
|
+
let rbac = false;
|
|
207
|
+
if (auth) {
|
|
208
|
+
const rbacChoice = await confirm({
|
|
209
|
+
message: 'Include Role-Based Access Control (RBAC) middleware?',
|
|
210
|
+
initialValue: true,
|
|
211
|
+
});
|
|
212
|
+
if (isCancel(rbacChoice)) {
|
|
213
|
+
cancel('Generation cancelled.');
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
rbac = Boolean(rbacChoice);
|
|
217
|
+
}
|
|
218
|
+
// 9. File Upload
|
|
219
|
+
const fileUpload = await confirm({
|
|
220
|
+
message: 'Include File Upload module? (Multer setup)',
|
|
221
|
+
initialValue: true,
|
|
222
|
+
});
|
|
223
|
+
if (isCancel(fileUpload)) {
|
|
224
|
+
cancel('Generation cancelled.');
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
// 10. Swagger Docs
|
|
228
|
+
const swagger = await confirm({
|
|
229
|
+
message: 'Include Swagger API Documentation UI?',
|
|
230
|
+
initialValue: true,
|
|
231
|
+
});
|
|
232
|
+
if (isCancel(swagger)) {
|
|
233
|
+
cancel('Generation cancelled.');
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
236
|
+
// 11. Docker
|
|
237
|
+
const docker = await confirm({
|
|
238
|
+
message: 'Include Docker & docker-compose configurations?',
|
|
239
|
+
initialValue: true,
|
|
240
|
+
});
|
|
241
|
+
if (isCancel(docker)) {
|
|
242
|
+
cancel('Generation cancelled.');
|
|
243
|
+
process.exit(0);
|
|
244
|
+
}
|
|
245
|
+
// 12. Rate Limiting
|
|
246
|
+
const rateLimiting = await confirm({
|
|
247
|
+
message: 'Include API Rate Limiting security middleware?',
|
|
248
|
+
initialValue: true,
|
|
249
|
+
});
|
|
250
|
+
if (isCancel(rateLimiting)) {
|
|
251
|
+
cancel('Generation cancelled.');
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
// 13. Caching (Redis)
|
|
255
|
+
const caching = await confirm({
|
|
256
|
+
message: 'Include Caching layer integration (Redis client)?',
|
|
257
|
+
initialValue: false,
|
|
258
|
+
});
|
|
259
|
+
if (isCancel(caching)) {
|
|
260
|
+
cancel('Generation cancelled.');
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
// 14. Testing
|
|
264
|
+
const testing = await confirm({
|
|
265
|
+
message: 'Include testing setup? (Jest)',
|
|
266
|
+
initialValue: true,
|
|
267
|
+
});
|
|
268
|
+
if (isCancel(testing)) {
|
|
269
|
+
cancel('Generation cancelled.');
|
|
270
|
+
process.exit(0);
|
|
271
|
+
}
|
|
272
|
+
const options = {
|
|
273
|
+
projectName: projectName,
|
|
274
|
+
architecture: architecture,
|
|
275
|
+
framework,
|
|
276
|
+
database,
|
|
277
|
+
orm,
|
|
278
|
+
frontend,
|
|
279
|
+
auth,
|
|
280
|
+
docker,
|
|
281
|
+
validation,
|
|
282
|
+
rateLimiting,
|
|
283
|
+
swagger,
|
|
284
|
+
caching,
|
|
285
|
+
rbac,
|
|
286
|
+
fileUpload,
|
|
287
|
+
testing,
|
|
288
|
+
};
|
|
289
|
+
const s = spinner();
|
|
290
|
+
let lastMessage = '';
|
|
291
|
+
try {
|
|
292
|
+
await generateProject(options, (msg, status) => {
|
|
293
|
+
if (status === 'start') {
|
|
294
|
+
lastMessage = msg;
|
|
295
|
+
s.start(msg);
|
|
296
|
+
}
|
|
297
|
+
else if (status === 'stop') {
|
|
298
|
+
s.stop(pc.green(`ā ${lastMessage || msg}`));
|
|
299
|
+
}
|
|
300
|
+
else if (status === 'error') {
|
|
301
|
+
s.stop(pc.red(`ā ${msg}`));
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
s.message(msg);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
outro(pc.bold(pc.green('š Project generated successfully!')));
|
|
308
|
+
// Output next steps
|
|
309
|
+
console.log(pc.cyan('\nNext Steps:'));
|
|
310
|
+
console.log(` cd ${projectName}`);
|
|
311
|
+
if (frontend) {
|
|
312
|
+
console.log(' npm install (at root to install workspace dependencies)');
|
|
313
|
+
console.log(' npm run dev (starts both backend and frontend concurrently)');
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
console.log(' npm install');
|
|
317
|
+
console.log(' npm run dev');
|
|
318
|
+
}
|
|
319
|
+
if (orm === 'prisma') {
|
|
320
|
+
console.log(' npm run db:push (or npx prisma db push from backend)');
|
|
321
|
+
}
|
|
322
|
+
console.log('\nEnjoy building your application!\n');
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
s.stop(pc.red('ā Project generation failed.'));
|
|
326
|
+
outro(pc.red(`Error: ${error.message}`));
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
main();
|