boiling-fullstack 0.1.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +313 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +33 -0
  7. package/dist/scaffolder.d.ts +2 -0
  8. package/dist/scaffolder.js +89 -0
  9. package/dist/types.d.ts +28 -0
  10. package/dist/types.js +2 -0
  11. package/dist/utils/shell.d.ts +6 -0
  12. package/dist/utils/shell.js +53 -0
  13. package/dist/utils/template.d.ts +2 -0
  14. package/dist/utils/template.js +51 -0
  15. package/dist/utils/validation.d.ts +6 -0
  16. package/dist/utils/validation.js +59 -0
  17. package/package.json +40 -0
  18. package/templates/backend/nestjs/.dockerignore +3 -0
  19. package/templates/backend/nestjs/.eslintrc.cjs.ejs +19 -0
  20. package/templates/backend/nestjs/.prettierrc +6 -0
  21. package/templates/backend/nestjs/Dockerfile +44 -0
  22. package/templates/backend/nestjs/nest-cli.json +8 -0
  23. package/templates/backend/nestjs/package.json.ejs +52 -0
  24. package/templates/backend/nestjs/src/app.controller.ts +17 -0
  25. package/templates/backend/nestjs/src/app.module.ts.ejs +20 -0
  26. package/templates/backend/nestjs/src/app.service.ts +4 -0
  27. package/templates/backend/nestjs/src/auth/auth.controller.ts +19 -0
  28. package/templates/backend/nestjs/src/auth/auth.guard.ts +5 -0
  29. package/templates/backend/nestjs/src/auth/auth.module.ts +28 -0
  30. package/templates/backend/nestjs/src/auth/auth.service.ts +50 -0
  31. package/templates/backend/nestjs/src/auth/dto/login.dto.ts +10 -0
  32. package/templates/backend/nestjs/src/auth/dto/register.dto.ts +10 -0
  33. package/templates/backend/nestjs/src/auth/entities/user.entity.ts +25 -0
  34. package/templates/backend/nestjs/src/auth/jwt.strategy.ts +19 -0
  35. package/templates/backend/nestjs/src/config/data-source.ts +9 -0
  36. package/templates/backend/nestjs/src/config/typeorm.config.ts.ejs +8 -0
  37. package/templates/backend/nestjs/src/main.ts.ejs +20 -0
  38. package/templates/backend/nestjs/src/migrations/.gitkeep +0 -0
  39. package/templates/backend/nestjs/tsconfig.build.json +4 -0
  40. package/templates/backend/nestjs/tsconfig.json +21 -0
  41. package/templates/frontend/nuxt/.dockerignore +5 -0
  42. package/templates/frontend/nuxt/.eslintrc.cjs.ejs +5 -0
  43. package/templates/frontend/nuxt/.prettierrc +6 -0
  44. package/templates/frontend/nuxt/Dockerfile +38 -0
  45. package/templates/frontend/nuxt/app/app.vue +3 -0
  46. package/templates/frontend/nuxt/app/pages/index.vue.ejs +25 -0
  47. package/templates/frontend/nuxt/nuxt.config.ts.ejs +19 -0
  48. package/templates/frontend/nuxt/package.json.ejs +25 -0
  49. package/templates/frontend/nuxt/tsconfig.json +3 -0
  50. package/templates/frontend/vue/.dockerignore +3 -0
  51. package/templates/frontend/vue/.eslintrc.cjs.ejs +14 -0
  52. package/templates/frontend/vue/.prettierrc +6 -0
  53. package/templates/frontend/vue/Dockerfile +33 -0
  54. package/templates/frontend/vue/index.html.ejs +13 -0
  55. package/templates/frontend/vue/package.json.ejs +28 -0
  56. package/templates/frontend/vue/src/App.vue.ejs +22 -0
  57. package/templates/frontend/vue/src/main.ts +4 -0
  58. package/templates/frontend/vue/src/vite-env.d.ts +1 -0
  59. package/templates/frontend/vue/tsconfig.json +20 -0
  60. package/templates/frontend/vue/vite.config.ts.ejs +14 -0
  61. package/templates/root/.env.ejs +24 -0
  62. package/templates/root/.env.example.ejs +24 -0
  63. package/templates/root/.env.production.ejs +17 -0
  64. package/templates/root/Makefile.ejs +116 -0
  65. package/templates/root/README.md.ejs +158 -0
  66. package/templates/root/docker-compose.prod.yml.ejs +45 -0
  67. package/templates/root/docker-compose.yml.ejs +77 -0
  68. package/templates/root/gitignore +8 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lebowvsky
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,140 @@
1
+ # boiling-fullstack
2
+
3
+ Scaffold fullstack projects with Docker, NestJS backend, and Vue/Nuxt frontends in seconds.
4
+
5
+ ## Features
6
+
7
+ - **Multi-frontend** — 1 to 5 frontends per project, each independently configured
8
+ - **Framework choice** — Nuxt 4 (SSR) or Vue 3 + Vite (SPA) per frontend
9
+ - **Styling choice** — CSS or Sass per frontend
10
+ - **Backend** — NestJS + TypeORM + PostgreSQL with JWT authentication pre-configured
11
+ - **Database migrations** — TypeORM migration scripts ready to use
12
+ - **Docker Compose** — Dev and production configs with hot reloading
13
+ - **Makefile** — All common commands available via `make`
14
+ - **Code quality** — ESLint + Prettier pre-configured for all services
15
+ - **DB admin** — Optional pgAdmin or Adminer
16
+ - **Auto-generated README** — Each scaffolded project includes full documentation
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ npx boiling-fullstack my-project
22
+ ```
23
+
24
+ The interactive CLI guides you through the setup:
25
+
26
+ ```
27
+ ◆ Project name: my-project
28
+ ◆ Number of frontends (1-5): 2
29
+ ◆ Frontend 1 - Service name: app
30
+ ◆ Frontend 1 - Framework: Nuxt
31
+ ◆ Frontend 1 - Styling: Sass
32
+ ◆ Frontend 1 - Port: 3000
33
+ ◆ Frontend 2 - Service name: backoffice
34
+ ◆ Frontend 2 - Framework: Vue
35
+ ◆ Frontend 2 - Styling: Plain CSS
36
+ ◆ Frontend 2 - Port: 3010
37
+ ◆ Backend port: 3001
38
+ ◆ Database, DB admin tool...
39
+ ◆ Generate project? Yes
40
+
41
+ ✔ Project generated successfully!
42
+ ```
43
+
44
+ Then start everything:
45
+
46
+ ```bash
47
+ cd my-project
48
+ make up
49
+ ```
50
+
51
+ ## Prerequisites
52
+
53
+ - [Node.js](https://nodejs.org/) >= 20
54
+ - [Docker](https://docs.docker.com/get-docker/) & Docker Compose V2
55
+
56
+ ## Generated Structure
57
+
58
+ Example with 2 frontends (`app` Nuxt + `backoffice` Vue):
59
+
60
+ ```
61
+ my-project/
62
+ ├── app/ # Nuxt 4 frontend
63
+ │ ├── app/
64
+ │ │ ├── app.vue
65
+ │ │ └── pages/
66
+ │ │ └── index.vue
67
+ │ ├── Dockerfile
68
+ │ ├── nuxt.config.ts
69
+ │ ├── package.json
70
+ │ └── tsconfig.json
71
+ ├── backoffice/ # Vue 3 + Vite frontend
72
+ │ ├── src/
73
+ │ │ ├── App.vue
74
+ │ │ └── main.ts
75
+ │ ├── Dockerfile
76
+ │ ├── index.html
77
+ │ ├── package.json
78
+ │ ├── vite.config.ts
79
+ │ └── tsconfig.json
80
+ ├── backend/ # NestJS + TypeORM
81
+ │ ├── src/
82
+ │ │ ├── auth/
83
+ │ │ │ ├── auth.controller.ts
84
+ │ │ │ ├── auth.service.ts
85
+ │ │ │ ├── auth.module.ts
86
+ │ │ │ ├── jwt.strategy.ts
87
+ │ │ │ ├── auth.guard.ts
88
+ │ │ │ ├── dto/
89
+ │ │ │ └── entities/
90
+ │ │ │ └── user.entity.ts
91
+ │ │ ├── config/
92
+ │ │ │ ├── typeorm.config.ts
93
+ │ │ │ └── data-source.ts
94
+ │ │ ├── migrations/
95
+ │ │ ├── app.module.ts
96
+ │ │ └── main.ts
97
+ │ ├── Dockerfile
98
+ │ └── package.json
99
+ ├── docker-compose.yml # Dev
100
+ ├── docker-compose.prod.yml # Production
101
+ ├── .env # Dev env variables
102
+ ├── .env.production # Prod env variables (placeholders)
103
+ ├── Makefile
104
+ ├── README.md
105
+ └── .gitignore
106
+ ```
107
+
108
+ ## Available Commands
109
+
110
+ | Command | Description |
111
+ |---------|-------------|
112
+ | `make up` | Start all services (dev) |
113
+ | `make down` | Stop all services |
114
+ | `make logs` | View all logs |
115
+ | `make build` | Rebuild all services |
116
+ | `make restart` | Restart all services |
117
+ | `make clean` | Stop & remove volumes |
118
+ | `make db-shell` | Open PostgreSQL shell |
119
+ | `make backend-shell` | Open backend container shell |
120
+ | `make lint-back` | Run ESLint on backend |
121
+ | `make format-back` | Run Prettier on backend |
122
+ | `make migration-generate name=Name` | Generate a TypeORM migration |
123
+ | `make migration-run` | Run pending migrations |
124
+ | `make up-prod` | Start all services (production) |
125
+
126
+ ## CLI Options
127
+
128
+ ```
129
+ Usage: boiling [options] [project-name]
130
+
131
+ Options:
132
+ -f, --force Overwrite existing directory
133
+ -v, --verbose Show shell command output
134
+ -V, --version Output version number
135
+ -h, --help Display help
136
+ ```
137
+
138
+ ## License
139
+
140
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { CliOptions } from './types';
2
+ export declare function runCli(projectName?: string, options?: CliOptions): Promise<void>;
package/dist/cli.js ADDED
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runCli = runCli;
40
+ const clack = __importStar(require("@clack/prompts"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const validation_1 = require("./utils/validation");
43
+ const scaffolder_1 = require("./scaffolder");
44
+ const shell_1 = require("./utils/shell");
45
+ function handleCancel() {
46
+ clack.cancel('Operation cancelled.');
47
+ process.exit(0);
48
+ }
49
+ async function runCli(projectName, options = { force: false, verbose: false }) {
50
+ clack.intro(chalk_1.default.bgCyan.black(' boiling-fullstack '));
51
+ (0, shell_1.setVerbose)(options.verbose);
52
+ await (0, shell_1.checkEnvironment)();
53
+ // --- Project name ---
54
+ const nameResult = await clack.text({
55
+ message: 'Project name:',
56
+ placeholder: 'my-project',
57
+ initialValue: projectName,
58
+ validate(value) {
59
+ const result = (0, validation_1.isValidProjectName)(value);
60
+ if (result !== true)
61
+ return result;
62
+ },
63
+ });
64
+ if (clack.isCancel(nameResult))
65
+ handleCancel();
66
+ const finalProjectName = nameResult;
67
+ // --- Number of frontends ---
68
+ const frontendCountResult = await clack.text({
69
+ message: 'Number of frontends (1-5):',
70
+ defaultValue: '1',
71
+ validate(value) {
72
+ const n = parseInt(value, 10);
73
+ if (isNaN(n) || n < 1 || n > 5)
74
+ return 'Enter a number between 1 and 5';
75
+ },
76
+ });
77
+ if (clack.isCancel(frontendCountResult))
78
+ handleCancel();
79
+ const frontendCount = parseInt(frontendCountResult, 10);
80
+ // --- Frontend configs ---
81
+ const usedPorts = [5432];
82
+ const usedServiceNames = ['backend', 'db'];
83
+ const frontends = [];
84
+ for (let i = 0; i < frontendCount; i++) {
85
+ const defaultPort = 3000 + i * 10;
86
+ const prefix = frontendCount > 1 ? `Frontend ${i + 1}` : 'Frontend';
87
+ clack.log.step(chalk_1.default.cyan(`${prefix} configuration`));
88
+ // Name
89
+ const nameRes = await clack.text({
90
+ message: `${prefix} - Service name:`,
91
+ placeholder: frontendCount === 1 ? 'frontend' : `frontend-${i + 1}`,
92
+ defaultValue: frontendCount === 1 ? 'frontend' : `frontend-${i + 1}`,
93
+ validate(value) {
94
+ const result = (0, validation_1.isValidServiceName)(value, usedServiceNames);
95
+ if (result !== true)
96
+ return result;
97
+ },
98
+ });
99
+ if (clack.isCancel(nameRes))
100
+ handleCancel();
101
+ const serviceName = nameRes;
102
+ usedServiceNames.push(serviceName);
103
+ // Framework
104
+ const frameworkRes = await clack.select({
105
+ message: `${prefix} - Framework:`,
106
+ options: [
107
+ { value: 'nuxt', label: 'Nuxt', hint: 'full-stack Vue framework' },
108
+ { value: 'vue', label: 'Vue', hint: 'SPA with Vite' },
109
+ ],
110
+ initialValue: 'nuxt',
111
+ });
112
+ if (clack.isCancel(frameworkRes))
113
+ handleCancel();
114
+ const framework = frameworkRes;
115
+ // Styling
116
+ const stylingRes = await clack.select({
117
+ message: `${prefix} - Styling:`,
118
+ options: [
119
+ { value: 'css', label: 'Plain CSS' },
120
+ { value: 'sass', label: 'Sass', hint: 'installs sass as dependency' },
121
+ ],
122
+ initialValue: 'css',
123
+ });
124
+ if (clack.isCancel(stylingRes))
125
+ handleCancel();
126
+ const styling = stylingRes;
127
+ // Port
128
+ const portRes = await clack.text({
129
+ message: `${prefix} - Port:`,
130
+ defaultValue: String(defaultPort),
131
+ validate(value) {
132
+ const port = parseInt(value, 10);
133
+ if (isNaN(port))
134
+ return 'Enter a valid number';
135
+ const result = (0, validation_1.isValidPort)(port, usedPorts);
136
+ if (result !== true)
137
+ return result;
138
+ },
139
+ });
140
+ if (clack.isCancel(portRes))
141
+ handleCancel();
142
+ const port = parseInt(portRes, 10);
143
+ usedPorts.push(port);
144
+ frontends.push({ name: serviceName, framework, styling, port });
145
+ }
146
+ // --- Backend port ---
147
+ const backendPortRes = await clack.text({
148
+ message: 'Backend port:',
149
+ defaultValue: '3001',
150
+ validate(value) {
151
+ const port = parseInt(value, 10);
152
+ if (isNaN(port))
153
+ return 'Enter a valid number';
154
+ const result = (0, validation_1.isValidPort)(port, usedPorts);
155
+ if (result !== true)
156
+ return result;
157
+ },
158
+ });
159
+ if (clack.isCancel(backendPortRes))
160
+ handleCancel();
161
+ const backendPort = parseInt(backendPortRes, 10);
162
+ usedPorts.push(backendPort);
163
+ // --- Database config ---
164
+ clack.log.step(chalk_1.default.cyan('Database configuration'));
165
+ const dbNameRes = await clack.text({
166
+ message: 'Database name:',
167
+ defaultValue: `${finalProjectName.replace(/-/g, '_')}_db`,
168
+ validate(value) {
169
+ if (!value)
170
+ return 'Name is required';
171
+ },
172
+ });
173
+ if (clack.isCancel(dbNameRes))
174
+ handleCancel();
175
+ const dbName = dbNameRes;
176
+ const dbUserRes = await clack.text({
177
+ message: 'Database user:',
178
+ defaultValue: 'postgres',
179
+ validate(value) {
180
+ if (!value)
181
+ return 'User is required';
182
+ },
183
+ });
184
+ if (clack.isCancel(dbUserRes))
185
+ handleCancel();
186
+ const dbUser = dbUserRes;
187
+ const defaultPassword = (0, validation_1.generatePassword)();
188
+ const dbPasswordRes = await clack.text({
189
+ message: 'Database password:',
190
+ defaultValue: defaultPassword,
191
+ validate(value) {
192
+ if (!value)
193
+ return 'Password is required';
194
+ if (value.length < 8)
195
+ return 'Password must be at least 8 characters';
196
+ },
197
+ });
198
+ if (clack.isCancel(dbPasswordRes))
199
+ handleCancel();
200
+ const dbPassword = dbPasswordRes;
201
+ // --- JWT Secret ---
202
+ const jwtSecret = (0, validation_1.generateJwtSecret)();
203
+ // --- DB Admin tool ---
204
+ clack.log.step(chalk_1.default.cyan('DB admin tool'));
205
+ const dbAdminToolRes = await clack.select({
206
+ message: 'Database admin tool:',
207
+ options: [
208
+ { value: 'none', label: 'None' },
209
+ { value: 'pgadmin', label: 'pgAdmin', hint: 'feature-rich interface' },
210
+ { value: 'adminer', label: 'Adminer', hint: 'lightweight and fast' },
211
+ ],
212
+ initialValue: 'none',
213
+ });
214
+ if (clack.isCancel(dbAdminToolRes))
215
+ handleCancel();
216
+ const dbAdminTool = dbAdminToolRes;
217
+ let dbAdmin;
218
+ if (dbAdminTool !== 'none') {
219
+ const defaultPort = dbAdminTool === 'pgadmin' ? 5050 : 8080;
220
+ const dbAdminPortRes = await clack.text({
221
+ message: `${dbAdminTool === 'pgadmin' ? 'pgAdmin' : 'Adminer'} port:`,
222
+ defaultValue: String(defaultPort),
223
+ validate(value) {
224
+ const port = parseInt(value, 10);
225
+ if (isNaN(port))
226
+ return 'Enter a valid number';
227
+ const result = (0, validation_1.isValidPort)(port, usedPorts);
228
+ if (result !== true)
229
+ return result;
230
+ },
231
+ });
232
+ if (clack.isCancel(dbAdminPortRes))
233
+ handleCancel();
234
+ const dbAdminPort = parseInt(dbAdminPortRes, 10);
235
+ usedPorts.push(dbAdminPort);
236
+ if (dbAdminTool === 'pgadmin') {
237
+ const pgAdminEmailRes = await clack.text({
238
+ message: 'pgAdmin email:',
239
+ defaultValue: 'admin@admin.com',
240
+ validate(value) {
241
+ if (!value)
242
+ return 'Email is required';
243
+ },
244
+ });
245
+ if (clack.isCancel(pgAdminEmailRes))
246
+ handleCancel();
247
+ const pgAdminEmail = pgAdminEmailRes;
248
+ const defaultPgAdminPassword = (0, validation_1.generatePassword)();
249
+ const pgAdminPasswordRes = await clack.text({
250
+ message: 'pgAdmin password:',
251
+ defaultValue: defaultPgAdminPassword,
252
+ validate(value) {
253
+ if (!value)
254
+ return 'Password is required';
255
+ if (value.length < 8)
256
+ return 'Password must be at least 8 characters';
257
+ },
258
+ });
259
+ if (clack.isCancel(pgAdminPasswordRes))
260
+ handleCancel();
261
+ const pgAdminPassword = pgAdminPasswordRes;
262
+ dbAdmin = { tool: 'pgadmin', port: dbAdminPort, email: pgAdminEmail, password: pgAdminPassword };
263
+ }
264
+ else {
265
+ dbAdmin = { tool: 'adminer', port: dbAdminPort };
266
+ }
267
+ }
268
+ // --- Build config ---
269
+ const config = {
270
+ projectName: finalProjectName,
271
+ frontends,
272
+ backendPort,
273
+ dbName,
274
+ dbUser,
275
+ dbPassword,
276
+ jwtSecret,
277
+ dbAdmin,
278
+ };
279
+ // --- Recap ---
280
+ const frontendLines = config.frontends
281
+ .map((f, i) => ` ${i + 1}. ${f.name} (${f.framework}, ${f.styling}, port ${f.port})`)
282
+ .join('\n');
283
+ const dbAdminLabel = config.dbAdmin
284
+ ? `${config.dbAdmin.tool === 'pgadmin' ? 'pgAdmin' : 'Adminer'} (port ${config.dbAdmin.port})`
285
+ : 'None';
286
+ const recap = [
287
+ `${chalk_1.default.bold('Project')} : ${config.projectName}`,
288
+ `${chalk_1.default.bold('Frontends')} :`,
289
+ frontendLines,
290
+ `${chalk_1.default.bold('Backend')} : port ${config.backendPort}`,
291
+ `${chalk_1.default.bold('Database')} :`,
292
+ ` Name : ${config.dbName}`,
293
+ ` User : ${config.dbUser}`,
294
+ ` Password : ${config.dbPassword}`,
295
+ `${chalk_1.default.bold('DB Admin')} : ${dbAdminLabel}`,
296
+ ].join('\n');
297
+ clack.note(recap, 'Summary');
298
+ // --- Confirmation ---
299
+ const confirmRes = await clack.confirm({
300
+ message: 'Generate project?',
301
+ initialValue: true,
302
+ });
303
+ if (clack.isCancel(confirmRes))
304
+ handleCancel();
305
+ if (!confirmRes) {
306
+ clack.outro('Generation cancelled.');
307
+ return;
308
+ }
309
+ // --- Scaffold ---
310
+ await (0, scaffolder_1.scaffold)(config, options);
311
+ clack.note(`cd ${config.projectName}\nmake up`, 'Getting started');
312
+ clack.outro(chalk_1.default.green('Project generated successfully!'));
313
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const cli_1 = require("./cli");
10
+ const { version } = require('../package.json');
11
+ const program = new commander_1.Command();
12
+ program
13
+ .name('boiling')
14
+ .description('Scaffold a fullstack project with Docker, NestJS backend, and Vue/Nuxt frontends')
15
+ .version(version)
16
+ .argument('[project-name]', 'Project name')
17
+ .option('-f, --force', 'Overwrite existing directory', false)
18
+ .option('-v, --verbose', 'Show shell command output', false)
19
+ .action(async (projectName, opts) => {
20
+ try {
21
+ const options = { force: opts.force, verbose: opts.verbose };
22
+ await (0, cli_1.runCli)(projectName, options);
23
+ }
24
+ catch (error) {
25
+ console.error(chalk_1.default.red(`\nError: ${error.message}`));
26
+ process.exit(1);
27
+ }
28
+ });
29
+ process.on('unhandledRejection', (reason) => {
30
+ console.error(chalk_1.default.red(`\nUnexpected error: ${reason?.message || reason}`));
31
+ process.exit(1);
32
+ });
33
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import type { ProjectConfig, CliOptions } from './types';
2
+ export declare function scaffold(config: ProjectConfig, options: CliOptions): Promise<void>;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.scaffold = scaffold;
40
+ const path_1 = __importDefault(require("path"));
41
+ const fs_extra_1 = __importDefault(require("fs-extra"));
42
+ const clack = __importStar(require("@clack/prompts"));
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ const template_1 = require("./utils/template");
45
+ const shell_1 = require("./utils/shell");
46
+ function getTemplatesDir() {
47
+ return path_1.default.resolve(__dirname, '..', 'templates');
48
+ }
49
+ async function scaffold(config, options) {
50
+ const s = clack.spinner();
51
+ const projectDir = path_1.default.resolve(process.cwd(), config.projectName);
52
+ const templatesDir = getTemplatesDir();
53
+ if (await fs_extra_1.default.pathExists(projectDir)) {
54
+ if (options.force) {
55
+ await fs_extra_1.default.remove(projectDir);
56
+ }
57
+ else {
58
+ throw new Error(`Directory "${config.projectName}" already exists. Use --force to overwrite.`);
59
+ }
60
+ }
61
+ try {
62
+ await fs_extra_1.default.ensureDir(projectDir);
63
+ // Frontends
64
+ for (const fe of config.frontends) {
65
+ s.start(`Generating ${chalk_1.default.cyan(fe.name)} (${fe.framework})...`);
66
+ await (0, template_1.copyAndRenderDir)(path_1.default.join(templatesDir, 'frontend', fe.framework), path_1.default.join(projectDir, fe.name), { name: fe.name, styling: fe.styling });
67
+ s.stop(`${chalk_1.default.cyan(fe.name)} created`);
68
+ }
69
+ // Backend
70
+ s.start(`Generating ${chalk_1.default.cyan('backend')} (NestJS)...`);
71
+ await (0, template_1.copyAndRenderDir)(path_1.default.join(templatesDir, 'backend', 'nestjs'), path_1.default.join(projectDir, 'backend'), { projectName: config.projectName, backendPort: config.backendPort });
72
+ s.stop(`${chalk_1.default.cyan('backend')} created`);
73
+ // Root files
74
+ s.start('Generating root files...');
75
+ await (0, template_1.copyAndRenderDir)(path_1.default.join(templatesDir, 'root'), projectDir, { ...config });
76
+ s.stop('Root files created');
77
+ // Git init
78
+ s.start('Initializing git repository...');
79
+ await (0, shell_1.gitInit)(projectDir);
80
+ s.stop('Git repository initialized');
81
+ }
82
+ catch (error) {
83
+ s.stop(chalk_1.default.red('Error'));
84
+ if (await fs_extra_1.default.pathExists(projectDir)) {
85
+ await fs_extra_1.default.remove(projectDir);
86
+ }
87
+ throw error;
88
+ }
89
+ }
@@ -0,0 +1,28 @@
1
+ export interface FrontendConfig {
2
+ name: string;
3
+ framework: 'nuxt' | 'vue';
4
+ styling: 'css' | 'sass';
5
+ port: number;
6
+ }
7
+ export interface ProjectConfig {
8
+ projectName: string;
9
+ frontends: FrontendConfig[];
10
+ backendPort: number;
11
+ dbName: string;
12
+ dbUser: string;
13
+ dbPassword: string;
14
+ jwtSecret?: string;
15
+ dbAdmin?: DbAdminConfig;
16
+ }
17
+ export type DbAdminTool = 'none' | 'pgadmin' | 'adminer';
18
+ export interface DbAdminConfig {
19
+ tool: DbAdminTool;
20
+ port: number;
21
+ email?: string;
22
+ password?: string;
23
+ }
24
+ export interface CliOptions {
25
+ force: boolean;
26
+ verbose: boolean;
27
+ }
28
+ export type ValidationResult = true | string;
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export declare function setVerbose(v: boolean): void;
2
+ export declare function runCommand(command: string, args: string[], cwd: string): Promise<void>;
3
+ export declare function checkCommand(cmd: string): Promise<boolean>;
4
+ export declare function checkEnvironment(): Promise<void>;
5
+ export declare function gitInit(cwd: string): Promise<void>;
6
+ export declare function npmInstall(cwd: string): Promise<void>;