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.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +313 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +33 -0
- package/dist/scaffolder.d.ts +2 -0
- package/dist/scaffolder.js +89 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.js +2 -0
- package/dist/utils/shell.d.ts +6 -0
- package/dist/utils/shell.js +53 -0
- package/dist/utils/template.d.ts +2 -0
- package/dist/utils/template.js +51 -0
- package/dist/utils/validation.d.ts +6 -0
- package/dist/utils/validation.js +59 -0
- package/package.json +40 -0
- package/templates/backend/nestjs/.dockerignore +3 -0
- package/templates/backend/nestjs/.eslintrc.cjs.ejs +19 -0
- package/templates/backend/nestjs/.prettierrc +6 -0
- package/templates/backend/nestjs/Dockerfile +44 -0
- package/templates/backend/nestjs/nest-cli.json +8 -0
- package/templates/backend/nestjs/package.json.ejs +52 -0
- package/templates/backend/nestjs/src/app.controller.ts +17 -0
- package/templates/backend/nestjs/src/app.module.ts.ejs +20 -0
- package/templates/backend/nestjs/src/app.service.ts +4 -0
- package/templates/backend/nestjs/src/auth/auth.controller.ts +19 -0
- package/templates/backend/nestjs/src/auth/auth.guard.ts +5 -0
- package/templates/backend/nestjs/src/auth/auth.module.ts +28 -0
- package/templates/backend/nestjs/src/auth/auth.service.ts +50 -0
- package/templates/backend/nestjs/src/auth/dto/login.dto.ts +10 -0
- package/templates/backend/nestjs/src/auth/dto/register.dto.ts +10 -0
- package/templates/backend/nestjs/src/auth/entities/user.entity.ts +25 -0
- package/templates/backend/nestjs/src/auth/jwt.strategy.ts +19 -0
- package/templates/backend/nestjs/src/config/data-source.ts +9 -0
- package/templates/backend/nestjs/src/config/typeorm.config.ts.ejs +8 -0
- package/templates/backend/nestjs/src/main.ts.ejs +20 -0
- package/templates/backend/nestjs/src/migrations/.gitkeep +0 -0
- package/templates/backend/nestjs/tsconfig.build.json +4 -0
- package/templates/backend/nestjs/tsconfig.json +21 -0
- package/templates/frontend/nuxt/.dockerignore +5 -0
- package/templates/frontend/nuxt/.eslintrc.cjs.ejs +5 -0
- package/templates/frontend/nuxt/.prettierrc +6 -0
- package/templates/frontend/nuxt/Dockerfile +38 -0
- package/templates/frontend/nuxt/app/app.vue +3 -0
- package/templates/frontend/nuxt/app/pages/index.vue.ejs +25 -0
- package/templates/frontend/nuxt/nuxt.config.ts.ejs +19 -0
- package/templates/frontend/nuxt/package.json.ejs +25 -0
- package/templates/frontend/nuxt/tsconfig.json +3 -0
- package/templates/frontend/vue/.dockerignore +3 -0
- package/templates/frontend/vue/.eslintrc.cjs.ejs +14 -0
- package/templates/frontend/vue/.prettierrc +6 -0
- package/templates/frontend/vue/Dockerfile +33 -0
- package/templates/frontend/vue/index.html.ejs +13 -0
- package/templates/frontend/vue/package.json.ejs +28 -0
- package/templates/frontend/vue/src/App.vue.ejs +22 -0
- package/templates/frontend/vue/src/main.ts +4 -0
- package/templates/frontend/vue/src/vite-env.d.ts +1 -0
- package/templates/frontend/vue/tsconfig.json +20 -0
- package/templates/frontend/vue/vite.config.ts.ejs +14 -0
- package/templates/root/.env.ejs +24 -0
- package/templates/root/.env.example.ejs +24 -0
- package/templates/root/.env.production.ejs +17 -0
- package/templates/root/Makefile.ejs +116 -0
- package/templates/root/README.md.ejs +158 -0
- package/templates/root/docker-compose.prod.yml.ejs +45 -0
- package/templates/root/docker-compose.yml.ejs +77 -0
- 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
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
|
+
}
|
package/dist/index.d.ts
ADDED
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,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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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,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>;
|