boilercraft 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 +171 -0
- package/bin/boilercraft.js +110 -0
- package/cli/app.js +596 -0
- package/cli/ui.js +356 -0
- package/docs/STACK_ENGINE.md +93 -0
- package/docs/assets/cli-create.svg +52 -0
- package/docs/assets/cli-menu.svg +47 -0
- package/docs/stacks/dotnet-core.md +69 -0
- package/docs/stacks/laravel-vue.md +46 -0
- package/docs/stacks/nextjs.md +58 -0
- package/docs/stacks/node-express.md +58 -0
- package/docs/stacks/raw-php.md +130 -0
- package/main.js +82 -0
- package/package.json +86 -0
- package/public/index.html +721 -0
- package/public/logo.png +17 -0
- package/public/logo.svg +17 -0
- package/server.js +84 -0
- package/services/componentGenerator.js +195 -0
- package/services/engine/scaffoldEngine.js +517 -0
- package/services/engine/toolInstaller.js +232 -0
- package/services/engine/versionCatalog.js +115 -0
- package/services/generate.js +46 -0
- package/services/projectGenerator.js +686 -0
- package/services/readmeGenerator.js +221 -0
- package/services/themeGenerator.js +150 -0
- package/services/versionResolver.js +176 -0
- package/stacks/dotnet-core/overlays/api/Controllers/ProductsController.cs +33 -0
- package/stacks/dotnet-core/overlays/api/Program.cs +33 -0
- package/stacks/dotnet-core/overlays/api/wwwroot/index.html +38 -0
- package/stacks/dotnet-core/overlays/api-auth/Controllers/AuthController.cs +87 -0
- package/stacks/dotnet-core/overlays/common/Data/ApplicationDbContext.cs +25 -0
- package/stacks/dotnet-core/overlays/common/Models/Product.cs +19 -0
- package/stacks/dotnet-core/overlays/mvc/Controllers/ProductsController.cs +84 -0
- package/stacks/dotnet-core/overlays/mvc/Views/Home/Index.cshtml +23 -0
- package/stacks/dotnet-core/overlays/mvc/Views/Products/Create.cshtml +16 -0
- package/stacks/dotnet-core/overlays/mvc/Views/Products/Delete.cshtml +16 -0
- package/stacks/dotnet-core/overlays/mvc/Views/Products/Edit.cshtml +17 -0
- package/stacks/dotnet-core/overlays/mvc/Views/Products/Index.cshtml +46 -0
- package/stacks/dotnet-core/overlays/mvc/Views/Products/_Form.cshtml +19 -0
- package/stacks/dotnet-core/overlays/mvc-auth/Controllers/AccountController.cs +98 -0
- package/stacks/dotnet-core/overlays/mvc-auth/Models/AppUser.cs +48 -0
- package/stacks/dotnet-core/overlays/mvc-auth/Views/Account/Login.cshtml +35 -0
- package/stacks/dotnet-core/overlays/mvc-auth/Views/Account/Register.cshtml +41 -0
- package/stacks/dotnet-core/overlays/mvc-auth/Views/Shared/_AuthNav.cshtml +22 -0
- package/stacks/dotnet-core/snippets/packages-auth.xml +4 -0
- package/stacks/dotnet-core/snippets/packages-common.xml +5 -0
- package/stacks/dotnet-core/snippets/packages-ef.xml +4 -0
- package/stacks/dotnet-core/snippets/packages-mssql.xml +4 -0
- package/stacks/dotnet-core/snippets/packages-mysql.xml +4 -0
- package/stacks/dotnet-core/snippets/packages-swagger.xml +4 -0
- package/stacks/dotnet-core/stack.json +426 -0
- package/stacks/laravel/overlays/auth/app/Http/Controllers/Api/AuthController.php +67 -0
- package/stacks/laravel/overlays/common/app/Http/Controllers/BoilerCraftController.php +14 -0
- package/stacks/laravel/overlays/common/resources/views/boilercraft.blade.php +36 -0
- package/stacks/laravel/overlays/inertia-common/resources/views/app.blade.php +15 -0
- package/stacks/laravel/overlays/inertia-react/resources/js/Pages/Welcome.jsx +18 -0
- package/stacks/laravel/overlays/inertia-react/resources/js/app.jsx +13 -0
- package/stacks/laravel/overlays/inertia-vue/resources/js/Pages/Welcome.vue +23 -0
- package/stacks/laravel/overlays/inertia-vue/resources/js/app.js +13 -0
- package/stacks/laravel/snippets/api-routes.php +9 -0
- package/stacks/laravel/snippets/web-routes-inertia.php +7 -0
- package/stacks/laravel/snippets/web-routes.php +4 -0
- package/stacks/laravel/stack.json +477 -0
- package/stacks/nextjs/overlays/auth/src/lib/auth.js +36 -0
- package/stacks/nextjs/overlays/auth-app/src/app/api/auth/login/route.js +11 -0
- package/stacks/nextjs/overlays/auth-app/src/app/api/auth/register/route.js +14 -0
- package/stacks/nextjs/overlays/auth-pages/src/pages/api/auth/login.js +14 -0
- package/stacks/nextjs/overlays/auth-pages/src/pages/api/auth/register.js +17 -0
- package/stacks/nextjs/overlays/common/.env.example +3 -0
- package/stacks/nextjs/overlays/common/.env.local +3 -0
- package/stacks/nextjs/overlays/db/mongodb/src/lib/db.js +12 -0
- package/stacks/nextjs/overlays/db/mssql/src/lib/db.js +17 -0
- package/stacks/nextjs/overlays/db/mysql/src/lib/db.js +13 -0
- package/stacks/nextjs/overlays/router-app/src/app/api/health/route.js +10 -0
- package/stacks/nextjs/overlays/router-app/src/app/layout.js +16 -0
- package/stacks/nextjs/overlays/router-app/src/app/page.js +19 -0
- package/stacks/nextjs/overlays/router-pages/src/pages/_app.js +7 -0
- package/stacks/nextjs/overlays/router-pages/src/pages/_document.js +13 -0
- package/stacks/nextjs/overlays/router-pages/src/pages/api/health.js +8 -0
- package/stacks/nextjs/overlays/router-pages/src/pages/index.js +27 -0
- package/stacks/nextjs/stack.json +257 -0
- package/stacks/node-express/overlays/auth/src/controllers/authController.js +53 -0
- package/stacks/node-express/overlays/auth/src/middlewares/auth.js +36 -0
- package/stacks/node-express/overlays/auth/src/routes/authRoutes.js +16 -0
- package/stacks/node-express/overlays/common/.env.example +5 -0
- package/stacks/node-express/overlays/common/public/index.html +31 -0
- package/stacks/node-express/overlays/common/src/server.js +32 -0
- package/stacks/node-express/overlays/db/mongodb/src/db.js +13 -0
- package/stacks/node-express/overlays/db/mssql/src/db.js +22 -0
- package/stacks/node-express/overlays/db/mysql/src/db.js +22 -0
- package/stacks/node-express/stack.json +165 -0
- package/templates/dotnet-bootstrap-mssql/Controllers/AuthController.cs +74 -0
- package/templates/dotnet-bootstrap-mssql/Controllers/ProductsController.cs +33 -0
- package/templates/dotnet-bootstrap-mssql/Data/ApplicationDbContext.cs +24 -0
- package/templates/dotnet-bootstrap-mssql/Models/Product.cs +10 -0
- package/templates/dotnet-core/Controllers/AuthController.cs +74 -0
- package/templates/laravel-full/app/Http/Controllers/AuthController.php +79 -0
- package/templates/laravel-full/app/Http/Controllers/HomeController.php +22 -0
- package/templates/laravel-full/app/Models/User.php +49 -0
- package/templates/laravel-full/artisan +22 -0
- package/templates/laravel-full/bootstrap/app.php +21 -0
- package/templates/laravel-full/config/database.php +54 -0
- package/templates/laravel-full/database/migrations/2024_01_01_000001_create_users_table.php +27 -0
- package/templates/laravel-full/public/index.php +17 -0
- package/templates/laravel-full/resources/css/app.css +3 -0
- package/templates/laravel-full/resources/js/Pages/Home.vue +76 -0
- package/templates/laravel-full/resources/js/app.js +21 -0
- package/templates/laravel-full/resources/js/bootstrap.js +3 -0
- package/templates/laravel-full/resources/views/app.blade.php +15 -0
- package/templates/laravel-full/routes/api.php +8 -0
- package/templates/laravel-full/routes/console.php +7 -0
- package/templates/laravel-full/routes/web.php +24 -0
- package/templates/laravel-full/tailwind.config.js +12 -0
- package/templates/laravel-full/vite.config.js +20 -0
- package/templates/laravel-vue/app/Http/Controllers/Auth/AuthenticatedSessionController.php +43 -0
- package/templates/laravel-vue-mysql/app/Http/Controllers/HomeController.php +25 -0
- package/templates/laravel-vue-mysql/resources/js/Pages/Home.vue +70 -0
- package/templates/nextjs/src/app/api/auth/login/route.js +28 -0
- package/templates/nextjs-mongodb/src/app/api/health/route.js +17 -0
- package/templates/nextjs-mongodb/src/lib/mongodb.js +18 -0
- package/templates/nextjs-mssql/src/app/api/health/route.js +24 -0
- package/templates/nextjs-mssql/src/lib/mssql.js +38 -0
- package/templates/node-express/src/controllers/authController.js +53 -0
- package/templates/node-express/src/middlewares/auth.js +36 -0
- package/templates/node-express/src/routes/authRoutes.js +16 -0
- package/templates/node-mongodb/src/controllers/userController.js +30 -0
- package/templates/node-mongodb/src/middlewares/errorHandler.js +8 -0
- package/templates/node-mongodb/src/models/User.js +23 -0
- package/templates/node-mongodb/src/routes/api.js +9 -0
- package/templates/raw-php-mysql/app/Controllers/AuthController.php +73 -0
- package/templates/raw-php-mysql/app/Controllers/HomeController.php +65 -0
- package/templates/raw-php-mysql/app/Models/User.php +60 -0
- package/templates/raw-php-mysql/app/Views/404.php +15 -0
- package/templates/raw-php-mysql/app/Views/auth/login.php +66 -0
- package/templates/raw-php-mysql/app/Views/auth/register.php +71 -0
- package/templates/raw-php-mysql/app/Views/dashboard.php +50 -0
- package/templates/raw-php-mysql/app/Views/home.php +83 -0
- package/templates/raw-php-mysql/config/Auth.php +62 -0
- package/templates/raw-php-mysql/config/Database.php +100 -0
- package/templates/raw-php-mysql/config/Env.php +30 -0
- package/templates/raw-php-mysql/config/Middleware.php +53 -0
- package/templates/raw-php-mysql/config/QueryBuilder.php +109 -0
- package/templates/raw-php-mysql/config/Router.php +33 -0
- package/templates/raw-php-mysql/public/index.php +57 -0
package/cli/ui.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
|
|
3
|
+
// Terminal UI primitives: colors, banner, boxes, menus, prompts, spinner.
|
|
4
|
+
// No dependencies; falls back to plain numbered prompts when not in a TTY.
|
|
5
|
+
|
|
6
|
+
const isTTY = !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
7
|
+
const useColor = !!process.stdout.isTTY && !process.env.NO_COLOR;
|
|
8
|
+
const trueColor = useColor && (process.env.COLORTERM === 'truecolor' || process.env.COLORTERM === '24bit' ||
|
|
9
|
+
!!process.env.WT_SESSION || process.env.TERM_PROGRAM === 'vscode' || process.platform === 'win32');
|
|
10
|
+
|
|
11
|
+
const paint = code => s => (useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
12
|
+
const c = {
|
|
13
|
+
bold: paint(1), dim: paint(2), italic: paint(3),
|
|
14
|
+
red: paint(31), green: paint(32), yellow: paint(33), blue: paint(34), magenta: paint(35), cyan: paint(36), gray: paint(90)
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function rgb(r, g, b, s) {
|
|
18
|
+
if (!useColor) return s;
|
|
19
|
+
return trueColor ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[0m` : c.cyan(s);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function swatch(hex) {
|
|
23
|
+
if (!useColor) return '';
|
|
24
|
+
const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16));
|
|
25
|
+
return trueColor ? `\x1b[48;2;${r};${g};${b}m \x1b[0m` : '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const stripAnsi = s => String(s).replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
29
|
+
const width = s => [...stripAnsi(s)].length;
|
|
30
|
+
const columns = () => process.stdout.columns || 80;
|
|
31
|
+
|
|
32
|
+
// Truncates to a visible width, keeping ANSI styling intact.
|
|
33
|
+
function truncate(s, max) {
|
|
34
|
+
if (width(s) <= max) return s;
|
|
35
|
+
let out = '';
|
|
36
|
+
let visible = 0;
|
|
37
|
+
for (const part of String(s).split(/(\x1b\[[0-9;?]*[A-Za-z])/)) {
|
|
38
|
+
if (/^\x1b\[/.test(part)) {
|
|
39
|
+
out += part;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for (const ch of part) {
|
|
43
|
+
if (visible >= max - 1) return out + '…' + (useColor ? '\x1b[0m' : '');
|
|
44
|
+
out += ch;
|
|
45
|
+
visible++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Brand gradient: cyan → blue → violet.
|
|
52
|
+
const STOPS = [[34, 211, 238], [59, 130, 246], [139, 92, 246]];
|
|
53
|
+
function gradientAt(t) {
|
|
54
|
+
const seg = t < 0.5 ? 0 : 1;
|
|
55
|
+
const local = t < 0.5 ? t * 2 : (t - 0.5) * 2;
|
|
56
|
+
const [a, b] = [STOPS[seg], STOPS[seg + 1]];
|
|
57
|
+
return a.map((v, i) => Math.round(v + (b[i] - v) * local));
|
|
58
|
+
}
|
|
59
|
+
function gradient(text) {
|
|
60
|
+
const chars = [...text];
|
|
61
|
+
return chars.map((ch, i) => (ch === ' ' ? ch : rgb(...gradientAt(chars.length > 1 ? i / (chars.length - 1) : 0), ch))).join('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// "ANSI Shadow" glyphs for the banner.
|
|
65
|
+
const GLYPHS = {
|
|
66
|
+
B: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '╚═════╝ '],
|
|
67
|
+
O: [' ██████╗ ', '██╔═══██╗', '██║ ██║', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
68
|
+
I: ['██╗', '██║', '██║', '██║', '██║', '╚═╝'],
|
|
69
|
+
L: ['██╗ ', '██║ ', '██║ ', '██║ ', '███████╗', '╚══════╝'],
|
|
70
|
+
E: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
|
71
|
+
R: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
|
|
72
|
+
C: [' ██████╗', '██╔════╝', '██║ ', '██║ ', '╚██████╗', ' ╚═════╝'],
|
|
73
|
+
A: [' █████╗ ', '██╔══██╗', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
|
74
|
+
F: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '██║ ', '╚═╝ '],
|
|
75
|
+
T: ['████████╗', '╚══██╔══╝', ' ██║ ', ' ██║ ', ' ██║ ', ' ╚═╝ ']
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function banner({ version, tagline, author }) {
|
|
79
|
+
const word = 'BOILERCRAFT';
|
|
80
|
+
const rows = GLYPHS.B.map((_, r) => [...word].map(ch => GLYPHS[ch][r]).join(''));
|
|
81
|
+
const artWidth = width(rows[0]);
|
|
82
|
+
const out = [''];
|
|
83
|
+
|
|
84
|
+
if (columns() >= artWidth + 4) {
|
|
85
|
+
for (const row of rows) out.push(' ' + gradient(row));
|
|
86
|
+
} else {
|
|
87
|
+
out.push(' ' + c.bold(gradient('◆ B O I L E R C R A F T')));
|
|
88
|
+
}
|
|
89
|
+
const byline = `${c.dim('by')} ${c.bold(author)} ${c.gray('·')} ${c.dim(`v${version}`)}`;
|
|
90
|
+
out.push('');
|
|
91
|
+
out.push(` ${byline}`);
|
|
92
|
+
out.push(` ${c.gray(tagline)}`);
|
|
93
|
+
out.push('');
|
|
94
|
+
return out.join('\n');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function box(lines, { title, color = c.cyan, padding = 1 } = {}) {
|
|
98
|
+
const maxLine = Math.max(20, columns() - 8 - padding * 2);
|
|
99
|
+
lines = lines.map(l => truncate(l, maxLine));
|
|
100
|
+
const inner = Math.max(...lines.map(width), title ? width(title) + 2 : 0) + padding * 2;
|
|
101
|
+
const pad = ' '.repeat(padding);
|
|
102
|
+
const top = title
|
|
103
|
+
? `${color('╭─')} ${c.bold(title)} ${color('─'.repeat(Math.max(0, inner - width(title) - 3)) + '╮')}`
|
|
104
|
+
: color(`╭${'─'.repeat(inner)}╮`);
|
|
105
|
+
const body = lines.map(l => `${color('│')}${pad}${l}${' '.repeat(inner - padding * 2 - width(l))}${pad}${color('│')}`);
|
|
106
|
+
return [' ' + top, ...body.map(b => ' ' + b), ' ' + color(`╰${'─'.repeat(inner)}╯`)].join('\n');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function divider(label = '') {
|
|
110
|
+
const w = Math.min(columns() - 4, 72);
|
|
111
|
+
if (!label) return ' ' + c.gray('─'.repeat(w));
|
|
112
|
+
return ' ' + c.gray('── ') + c.bold(label) + ' ' + c.gray('─'.repeat(Math.max(0, w - width(label) - 4)));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Input
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
// Buffered line reader for non-TTY input (piped answers), so answers sent
|
|
120
|
+
// ahead of time aren't lost while async work (version lookups) runs.
|
|
121
|
+
let queue;
|
|
122
|
+
function nextLine() {
|
|
123
|
+
if (!queue) {
|
|
124
|
+
queue = { lines: [], waiters: [], closed: false };
|
|
125
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
126
|
+
rl.on('line', l => (queue.waiters.length ? queue.waiters.shift()(l) : queue.lines.push(l)));
|
|
127
|
+
rl.on('close', () => {
|
|
128
|
+
queue.closed = true;
|
|
129
|
+
while (queue.waiters.length) queue.waiters.shift()('');
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (queue.lines.length) return Promise.resolve(queue.lines.shift());
|
|
133
|
+
if (queue.closed) return Promise.resolve(null);
|
|
134
|
+
return new Promise(resolve => queue.waiters.push(resolve));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class CancelError extends Error {
|
|
138
|
+
constructor() {
|
|
139
|
+
super('Cancelled');
|
|
140
|
+
this.name = 'CancelError';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function input(question, { initial, validate } = {}) {
|
|
145
|
+
for (;;) {
|
|
146
|
+
const prompt = ` ${c.cyan('?')} ${c.bold(question)}${initial ? c.gray(` (${initial})`) : ''} ${c.gray('›')} `;
|
|
147
|
+
let answer;
|
|
148
|
+
if (isTTY) {
|
|
149
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
150
|
+
answer = await new Promise(resolve => {
|
|
151
|
+
rl.on('SIGINT', () => {
|
|
152
|
+
rl.close();
|
|
153
|
+
process.stdout.write('\n');
|
|
154
|
+
resolve(null);
|
|
155
|
+
});
|
|
156
|
+
rl.question(prompt, resolve);
|
|
157
|
+
});
|
|
158
|
+
rl.close();
|
|
159
|
+
if (answer === null) throw new CancelError();
|
|
160
|
+
} else {
|
|
161
|
+
process.stdout.write(prompt);
|
|
162
|
+
answer = await nextLine();
|
|
163
|
+
if (answer === null) throw new CancelError();
|
|
164
|
+
process.stdout.write(`${answer}\n`);
|
|
165
|
+
}
|
|
166
|
+
const value = answer.trim() || initial || '';
|
|
167
|
+
const error = validate ? validate(value) : null;
|
|
168
|
+
if (!error) {
|
|
169
|
+
if (isTTY) {
|
|
170
|
+
// Replace the prompt line with a compact answered line.
|
|
171
|
+
process.stdout.write('\x1b[1A\x1b[2K');
|
|
172
|
+
console.log(` ${c.green('✔')} ${c.bold(question)} ${c.gray('·')} ${c.cyan(value)}`);
|
|
173
|
+
}
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
console.log(` ${c.red('✖')} ${c.red(error)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* options: [{ value, label, hint, short, disabled }]
|
|
182
|
+
* Arrow keys / j k / number keys; Enter selects; Esc or Ctrl+C cancels.
|
|
183
|
+
*/
|
|
184
|
+
async function select(question, options, { initial = 0 } = {}) {
|
|
185
|
+
const enabled = options.map((o, i) => (o.disabled ? -1 : i)).filter(i => i >= 0);
|
|
186
|
+
let index = enabled.includes(initial) ? initial : enabled[0];
|
|
187
|
+
if (!isTTY) return selectPlain(question, options, index);
|
|
188
|
+
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const out = process.stdout;
|
|
191
|
+
readline.emitKeypressEvents(process.stdin);
|
|
192
|
+
process.stdin.setRawMode(true);
|
|
193
|
+
process.stdin.resume();
|
|
194
|
+
out.write('\x1b[?25l');
|
|
195
|
+
|
|
196
|
+
const header = truncate(` ${c.cyan('?')} ${c.bold(question)} ${c.gray('· ↑↓ to move, enter to select')}`, columns() - 1);
|
|
197
|
+
const lineFor = (o, i) => {
|
|
198
|
+
const active = i === index;
|
|
199
|
+
const pointer = active ? c.cyan('❯') : ' ';
|
|
200
|
+
const label = o.disabled ? c.gray(o.label) : active ? c.cyan(c.bold(o.label)) : o.label;
|
|
201
|
+
const hint = o.hint ? ` ${c.gray(o.hint)}` : '';
|
|
202
|
+
// Never wrap: a wrapped line would break the redraw (cursor-up count).
|
|
203
|
+
const full = ` ${pointer} ${label}${hint}`;
|
|
204
|
+
const fit = width(full) <= columns() - 1 ? full : truncate(` ${pointer} ${label}`, columns() - 1);
|
|
205
|
+
return `\x1b[2K${fit}`;
|
|
206
|
+
};
|
|
207
|
+
out.write(header + '\n');
|
|
208
|
+
const render = first => {
|
|
209
|
+
if (!first) out.write(`\x1b[${options.length}A`);
|
|
210
|
+
options.forEach((o, i) => out.write(lineFor(o, i) + '\n'));
|
|
211
|
+
};
|
|
212
|
+
render(true);
|
|
213
|
+
|
|
214
|
+
const move = step => {
|
|
215
|
+
const pos = enabled.indexOf(index);
|
|
216
|
+
index = enabled[(pos + step + enabled.length) % enabled.length];
|
|
217
|
+
};
|
|
218
|
+
const finish = () => {
|
|
219
|
+
process.stdin.removeListener('keypress', onKey);
|
|
220
|
+
process.stdin.setRawMode(false);
|
|
221
|
+
process.stdin.pause();
|
|
222
|
+
out.write(`\x1b[${options.length + 1}A\x1b[0J\x1b[?25h`);
|
|
223
|
+
};
|
|
224
|
+
const onKey = (str, key = {}) => {
|
|
225
|
+
if ((key.ctrl && key.name === 'c') || key.name === 'escape') {
|
|
226
|
+
finish();
|
|
227
|
+
return reject(new CancelError());
|
|
228
|
+
}
|
|
229
|
+
if (key.name === 'up' || key.name === 'k') move(-1);
|
|
230
|
+
else if (key.name === 'down' || key.name === 'j' || key.name === 'tab') move(1);
|
|
231
|
+
else if (/^[1-9]$/.test(str || '') && enabled.includes(+str - 1)) index = +str - 1;
|
|
232
|
+
else if (key.name === 'return' || key.name === 'enter') {
|
|
233
|
+
finish();
|
|
234
|
+
const chosen = options[index];
|
|
235
|
+
out.write(` ${c.green('✔')} ${c.bold(question)} ${c.gray('·')} ${c.cyan(chosen.short || stripAnsi(chosen.label).trim())}\n`);
|
|
236
|
+
return resolve(chosen.value);
|
|
237
|
+
}
|
|
238
|
+
render(false);
|
|
239
|
+
};
|
|
240
|
+
process.stdin.on('keypress', onKey);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function selectPlain(question, options, initial) {
|
|
245
|
+
console.log(` ${c.cyan('?')} ${c.bold(question)}`);
|
|
246
|
+
options.forEach((o, i) => {
|
|
247
|
+
if (o.disabled) return console.log(c.gray(` - ${stripAnsi(o.label)}`));
|
|
248
|
+
console.log(` ${String(i + 1).padStart(2)}) ${o.label}${o.hint ? c.gray(` ${o.hint}`) : ''}`);
|
|
249
|
+
});
|
|
250
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
251
|
+
process.stdout.write(` ${c.gray(`choose [${initial + 1}]:`)} `);
|
|
252
|
+
const answer = await nextLine();
|
|
253
|
+
if (answer === null) throw new CancelError();
|
|
254
|
+
process.stdout.write(`${answer}\n`);
|
|
255
|
+
if (!answer.trim()) return options[initial].value;
|
|
256
|
+
const n = parseInt(answer, 10);
|
|
257
|
+
if (n >= 1 && n <= options.length && !options[n - 1].disabled) return options[n - 1].value;
|
|
258
|
+
console.log(` ${c.red('✖ Invalid choice')}`);
|
|
259
|
+
}
|
|
260
|
+
throw new CancelError();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function confirm(question, initial = true) {
|
|
264
|
+
return select(question, [
|
|
265
|
+
{ value: true, label: 'Yes', short: 'Yes' },
|
|
266
|
+
{ value: false, label: 'No', short: 'No' }
|
|
267
|
+
], { initial: initial ? 0 : 1 });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function spinner(text, fn) {
|
|
271
|
+
if (!isTTY) return fn();
|
|
272
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
273
|
+
let i = 0;
|
|
274
|
+
process.stdout.write('\x1b[?25l');
|
|
275
|
+
const timer = setInterval(() => process.stdout.write(`\r ${c.cyan(frames[i++ % frames.length])} ${c.dim(text)}`), 80);
|
|
276
|
+
try {
|
|
277
|
+
return await fn();
|
|
278
|
+
} finally {
|
|
279
|
+
clearInterval(timer);
|
|
280
|
+
process.stdout.write('\r\x1b[2K\x1b[?25h');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Checklist-style progress: each update() ticks off the previous step and
|
|
286
|
+
* spins on the new one. pause()/resume() let interactive child processes
|
|
287
|
+
* (installers asking for a password) use the terminal in between.
|
|
288
|
+
*/
|
|
289
|
+
function tasks() {
|
|
290
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
291
|
+
let current = null;
|
|
292
|
+
let detail = '';
|
|
293
|
+
let timer = null;
|
|
294
|
+
let i = 0;
|
|
295
|
+
const started = Date.now();
|
|
296
|
+
|
|
297
|
+
const draw = () => {
|
|
298
|
+
const extra = detail ? ` ${c.gray(detail.length > 60 ? detail.slice(0, 57) + '…' : detail)}` : '';
|
|
299
|
+
process.stdout.write(`\r\x1b[2K ${c.cyan(frames[i++ % frames.length])} ${current}${extra}`);
|
|
300
|
+
};
|
|
301
|
+
const startTimer = () => {
|
|
302
|
+
if (!isTTY || !current || timer) return;
|
|
303
|
+
process.stdout.write('\x1b[?25l');
|
|
304
|
+
timer = setInterval(draw, 80);
|
|
305
|
+
};
|
|
306
|
+
const stopTimer = () => {
|
|
307
|
+
if (!timer) return;
|
|
308
|
+
clearInterval(timer);
|
|
309
|
+
timer = null;
|
|
310
|
+
process.stdout.write('\r\x1b[2K\x1b[?25h');
|
|
311
|
+
};
|
|
312
|
+
const complete = mark => {
|
|
313
|
+
if (!current) return;
|
|
314
|
+
stopTimer();
|
|
315
|
+
console.log(` ${mark} ${current}`);
|
|
316
|
+
current = null;
|
|
317
|
+
detail = '';
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
update(text) {
|
|
322
|
+
complete(c.green('✔'));
|
|
323
|
+
current = text;
|
|
324
|
+
if (isTTY) startTimer();
|
|
325
|
+
},
|
|
326
|
+
detail(text) {
|
|
327
|
+
detail = text;
|
|
328
|
+
},
|
|
329
|
+
warn(text) {
|
|
330
|
+
const wasRunning = !!timer;
|
|
331
|
+
stopTimer();
|
|
332
|
+
log.warn(text);
|
|
333
|
+
if (wasRunning) startTimer();
|
|
334
|
+
},
|
|
335
|
+
pause: stopTimer,
|
|
336
|
+
resume: startTimer,
|
|
337
|
+
done() {
|
|
338
|
+
complete(c.green('✔'));
|
|
339
|
+
return ((Date.now() - started) / 1000).toFixed(1);
|
|
340
|
+
},
|
|
341
|
+
fail() {
|
|
342
|
+
complete(c.red('✖'));
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const log = {
|
|
348
|
+
step: msg => console.log(` ${c.cyan('›')} ${msg}`),
|
|
349
|
+
cmd: msg => console.log(` ${c.gray(msg)}`),
|
|
350
|
+
ok: msg => console.log(` ${c.green('✔')} ${msg}`),
|
|
351
|
+
warn: msg => console.log(` ${c.yellow('▲')} ${c.yellow(msg)}`),
|
|
352
|
+
error: msg => console.log(` ${c.red('✖')} ${c.red(msg)}`),
|
|
353
|
+
info: msg => console.log(` ${c.blue('ℹ')} ${msg}`)
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
module.exports = { truncate, c, isTTY, swatch, gradient, banner, box, divider, input, select, confirm, spinner, tasks, log, width, stripAnsi, CancelError };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Stack engine: how versions are handled
|
|
2
|
+
|
|
3
|
+
BoilerCraft does not ship a hand-written copy of each framework. For Laravel, Next.js, Node + Express and .NET it:
|
|
4
|
+
|
|
5
|
+
1. **Lists versions live** from the upstream registry (Packagist, npm, the .NET release index), so a new major shows up in the UI without a code change.
|
|
6
|
+
2. **Creates the base project with the framework's official tool** for the chosen version:
|
|
7
|
+
| Stack | Official creator |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Laravel | `composer create-project laravel/laravel:^N.0` |
|
|
10
|
+
| Next.js | `npx create-next-app@N` |
|
|
11
|
+
| Node + Express | `npm init` + `npm install express@N` |
|
|
12
|
+
| .NET | `dotnet new webapi --framework netN.0` + `dotnet new sln` |
|
|
13
|
+
3. **Applies BoilerCraft overlays** on top: auth, DB wiring, theme, landing page. Any step can be limited to certain versions.
|
|
14
|
+
|
|
15
|
+
Raw PHP has no upstream framework, so it still uses `templates/raw-php-mysql`.
|
|
16
|
+
|
|
17
|
+
If a required tool is missing, the CLI offers to install it (`services/engine/toolInstaller.js`):
|
|
18
|
+
- .NET SDK and Composer go into `~/.boilercraft/tools`, no admin rights needed.
|
|
19
|
+
- PHP comes from winget, brew or apt/dnf/pacman.
|
|
20
|
+
|
|
21
|
+
That folder is put first on PATH for every command BoilerCraft runs, and is added to the user PATH for new terminals. If the user declines, generation falls back to the old template generator. A `requires` entry's `tool` field (defaulting to `cmd`) links it to an installer recipe: `php`, `composer` or `dotnet`.
|
|
22
|
+
|
|
23
|
+
## Where things live
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
stacks/<id>/stack.json manifest: versions, required tools, steps
|
|
27
|
+
stacks/<id>/overlays/… files copied into the project ({{PLACEHOLDERS}} filled in)
|
|
28
|
+
stacks/<id>/snippets/… text appended or inserted into generated files
|
|
29
|
+
services/engine/ scaffoldEngine.js (runs manifests), versionCatalog.js (live versions)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## A new framework version is released: what to do
|
|
33
|
+
|
|
34
|
+
**Usually nothing.** It appears in the dropdown labelled *Untested*, and users can already generate with it. The base comes from the official tool, so it matches that version exactly.
|
|
35
|
+
|
|
36
|
+
To verify it:
|
|
37
|
+
|
|
38
|
+
1. Generate a project with the new version and run it (the `BOILERCRAFT.md` in the output lists the start commands).
|
|
39
|
+
2. If it works, raise `versions.testedUpTo` in `stack.json`. The *Untested* label goes away and the version becomes the recommended default.
|
|
40
|
+
3. If one of our overlays breaks, add a version-scoped step. Don't edit the existing one:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{ "when": { "version": "<13" }, "copy": "overlays/auth" },
|
|
44
|
+
{ "when": { "version": ">=13" }, "copy": "overlays/auth-v13" }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Real example, from `stacks/dotnet-core/stack.json`: Swashbuckle 10 needs OpenAPI 2.x, which only .NET 10+ uses. So the package version is a version-scoped var:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{ "name": "SWASHBUCKLE_VERSION", "when": { "version": "<10" }, "value": "9.*" },
|
|
51
|
+
{ "name": "SWASHBUCKLE_VERSION", "when": { "version": ">=10" }, "value": "10.*" }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To drop an old version, raise `versions.minMajor`.
|
|
55
|
+
|
|
56
|
+
## Manifest reference
|
|
57
|
+
|
|
58
|
+
**Stack-specific questions.** A manifest can declare extra choices in `options`. The CLI asks them automatically, and they can be used in `when` and `vars` like any other filter. Next.js uses this for App Router vs Pages Router:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
"options": [{
|
|
62
|
+
"id": "router", "label": "Router", "default": "app",
|
|
63
|
+
"choices": [
|
|
64
|
+
{ "value": "app", "label": "App Router", "hint": "recommended" },
|
|
65
|
+
{ "value": "pages", "label": "Pages Router", "hint": "classic" }
|
|
66
|
+
]
|
|
67
|
+
}]
|
|
68
|
+
```
|
|
69
|
+
```json
|
|
70
|
+
{ "name": "NEXT_ROUTER_FLAG", "when": { "router": "pages" }, "value": "--no-app" },
|
|
71
|
+
{ "when": { "router": "pages" }, "copy": "overlays/router-pages" }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`when` filters (all optional, combined with AND):
|
|
75
|
+
`version` (`"11"`, `">=11"`, `"<11"`, `"10-12"`, `">=9 <11"`), `database`, `styling` (a value or an array), `auth`, `roles`, `platform`.
|
|
76
|
+
|
|
77
|
+
| Step | What it does |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `run: [cmd, ...args]` | Runs a command in the project folder (`cwd: "parent"` runs it one level up, for creators). `optional: true` turns a failure into a warning. |
|
|
80
|
+
| `copy: "overlays/x"` | Copies a folder into the project and fills in placeholders. |
|
|
81
|
+
| `write: "path", content` | Writes a file. |
|
|
82
|
+
| `append: "path", from/content` | Appends text to a file. |
|
|
83
|
+
| `insertBefore: "path", marker, from/content` | Inserts text before the last occurrence of `marker`. |
|
|
84
|
+
| `replace: "path", find / findRegex, with, skipIfContains` | Replaces the first match. `find` is plain text, so no escaping is needed. Use `findRegex` only when you need a pattern (remember to double backslashes in JSON). `$&` in `with` inserts the matched text. If nothing matches you get a warning, not a failure. |
|
|
85
|
+
| `env: {KEY: value}`, `files` | Sets or uncomments keys in `.env` / `.env.example`. |
|
|
86
|
+
| `json: "path", merge` | Deep-merges into a JSON file (for example `package.json` scripts). |
|
|
87
|
+
| `remove: "path"` | Deletes a file or folder. |
|
|
88
|
+
|
|
89
|
+
Built-in placeholders: `PROJECT_NAME`, `NAMESPACE`, `APP_TITLE`, `APP_DESC`, `AUTHOR` (HTML-escaped), `APP_TITLE_JS`, `APP_DESC_JS`, `AUTHOR_JS` (JSON strings), `DB_NAME`, `DB_TYPE`, `DATABASE`, `STYLING`, `THEME`, `COLOR_MODE`, `PORT`, `VERSION` (the major), `AUTH_ENABLED`, `LOGO_HTML`, `STYLESHEET_LINK`, `UI_COMPONENTS`, `THEME_CSS`, `THEME_JS`, `DB_ENV`, `DB_ENV_EXAMPLE`, `RANDOM_SECRET`, `RANDOM_PASSWORD`. Stack-specific ones go in the manifest's `vars` array; each entry can have its own `when`.
|
|
90
|
+
|
|
91
|
+
## Adding a new stack
|
|
92
|
+
|
|
93
|
+
Create `stacks/<id>/stack.json` with `versions`, `requires` and `steps`. The API (`GET /api/stacks`) and generation pick it up automatically. The only UI change needed is a backend card and a version `<select>` mapped in `VERSION_SELECTS` in `public/index.html`.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="880" height="600" viewBox="0 0 880 600" role="img" aria-label="Creating a Laravel project with BoilerCraft">
|
|
2
|
+
<defs>
|
|
3
|
+
<filter id="shadow" x="-5%" y="-5%" width="110%" height="115%">
|
|
4
|
+
<feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#000" flood-opacity="0.35"/>
|
|
5
|
+
</filter>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 15px; }
|
|
9
|
+
.dim { fill: #7d8590; }
|
|
10
|
+
.txt { fill: #e6edf3; }
|
|
11
|
+
.cyan { fill: #22d3ee; }
|
|
12
|
+
.green { fill: #3fb950; }
|
|
13
|
+
.bold { font-weight: 700; }
|
|
14
|
+
</style>
|
|
15
|
+
|
|
16
|
+
<rect x="10" y="6" width="860" height="584" rx="12" fill="#0d1117" stroke="#30363d" filter="url(#shadow)"/>
|
|
17
|
+
<rect x="10" y="6" width="860" height="38" rx="12" fill="#161b22"/>
|
|
18
|
+
<rect x="10" y="32" width="860" height="12" fill="#161b22"/>
|
|
19
|
+
<circle cx="36" cy="25" r="6" fill="#ff5f57"/>
|
|
20
|
+
<circle cx="56" cy="25" r="6" fill="#febc2e"/>
|
|
21
|
+
<circle cx="76" cy="25" r="6" fill="#28c840"/>
|
|
22
|
+
<text x="440" y="30" text-anchor="middle" class="mono dim" font-size="13">Terminal</text>
|
|
23
|
+
|
|
24
|
+
<!-- answered questions -->
|
|
25
|
+
<text x="40" y="82" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> Project name</tspan><tspan class="dim"> · </tspan><tspan class="cyan">shop</tspan></text>
|
|
26
|
+
<text x="40" y="108" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> Framework</tspan><tspan class="dim"> · </tspan><tspan class="cyan">Laravel</tspan></text>
|
|
27
|
+
<text x="40" y="134" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> Laravel version</tspan><tspan class="dim"> · </tspan><tspan class="cyan">13.x</tspan></text>
|
|
28
|
+
<text x="40" y="160" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> Frontend</tspan><tspan class="dim"> · </tspan><tspan class="cyan">Vue 3 (Inertia)</tspan></text>
|
|
29
|
+
<text x="40" y="186" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> Authentication</tspan><tspan class="dim"> · </tspan><tspan class="cyan">Included</tspan></text>
|
|
30
|
+
<text x="40" y="212" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> Theme</tspan><tspan class="dim"> · </tspan><tspan class="cyan">Cyber Emerald</tspan></text>
|
|
31
|
+
|
|
32
|
+
<!-- summary box -->
|
|
33
|
+
<rect x="40" y="234" width="470" height="170" rx="8" fill="none" stroke="#22d3ee" stroke-opacity="0.7"/>
|
|
34
|
+
<rect x="56" y="226" width="86" height="16" fill="#0d1117"/>
|
|
35
|
+
<text x="62" y="239" class="mono txt bold">Summary</text>
|
|
36
|
+
<text x="60" y="266" class="mono"><tspan class="dim">Framework </tspan><tspan class="txt">Laravel 13.x</tspan></text>
|
|
37
|
+
<text x="60" y="290" class="mono"><tspan class="dim">Frontend </tspan><tspan class="txt">Vue 3 (Inertia)</tspan></text>
|
|
38
|
+
<text x="60" y="314" class="mono"><tspan class="dim">Auth </tspan><tspan class="txt">Included</tspan></text>
|
|
39
|
+
<text x="60" y="338" class="mono"><tspan class="dim">Theme </tspan></text>
|
|
40
|
+
<rect x="160" y="326" width="16" height="14" rx="2" fill="#10b981"/>
|
|
41
|
+
<text x="184" y="338" class="mono txt">Cyber Emerald · Dark + Light</text>
|
|
42
|
+
<text x="60" y="362" class="mono"><tspan class="dim">Database </tspan><tspan class="txt">MySQL</tspan></text>
|
|
43
|
+
<text x="60" y="386" class="mono"><tspan class="dim">Location </tspan><tspan class="dim">./shop</tspan></text>
|
|
44
|
+
|
|
45
|
+
<!-- progress -->
|
|
46
|
+
<text x="40" y="440" class="mono"><tspan class="green">✔</tspan><tspan class="txt"> Running official composer create-project laravel/laravel:^13.0</tspan></text>
|
|
47
|
+
<text x="40" y="466" class="mono"><tspan class="green">✔</tspan><tspan class="txt"> Installing Laravel Breeze (vue) with authentication</tspan></text>
|
|
48
|
+
<text x="40" y="492" class="mono"><tspan class="green">✔</tspan><tspan class="txt"> Running php artisan breeze:install vue</tspan></text>
|
|
49
|
+
|
|
50
|
+
<text x="40" y="534" class="mono"><tspan class="green">✔</tspan><tspan class="txt bold"> shop</tspan><tspan class="txt"> is ready</tspan></text>
|
|
51
|
+
<text x="40" y="566" class="mono"><tspan class="cyan">?</tspan><tspan class="txt bold"> What next?</tspan><tspan class="cyan bold"> ❯ Start the dev server</tspan><tspan class="dim"> Open in VS Code Exit</tspan></text>
|
|
52
|
+
</svg>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="880" height="500" viewBox="0 0 880 500" role="img" aria-label="BoilerCraft CLI main menu">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="brand" x1="0" y1="0" x2="1" y2="0">
|
|
4
|
+
<stop offset="0" stop-color="#22d3ee"/>
|
|
5
|
+
<stop offset="0.5" stop-color="#3b82f6"/>
|
|
6
|
+
<stop offset="1" stop-color="#8b5cf6"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<filter id="shadow" x="-5%" y="-5%" width="110%" height="115%">
|
|
9
|
+
<feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#000" flood-opacity="0.35"/>
|
|
10
|
+
</filter>
|
|
11
|
+
</defs>
|
|
12
|
+
<style>
|
|
13
|
+
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 15px; }
|
|
14
|
+
.dim { fill: #7d8590; }
|
|
15
|
+
.txt { fill: #e6edf3; }
|
|
16
|
+
.cyan { fill: #22d3ee; }
|
|
17
|
+
.green { fill: #3fb950; }
|
|
18
|
+
.bold { font-weight: 700; }
|
|
19
|
+
</style>
|
|
20
|
+
|
|
21
|
+
<rect x="10" y="6" width="860" height="484" rx="12" fill="#0d1117" stroke="#30363d" filter="url(#shadow)"/>
|
|
22
|
+
<rect x="10" y="6" width="860" height="38" rx="12" fill="#161b22"/>
|
|
23
|
+
<rect x="10" y="32" width="860" height="12" fill="#161b22"/>
|
|
24
|
+
<circle cx="36" cy="25" r="6" fill="#ff5f57"/>
|
|
25
|
+
<circle cx="56" cy="25" r="6" fill="#febc2e"/>
|
|
26
|
+
<circle cx="76" cy="25" r="6" fill="#28c840"/>
|
|
27
|
+
<text x="440" y="30" text-anchor="middle" class="mono dim" font-size="13">Terminal</text>
|
|
28
|
+
|
|
29
|
+
<text x="40" y="80" class="mono"><tspan class="green">$</tspan><tspan class="txt"> npx boilercraft</tspan></text>
|
|
30
|
+
|
|
31
|
+
<text x="40" y="150" font-family="'Segoe UI', Inter, Helvetica, Arial, sans-serif" font-size="58" font-weight="900" letter-spacing="7" fill="url(#brand)">BOILERCRAFT</text>
|
|
32
|
+
|
|
33
|
+
<text x="42" y="186" class="mono"><tspan class="dim">by </tspan><tspan class="txt bold">ARG RABBI</tspan><tspan class="dim"> · v1.0.0</tspan></text>
|
|
34
|
+
<text x="42" y="210" class="mono dim">Production-ready projects on the official tooling · any version · auth · themes</text>
|
|
35
|
+
|
|
36
|
+
<text x="42" y="258" class="mono"><tspan class="cyan">?</tspan><tspan class="txt bold"> What would you like to do?</tspan><tspan class="dim"> · ↑↓ to move, enter to select</tspan></text>
|
|
37
|
+
|
|
38
|
+
<text x="62" y="292" class="mono"><tspan class="cyan bold">❯ Create a new project</tspan></text>
|
|
39
|
+
<text x="400" y="292" class="mono dim">Laravel · Next.js · Express · .NET · PHP</text>
|
|
40
|
+
<text x="62" y="320" class="mono txt"> Check & install tools</text>
|
|
41
|
+
<text x="400" y="320" class="mono dim">PHP · Composer · .NET SDK</text>
|
|
42
|
+
<text x="62" y="348" class="mono txt"> Browse frameworks & versions</text>
|
|
43
|
+
<text x="62" y="376" class="mono txt"> Open the web studio</text>
|
|
44
|
+
<text x="400" y="376" class="mono dim">same features in your browser</text>
|
|
45
|
+
<text x="62" y="404" class="mono txt"> About BoilerCraft</text>
|
|
46
|
+
<text x="62" y="432" class="mono txt"> Exit</text>
|
|
47
|
+
</svg>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 🟣 .NET Core 8 Web API Full Documentation & Maintenance Guide
|
|
2
|
+
|
|
3
|
+
## 📌 Overview
|
|
4
|
+
High-performance **.NET Core 8 Web API** with **Entity Framework Core**, **Swagger / OpenAPI Documentation**, **JWT Bearer Token Authentication**, and pre-wired CSS systems (Tailwind CSS or Bootstrap 5).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📂 Architecture & Directory Tree
|
|
9
|
+
```text
|
|
10
|
+
├── Controllers/
|
|
11
|
+
│ ├── ProductsController.cs # CRUD Web API Controller
|
|
12
|
+
│ └── AuthController.cs # JWT Authentication & Role Claims
|
|
13
|
+
├── Models/
|
|
14
|
+
│ └── Product.cs # Entity Model
|
|
15
|
+
├── Data/
|
|
16
|
+
│ └── ApplicationDbContext.cs # EF Core DbContext with Seeding
|
|
17
|
+
├── Program.cs # Dependency Injection, Middleware, Swagger
|
|
18
|
+
└── {ProjectName}.csproj # Project file with Nuget packages
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ⚡ Quick Start & Running
|
|
24
|
+
1. Restore packages and run migrations:
|
|
25
|
+
```bash
|
|
26
|
+
dotnet restore
|
|
27
|
+
dotnet build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Start the API server:
|
|
31
|
+
```bash
|
|
32
|
+
dotnet run
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Open Swagger UI in your browser:
|
|
36
|
+
```text
|
|
37
|
+
http://localhost:5000/swagger
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🗄️ Database Migrations (EF Core)
|
|
43
|
+
|
|
44
|
+
### Creating Migrations:
|
|
45
|
+
Whenever you change or add models in `Models/`:
|
|
46
|
+
```bash
|
|
47
|
+
dotnet ef migrations add AddNewEntity
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Applying Migrations to Database:
|
|
51
|
+
```bash
|
|
52
|
+
dotnet ef database update
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🔐 JWT Authentication
|
|
58
|
+
- Endpoint: `POST /api/auth/login`
|
|
59
|
+
- Sample Body:
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"email": "admin@example.com",
|
|
63
|
+
"password": "admin123"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
- In Swagger or Postman, include the returned Bearer token in the `Authorization` header:
|
|
67
|
+
```text
|
|
68
|
+
Authorization: Bearer <your_jwt_token>
|
|
69
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 🔴 Laravel 11 + Vue 3 Full Documentation & Maintenance Guide
|
|
2
|
+
|
|
3
|
+
## 📌 Overview
|
|
4
|
+
Full-stack enterprise framework combining **Laravel 11**, **Vue 3 Composition API**, **Inertia.js**, and **Vite**, with database support for **MySQL** and **MSSQL**.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📂 Architecture & Directory Tree
|
|
9
|
+
```text
|
|
10
|
+
├── app/
|
|
11
|
+
│ └── Http/
|
|
12
|
+
│ └── Controllers/
|
|
13
|
+
│ ├── HomeController.php
|
|
14
|
+
│ └── Auth/AuthenticatedSessionController.php
|
|
15
|
+
├── resources/
|
|
16
|
+
│ ├── js/
|
|
17
|
+
│ │ ├── Pages/
|
|
18
|
+
│ │ │ ├── Home.vue # Vue 3 Composition API Landing
|
|
19
|
+
│ │ │ └── Auth/Login.vue # Vue Login Component
|
|
20
|
+
│ │ └── Components/ # Skeletons, Modals, Offcanvas
|
|
21
|
+
│ └── css/ # Theme styles
|
|
22
|
+
├── routes/
|
|
23
|
+
│ └── web.php # Inertia web routes
|
|
24
|
+
├── vite.config.js
|
|
25
|
+
└── composer.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ⚡ Quick Start & Running
|
|
31
|
+
1. Install backend and frontend dependencies:
|
|
32
|
+
```bash
|
|
33
|
+
composer install
|
|
34
|
+
npm install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
2. Start the Vite asset compiler and Laravel artisan server:
|
|
38
|
+
```bash
|
|
39
|
+
# Terminal 1:
|
|
40
|
+
npm run dev
|
|
41
|
+
|
|
42
|
+
# Terminal 2:
|
|
43
|
+
php artisan serve
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. Open in browser: `http://localhost:8000`
|