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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/bin/boilercraft.js +110 -0
  4. package/cli/app.js +596 -0
  5. package/cli/ui.js +356 -0
  6. package/docs/STACK_ENGINE.md +93 -0
  7. package/docs/assets/cli-create.svg +52 -0
  8. package/docs/assets/cli-menu.svg +47 -0
  9. package/docs/stacks/dotnet-core.md +69 -0
  10. package/docs/stacks/laravel-vue.md +46 -0
  11. package/docs/stacks/nextjs.md +58 -0
  12. package/docs/stacks/node-express.md +58 -0
  13. package/docs/stacks/raw-php.md +130 -0
  14. package/main.js +82 -0
  15. package/package.json +86 -0
  16. package/public/index.html +721 -0
  17. package/public/logo.png +17 -0
  18. package/public/logo.svg +17 -0
  19. package/server.js +84 -0
  20. package/services/componentGenerator.js +195 -0
  21. package/services/engine/scaffoldEngine.js +517 -0
  22. package/services/engine/toolInstaller.js +232 -0
  23. package/services/engine/versionCatalog.js +115 -0
  24. package/services/generate.js +46 -0
  25. package/services/projectGenerator.js +686 -0
  26. package/services/readmeGenerator.js +221 -0
  27. package/services/themeGenerator.js +150 -0
  28. package/services/versionResolver.js +176 -0
  29. package/stacks/dotnet-core/overlays/api/Controllers/ProductsController.cs +33 -0
  30. package/stacks/dotnet-core/overlays/api/Program.cs +33 -0
  31. package/stacks/dotnet-core/overlays/api/wwwroot/index.html +38 -0
  32. package/stacks/dotnet-core/overlays/api-auth/Controllers/AuthController.cs +87 -0
  33. package/stacks/dotnet-core/overlays/common/Data/ApplicationDbContext.cs +25 -0
  34. package/stacks/dotnet-core/overlays/common/Models/Product.cs +19 -0
  35. package/stacks/dotnet-core/overlays/mvc/Controllers/ProductsController.cs +84 -0
  36. package/stacks/dotnet-core/overlays/mvc/Views/Home/Index.cshtml +23 -0
  37. package/stacks/dotnet-core/overlays/mvc/Views/Products/Create.cshtml +16 -0
  38. package/stacks/dotnet-core/overlays/mvc/Views/Products/Delete.cshtml +16 -0
  39. package/stacks/dotnet-core/overlays/mvc/Views/Products/Edit.cshtml +17 -0
  40. package/stacks/dotnet-core/overlays/mvc/Views/Products/Index.cshtml +46 -0
  41. package/stacks/dotnet-core/overlays/mvc/Views/Products/_Form.cshtml +19 -0
  42. package/stacks/dotnet-core/overlays/mvc-auth/Controllers/AccountController.cs +98 -0
  43. package/stacks/dotnet-core/overlays/mvc-auth/Models/AppUser.cs +48 -0
  44. package/stacks/dotnet-core/overlays/mvc-auth/Views/Account/Login.cshtml +35 -0
  45. package/stacks/dotnet-core/overlays/mvc-auth/Views/Account/Register.cshtml +41 -0
  46. package/stacks/dotnet-core/overlays/mvc-auth/Views/Shared/_AuthNav.cshtml +22 -0
  47. package/stacks/dotnet-core/snippets/packages-auth.xml +4 -0
  48. package/stacks/dotnet-core/snippets/packages-common.xml +5 -0
  49. package/stacks/dotnet-core/snippets/packages-ef.xml +4 -0
  50. package/stacks/dotnet-core/snippets/packages-mssql.xml +4 -0
  51. package/stacks/dotnet-core/snippets/packages-mysql.xml +4 -0
  52. package/stacks/dotnet-core/snippets/packages-swagger.xml +4 -0
  53. package/stacks/dotnet-core/stack.json +426 -0
  54. package/stacks/laravel/overlays/auth/app/Http/Controllers/Api/AuthController.php +67 -0
  55. package/stacks/laravel/overlays/common/app/Http/Controllers/BoilerCraftController.php +14 -0
  56. package/stacks/laravel/overlays/common/resources/views/boilercraft.blade.php +36 -0
  57. package/stacks/laravel/overlays/inertia-common/resources/views/app.blade.php +15 -0
  58. package/stacks/laravel/overlays/inertia-react/resources/js/Pages/Welcome.jsx +18 -0
  59. package/stacks/laravel/overlays/inertia-react/resources/js/app.jsx +13 -0
  60. package/stacks/laravel/overlays/inertia-vue/resources/js/Pages/Welcome.vue +23 -0
  61. package/stacks/laravel/overlays/inertia-vue/resources/js/app.js +13 -0
  62. package/stacks/laravel/snippets/api-routes.php +9 -0
  63. package/stacks/laravel/snippets/web-routes-inertia.php +7 -0
  64. package/stacks/laravel/snippets/web-routes.php +4 -0
  65. package/stacks/laravel/stack.json +477 -0
  66. package/stacks/nextjs/overlays/auth/src/lib/auth.js +36 -0
  67. package/stacks/nextjs/overlays/auth-app/src/app/api/auth/login/route.js +11 -0
  68. package/stacks/nextjs/overlays/auth-app/src/app/api/auth/register/route.js +14 -0
  69. package/stacks/nextjs/overlays/auth-pages/src/pages/api/auth/login.js +14 -0
  70. package/stacks/nextjs/overlays/auth-pages/src/pages/api/auth/register.js +17 -0
  71. package/stacks/nextjs/overlays/common/.env.example +3 -0
  72. package/stacks/nextjs/overlays/common/.env.local +3 -0
  73. package/stacks/nextjs/overlays/db/mongodb/src/lib/db.js +12 -0
  74. package/stacks/nextjs/overlays/db/mssql/src/lib/db.js +17 -0
  75. package/stacks/nextjs/overlays/db/mysql/src/lib/db.js +13 -0
  76. package/stacks/nextjs/overlays/router-app/src/app/api/health/route.js +10 -0
  77. package/stacks/nextjs/overlays/router-app/src/app/layout.js +16 -0
  78. package/stacks/nextjs/overlays/router-app/src/app/page.js +19 -0
  79. package/stacks/nextjs/overlays/router-pages/src/pages/_app.js +7 -0
  80. package/stacks/nextjs/overlays/router-pages/src/pages/_document.js +13 -0
  81. package/stacks/nextjs/overlays/router-pages/src/pages/api/health.js +8 -0
  82. package/stacks/nextjs/overlays/router-pages/src/pages/index.js +27 -0
  83. package/stacks/nextjs/stack.json +257 -0
  84. package/stacks/node-express/overlays/auth/src/controllers/authController.js +53 -0
  85. package/stacks/node-express/overlays/auth/src/middlewares/auth.js +36 -0
  86. package/stacks/node-express/overlays/auth/src/routes/authRoutes.js +16 -0
  87. package/stacks/node-express/overlays/common/.env.example +5 -0
  88. package/stacks/node-express/overlays/common/public/index.html +31 -0
  89. package/stacks/node-express/overlays/common/src/server.js +32 -0
  90. package/stacks/node-express/overlays/db/mongodb/src/db.js +13 -0
  91. package/stacks/node-express/overlays/db/mssql/src/db.js +22 -0
  92. package/stacks/node-express/overlays/db/mysql/src/db.js +22 -0
  93. package/stacks/node-express/stack.json +165 -0
  94. package/templates/dotnet-bootstrap-mssql/Controllers/AuthController.cs +74 -0
  95. package/templates/dotnet-bootstrap-mssql/Controllers/ProductsController.cs +33 -0
  96. package/templates/dotnet-bootstrap-mssql/Data/ApplicationDbContext.cs +24 -0
  97. package/templates/dotnet-bootstrap-mssql/Models/Product.cs +10 -0
  98. package/templates/dotnet-core/Controllers/AuthController.cs +74 -0
  99. package/templates/laravel-full/app/Http/Controllers/AuthController.php +79 -0
  100. package/templates/laravel-full/app/Http/Controllers/HomeController.php +22 -0
  101. package/templates/laravel-full/app/Models/User.php +49 -0
  102. package/templates/laravel-full/artisan +22 -0
  103. package/templates/laravel-full/bootstrap/app.php +21 -0
  104. package/templates/laravel-full/config/database.php +54 -0
  105. package/templates/laravel-full/database/migrations/2024_01_01_000001_create_users_table.php +27 -0
  106. package/templates/laravel-full/public/index.php +17 -0
  107. package/templates/laravel-full/resources/css/app.css +3 -0
  108. package/templates/laravel-full/resources/js/Pages/Home.vue +76 -0
  109. package/templates/laravel-full/resources/js/app.js +21 -0
  110. package/templates/laravel-full/resources/js/bootstrap.js +3 -0
  111. package/templates/laravel-full/resources/views/app.blade.php +15 -0
  112. package/templates/laravel-full/routes/api.php +8 -0
  113. package/templates/laravel-full/routes/console.php +7 -0
  114. package/templates/laravel-full/routes/web.php +24 -0
  115. package/templates/laravel-full/tailwind.config.js +12 -0
  116. package/templates/laravel-full/vite.config.js +20 -0
  117. package/templates/laravel-vue/app/Http/Controllers/Auth/AuthenticatedSessionController.php +43 -0
  118. package/templates/laravel-vue-mysql/app/Http/Controllers/HomeController.php +25 -0
  119. package/templates/laravel-vue-mysql/resources/js/Pages/Home.vue +70 -0
  120. package/templates/nextjs/src/app/api/auth/login/route.js +28 -0
  121. package/templates/nextjs-mongodb/src/app/api/health/route.js +17 -0
  122. package/templates/nextjs-mongodb/src/lib/mongodb.js +18 -0
  123. package/templates/nextjs-mssql/src/app/api/health/route.js +24 -0
  124. package/templates/nextjs-mssql/src/lib/mssql.js +38 -0
  125. package/templates/node-express/src/controllers/authController.js +53 -0
  126. package/templates/node-express/src/middlewares/auth.js +36 -0
  127. package/templates/node-express/src/routes/authRoutes.js +16 -0
  128. package/templates/node-mongodb/src/controllers/userController.js +30 -0
  129. package/templates/node-mongodb/src/middlewares/errorHandler.js +8 -0
  130. package/templates/node-mongodb/src/models/User.js +23 -0
  131. package/templates/node-mongodb/src/routes/api.js +9 -0
  132. package/templates/raw-php-mysql/app/Controllers/AuthController.php +73 -0
  133. package/templates/raw-php-mysql/app/Controllers/HomeController.php +65 -0
  134. package/templates/raw-php-mysql/app/Models/User.php +60 -0
  135. package/templates/raw-php-mysql/app/Views/404.php +15 -0
  136. package/templates/raw-php-mysql/app/Views/auth/login.php +66 -0
  137. package/templates/raw-php-mysql/app/Views/auth/register.php +71 -0
  138. package/templates/raw-php-mysql/app/Views/dashboard.php +50 -0
  139. package/templates/raw-php-mysql/app/Views/home.php +83 -0
  140. package/templates/raw-php-mysql/config/Auth.php +62 -0
  141. package/templates/raw-php-mysql/config/Database.php +100 -0
  142. package/templates/raw-php-mysql/config/Env.php +30 -0
  143. package/templates/raw-php-mysql/config/Middleware.php +53 -0
  144. package/templates/raw-php-mysql/config/QueryBuilder.php +109 -0
  145. package/templates/raw-php-mysql/config/Router.php +33 -0
  146. 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 &amp; 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 &amp; 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`